@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.
Files changed (131) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +278 -0
  3. package/RELEASE_SUMMARY.md +53 -0
  4. package/STANDARDS_COMPLIANCE.md +85 -0
  5. package/bin/terraflow.js +3 -0
  6. package/bin/tf.js +3 -0
  7. package/dist/commands/apply.d.ts +7 -0
  8. package/dist/commands/apply.js +12 -0
  9. package/dist/commands/base.d.ts +7 -0
  10. package/dist/commands/base.js +12 -0
  11. package/dist/commands/config.d.ts +25 -0
  12. package/dist/commands/config.js +354 -0
  13. package/dist/commands/destroy.d.ts +7 -0
  14. package/dist/commands/destroy.js +12 -0
  15. package/dist/commands/init.d.ts +68 -0
  16. package/dist/commands/init.js +131 -0
  17. package/dist/commands/plan.d.ts +7 -0
  18. package/dist/commands/plan.js +12 -0
  19. package/dist/core/backend-state.d.ts +25 -0
  20. package/dist/core/backend-state.js +77 -0
  21. package/dist/core/config.d.ts +83 -0
  22. package/dist/core/config.js +295 -0
  23. package/dist/core/context.d.ts +52 -0
  24. package/dist/core/context.js +192 -0
  25. package/dist/core/environment.d.ts +62 -0
  26. package/dist/core/environment.js +205 -0
  27. package/dist/core/errors.d.ts +22 -0
  28. package/dist/core/errors.js +36 -0
  29. package/dist/core/plugin-loader.d.ts +21 -0
  30. package/dist/core/plugin-loader.js +136 -0
  31. package/dist/core/terraform.d.ts +45 -0
  32. package/dist/core/terraform.js +247 -0
  33. package/dist/core/validator.d.ts +103 -0
  34. package/dist/core/validator.js +304 -0
  35. package/dist/index.d.ts +7 -0
  36. package/dist/index.js +184 -0
  37. package/dist/plugins/auth/aws-assume-role.d.ts +10 -0
  38. package/dist/plugins/auth/aws-assume-role.js +110 -0
  39. package/dist/plugins/auth/azure-service-principal.d.ts +10 -0
  40. package/dist/plugins/auth/azure-service-principal.js +99 -0
  41. package/dist/plugins/auth/gcp-service-account.d.ts +10 -0
  42. package/dist/plugins/auth/gcp-service-account.js +105 -0
  43. package/dist/plugins/backends/azurerm.d.ts +10 -0
  44. package/dist/plugins/backends/azurerm.js +117 -0
  45. package/dist/plugins/backends/gcs.d.ts +10 -0
  46. package/dist/plugins/backends/gcs.js +75 -0
  47. package/dist/plugins/backends/local.d.ts +11 -0
  48. package/dist/plugins/backends/local.js +37 -0
  49. package/dist/plugins/backends/s3.d.ts +10 -0
  50. package/dist/plugins/backends/s3.js +185 -0
  51. package/dist/plugins/secrets/aws-secrets.d.ts +12 -0
  52. package/dist/plugins/secrets/aws-secrets.js +125 -0
  53. package/dist/plugins/secrets/azure-keyvault.d.ts +12 -0
  54. package/dist/plugins/secrets/azure-keyvault.js +178 -0
  55. package/dist/plugins/secrets/env.d.ts +24 -0
  56. package/dist/plugins/secrets/env.js +62 -0
  57. package/dist/plugins/secrets/gcp-secret-manager.d.ts +12 -0
  58. package/dist/plugins/secrets/gcp-secret-manager.js +157 -0
  59. package/dist/templates/application/go/go.mod.template +4 -0
  60. package/dist/templates/application/go/main.template +8 -0
  61. package/dist/templates/application/go/test.template +11 -0
  62. package/dist/templates/application/javascript/main.template +14 -0
  63. package/dist/templates/application/javascript/test.template +8 -0
  64. package/dist/templates/application/python/main.template +13 -0
  65. package/dist/templates/application/python/requirements.txt.template +3 -0
  66. package/dist/templates/application/python/test.template +8 -0
  67. package/dist/templates/application/typescript/main.template +14 -0
  68. package/dist/templates/application/typescript/test.template +8 -0
  69. package/dist/templates/application/typescript/tsconfig.json.template +20 -0
  70. package/dist/templates/config/README.md.template +82 -0
  71. package/dist/templates/config/env.example.template +22 -0
  72. package/dist/templates/config/gitignore.template +40 -0
  73. package/dist/templates/config/tfwconfig.yml.template +69 -0
  74. package/dist/templates/templates/application/go/go.mod.template +4 -0
  75. package/dist/templates/templates/application/go/main.template +8 -0
  76. package/dist/templates/templates/application/go/test.template +11 -0
  77. package/dist/templates/templates/application/javascript/main.template +14 -0
  78. package/dist/templates/templates/application/javascript/test.template +8 -0
  79. package/dist/templates/templates/application/python/main.template +13 -0
  80. package/dist/templates/templates/application/python/requirements.txt.template +3 -0
  81. package/dist/templates/templates/application/python/test.template +8 -0
  82. package/dist/templates/templates/application/typescript/main.template +14 -0
  83. package/dist/templates/templates/application/typescript/test.template +8 -0
  84. package/dist/templates/templates/application/typescript/tsconfig.json.template +20 -0
  85. package/dist/templates/templates/config/README.md.template +82 -0
  86. package/dist/templates/templates/config/env.example.template +22 -0
  87. package/dist/templates/templates/config/gitignore.template +40 -0
  88. package/dist/templates/templates/config/tfwconfig.yml.template +69 -0
  89. package/dist/templates/templates/terraform/aws/_init.tf.template +24 -0
  90. package/dist/templates/templates/terraform/aws/inputs.tf.template +11 -0
  91. package/dist/templates/templates/terraform/azure/_init.tf.template +19 -0
  92. package/dist/templates/templates/terraform/azure/inputs.tf.template +11 -0
  93. package/dist/templates/templates/terraform/gcp/_init.tf.template +20 -0
  94. package/dist/templates/templates/terraform/gcp/inputs.tf.template +16 -0
  95. package/dist/templates/templates/terraform/locals.tf.template +9 -0
  96. package/dist/templates/templates/terraform/main.tf.template +8 -0
  97. package/dist/templates/templates/terraform/modules/inputs.tf.template +5 -0
  98. package/dist/templates/templates/terraform/modules/main.tf.template +2 -0
  99. package/dist/templates/templates/terraform/modules/outputs.tf.template +2 -0
  100. package/dist/templates/templates/terraform/outputs.tf.template +6 -0
  101. package/dist/templates/terraform/aws/_init.tf.template +24 -0
  102. package/dist/templates/terraform/aws/inputs.tf.template +11 -0
  103. package/dist/templates/terraform/azure/_init.tf.template +19 -0
  104. package/dist/templates/terraform/azure/inputs.tf.template +11 -0
  105. package/dist/templates/terraform/gcp/_init.tf.template +20 -0
  106. package/dist/templates/terraform/gcp/inputs.tf.template +16 -0
  107. package/dist/templates/terraform/locals.tf.template +9 -0
  108. package/dist/templates/terraform/main.tf.template +8 -0
  109. package/dist/templates/terraform/modules/inputs.tf.template +5 -0
  110. package/dist/templates/terraform/modules/main.tf.template +2 -0
  111. package/dist/templates/terraform/modules/outputs.tf.template +2 -0
  112. package/dist/templates/terraform/outputs.tf.template +6 -0
  113. package/dist/types/config.d.ts +92 -0
  114. package/dist/types/config.js +6 -0
  115. package/dist/types/context.d.ts +59 -0
  116. package/dist/types/context.js +6 -0
  117. package/dist/types/index.d.ts +7 -0
  118. package/dist/types/index.js +23 -0
  119. package/dist/types/plugins.d.ts +77 -0
  120. package/dist/types/plugins.js +6 -0
  121. package/dist/utils/cloud.d.ts +43 -0
  122. package/dist/utils/cloud.js +150 -0
  123. package/dist/utils/git.d.ts +88 -0
  124. package/dist/utils/git.js +258 -0
  125. package/dist/utils/logger.d.ts +67 -0
  126. package/dist/utils/logger.js +121 -0
  127. package/dist/utils/scaffolding.d.ts +92 -0
  128. package/dist/utils/scaffolding.js +338 -0
  129. package/dist/utils/templates.d.ts +25 -0
  130. package/dist/utils/templates.js +70 -0
  131. 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