@aifabrix/builder 2.11.0 → 2.21.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/.cursor/rules/project-rules.mdc +194 -0
- package/README.md +12 -0
- package/lib/api/applications.api.js +164 -0
- package/lib/api/auth.api.js +303 -0
- package/lib/api/datasources-core.api.js +87 -0
- package/lib/api/datasources-extended.api.js +117 -0
- package/lib/api/datasources.api.js +13 -0
- package/lib/api/deployments.api.js +126 -0
- package/lib/api/environments.api.js +245 -0
- package/lib/api/external-systems.api.js +251 -0
- package/lib/api/index.js +236 -0
- package/lib/api/pipeline.api.js +234 -0
- package/lib/api/types/applications.types.js +136 -0
- package/lib/api/types/auth.types.js +218 -0
- package/lib/api/types/datasources.types.js +272 -0
- package/lib/api/types/deployments.types.js +184 -0
- package/lib/api/types/environments.types.js +197 -0
- package/lib/api/types/external-systems.types.js +244 -0
- package/lib/api/types/pipeline.types.js +125 -0
- package/lib/app-list.js +5 -7
- package/lib/app-rotate-secret.js +4 -10
- package/lib/cli.js +30 -0
- package/lib/commands/login.js +41 -12
- package/lib/datasource-deploy.js +7 -30
- package/lib/datasource-list.js +9 -6
- package/lib/deployer.js +103 -135
- package/lib/environment-deploy.js +15 -26
- package/lib/external-system-deploy.js +12 -39
- package/lib/external-system-download.js +5 -13
- package/lib/external-system-test.js +9 -12
- package/lib/generator-split.js +342 -0
- package/lib/generator.js +94 -5
- package/lib/utils/app-register-api.js +5 -10
- package/lib/utils/deployment-errors.js +88 -6
- package/package.json +1 -1
- package/tatus +0 -181
package/lib/commands/login.js
CHANGED
|
@@ -13,7 +13,8 @@ const inquirer = require('inquirer');
|
|
|
13
13
|
const chalk = require('chalk');
|
|
14
14
|
const ora = require('ora');
|
|
15
15
|
const { setCurrentEnvironment, saveDeviceToken, saveClientToken } = require('../config');
|
|
16
|
-
const {
|
|
16
|
+
const { getToken, initiateDeviceCodeFlow } = require('../api/auth.api');
|
|
17
|
+
const { pollDeviceCodeToken, displayDeviceCodeInfo } = require('../utils/api');
|
|
17
18
|
const { formatApiError } = require('../utils/api-error-handler');
|
|
18
19
|
const { loadClientCredentials } = require('../utils/token-manager');
|
|
19
20
|
const logger = require('../utils/logger');
|
|
@@ -187,16 +188,8 @@ async function handleCredentialsLogin(controllerUrl, appName, clientId, clientSe
|
|
|
187
188
|
credentials = await promptForCredentials(clientId, clientSecret);
|
|
188
189
|
}
|
|
189
190
|
|
|
190
|
-
//
|
|
191
|
-
|
|
192
|
-
const response = await makeApiCall(`${controllerUrl}/api/v1/auth/token`, {
|
|
193
|
-
method: 'POST',
|
|
194
|
-
headers: {
|
|
195
|
-
'Content-Type': 'application/json',
|
|
196
|
-
'x-client-id': credentials.clientId,
|
|
197
|
-
'x-client-secret': credentials.clientSecret
|
|
198
|
-
}
|
|
199
|
-
});
|
|
191
|
+
// Use centralized API client for token generation
|
|
192
|
+
const response = await getToken(credentials.clientId, credentials.clientSecret, controllerUrl);
|
|
200
193
|
|
|
201
194
|
if (!response.success) {
|
|
202
195
|
const formattedError = response.formattedError || formatApiError(response);
|
|
@@ -346,7 +339,43 @@ async function handleDeviceCodeLogin(controllerUrl, environment, offline, scope)
|
|
|
346
339
|
}
|
|
347
340
|
|
|
348
341
|
try {
|
|
349
|
-
|
|
342
|
+
// Use centralized API client for device code flow initiation
|
|
343
|
+
const deviceCodeApiResponse = await initiateDeviceCodeFlow(controllerUrl, envKey, requestScope);
|
|
344
|
+
|
|
345
|
+
// Validate response structure
|
|
346
|
+
if (!deviceCodeApiResponse) {
|
|
347
|
+
throw new Error('Device code flow initiation returned no response');
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Check if API call was successful
|
|
351
|
+
if (!deviceCodeApiResponse.success) {
|
|
352
|
+
const errorMessage = deviceCodeApiResponse.formattedError ||
|
|
353
|
+
deviceCodeApiResponse.error ||
|
|
354
|
+
'Device code flow initiation failed';
|
|
355
|
+
const error = new Error(errorMessage);
|
|
356
|
+
// Preserve formattedError for better error display
|
|
357
|
+
if (deviceCodeApiResponse.formattedError) {
|
|
358
|
+
error.formattedError = deviceCodeApiResponse.formattedError;
|
|
359
|
+
}
|
|
360
|
+
throw error;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Handle API response format: { success: boolean, data: DeviceCodeResponse }
|
|
364
|
+
if (!deviceCodeApiResponse.data) {
|
|
365
|
+
throw new Error('Device code flow initiation returned no data');
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const apiResponse = deviceCodeApiResponse.data;
|
|
369
|
+
const deviceCodeData = apiResponse.data || apiResponse;
|
|
370
|
+
|
|
371
|
+
// Convert camelCase from API to snake_case for compatibility with existing code
|
|
372
|
+
const deviceCodeResponse = {
|
|
373
|
+
device_code: deviceCodeData.deviceCode || deviceCodeData.device_code,
|
|
374
|
+
user_code: deviceCodeData.userCode || deviceCodeData.user_code,
|
|
375
|
+
verification_uri: deviceCodeData.verificationUri || deviceCodeData.verification_uri,
|
|
376
|
+
expires_in: deviceCodeData.expiresIn || deviceCodeData.expires_in || 600,
|
|
377
|
+
interval: deviceCodeData.interval || 5
|
|
378
|
+
};
|
|
350
379
|
|
|
351
380
|
displayDeviceCodeInfo(deviceCodeResponse.user_code, deviceCodeResponse.verification_uri, logger, chalk);
|
|
352
381
|
|
package/lib/datasource-deploy.js
CHANGED
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
const fs = require('fs');
|
|
13
13
|
const chalk = require('chalk');
|
|
14
14
|
const { getDeploymentAuth } = require('./utils/token-manager');
|
|
15
|
-
const {
|
|
15
|
+
const { getEnvironmentApplication } = require('./api/environments.api');
|
|
16
|
+
const { publishDatasourceViaPipeline } = require('./api/pipeline.api');
|
|
16
17
|
const { formatApiError } = require('./utils/api-error-handler');
|
|
17
18
|
const logger = require('./utils/logger');
|
|
18
19
|
const { validateDatasourceFile } = require('./datasource-validate');
|
|
@@ -30,18 +31,8 @@ const { validateDatasourceFile } = require('./datasource-validate');
|
|
|
30
31
|
* @throws {Error} If dataplane URL cannot be retrieved
|
|
31
32
|
*/
|
|
32
33
|
async function getDataplaneUrl(controllerUrl, appKey, environment, authConfig) {
|
|
33
|
-
// Call controller API to get application details
|
|
34
|
-
|
|
35
|
-
const endpoint = `${controllerUrl}/api/v1/environments/${environment}/applications/${appKey}`;
|
|
36
|
-
|
|
37
|
-
let response;
|
|
38
|
-
if (authConfig.type === 'bearer' && authConfig.token) {
|
|
39
|
-
response = await authenticatedApiCall(endpoint, {}, authConfig.token);
|
|
40
|
-
} else {
|
|
41
|
-
// For credentials, we'd need to use a different API call method
|
|
42
|
-
// For now, use bearer token approach
|
|
43
|
-
throw new Error('Bearer token authentication required for getting dataplane URL');
|
|
44
|
-
}
|
|
34
|
+
// Call controller API to get application details using centralized API client
|
|
35
|
+
const response = await getEnvironmentApplication(controllerUrl, environment, appKey, authConfig);
|
|
45
36
|
|
|
46
37
|
if (!response.success || !response.data) {
|
|
47
38
|
const formattedError = response.formattedError || formatApiError(response);
|
|
@@ -129,24 +120,10 @@ async function deployDatasource(appKey, filePath, options) {
|
|
|
129
120
|
const dataplaneUrl = await getDataplaneUrl(options.controller, appKey, options.environment, authConfig);
|
|
130
121
|
logger.log(chalk.green(`✓ Dataplane URL: ${dataplaneUrl}`));
|
|
131
122
|
|
|
132
|
-
// Publish to dataplane
|
|
123
|
+
// Publish to dataplane using pipeline workflow endpoint
|
|
133
124
|
logger.log(chalk.blue('\n🚀 Publishing datasource to dataplane...'));
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
// Prepare publish request - send datasource configuration directly
|
|
137
|
-
let publishResponse;
|
|
138
|
-
if (authConfig.type === 'bearer' && authConfig.token) {
|
|
139
|
-
publishResponse = await authenticatedApiCall(
|
|
140
|
-
publishEndpoint,
|
|
141
|
-
{
|
|
142
|
-
method: 'POST',
|
|
143
|
-
body: JSON.stringify(datasourceConfig)
|
|
144
|
-
},
|
|
145
|
-
authConfig.token
|
|
146
|
-
);
|
|
147
|
-
} else {
|
|
148
|
-
throw new Error('Bearer token authentication required for dataplane publish');
|
|
149
|
-
}
|
|
125
|
+
|
|
126
|
+
const publishResponse = await publishDatasourceViaPipeline(dataplaneUrl, systemKey, authConfig, datasourceConfig);
|
|
150
127
|
|
|
151
128
|
if (!publishResponse.success) {
|
|
152
129
|
const formattedError = publishResponse.formattedError || formatApiError(publishResponse);
|
package/lib/datasource-list.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
const chalk = require('chalk');
|
|
12
12
|
const { getConfig } = require('./config');
|
|
13
13
|
const { getOrRefreshDeviceToken } = require('./utils/token-manager');
|
|
14
|
-
const {
|
|
14
|
+
const { listEnvironmentDatasources } = require('./api/environments.api');
|
|
15
15
|
const { formatApiError } = require('./utils/api-error-handler');
|
|
16
16
|
const logger = require('./utils/logger');
|
|
17
17
|
|
|
@@ -20,11 +20,15 @@ const logger = require('./utils/logger');
|
|
|
20
20
|
* Handles multiple response formats similar to applications list
|
|
21
21
|
*
|
|
22
22
|
* @function extractDatasources
|
|
23
|
-
* @param {Object} response - API response from
|
|
23
|
+
* @param {Object} response - API response from centralized API client
|
|
24
24
|
* @returns {Array} Array of datasources
|
|
25
25
|
* @throws {Error} If response format is invalid
|
|
26
26
|
*/
|
|
27
27
|
function extractDatasources(response) {
|
|
28
|
+
if (!response.success || !response.data) {
|
|
29
|
+
throw new Error('Invalid API response: missing success or data');
|
|
30
|
+
}
|
|
31
|
+
|
|
28
32
|
const apiResponse = response.data;
|
|
29
33
|
let datasources;
|
|
30
34
|
|
|
@@ -112,10 +116,9 @@ async function listDatasources(options) {
|
|
|
112
116
|
process.exit(1);
|
|
113
117
|
}
|
|
114
118
|
|
|
115
|
-
// Call controller API
|
|
116
|
-
|
|
117
|
-
const
|
|
118
|
-
const response = await authenticatedApiCall(endpoint, {}, token);
|
|
119
|
+
// Call controller API using centralized API client
|
|
120
|
+
const authConfig = { type: 'bearer', token };
|
|
121
|
+
const response = await listEnvironmentDatasources(controllerUrl, options.environment, authConfig);
|
|
119
122
|
|
|
120
123
|
if (!response.success || !response.data) {
|
|
121
124
|
const formattedError = response.formattedError || formatApiError(response);
|
package/lib/deployer.js
CHANGED
|
@@ -9,84 +9,12 @@
|
|
|
9
9
|
* @version 2.0.0
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
const axios = require('axios');
|
|
13
12
|
const chalk = require('chalk');
|
|
14
13
|
const auditLogger = require('./audit-logger');
|
|
15
14
|
const logger = require('./utils/logger');
|
|
16
|
-
const { createAuthHeaders } = require('./utils/auth-headers');
|
|
17
15
|
const { validateControllerUrl, validateEnvironmentKey } = require('./utils/deployment-validation');
|
|
18
16
|
const { handleDeploymentError, handleDeploymentErrors } = require('./utils/deployment-errors');
|
|
19
|
-
|
|
20
|
-
/**
|
|
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
|
|
30
|
-
*/
|
|
31
|
-
async function handleDeploymentRetry(endpoint, manifest, requestConfig, maxRetries) {
|
|
32
|
-
let lastError;
|
|
33
|
-
|
|
34
|
-
// Validate manifest before sending
|
|
35
|
-
if (!manifest || typeof manifest !== 'object') {
|
|
36
|
-
throw new Error('Deployment manifest is required and must be an object');
|
|
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);
|
|
43
|
-
|
|
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
|
-
}
|
|
55
|
-
|
|
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
|
-
}
|
|
63
|
-
|
|
64
|
-
return response.data;
|
|
65
|
-
} catch (error) {
|
|
66
|
-
lastError = error;
|
|
67
|
-
|
|
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
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
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;
|
|
89
|
-
}
|
|
17
|
+
const { validatePipeline, deployPipeline, getPipelineDeployment } = require('./api/pipeline.api');
|
|
90
18
|
|
|
91
19
|
/**
|
|
92
20
|
* Validates deployment configuration via validate endpoint
|
|
@@ -103,8 +31,6 @@ async function handleDeploymentRetry(endpoint, manifest, requestConfig, maxRetri
|
|
|
103
31
|
*/
|
|
104
32
|
async function validateDeployment(url, envKey, manifest, authConfig, options = {}) {
|
|
105
33
|
const validatedEnvKey = validateEnvironmentKey(envKey);
|
|
106
|
-
const endpoint = `${url}/api/v1/pipeline/${validatedEnvKey}/validate`;
|
|
107
|
-
const timeout = options.timeout || 30000;
|
|
108
34
|
const maxRetries = options.maxRetries || 3;
|
|
109
35
|
|
|
110
36
|
// Extract clientId and clientSecret
|
|
@@ -118,58 +44,68 @@ async function validateDeployment(url, envKey, manifest, authConfig, options = {
|
|
|
118
44
|
|
|
119
45
|
// Build validation request
|
|
120
46
|
const repositoryUrl = options.repositoryUrl || `https://github.com/aifabrix/${manifest.key}`;
|
|
121
|
-
const
|
|
47
|
+
const validationData = {
|
|
122
48
|
clientId: clientId,
|
|
123
49
|
clientSecret: clientSecret,
|
|
124
50
|
repositoryUrl: repositoryUrl,
|
|
125
51
|
applicationConfig: manifest
|
|
126
52
|
};
|
|
127
53
|
|
|
128
|
-
//
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
'x-client-id': clientId,
|
|
134
|
-
'x-client-secret': clientSecret
|
|
135
|
-
},
|
|
136
|
-
timeout,
|
|
137
|
-
validateStatus: (status) => status < 600 // Don't throw on any status
|
|
54
|
+
// Use centralized API client with retry logic
|
|
55
|
+
const pipelineAuthConfig = {
|
|
56
|
+
type: 'client-credentials',
|
|
57
|
+
clientId: clientId,
|
|
58
|
+
clientSecret: clientSecret
|
|
138
59
|
};
|
|
139
60
|
|
|
140
61
|
let lastError;
|
|
141
62
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
142
63
|
try {
|
|
143
|
-
const response = await
|
|
64
|
+
const response = await validatePipeline(url, validatedEnvKey, pipelineAuthConfig, validationData);
|
|
144
65
|
|
|
145
66
|
// Handle successful validation (200 OK with valid: true)
|
|
146
|
-
if (response.
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
67
|
+
if (response.success && response.data) {
|
|
68
|
+
const responseData = response.data.data || response.data;
|
|
69
|
+
if (responseData.valid === true) {
|
|
70
|
+
return {
|
|
71
|
+
success: true,
|
|
72
|
+
validateToken: responseData.validateToken,
|
|
73
|
+
draftDeploymentId: responseData.draftDeploymentId,
|
|
74
|
+
imageServer: responseData.imageServer,
|
|
75
|
+
imageUsername: responseData.imageUsername,
|
|
76
|
+
imagePassword: responseData.imagePassword,
|
|
77
|
+
expiresAt: responseData.expiresAt
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
// Handle validation failure (valid: false)
|
|
81
|
+
if (responseData.valid === false) {
|
|
82
|
+
const errorMessage = responseData.errors && responseData.errors.length > 0
|
|
83
|
+
? `Validation failed: ${responseData.errors.join(', ')}`
|
|
84
|
+
: 'Validation failed: Invalid configuration';
|
|
85
|
+
const error = new Error(errorMessage);
|
|
86
|
+
error.status = 400;
|
|
87
|
+
error.data = responseData;
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
156
90
|
}
|
|
157
91
|
|
|
158
|
-
// Handle validation errors
|
|
159
|
-
if (response.
|
|
160
|
-
const error = new Error(`Validation request failed: ${response.
|
|
161
|
-
error.status = response.status;
|
|
162
|
-
error.response = {
|
|
163
|
-
status: response.status,
|
|
164
|
-
statusText: response.statusText,
|
|
165
|
-
data: response.data
|
|
166
|
-
};
|
|
92
|
+
// Handle validation errors (non-success responses)
|
|
93
|
+
if (!response.success) {
|
|
94
|
+
const error = new Error(`Validation request failed: ${response.formattedError || response.error || 'Unknown error'}`);
|
|
95
|
+
error.status = response.status || 400;
|
|
167
96
|
error.data = response.data;
|
|
168
97
|
throw error;
|
|
169
98
|
}
|
|
99
|
+
|
|
100
|
+
// If we get here, response.success is true but valid is not true and not false
|
|
101
|
+
// This is an unexpected state, throw an error
|
|
102
|
+
const error = new Error('Validation response is in an unexpected state');
|
|
103
|
+
error.status = 400;
|
|
104
|
+
error.data = response.data;
|
|
105
|
+
throw error;
|
|
170
106
|
} catch (error) {
|
|
171
107
|
lastError = error;
|
|
172
|
-
const shouldRetry = attempt < maxRetries && error.
|
|
108
|
+
const shouldRetry = attempt < maxRetries && error.status && error.status >= 500;
|
|
173
109
|
if (shouldRetry) {
|
|
174
110
|
const delay = Math.min(1000 * Math.pow(2, attempt - 1), 5000);
|
|
175
111
|
logger.log(chalk.yellow(`⚠️ Validation attempt ${attempt} failed, retrying in ${delay}ms...`));
|
|
@@ -198,8 +134,6 @@ async function validateDeployment(url, envKey, manifest, authConfig, options = {
|
|
|
198
134
|
*/
|
|
199
135
|
async function sendDeploymentRequest(url, envKey, validateToken, authConfig, options = {}) {
|
|
200
136
|
const validatedEnvKey = validateEnvironmentKey(envKey);
|
|
201
|
-
const endpoint = `${url}/api/v1/pipeline/${validatedEnvKey}/deploy`;
|
|
202
|
-
const timeout = options.timeout || 30000;
|
|
203
137
|
const maxRetries = options.maxRetries || 3;
|
|
204
138
|
|
|
205
139
|
// Extract clientId and clientSecret for deploy endpoint
|
|
@@ -212,24 +146,58 @@ async function sendDeploymentRequest(url, envKey, validateToken, authConfig, opt
|
|
|
212
146
|
|
|
213
147
|
// Build deployment request
|
|
214
148
|
const imageTag = options.imageTag || 'latest';
|
|
215
|
-
const
|
|
149
|
+
const deployData = {
|
|
216
150
|
validateToken: validateToken,
|
|
217
151
|
imageTag: imageTag
|
|
218
152
|
};
|
|
219
153
|
|
|
220
|
-
//
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
'x-client-id': clientId,
|
|
226
|
-
'x-client-secret': clientSecret
|
|
227
|
-
},
|
|
228
|
-
timeout,
|
|
229
|
-
validateStatus: (status) => status < 500
|
|
154
|
+
// Use centralized API client with retry logic
|
|
155
|
+
const pipelineAuthConfig = {
|
|
156
|
+
type: 'client-credentials',
|
|
157
|
+
clientId: clientId,
|
|
158
|
+
clientSecret: clientSecret
|
|
230
159
|
};
|
|
231
160
|
|
|
232
|
-
|
|
161
|
+
// Wrap API call with retry logic
|
|
162
|
+
let lastError;
|
|
163
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
164
|
+
try {
|
|
165
|
+
const response = await deployPipeline(url, validatedEnvKey, pipelineAuthConfig, deployData);
|
|
166
|
+
|
|
167
|
+
if (response.success) {
|
|
168
|
+
return response.data.data || response.data;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Handle deployment errors
|
|
172
|
+
if (response.status >= 400) {
|
|
173
|
+
const error = new Error(`Deployment request failed: ${response.formattedError || response.error || 'Unknown error'}`);
|
|
174
|
+
error.status = response.status;
|
|
175
|
+
error.data = response.data;
|
|
176
|
+
throw error;
|
|
177
|
+
}
|
|
178
|
+
} catch (error) {
|
|
179
|
+
lastError = error;
|
|
180
|
+
if (attempt < maxRetries) {
|
|
181
|
+
const delay = Math.min(1000 * Math.pow(2, attempt - 1), 5000);
|
|
182
|
+
logger.log(chalk.yellow(`⚠️ Deployment attempt ${attempt} failed, retrying in ${delay}ms...`));
|
|
183
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Preserve formatted error if available
|
|
189
|
+
const errorMessage = lastError.formatted || lastError.message;
|
|
190
|
+
const error = new Error(`Deployment failed after ${maxRetries} attempts: ${errorMessage}`);
|
|
191
|
+
if (lastError.formatted) {
|
|
192
|
+
error.formatted = lastError.formatted;
|
|
193
|
+
}
|
|
194
|
+
if (lastError.status) {
|
|
195
|
+
error.status = lastError.status;
|
|
196
|
+
}
|
|
197
|
+
if (lastError.data) {
|
|
198
|
+
error.data = lastError.data;
|
|
199
|
+
}
|
|
200
|
+
throw error;
|
|
233
201
|
}
|
|
234
202
|
|
|
235
203
|
/**
|
|
@@ -259,20 +227,17 @@ async function pollDeploymentStatus(deploymentId, controllerUrl, envKey, authCon
|
|
|
259
227
|
const maxAttempts = options.maxAttempts || 60;
|
|
260
228
|
|
|
261
229
|
const validatedEnvKey = validateEnvironmentKey(envKey);
|
|
262
|
-
|
|
230
|
+
|
|
231
|
+
// Convert authConfig to format expected by API client
|
|
232
|
+
const pipelineAuthConfig = authConfig.type === 'bearer'
|
|
233
|
+
? { type: 'bearer', token: authConfig.token }
|
|
234
|
+
: { type: 'client-credentials', clientId: authConfig.clientId, clientSecret: authConfig.clientSecret };
|
|
263
235
|
|
|
264
236
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
265
237
|
try {
|
|
266
|
-
const response = await
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
...createAuthHeaders(authConfig)
|
|
270
|
-
},
|
|
271
|
-
timeout: 10000,
|
|
272
|
-
validateStatus: (status) => status < 500
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
if (response.status === 200) {
|
|
238
|
+
const response = await getPipelineDeployment(controllerUrl, validatedEnvKey, deploymentId, pipelineAuthConfig);
|
|
239
|
+
|
|
240
|
+
if (response.success && response.data) {
|
|
276
241
|
// OpenAPI spec: Response structure { success: boolean, data: { status, progress, ... }, timestamp: string }
|
|
277
242
|
const responseData = response.data;
|
|
278
243
|
const deploymentData = responseData.data || responseData;
|
|
@@ -289,11 +254,14 @@ async function pollDeploymentStatus(deploymentId, controllerUrl, envKey, authCon
|
|
|
289
254
|
await new Promise(resolve => setTimeout(resolve, interval));
|
|
290
255
|
}
|
|
291
256
|
} else {
|
|
292
|
-
|
|
257
|
+
if (response.status === 404) {
|
|
258
|
+
throw new Error(`Deployment ${deploymentId} not found`);
|
|
259
|
+
}
|
|
260
|
+
throw new Error(`Status check failed: ${response.formattedError || response.error || 'Unknown error'}`);
|
|
293
261
|
}
|
|
294
262
|
} catch (error) {
|
|
295
|
-
if (error.
|
|
296
|
-
throw
|
|
263
|
+
if (error.message && error.message.includes('not found')) {
|
|
264
|
+
throw error;
|
|
297
265
|
}
|
|
298
266
|
throw error;
|
|
299
267
|
}
|
|
@@ -14,7 +14,8 @@ const logger = require('./utils/logger');
|
|
|
14
14
|
const config = require('./config');
|
|
15
15
|
const { validateControllerUrl, validateEnvironmentKey } = require('./utils/deployment-validation');
|
|
16
16
|
const { getOrRefreshDeviceToken } = require('./utils/token-manager');
|
|
17
|
-
const {
|
|
17
|
+
const { getEnvironmentStatus } = require('./api/environments.api');
|
|
18
|
+
const { deployEnvironment: deployEnvironmentInfra } = require('./api/deployments.api');
|
|
18
19
|
const { handleDeploymentErrors } = require('./utils/deployment-errors');
|
|
19
20
|
const auditLogger = require('./audit-logger');
|
|
20
21
|
|
|
@@ -93,32 +94,23 @@ async function sendEnvironmentDeployment(controllerUrl, envKey, authConfig, opti
|
|
|
93
94
|
deploymentRequest.description += ` (config: ${options.config})`;
|
|
94
95
|
}
|
|
95
96
|
|
|
96
|
-
// API endpoint: POST /api/v1/environments/{env}/deploy
|
|
97
|
-
// Alternative: POST /api/v1/environments/deploy with environment in body
|
|
98
|
-
const endpoint = `${validatedUrl}/api/v1/environments/${validatedEnvKey}/deploy`;
|
|
99
|
-
|
|
100
97
|
// Log deployment attempt for audit
|
|
101
98
|
await auditLogger.logDeploymentAttempt(validatedEnvKey, validatedUrl, options);
|
|
102
99
|
|
|
103
100
|
try {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
method: 'POST',
|
|
108
|
-
body: JSON.stringify(deploymentRequest)
|
|
109
|
-
},
|
|
110
|
-
authConfig.token
|
|
111
|
-
);
|
|
101
|
+
// Use centralized API client
|
|
102
|
+
const apiAuthConfig = { type: 'bearer', token: authConfig.token };
|
|
103
|
+
const response = await deployEnvironmentInfra(validatedUrl, validatedEnvKey, apiAuthConfig, deploymentRequest);
|
|
112
104
|
|
|
113
105
|
if (!response.success) {
|
|
114
106
|
const error = new Error(response.formattedError || response.error || 'Environment deployment failed');
|
|
115
107
|
error.status = response.status;
|
|
116
|
-
error.data = response.errorData;
|
|
108
|
+
error.data = response.errorData || response.data;
|
|
117
109
|
throw error;
|
|
118
110
|
}
|
|
119
111
|
|
|
120
112
|
// Handle response structure
|
|
121
|
-
const responseData = response.data || {};
|
|
113
|
+
const responseData = response.data.data || response.data || {};
|
|
122
114
|
return {
|
|
123
115
|
success: true,
|
|
124
116
|
environment: validatedEnvKey,
|
|
@@ -150,25 +142,22 @@ async function pollEnvironmentStatus(deploymentId, controllerUrl, envKey, authCo
|
|
|
150
142
|
|
|
151
143
|
const pollInterval = options.pollInterval || 5000;
|
|
152
144
|
const maxAttempts = options.maxAttempts || 60;
|
|
153
|
-
const statusEndpoint = `${validatedUrl}/api/v1/environments/${validatedEnvKey}/status`;
|
|
154
145
|
|
|
155
146
|
logger.log(chalk.blue(`⏳ Polling environment status (${pollInterval}ms intervals)...`));
|
|
156
147
|
|
|
148
|
+
// Use centralized API client
|
|
149
|
+
const apiAuthConfig = { type: 'bearer', token: authConfig.token };
|
|
150
|
+
|
|
157
151
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
158
152
|
try {
|
|
159
153
|
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
160
154
|
|
|
161
|
-
const response = await
|
|
162
|
-
statusEndpoint,
|
|
163
|
-
{
|
|
164
|
-
method: 'GET'
|
|
165
|
-
},
|
|
166
|
-
authConfig.token
|
|
167
|
-
);
|
|
155
|
+
const response = await getEnvironmentStatus(validatedUrl, validatedEnvKey, apiAuthConfig);
|
|
168
156
|
|
|
169
157
|
if (response.success && response.data) {
|
|
170
|
-
const
|
|
171
|
-
const
|
|
158
|
+
const responseData = response.data.data || response.data;
|
|
159
|
+
const status = responseData.status || responseData.ready;
|
|
160
|
+
const isReady = status === 'ready' || status === 'completed' || responseData.ready === true;
|
|
172
161
|
|
|
173
162
|
if (isReady) {
|
|
174
163
|
return {
|
|
@@ -181,7 +170,7 @@ async function pollEnvironmentStatus(deploymentId, controllerUrl, envKey, authCo
|
|
|
181
170
|
|
|
182
171
|
// Check for terminal failure states
|
|
183
172
|
if (status === 'failed' || status === 'error') {
|
|
184
|
-
throw new Error(`Environment deployment failed: ${
|
|
173
|
+
throw new Error(`Environment deployment failed: ${responseData.message || 'Unknown error'}`);
|
|
185
174
|
}
|
|
186
175
|
}
|
|
187
176
|
} catch (error) {
|