@aifabrix/builder 2.0.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/LICENSE +21 -0
- package/README.md +75 -0
- package/bin/aifabrix.js +51 -0
- package/lib/app-deploy.js +209 -0
- package/lib/app-run.js +291 -0
- package/lib/app.js +472 -0
- package/lib/audit-logger.js +162 -0
- package/lib/build.js +313 -0
- package/lib/cli.js +307 -0
- package/lib/deployer.js +256 -0
- package/lib/env-reader.js +250 -0
- package/lib/generator.js +361 -0
- package/lib/github-generator.js +220 -0
- package/lib/infra.js +300 -0
- package/lib/key-generator.js +93 -0
- package/lib/push.js +141 -0
- package/lib/schema/application-schema.json +649 -0
- package/lib/schema/env-config.yaml +15 -0
- package/lib/secrets.js +282 -0
- package/lib/templates.js +301 -0
- package/lib/validator.js +377 -0
- package/package.json +59 -0
- package/templates/README.md +51 -0
- package/templates/github/ci.yaml.hbs +15 -0
- package/templates/github/pr-checks.yaml.hbs +35 -0
- package/templates/github/release.yaml.hbs +79 -0
- package/templates/github/test.hbs +11 -0
- package/templates/github/test.yaml.hbs +11 -0
- package/templates/infra/compose.yaml +93 -0
- package/templates/python/Dockerfile.hbs +49 -0
- package/templates/python/docker-compose.hbs +69 -0
- package/templates/typescript/Dockerfile.hbs +46 -0
- package/templates/typescript/docker-compose.hbs +69 -0
package/lib/secrets.js
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Fabrix Builder Secrets Management
|
|
3
|
+
*
|
|
4
|
+
* This module handles secret resolution and environment file generation.
|
|
5
|
+
* Resolves kv:// references from secrets files and generates .env files.
|
|
6
|
+
*
|
|
7
|
+
* @fileoverview Secret resolution and environment management for AI Fabrix Builder
|
|
8
|
+
* @author AI Fabrix Team
|
|
9
|
+
* @version 2.0.0
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
const yaml = require('js-yaml');
|
|
15
|
+
const os = require('os');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Loads environment configuration for docker/local context
|
|
19
|
+
* @returns {Object} Environment configuration
|
|
20
|
+
*/
|
|
21
|
+
function loadEnvConfig() {
|
|
22
|
+
const envConfigPath = path.join(__dirname, 'schema', 'env-config.yaml');
|
|
23
|
+
const content = fs.readFileSync(envConfigPath, 'utf8');
|
|
24
|
+
return yaml.load(content);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Loads secrets from the specified file or default location
|
|
29
|
+
* Supports both user secrets (~/.aifabrix/secrets.yaml) and project overrides
|
|
30
|
+
*
|
|
31
|
+
* @async
|
|
32
|
+
* @function loadSecrets
|
|
33
|
+
* @param {string} [secretsPath] - Path to secrets file (optional)
|
|
34
|
+
* @returns {Promise<Object>} Loaded secrets object
|
|
35
|
+
* @throws {Error} If secrets file cannot be loaded or parsed
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* const secrets = await loadSecrets('../../secrets.local.yaml');
|
|
39
|
+
* // Returns: { 'postgres-passwordKeyVault': 'admin123', ... }
|
|
40
|
+
*/
|
|
41
|
+
async function loadSecrets(secretsPath) {
|
|
42
|
+
let resolvedPath = secretsPath;
|
|
43
|
+
|
|
44
|
+
if (!resolvedPath) {
|
|
45
|
+
resolvedPath = path.join(os.homedir(), '.aifabrix', 'secrets.yaml');
|
|
46
|
+
} else if (secretsPath.startsWith('..')) {
|
|
47
|
+
resolvedPath = path.resolve(process.cwd(), secretsPath);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
51
|
+
throw new Error(`Secrets file not found: ${resolvedPath}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const content = fs.readFileSync(resolvedPath, 'utf8');
|
|
55
|
+
const secrets = yaml.load(content);
|
|
56
|
+
|
|
57
|
+
if (!secrets || typeof secrets !== 'object') {
|
|
58
|
+
throw new Error(`Invalid secrets file format: ${resolvedPath}`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return secrets;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Resolves kv:// references in environment template
|
|
66
|
+
* Replaces kv://keyName with actual values from secrets
|
|
67
|
+
*
|
|
68
|
+
* @async
|
|
69
|
+
* @function resolveKvReferences
|
|
70
|
+
* @param {string} envTemplate - Environment template content
|
|
71
|
+
* @param {Object} secrets - Secrets object from loadSecrets()
|
|
72
|
+
* @param {string} [environment='local'] - Environment context (docker/local)
|
|
73
|
+
* @returns {Promise<string>} Resolved environment content
|
|
74
|
+
* @throws {Error} If kv:// reference cannot be resolved
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* const resolved = await resolveKvReferences(template, secrets, 'local');
|
|
78
|
+
* // Returns: 'DATABASE_URL=postgresql://user:pass@localhost:5432/db'
|
|
79
|
+
*/
|
|
80
|
+
async function resolveKvReferences(envTemplate, secrets, environment = 'local') {
|
|
81
|
+
const envConfig = loadEnvConfig();
|
|
82
|
+
const envVars = envConfig.environments[environment] || envConfig.environments.local;
|
|
83
|
+
|
|
84
|
+
let resolved = envTemplate;
|
|
85
|
+
const kvPattern = /kv:\/\/([a-zA-Z0-9-_]+)/g;
|
|
86
|
+
const missingSecrets = [];
|
|
87
|
+
|
|
88
|
+
let match;
|
|
89
|
+
while ((match = kvPattern.exec(envTemplate)) !== null) {
|
|
90
|
+
const secretKey = match[1];
|
|
91
|
+
if (!(secretKey in secrets)) {
|
|
92
|
+
missingSecrets.push(`kv://${secretKey}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (missingSecrets.length > 0) {
|
|
97
|
+
throw new Error(`Missing secrets: ${missingSecrets.join(', ')}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
resolved = resolved.replace(kvPattern, (match, secretKey) => {
|
|
101
|
+
let value = secrets[secretKey];
|
|
102
|
+
if (typeof value === 'string') {
|
|
103
|
+
value = value.replace(/\$\{([A-Z_]+)\}/g, (m, envVar) => {
|
|
104
|
+
return envVars[envVar] || m;
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
return value;
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
return resolved;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Generates .env file from template and secrets
|
|
115
|
+
* Creates environment file for local development
|
|
116
|
+
*
|
|
117
|
+
* @async
|
|
118
|
+
* @function generateEnvFile
|
|
119
|
+
* @param {string} appName - Name of the application
|
|
120
|
+
* @param {string} [secretsPath] - Path to secrets file (optional)
|
|
121
|
+
* @param {string} [environment='local'] - Environment context
|
|
122
|
+
* @returns {Promise<string>} Path to generated .env file
|
|
123
|
+
* @throws {Error} If generation fails
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* const envPath = await generateEnvFile('myapp', '../../secrets.local.yaml');
|
|
127
|
+
* // Returns: './builder/myapp/.env'
|
|
128
|
+
*/
|
|
129
|
+
async function generateEnvFile(appName, secretsPath, environment = 'local') {
|
|
130
|
+
const builderPath = path.join(process.cwd(), 'builder', appName);
|
|
131
|
+
const templatePath = path.join(builderPath, 'env.template');
|
|
132
|
+
const variablesPath = path.join(builderPath, 'variables.yaml');
|
|
133
|
+
const envPath = path.join(builderPath, '.env');
|
|
134
|
+
|
|
135
|
+
if (!fs.existsSync(templatePath)) {
|
|
136
|
+
throw new Error(`env.template not found: ${templatePath}`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const template = fs.readFileSync(templatePath, 'utf8');
|
|
140
|
+
const secrets = await loadSecrets(secretsPath);
|
|
141
|
+
const resolved = await resolveKvReferences(template, secrets, environment);
|
|
142
|
+
|
|
143
|
+
fs.writeFileSync(envPath, resolved, { mode: 0o600 });
|
|
144
|
+
|
|
145
|
+
if (fs.existsSync(variablesPath)) {
|
|
146
|
+
const variablesContent = fs.readFileSync(variablesPath, 'utf8');
|
|
147
|
+
const variables = yaml.load(variablesContent);
|
|
148
|
+
|
|
149
|
+
if (variables?.build?.envOutputPath) {
|
|
150
|
+
const outputPath = path.resolve(builderPath, variables.build.envOutputPath);
|
|
151
|
+
const outputDir = path.dirname(outputPath);
|
|
152
|
+
|
|
153
|
+
if (!fs.existsSync(outputDir)) {
|
|
154
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
fs.copyFileSync(envPath, outputPath);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return envPath;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Generates admin secrets for infrastructure
|
|
166
|
+
* Creates ~/.aifabrix/admin-secrets.env with Postgres and Redis credentials
|
|
167
|
+
*
|
|
168
|
+
* @async
|
|
169
|
+
* @function generateAdminSecretsEnv
|
|
170
|
+
* @param {string} [secretsPath] - Path to secrets file (optional)
|
|
171
|
+
* @returns {Promise<string>} Path to generated admin-secrets.env file
|
|
172
|
+
* @throws {Error} If generation fails
|
|
173
|
+
*
|
|
174
|
+
* @example
|
|
175
|
+
* const adminEnvPath = await generateAdminSecretsEnv('../../secrets.local.yaml');
|
|
176
|
+
* // Returns: '~/.aifabrix/admin-secrets.env'
|
|
177
|
+
*/
|
|
178
|
+
async function generateAdminSecretsEnv(secretsPath) {
|
|
179
|
+
const secrets = await loadSecrets(secretsPath);
|
|
180
|
+
const aifabrixDir = path.join(os.homedir(), '.aifabrix');
|
|
181
|
+
const adminEnvPath = path.join(aifabrixDir, 'admin-secrets.env');
|
|
182
|
+
|
|
183
|
+
if (!fs.existsSync(aifabrixDir)) {
|
|
184
|
+
fs.mkdirSync(aifabrixDir, { recursive: true, mode: 0o700 });
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const postgresPassword = secrets['postgres-passwordKeyVault'] || '';
|
|
188
|
+
|
|
189
|
+
const adminSecrets = `# Infrastructure Admin Credentials
|
|
190
|
+
POSTGRES_PASSWORD=${postgresPassword}
|
|
191
|
+
PGADMIN_DEFAULT_EMAIL=admin@aifabrix.ai
|
|
192
|
+
PGADMIN_DEFAULT_PASSWORD=${postgresPassword}
|
|
193
|
+
REDIS_HOST=local:localhost:6379
|
|
194
|
+
REDIS_COMMANDER_USER=admin
|
|
195
|
+
REDIS_COMMANDER_PASSWORD=${postgresPassword}
|
|
196
|
+
`;
|
|
197
|
+
|
|
198
|
+
fs.writeFileSync(adminEnvPath, adminSecrets, { mode: 0o600 });
|
|
199
|
+
return adminEnvPath;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Validates that all required secrets are present
|
|
204
|
+
* Checks for missing kv:// references and provides helpful error messages
|
|
205
|
+
*
|
|
206
|
+
* @function validateSecrets
|
|
207
|
+
* @param {string} envTemplate - Environment template content
|
|
208
|
+
* @param {Object} secrets - Available secrets
|
|
209
|
+
* @returns {Object} Validation result with missing secrets
|
|
210
|
+
*
|
|
211
|
+
* @example
|
|
212
|
+
* const validation = validateSecrets(template, secrets);
|
|
213
|
+
* // Returns: { valid: false, missing: ['kv://missing-secret'] }
|
|
214
|
+
*/
|
|
215
|
+
function validateSecrets(envTemplate, secrets) {
|
|
216
|
+
const kvPattern = /kv:\/\/([a-zA-Z0-9-_]+)/g;
|
|
217
|
+
const missing = [];
|
|
218
|
+
|
|
219
|
+
let match;
|
|
220
|
+
while ((match = kvPattern.exec(envTemplate)) !== null) {
|
|
221
|
+
const secretKey = match[1];
|
|
222
|
+
if (!(secretKey in secrets)) {
|
|
223
|
+
missing.push(`kv://${secretKey}`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return {
|
|
228
|
+
valid: missing.length === 0,
|
|
229
|
+
missing
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Creates default secrets file if it doesn't exist
|
|
235
|
+
* Generates template with common secrets for local development
|
|
236
|
+
*
|
|
237
|
+
* @async
|
|
238
|
+
* @function createDefaultSecrets
|
|
239
|
+
* @param {string} secretsPath - Path where to create secrets file
|
|
240
|
+
* @returns {Promise<void>} Resolves when file is created
|
|
241
|
+
* @throws {Error} If file creation fails
|
|
242
|
+
*
|
|
243
|
+
* @example
|
|
244
|
+
* await createDefaultSecrets('~/.aifabrix/secrets.yaml');
|
|
245
|
+
* // Default secrets file is created
|
|
246
|
+
*/
|
|
247
|
+
async function createDefaultSecrets(secretsPath) {
|
|
248
|
+
const resolvedPath = secretsPath.startsWith('~')
|
|
249
|
+
? path.join(os.homedir(), secretsPath.slice(1))
|
|
250
|
+
: secretsPath;
|
|
251
|
+
|
|
252
|
+
const dir = path.dirname(resolvedPath);
|
|
253
|
+
if (!fs.existsSync(dir)) {
|
|
254
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const defaultSecrets = `# Local Development Secrets
|
|
258
|
+
# Production uses Azure KeyVault
|
|
259
|
+
|
|
260
|
+
# Database Secrets
|
|
261
|
+
postgres-passwordKeyVault: "admin123"
|
|
262
|
+
|
|
263
|
+
# Redis Secrets
|
|
264
|
+
redis-passwordKeyVault: ""
|
|
265
|
+
redis-urlKeyVault: "redis://\${REDIS_HOST}:6379"
|
|
266
|
+
|
|
267
|
+
# Keycloak Secrets
|
|
268
|
+
keycloak-admin-passwordKeyVault: "admin123"
|
|
269
|
+
keycloak-auth-server-urlKeyVault: "http://\${KEYCLOAK_HOST}:8082"
|
|
270
|
+
`;
|
|
271
|
+
|
|
272
|
+
fs.writeFileSync(resolvedPath, defaultSecrets, { mode: 0o600 });
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
module.exports = {
|
|
276
|
+
loadSecrets,
|
|
277
|
+
resolveKvReferences,
|
|
278
|
+
generateEnvFile,
|
|
279
|
+
generateAdminSecretsEnv,
|
|
280
|
+
validateSecrets,
|
|
281
|
+
createDefaultSecrets
|
|
282
|
+
};
|
package/lib/templates.js
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* YAML Template Generation Module
|
|
3
|
+
*
|
|
4
|
+
* Generates configuration files for AI Fabrix applications
|
|
5
|
+
* following ISO 27001 security standards
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const yaml = require('js-yaml');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Generate variables.yaml content for an application
|
|
12
|
+
* @param {string} appName - Application name
|
|
13
|
+
* @param {Object} config - Configuration options
|
|
14
|
+
* @returns {string} YAML content
|
|
15
|
+
*/
|
|
16
|
+
function generateVariablesYaml(appName, config) {
|
|
17
|
+
const variables = {
|
|
18
|
+
app: {
|
|
19
|
+
key: appName,
|
|
20
|
+
name: appName.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase()),
|
|
21
|
+
description: `${appName.replace(/-/g, ' ')} application`,
|
|
22
|
+
version: '1.0.0'
|
|
23
|
+
},
|
|
24
|
+
build: {
|
|
25
|
+
language: config.language || 'typescript',
|
|
26
|
+
port: parseInt(config.port, 10) || 3000,
|
|
27
|
+
environment: 'development'
|
|
28
|
+
},
|
|
29
|
+
services: {
|
|
30
|
+
database: config.database || false,
|
|
31
|
+
redis: config.redis || false,
|
|
32
|
+
storage: config.storage || false,
|
|
33
|
+
authentication: config.authentication || false
|
|
34
|
+
},
|
|
35
|
+
security: {
|
|
36
|
+
enableRBAC: config.authentication || false,
|
|
37
|
+
requireAuth: config.authentication || false,
|
|
38
|
+
auditLogging: true
|
|
39
|
+
},
|
|
40
|
+
monitoring: {
|
|
41
|
+
healthCheck: true,
|
|
42
|
+
metrics: true,
|
|
43
|
+
logging: true
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
return yaml.dump(variables, {
|
|
48
|
+
indent: 2,
|
|
49
|
+
lineWidth: 120,
|
|
50
|
+
noRefs: true,
|
|
51
|
+
sortKeys: false
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Generate env.template content with conditional variables
|
|
57
|
+
* @param {Object} config - Configuration options
|
|
58
|
+
* @param {Object} existingEnv - Existing environment variables
|
|
59
|
+
* @returns {string} Environment template content
|
|
60
|
+
*/
|
|
61
|
+
function generateEnvTemplate(config, existingEnv = {}) {
|
|
62
|
+
const envVars = {
|
|
63
|
+
// Core application settings
|
|
64
|
+
'NODE_ENV': 'development',
|
|
65
|
+
'PORT': config.port || 3000,
|
|
66
|
+
'APP_NAME': config.appName || 'myapp',
|
|
67
|
+
'LOG_LEVEL': 'info'
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// Add database variables if enabled
|
|
71
|
+
if (config.database) {
|
|
72
|
+
envVars['DATABASE_URL'] = 'kv://database-url';
|
|
73
|
+
envVars['DB_HOST'] = 'localhost';
|
|
74
|
+
envVars['DB_PORT'] = '5432';
|
|
75
|
+
envVars['DB_NAME'] = config.appName || 'myapp';
|
|
76
|
+
envVars['DB_USER'] = 'kv://database-user';
|
|
77
|
+
envVars['DB_PASSWORD'] = 'kv://database-password';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Add Redis variables if enabled
|
|
81
|
+
if (config.redis) {
|
|
82
|
+
envVars['REDIS_URL'] = 'kv://redis-url';
|
|
83
|
+
envVars['REDIS_HOST'] = 'localhost';
|
|
84
|
+
envVars['REDIS_PORT'] = '6379';
|
|
85
|
+
envVars['REDIS_PASSWORD'] = 'kv://redis-password';
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Add storage variables if enabled
|
|
89
|
+
if (config.storage) {
|
|
90
|
+
envVars['STORAGE_TYPE'] = 'local';
|
|
91
|
+
envVars['STORAGE_PATH'] = '/app/storage';
|
|
92
|
+
envVars['STORAGE_URL'] = 'kv://storage-url';
|
|
93
|
+
envVars['STORAGE_KEY'] = 'kv://storage-key';
|
|
94
|
+
envVars['STORAGE_SECRET'] = 'kv://storage-secret';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Add authentication variables if enabled
|
|
98
|
+
if (config.authentication) {
|
|
99
|
+
envVars['JWT_SECRET'] = 'kv://jwt-secret';
|
|
100
|
+
envVars['JWT_EXPIRES_IN'] = '24h';
|
|
101
|
+
envVars['AUTH_PROVIDER'] = 'local';
|
|
102
|
+
envVars['SESSION_SECRET'] = 'kv://session-secret';
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Merge with existing environment variables
|
|
106
|
+
Object.assign(envVars, existingEnv);
|
|
107
|
+
|
|
108
|
+
// Generate template content
|
|
109
|
+
const lines = [
|
|
110
|
+
'# AI Fabrix Environment Template',
|
|
111
|
+
'# Copy this file to .env and fill in the actual values',
|
|
112
|
+
'# Values marked with kv:// are secrets stored in Azure Key Vault',
|
|
113
|
+
'',
|
|
114
|
+
'# Core Application Settings',
|
|
115
|
+
''
|
|
116
|
+
];
|
|
117
|
+
|
|
118
|
+
// Add core variables
|
|
119
|
+
Object.entries(envVars).forEach(([key, value]) => {
|
|
120
|
+
if (key.startsWith('NODE_ENV') || key.startsWith('PORT') ||
|
|
121
|
+
key.startsWith('APP_NAME') || key.startsWith('LOG_LEVEL')) {
|
|
122
|
+
lines.push(`${key}=${value}`);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Add service-specific sections
|
|
127
|
+
if (config.database) {
|
|
128
|
+
lines.push('', '# Database Configuration', '');
|
|
129
|
+
Object.entries(envVars).forEach(([key, value]) => {
|
|
130
|
+
if (key.startsWith('DB_') || key.startsWith('DATABASE_')) {
|
|
131
|
+
lines.push(`${key}=${value}`);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (config.redis) {
|
|
137
|
+
lines.push('', '# Redis Configuration', '');
|
|
138
|
+
Object.entries(envVars).forEach(([key, value]) => {
|
|
139
|
+
if (key.startsWith('REDIS_')) {
|
|
140
|
+
lines.push(`${key}=${value}`);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (config.storage) {
|
|
146
|
+
lines.push('', '# Storage Configuration', '');
|
|
147
|
+
Object.entries(envVars).forEach(([key, value]) => {
|
|
148
|
+
if (key.startsWith('STORAGE_')) {
|
|
149
|
+
lines.push(`${key}=${value}`);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (config.authentication) {
|
|
155
|
+
lines.push('', '# Authentication Configuration', '');
|
|
156
|
+
Object.entries(envVars).forEach(([key, value]) => {
|
|
157
|
+
if (key.startsWith('JWT_') || key.startsWith('AUTH_') || key.startsWith('SESSION_')) {
|
|
158
|
+
lines.push(`${key}=${value}`);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return lines.join('\n');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Generate rbac.yaml content for RBAC configuration
|
|
168
|
+
* @param {string} appName - Application name
|
|
169
|
+
* @param {Object} config - Configuration options
|
|
170
|
+
* @returns {string} RBAC YAML content
|
|
171
|
+
*/
|
|
172
|
+
function generateRbacYaml(appName, config) {
|
|
173
|
+
if (!config.authentication) {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const rbac = {
|
|
178
|
+
apiVersion: 'v1',
|
|
179
|
+
kind: 'RBACConfig',
|
|
180
|
+
metadata: {
|
|
181
|
+
name: `${appName}-rbac`,
|
|
182
|
+
namespace: 'default'
|
|
183
|
+
},
|
|
184
|
+
spec: {
|
|
185
|
+
roles: [
|
|
186
|
+
{
|
|
187
|
+
name: 'admin',
|
|
188
|
+
description: 'Full administrative access',
|
|
189
|
+
permissions: ['*']
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
name: 'user',
|
|
193
|
+
description: 'Standard user access',
|
|
194
|
+
permissions: ['read', 'write']
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
name: 'viewer',
|
|
198
|
+
description: 'Read-only access',
|
|
199
|
+
permissions: ['read']
|
|
200
|
+
}
|
|
201
|
+
],
|
|
202
|
+
policies: [
|
|
203
|
+
{
|
|
204
|
+
name: 'admin-policy',
|
|
205
|
+
role: 'admin',
|
|
206
|
+
resources: ['*'],
|
|
207
|
+
actions: ['*']
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
name: 'user-policy',
|
|
211
|
+
role: 'user',
|
|
212
|
+
resources: ['data', 'profile'],
|
|
213
|
+
actions: ['read', 'write']
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
name: 'viewer-policy',
|
|
217
|
+
role: 'viewer',
|
|
218
|
+
resources: ['data'],
|
|
219
|
+
actions: ['read']
|
|
220
|
+
}
|
|
221
|
+
],
|
|
222
|
+
bindings: [
|
|
223
|
+
{
|
|
224
|
+
name: 'admin-binding',
|
|
225
|
+
role: 'admin',
|
|
226
|
+
subjects: [
|
|
227
|
+
{
|
|
228
|
+
kind: 'User',
|
|
229
|
+
name: 'admin@example.com'
|
|
230
|
+
}
|
|
231
|
+
]
|
|
232
|
+
}
|
|
233
|
+
]
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
return yaml.dump(rbac, {
|
|
238
|
+
indent: 2,
|
|
239
|
+
lineWidth: 120,
|
|
240
|
+
noRefs: true,
|
|
241
|
+
sortKeys: false
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Generate secrets.yaml content for sensitive values
|
|
247
|
+
* @param {Object} config - Configuration options
|
|
248
|
+
* @param {Object} existingSecrets - Existing secrets from .env
|
|
249
|
+
* @returns {string} Secrets YAML content
|
|
250
|
+
*/
|
|
251
|
+
function generateSecretsYaml(config, existingSecrets = {}) {
|
|
252
|
+
const secrets = {
|
|
253
|
+
apiVersion: 'v1',
|
|
254
|
+
kind: 'Secret',
|
|
255
|
+
metadata: {
|
|
256
|
+
name: 'app-secrets',
|
|
257
|
+
namespace: 'default'
|
|
258
|
+
},
|
|
259
|
+
type: 'Opaque',
|
|
260
|
+
data: {}
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
// Add secrets based on enabled services
|
|
264
|
+
if (config.database) {
|
|
265
|
+
secrets.data['database-password'] = 'base64-encoded-password';
|
|
266
|
+
secrets.data['database-user'] = 'base64-encoded-user';
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (config.redis) {
|
|
270
|
+
secrets.data['redis-password'] = 'base64-encoded-redis-password';
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (config.storage) {
|
|
274
|
+
secrets.data['storage-key'] = 'base64-encoded-storage-key';
|
|
275
|
+
secrets.data['storage-secret'] = 'base64-encoded-storage-secret';
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (config.authentication) {
|
|
279
|
+
secrets.data['jwt-secret'] = 'base64-encoded-jwt-secret';
|
|
280
|
+
secrets.data['session-secret'] = 'base64-encoded-session-secret';
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Add existing secrets
|
|
284
|
+
Object.entries(existingSecrets).forEach(([key, value]) => {
|
|
285
|
+
secrets.data[key] = Buffer.from(value).toString('base64');
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
return yaml.dump(secrets, {
|
|
289
|
+
indent: 2,
|
|
290
|
+
lineWidth: 120,
|
|
291
|
+
noRefs: true,
|
|
292
|
+
sortKeys: false
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
module.exports = {
|
|
297
|
+
generateVariablesYaml,
|
|
298
|
+
generateEnvTemplate,
|
|
299
|
+
generateRbacYaml,
|
|
300
|
+
generateSecretsYaml
|
|
301
|
+
};
|