@aifabrix/builder 2.1.6 ā 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/app-deploy.js +73 -29
- package/lib/app-list.js +132 -0
- package/lib/app-readme.js +11 -4
- package/lib/app-register.js +435 -0
- package/lib/app-rotate-secret.js +164 -0
- package/lib/app-run.js +98 -84
- package/lib/app.js +13 -0
- package/lib/audit-logger.js +195 -15
- package/lib/build.js +57 -37
- package/lib/cli.js +90 -8
- package/lib/commands/app.js +8 -391
- package/lib/commands/login.js +130 -36
- package/lib/config.js +257 -4
- package/lib/deployer.js +221 -183
- package/lib/infra.js +177 -112
- package/lib/secrets.js +85 -99
- package/lib/utils/api-error-handler.js +465 -0
- package/lib/utils/api.js +165 -16
- package/lib/utils/auth-headers.js +84 -0
- package/lib/utils/build-copy.js +144 -0
- package/lib/utils/cli-utils.js +21 -0
- package/lib/utils/compose-generator.js +43 -14
- package/lib/utils/deployment-errors.js +90 -0
- package/lib/utils/deployment-validation.js +60 -0
- package/lib/utils/dev-config.js +83 -0
- package/lib/utils/env-template.js +30 -10
- package/lib/utils/health-check.js +18 -1
- package/lib/utils/infra-containers.js +101 -0
- package/lib/utils/local-secrets.js +0 -2
- package/lib/utils/secrets-path.js +18 -21
- package/lib/utils/secrets-utils.js +206 -0
- package/lib/utils/token-manager.js +381 -0
- package/package.json +1 -1
- package/templates/applications/README.md.hbs +155 -23
- package/templates/applications/miso-controller/Dockerfile +7 -119
- package/templates/infra/compose.yaml.hbs +93 -0
- package/templates/python/docker-compose.hbs +25 -17
- package/templates/typescript/docker-compose.hbs +25 -17
package/lib/deployer.js
CHANGED
|
@@ -13,110 +13,151 @@ const axios = require('axios');
|
|
|
13
13
|
const chalk = require('chalk');
|
|
14
14
|
const auditLogger = require('./audit-logger');
|
|
15
15
|
const logger = require('./utils/logger');
|
|
16
|
+
const { createAuthHeaders } = require('./utils/auth-headers');
|
|
17
|
+
const { validateControllerUrl, validateEnvironmentKey } = require('./utils/deployment-validation');
|
|
18
|
+
const { handleDeploymentError, handleDeploymentErrors } = require('./utils/deployment-errors');
|
|
16
19
|
|
|
17
20
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* @param {string}
|
|
22
|
-
* @
|
|
21
|
+
* Handles deployment request retry logic
|
|
22
|
+
* @async
|
|
23
|
+
* @function handleDeploymentRetry
|
|
24
|
+
* @param {string} endpoint - API endpoint
|
|
25
|
+
* @param {Object} manifest - Deployment manifest
|
|
26
|
+
* @param {Object} requestConfig - Request configuration
|
|
27
|
+
* @param {number} maxRetries - Maximum retry attempts
|
|
28
|
+
* @returns {Promise<Object>} Deployment result
|
|
29
|
+
* @throws {Error} If deployment fails after retries
|
|
23
30
|
*/
|
|
24
|
-
function
|
|
25
|
-
|
|
26
|
-
throw new Error('Controller URL is required and must be a string');
|
|
27
|
-
}
|
|
31
|
+
async function handleDeploymentRetry(endpoint, manifest, requestConfig, maxRetries) {
|
|
32
|
+
let lastError;
|
|
28
33
|
|
|
29
|
-
//
|
|
30
|
-
if (!
|
|
31
|
-
throw new Error('
|
|
34
|
+
// Validate manifest before sending
|
|
35
|
+
if (!manifest || typeof manifest !== 'object') {
|
|
36
|
+
throw new Error('Deployment manifest is required and must be an object');
|
|
32
37
|
}
|
|
33
38
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
40
|
+
try {
|
|
41
|
+
// Axios automatically serializes objects to JSON when Content-Type is application/json
|
|
42
|
+
const response = await axios.post(endpoint, manifest, requestConfig);
|
|
39
43
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
44
|
+
if (response.status >= 400) {
|
|
45
|
+
const error = new Error(`Controller returned error: ${response.status} ${response.statusText}`);
|
|
46
|
+
error.status = response.status;
|
|
47
|
+
error.response = {
|
|
48
|
+
status: response.status,
|
|
49
|
+
statusText: response.statusText,
|
|
50
|
+
data: response.data
|
|
51
|
+
};
|
|
52
|
+
error.data = response.data;
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
43
55
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
*/
|
|
52
|
-
function createClientCredentialsHeaders(clientId, clientSecret) {
|
|
53
|
-
if (!clientId || !clientSecret) {
|
|
54
|
-
throw new Error('Client ID and Client Secret are required for authentication');
|
|
55
|
-
}
|
|
56
|
-
return {
|
|
57
|
-
'x-client-id': clientId,
|
|
58
|
-
'x-client-secret': clientSecret
|
|
59
|
-
};
|
|
60
|
-
}
|
|
56
|
+
// OpenAPI spec: Response 202 structure { success: boolean, deploymentId: string, status: string, deploymentUrl?: string, healthCheckUrl?: string, message?: string }
|
|
57
|
+
// Handle both OpenAPI format and legacy format for backward compatibility
|
|
58
|
+
const responseData = response.data;
|
|
59
|
+
if (responseData && typeof responseData === 'object') {
|
|
60
|
+
// OpenAPI format: { success, deploymentId, status, deploymentUrl, healthCheckUrl, message }
|
|
61
|
+
return responseData;
|
|
62
|
+
}
|
|
61
63
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
* @throws {Error} If environment key is invalid
|
|
66
|
-
*/
|
|
67
|
-
function validateEnvironmentKey(envKey) {
|
|
68
|
-
if (!envKey || typeof envKey !== 'string') {
|
|
69
|
-
throw new Error('Environment key is required and must be a string');
|
|
70
|
-
}
|
|
64
|
+
return response.data;
|
|
65
|
+
} catch (error) {
|
|
66
|
+
lastError = error;
|
|
71
67
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
68
|
+
if (attempt < maxRetries) {
|
|
69
|
+
const delay = Math.min(1000 * Math.pow(2, attempt - 1), 5000);
|
|
70
|
+
logger.log(chalk.yellow(`ā ļø Deployment attempt ${attempt} failed, retrying in ${delay}ms...`));
|
|
71
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
75
74
|
}
|
|
76
75
|
|
|
77
|
-
|
|
76
|
+
// Preserve formatted error if available
|
|
77
|
+
const errorMessage = lastError.formatted || lastError.message;
|
|
78
|
+
const error = new Error(`Deployment failed after ${maxRetries} attempts: ${errorMessage}`);
|
|
79
|
+
if (lastError.formatted) {
|
|
80
|
+
error.formatted = lastError.formatted;
|
|
81
|
+
}
|
|
82
|
+
if (lastError.status) {
|
|
83
|
+
error.status = lastError.status;
|
|
84
|
+
}
|
|
85
|
+
if (lastError.data) {
|
|
86
|
+
error.data = lastError.data;
|
|
87
|
+
}
|
|
88
|
+
throw error;
|
|
78
89
|
}
|
|
79
90
|
|
|
80
91
|
/**
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
* @
|
|
85
|
-
* @param {
|
|
86
|
-
* @
|
|
92
|
+
* Validates deployment configuration via validate endpoint
|
|
93
|
+
* This is the first step in the deployment process
|
|
94
|
+
*
|
|
95
|
+
* @async
|
|
96
|
+
* @param {string} url - Controller URL
|
|
97
|
+
* @param {string} envKey - Environment key (miso, dev, tst, pro)
|
|
98
|
+
* @param {Object} manifest - Deployment manifest (applicationConfig)
|
|
99
|
+
* @param {Object} authConfig - Authentication configuration
|
|
100
|
+
* @param {Object} options - Validation options (repositoryUrl, timeout, retries, etc.)
|
|
101
|
+
* @returns {Promise<Object>} Validation result with validateToken
|
|
102
|
+
* @throws {Error} If validation fails
|
|
87
103
|
*/
|
|
88
|
-
function
|
|
89
|
-
|
|
104
|
+
async function validateDeployment(url, envKey, manifest, authConfig, options = {}) {
|
|
105
|
+
const validatedEnvKey = validateEnvironmentKey(envKey);
|
|
106
|
+
const endpoint = `${url}/api/v1/pipeline/${validatedEnvKey}/validate`;
|
|
107
|
+
const timeout = options.timeout || 30000;
|
|
108
|
+
const maxRetries = options.maxRetries || 3;
|
|
109
|
+
|
|
110
|
+
// Extract clientId and clientSecret
|
|
111
|
+
const tokenManager = require('./utils/token-manager');
|
|
112
|
+
const { clientId, clientSecret } = await tokenManager.extractClientCredentials(
|
|
113
|
+
authConfig,
|
|
114
|
+
manifest.key,
|
|
115
|
+
validatedEnvKey,
|
|
116
|
+
options
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
// Build validation request
|
|
120
|
+
const repositoryUrl = options.repositoryUrl || `https://github.com/aifabrix/${manifest.key}`;
|
|
121
|
+
const validationRequest = {
|
|
122
|
+
clientId: clientId,
|
|
123
|
+
clientSecret: clientSecret,
|
|
124
|
+
repositoryUrl: repositoryUrl,
|
|
125
|
+
applicationConfig: manifest
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// Create request config with client credentials headers
|
|
129
|
+
const requestConfig = {
|
|
90
130
|
headers: {
|
|
91
131
|
'Content-Type': 'application/json',
|
|
92
132
|
'User-Agent': 'aifabrix-builder/2.0.0',
|
|
93
|
-
|
|
133
|
+
'x-client-id': clientId,
|
|
134
|
+
'x-client-secret': clientSecret
|
|
94
135
|
},
|
|
95
136
|
timeout,
|
|
96
|
-
validateStatus: (status) => status <
|
|
137
|
+
validateStatus: (status) => status < 600 // Don't throw on any status
|
|
97
138
|
};
|
|
98
|
-
}
|
|
99
139
|
|
|
100
|
-
/**
|
|
101
|
-
* Handles deployment request retry logic
|
|
102
|
-
* @async
|
|
103
|
-
* @function handleDeploymentRetry
|
|
104
|
-
* @param {string} endpoint - API endpoint
|
|
105
|
-
* @param {Object} manifest - Deployment manifest
|
|
106
|
-
* @param {Object} requestConfig - Request configuration
|
|
107
|
-
* @param {number} maxRetries - Maximum retry attempts
|
|
108
|
-
* @returns {Promise<Object>} Deployment result
|
|
109
|
-
* @throws {Error} If deployment fails after retries
|
|
110
|
-
*/
|
|
111
|
-
async function handleDeploymentRetry(endpoint, manifest, requestConfig, maxRetries) {
|
|
112
140
|
let lastError;
|
|
113
|
-
|
|
114
141
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
115
142
|
try {
|
|
116
|
-
const response = await axios.post(endpoint,
|
|
143
|
+
const response = await axios.post(endpoint, validationRequest, requestConfig);
|
|
144
|
+
|
|
145
|
+
// Handle successful validation (200 OK with valid: true)
|
|
146
|
+
if (response.status === 200 && response.data.valid === true) {
|
|
147
|
+
return {
|
|
148
|
+
success: true,
|
|
149
|
+
validateToken: response.data.validateToken,
|
|
150
|
+
draftDeploymentId: response.data.draftDeploymentId,
|
|
151
|
+
imageServer: response.data.imageServer,
|
|
152
|
+
imageUsername: response.data.imageUsername,
|
|
153
|
+
imagePassword: response.data.imagePassword,
|
|
154
|
+
expiresAt: response.data.expiresAt
|
|
155
|
+
};
|
|
156
|
+
}
|
|
117
157
|
|
|
158
|
+
// Handle validation errors
|
|
118
159
|
if (response.status >= 400) {
|
|
119
|
-
const error = new Error(`
|
|
160
|
+
const error = new Error(`Validation request failed: ${response.status} ${response.statusText}`);
|
|
120
161
|
error.status = response.status;
|
|
121
162
|
error.response = {
|
|
122
163
|
status: response.status,
|
|
@@ -126,43 +167,69 @@ async function handleDeploymentRetry(endpoint, manifest, requestConfig, maxRetri
|
|
|
126
167
|
error.data = response.data;
|
|
127
168
|
throw error;
|
|
128
169
|
}
|
|
129
|
-
|
|
130
|
-
return response.data;
|
|
131
170
|
} catch (error) {
|
|
132
171
|
lastError = error;
|
|
133
|
-
|
|
134
|
-
if (
|
|
172
|
+
const shouldRetry = attempt < maxRetries && error.response && error.response.status >= 500;
|
|
173
|
+
if (shouldRetry) {
|
|
135
174
|
const delay = Math.min(1000 * Math.pow(2, attempt - 1), 5000);
|
|
136
|
-
logger.log(chalk.yellow(`ā ļø
|
|
175
|
+
logger.log(chalk.yellow(`ā ļø Validation attempt ${attempt} failed, retrying in ${delay}ms...`));
|
|
137
176
|
await new Promise(resolve => setTimeout(resolve, delay));
|
|
177
|
+
} else {
|
|
178
|
+
throw error;
|
|
138
179
|
}
|
|
139
180
|
}
|
|
140
181
|
}
|
|
141
182
|
|
|
142
|
-
throw
|
|
183
|
+
throw lastError;
|
|
143
184
|
}
|
|
144
185
|
|
|
145
186
|
/**
|
|
146
|
-
* Sends deployment
|
|
187
|
+
* Sends deployment request using validateToken from validation step
|
|
188
|
+
* This is the second step in the deployment process
|
|
147
189
|
*
|
|
148
190
|
* @async
|
|
149
191
|
* @param {string} url - Controller URL
|
|
150
|
-
* @param {string} envKey - Environment key (dev, tst, pro)
|
|
151
|
-
* @param {
|
|
152
|
-
* @param {
|
|
153
|
-
* @param {
|
|
154
|
-
* @param {Object} options - Deployment options (timeout, retries, etc.)
|
|
192
|
+
* @param {string} envKey - Environment key (miso, dev, tst, pro)
|
|
193
|
+
* @param {string} validateToken - Validation token from validate endpoint
|
|
194
|
+
* @param {Object} authConfig - Authentication configuration
|
|
195
|
+
* @param {Object} options - Deployment options (imageTag, timeout, retries, etc.)
|
|
155
196
|
* @returns {Promise<Object>} Deployment result from controller
|
|
156
197
|
* @throws {Error} If deployment fails
|
|
157
198
|
*/
|
|
158
|
-
async function sendDeploymentRequest(url, envKey,
|
|
199
|
+
async function sendDeploymentRequest(url, envKey, validateToken, authConfig, options = {}) {
|
|
159
200
|
const validatedEnvKey = validateEnvironmentKey(envKey);
|
|
160
201
|
const endpoint = `${url}/api/v1/pipeline/${validatedEnvKey}/deploy`;
|
|
161
202
|
const timeout = options.timeout || 30000;
|
|
162
203
|
const maxRetries = options.maxRetries || 3;
|
|
163
|
-
const requestConfig = createDeploymentRequestConfig(clientId, clientSecret, timeout);
|
|
164
204
|
|
|
165
|
-
|
|
205
|
+
// Extract clientId and clientSecret for deploy endpoint
|
|
206
|
+
// These should have been loaded during validation and stored in authConfig
|
|
207
|
+
if (!authConfig.clientId || !authConfig.clientSecret) {
|
|
208
|
+
throw new Error('Client ID and Client Secret are required for deployment. These should have been loaded during validation.');
|
|
209
|
+
}
|
|
210
|
+
const clientId = authConfig.clientId;
|
|
211
|
+
const clientSecret = authConfig.clientSecret;
|
|
212
|
+
|
|
213
|
+
// Build deployment request
|
|
214
|
+
const imageTag = options.imageTag || 'latest';
|
|
215
|
+
const deployRequest = {
|
|
216
|
+
validateToken: validateToken,
|
|
217
|
+
imageTag: imageTag
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
// Create request config with client credentials headers
|
|
221
|
+
const requestConfig = {
|
|
222
|
+
headers: {
|
|
223
|
+
'Content-Type': 'application/json',
|
|
224
|
+
'User-Agent': 'aifabrix-builder/2.0.0',
|
|
225
|
+
'x-client-id': clientId,
|
|
226
|
+
'x-client-secret': clientSecret
|
|
227
|
+
},
|
|
228
|
+
timeout,
|
|
229
|
+
validateStatus: (status) => status < 500
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
return handleDeploymentRetry(endpoint, deployRequest, requestConfig, maxRetries);
|
|
166
233
|
}
|
|
167
234
|
|
|
168
235
|
/**
|
|
@@ -177,39 +244,46 @@ function isTerminalStatus(status) {
|
|
|
177
244
|
|
|
178
245
|
/**
|
|
179
246
|
* Polls deployment status from controller
|
|
247
|
+
* Uses pipeline endpoint for CI/CD monitoring with minimal deployment info
|
|
180
248
|
*
|
|
181
249
|
* @async
|
|
182
250
|
* @param {string} deploymentId - Deployment ID to poll
|
|
183
251
|
* @param {string} controllerUrl - Controller URL
|
|
184
252
|
* @param {string} envKey - Environment key
|
|
185
|
-
* @param {
|
|
186
|
-
* @param {string} clientSecret - Client Secret for authentication
|
|
253
|
+
* @param {Object} authConfig - Authentication configuration
|
|
187
254
|
* @param {Object} options - Polling options (interval, maxAttempts, etc.)
|
|
188
255
|
* @returns {Promise<Object>} Deployment status
|
|
189
256
|
*/
|
|
190
|
-
async function pollDeploymentStatus(deploymentId, controllerUrl, envKey,
|
|
257
|
+
async function pollDeploymentStatus(deploymentId, controllerUrl, envKey, authConfig, options = {}) {
|
|
191
258
|
const interval = options.interval || 5000;
|
|
192
259
|
const maxAttempts = options.maxAttempts || 60;
|
|
193
260
|
|
|
194
261
|
const validatedEnvKey = validateEnvironmentKey(envKey);
|
|
195
|
-
const statusEndpoint = `${controllerUrl}/api/v1/
|
|
262
|
+
const statusEndpoint = `${controllerUrl}/api/v1/pipeline/${validatedEnvKey}/deployments/${deploymentId}`;
|
|
196
263
|
|
|
197
264
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
198
265
|
try {
|
|
199
266
|
const response = await axios.get(statusEndpoint, {
|
|
200
|
-
headers:
|
|
267
|
+
headers: {
|
|
268
|
+
'Content-Type': 'application/json',
|
|
269
|
+
...createAuthHeaders(authConfig)
|
|
270
|
+
},
|
|
201
271
|
timeout: 10000,
|
|
202
272
|
validateStatus: (status) => status < 500
|
|
203
273
|
});
|
|
204
274
|
|
|
205
275
|
if (response.status === 200) {
|
|
206
|
-
|
|
276
|
+
// OpenAPI spec: Response structure { success: boolean, data: { status, progress, ... }, timestamp: string }
|
|
277
|
+
const responseData = response.data;
|
|
278
|
+
const deploymentData = responseData.data || responseData;
|
|
279
|
+
const status = deploymentData.status;
|
|
207
280
|
|
|
208
281
|
if (isTerminalStatus(status)) {
|
|
209
|
-
return
|
|
282
|
+
return deploymentData;
|
|
210
283
|
}
|
|
211
284
|
|
|
212
|
-
|
|
285
|
+
const progress = deploymentData.progress || 0;
|
|
286
|
+
logger.log(chalk.blue(` Status: ${status} (${progress}%) (attempt ${attempt + 1}/${maxAttempts})`));
|
|
213
287
|
|
|
214
288
|
if (attempt < maxAttempts - 1) {
|
|
215
289
|
await new Promise(resolve => setTimeout(resolve, interval));
|
|
@@ -229,46 +303,45 @@ async function pollDeploymentStatus(deploymentId, controllerUrl, envKey, clientI
|
|
|
229
303
|
}
|
|
230
304
|
|
|
231
305
|
/**
|
|
232
|
-
*
|
|
233
|
-
*
|
|
234
|
-
* @param {Error} error - Error to handle
|
|
235
|
-
* @returns {Object} Structured error information
|
|
236
|
-
*/
|
|
237
|
-
function handleDeploymentError(error) {
|
|
238
|
-
const safeError = {
|
|
239
|
-
message: error.message,
|
|
240
|
-
code: error.code || 'UNKNOWN',
|
|
241
|
-
timeout: error.code === 'ECONNABORTED',
|
|
242
|
-
status: error.status || error.response?.status,
|
|
243
|
-
data: error.data || error.response?.data
|
|
244
|
-
};
|
|
245
|
-
|
|
246
|
-
// Mask sensitive information in error messages
|
|
247
|
-
safeError.message = auditLogger.maskSensitiveData(safeError.message);
|
|
248
|
-
|
|
249
|
-
return safeError;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
/**
|
|
253
|
-
* Sends deployment request to controller
|
|
306
|
+
* Validates and sends deployment request to controller
|
|
307
|
+
* Implements two-step process: validate then deploy
|
|
254
308
|
* @async
|
|
255
309
|
* @param {string} url - Controller URL
|
|
256
310
|
* @param {string} validatedEnvKey - Validated environment key
|
|
257
311
|
* @param {Object} manifest - Deployment manifest
|
|
258
|
-
* @param {
|
|
259
|
-
* @param {string} clientSecret - Client Secret
|
|
312
|
+
* @param {Object} authConfig - Authentication configuration
|
|
260
313
|
* @param {Object} options - Deployment options
|
|
261
314
|
* @returns {Promise<Object>} Deployment result
|
|
262
315
|
*/
|
|
263
|
-
async function sendDeployment(url, validatedEnvKey, manifest,
|
|
264
|
-
|
|
265
|
-
|
|
316
|
+
async function sendDeployment(url, validatedEnvKey, manifest, authConfig, options) {
|
|
317
|
+
// Step 1: Validate deployment
|
|
318
|
+
logger.log(chalk.blue('š Validating deployment configuration...'));
|
|
319
|
+
const validateResult = await validateDeployment(url, validatedEnvKey, manifest, authConfig, {
|
|
320
|
+
repositoryUrl: options.repositoryUrl,
|
|
321
|
+
controllerId: options.controllerId,
|
|
322
|
+
timeout: options.timeout || 30000,
|
|
323
|
+
maxRetries: options.maxRetries || 3
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
if (!validateResult.success || !validateResult.validateToken) {
|
|
327
|
+
throw new Error('Validation failed: validateToken not received');
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
logger.log(chalk.green('ā Validation successful'));
|
|
331
|
+
if (validateResult.draftDeploymentId) {
|
|
332
|
+
logger.log(chalk.gray(` Draft Deployment ID: ${validateResult.draftDeploymentId}`));
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Step 2: Deploy using validateToken
|
|
336
|
+
logger.log(chalk.blue('\nš Deploying application...'));
|
|
337
|
+
const result = await sendDeploymentRequest(url, validatedEnvKey, validateResult.validateToken, authConfig, {
|
|
338
|
+
imageTag: options.imageTag || 'latest',
|
|
266
339
|
timeout: options.timeout || 30000,
|
|
267
340
|
maxRetries: options.maxRetries || 3
|
|
268
341
|
});
|
|
269
342
|
|
|
270
343
|
if (result.deploymentId) {
|
|
271
|
-
auditLogger.logDeploymentSuccess(manifest.key, result.deploymentId, url);
|
|
344
|
+
await auditLogger.logDeploymentSuccess(manifest.key, result.deploymentId, url);
|
|
272
345
|
}
|
|
273
346
|
|
|
274
347
|
return result;
|
|
@@ -280,12 +353,11 @@ async function sendDeployment(url, validatedEnvKey, manifest, clientId, clientSe
|
|
|
280
353
|
* @param {Object} result - Deployment result
|
|
281
354
|
* @param {string} url - Controller URL
|
|
282
355
|
* @param {string} validatedEnvKey - Validated environment key
|
|
283
|
-
* @param {
|
|
284
|
-
* @param {string} clientSecret - Client Secret
|
|
356
|
+
* @param {Object} authConfig - Authentication configuration
|
|
285
357
|
* @param {Object} options - Deployment options
|
|
286
358
|
* @returns {Promise<Object>} Deployment result with status
|
|
287
359
|
*/
|
|
288
|
-
async function pollDeployment(result, url, validatedEnvKey,
|
|
360
|
+
async function pollDeployment(result, url, validatedEnvKey, authConfig, options) {
|
|
289
361
|
if (!options.poll || !result.deploymentId) {
|
|
290
362
|
return result;
|
|
291
363
|
}
|
|
@@ -295,8 +367,7 @@ async function pollDeployment(result, url, validatedEnvKey, clientId, clientSecr
|
|
|
295
367
|
result.deploymentId,
|
|
296
368
|
url,
|
|
297
369
|
validatedEnvKey,
|
|
298
|
-
|
|
299
|
-
clientSecret,
|
|
370
|
+
authConfig,
|
|
300
371
|
{
|
|
301
372
|
interval: options.pollInterval || 5000,
|
|
302
373
|
maxAttempts: options.pollMaxAttempts || 60
|
|
@@ -308,65 +379,30 @@ async function pollDeployment(result, url, validatedEnvKey, clientId, clientSecr
|
|
|
308
379
|
}
|
|
309
380
|
|
|
310
381
|
/**
|
|
311
|
-
*
|
|
312
|
-
*
|
|
313
|
-
* @param {string} manifestKey - Manifest key for audit logging
|
|
314
|
-
* @param {string} url - Controller URL for audit logging
|
|
315
|
-
* @throws {Error} User-friendly error message
|
|
316
|
-
*/
|
|
317
|
-
function handleDeploymentErrors(error, manifestKey, url) {
|
|
318
|
-
auditLogger.logDeploymentFailure(manifestKey, url, error);
|
|
319
|
-
|
|
320
|
-
const safeError = handleDeploymentError(error);
|
|
321
|
-
|
|
322
|
-
if (safeError.status === 401 || safeError.status === 403) {
|
|
323
|
-
throw new Error('Authentication failed. Check your client ID and client secret.');
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
if (safeError.status === 400) {
|
|
327
|
-
throw new Error('Invalid deployment manifest. Please check your configuration.');
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
if (safeError.status === 404) {
|
|
331
|
-
throw new Error('Controller endpoint not found. Check the controller URL and environment.');
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
if (safeError.code === 'ECONNREFUSED') {
|
|
335
|
-
throw new Error('Cannot connect to controller. Check if the controller is running.');
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
if (safeError.code === 'ENOTFOUND') {
|
|
339
|
-
throw new Error('Controller hostname not found. Check your controller URL.');
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
if (safeError.timeout) {
|
|
343
|
-
throw new Error('Request timed out. The controller may be overloaded.');
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
throw new Error(safeError.message);
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
/**
|
|
350
|
-
* Deploys application to Miso Controller with Client Credentials authentication
|
|
382
|
+
* Deploys application to Miso Controller with authentication
|
|
383
|
+
* Supports both Bearer token and client credentials authentication
|
|
351
384
|
* Main orchestrator for the deployment process
|
|
352
385
|
*
|
|
353
386
|
* @async
|
|
354
387
|
* @param {Object} manifest - Deployment manifest
|
|
355
388
|
* @param {string} controllerUrl - Controller URL
|
|
356
|
-
* @param {string} envKey - Environment key (dev, tst, pro)
|
|
357
|
-
* @param {
|
|
358
|
-
* @param {string}
|
|
389
|
+
* @param {string} envKey - Environment key (miso, dev, tst, pro)
|
|
390
|
+
* @param {Object} authConfig - Authentication configuration
|
|
391
|
+
* @param {string} authConfig.type - Auth type: 'bearer' or 'credentials'
|
|
392
|
+
* @param {string} [authConfig.token] - Bearer token (for type 'bearer')
|
|
393
|
+
* @param {string} [authConfig.clientId] - Client ID (for type 'credentials')
|
|
394
|
+
* @param {string} [authConfig.clientSecret] - Client secret (for type 'credentials')
|
|
359
395
|
* @param {Object} options - Deployment options
|
|
360
396
|
* @returns {Promise<Object>} Deployment result
|
|
361
397
|
* @throws {Error} If deployment fails
|
|
362
398
|
*/
|
|
363
|
-
async function deployToController(manifest, controllerUrl, envKey,
|
|
399
|
+
async function deployToController(manifest, controllerUrl, envKey, authConfig, options = {}) {
|
|
364
400
|
// Validate inputs
|
|
365
401
|
if (!envKey) {
|
|
366
402
|
throw new Error('Environment key is required');
|
|
367
403
|
}
|
|
368
|
-
if (!
|
|
369
|
-
throw new Error('
|
|
404
|
+
if (!authConfig || !authConfig.type) {
|
|
405
|
+
throw new Error('Authentication configuration is required');
|
|
370
406
|
}
|
|
371
407
|
|
|
372
408
|
// Validate and sanitize controller URL
|
|
@@ -374,27 +410,29 @@ async function deployToController(manifest, controllerUrl, envKey, clientId, cli
|
|
|
374
410
|
const validatedEnvKey = validateEnvironmentKey(envKey);
|
|
375
411
|
|
|
376
412
|
// Log deployment attempt for audit
|
|
377
|
-
auditLogger.logDeploymentAttempt(manifest.key, url, options);
|
|
413
|
+
await auditLogger.logDeploymentAttempt(manifest.key, url, options);
|
|
378
414
|
|
|
379
415
|
try {
|
|
380
416
|
// Send deployment request
|
|
381
|
-
const result = await sendDeployment(url, validatedEnvKey, manifest,
|
|
417
|
+
const result = await sendDeployment(url, validatedEnvKey, manifest, authConfig, options);
|
|
382
418
|
|
|
383
419
|
// Poll for deployment status if enabled
|
|
384
|
-
return await pollDeployment(result, url, validatedEnvKey,
|
|
420
|
+
return await pollDeployment(result, url, validatedEnvKey, authConfig, options);
|
|
385
421
|
|
|
386
422
|
} catch (error) {
|
|
387
|
-
|
|
423
|
+
// Use unified error handler (already logged in deployToController, so pass alreadyLogged=true)
|
|
424
|
+
await handleDeploymentErrors(error, manifest.key, url, false);
|
|
388
425
|
}
|
|
389
426
|
}
|
|
390
427
|
|
|
391
428
|
module.exports = {
|
|
392
429
|
deployToController,
|
|
393
430
|
sendDeploymentRequest,
|
|
431
|
+
validateDeployment,
|
|
432
|
+
handleDeploymentErrors,
|
|
394
433
|
pollDeploymentStatus,
|
|
395
434
|
validateControllerUrl,
|
|
396
435
|
handleDeploymentError,
|
|
397
|
-
createClientCredentialsHeaders,
|
|
398
436
|
validateEnvironmentKey
|
|
399
437
|
};
|
|
400
438
|
|