@aifabrix/builder 2.33.1 ā 2.33.4
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/README.md +13 -0
- package/lib/app/deploy-config.js +161 -0
- package/lib/app/deploy.js +28 -153
- package/lib/app/register.js +6 -5
- package/lib/app/run-helpers.js +23 -17
- package/lib/cli.js +31 -1
- package/lib/commands/logout.js +3 -4
- package/lib/commands/up-common.js +72 -0
- package/lib/commands/up-dataplane.js +109 -0
- package/lib/commands/up-miso.js +134 -0
- package/lib/core/config.js +32 -9
- package/lib/core/secrets-docker-env.js +88 -0
- package/lib/core/secrets.js +142 -115
- package/lib/infrastructure/helpers.js +82 -1
- package/lib/infrastructure/index.js +2 -0
- package/lib/schema/env-config.yaml +7 -0
- package/lib/utils/compose-generator.js +13 -13
- package/lib/utils/config-paths.js +13 -0
- package/lib/utils/device-code.js +2 -2
- package/lib/utils/env-endpoints.js +2 -5
- package/lib/utils/env-map.js +18 -14
- package/lib/utils/parse-image-ref.js +27 -0
- package/lib/utils/paths.js +28 -4
- package/lib/utils/secrets-generator.js +34 -12
- package/lib/utils/secrets-helpers.js +1 -2
- package/lib/utils/token-manager-refresh.js +5 -0
- package/package.json +1 -1
- package/templates/applications/dataplane/Dockerfile +16 -0
- package/templates/applications/dataplane/README.md +205 -0
- package/templates/applications/dataplane/env.template +143 -0
- package/templates/applications/dataplane/rbac.yaml +283 -0
- package/templates/applications/dataplane/variables.yaml +143 -0
- package/templates/applications/keycloak/Dockerfile +1 -1
- package/templates/applications/keycloak/README.md +193 -0
- package/templates/applications/keycloak/variables.yaml +5 -6
- package/templates/applications/miso-controller/Dockerfile +8 -8
- package/templates/applications/miso-controller/README.md +369 -0
- package/templates/applications/miso-controller/env.template +114 -6
- package/templates/applications/miso-controller/rbac.yaml +74 -0
- package/templates/applications/miso-controller/variables.yaml +93 -5
- package/templates/infra/compose.yaml.hbs +2 -1
- package/templates/applications/miso-controller/test.yaml +0 -1
package/README.md
CHANGED
|
@@ -36,6 +36,13 @@ aifabrix down myapp
|
|
|
36
36
|
|
|
37
37
|
Want authentication or deployment controller?
|
|
38
38
|
|
|
39
|
+
**Quick install from images (no build):**
|
|
40
|
+
```bash
|
|
41
|
+
aifabrix up # Start Postgres + Redis first
|
|
42
|
+
aifabrix up-miso # Install Keycloak + Miso Controller from images (auto-generated secrets for testing)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**Or create and build from templates:**
|
|
39
46
|
```bash
|
|
40
47
|
# Keycloak for authentication
|
|
41
48
|
aifabrix create keycloak --port 8082 --database --template keycloak
|
|
@@ -48,6 +55,12 @@ aifabrix build miso-controller
|
|
|
48
55
|
aifabrix run miso-controller
|
|
49
56
|
```
|
|
50
57
|
|
|
58
|
+
**Dataplane in dev (after login):**
|
|
59
|
+
```bash
|
|
60
|
+
aifabrix login --environment dev
|
|
61
|
+
aifabrix up-dataplane # Register or rotate, run, and deploy dataplane in dev
|
|
62
|
+
```
|
|
63
|
+
|
|
51
64
|
ā [Infrastructure Guide](docs/infrastructure.md)
|
|
52
65
|
|
|
53
66
|
## Documentation
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deployment configuration loading and validation for deploy flow.
|
|
3
|
+
* Extracted from deploy.js to keep file size within limits.
|
|
4
|
+
*
|
|
5
|
+
* @fileoverview Deployment config for AI Fabrix Builder
|
|
6
|
+
* @author AI Fabrix Team
|
|
7
|
+
* @version 2.0.0
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const fs = require('fs').promises;
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const yaml = require('js-yaml');
|
|
13
|
+
const config = require('../core/config');
|
|
14
|
+
const { getDeploymentAuth } = require('../utils/token-manager');
|
|
15
|
+
const { detectAppType } = require('../utils/paths');
|
|
16
|
+
const { resolveControllerUrl } = require('../utils/controller-url');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Validates that the application directory exists
|
|
20
|
+
* @async
|
|
21
|
+
* @param {string} builderPath - Path to builder directory
|
|
22
|
+
* @param {string} appName - Application name
|
|
23
|
+
* @throws {Error} If directory doesn't exist
|
|
24
|
+
*/
|
|
25
|
+
async function validateAppDirectory(builderPath, appName) {
|
|
26
|
+
try {
|
|
27
|
+
await fs.access(builderPath);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
if (error.code === 'ENOENT') {
|
|
30
|
+
throw new Error(`Application '${appName}' not found in builder/. Run 'aifabrix create ${appName}' first`);
|
|
31
|
+
}
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Loads variables.yaml file
|
|
38
|
+
* @async
|
|
39
|
+
* @param {string} variablesPath - Path to variables.yaml
|
|
40
|
+
* @returns {Promise<Object>} Variables object
|
|
41
|
+
* @throws {Error} If file cannot be loaded
|
|
42
|
+
*/
|
|
43
|
+
async function loadVariablesFile(variablesPath) {
|
|
44
|
+
try {
|
|
45
|
+
const variablesContent = await fs.readFile(variablesPath, 'utf8');
|
|
46
|
+
return yaml.load(variablesContent);
|
|
47
|
+
} catch (error) {
|
|
48
|
+
throw new Error(`Failed to load configuration from variables.yaml: ${error.message}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Extracts deployment configuration from config.yaml
|
|
54
|
+
* Resolves controller URL using fallback chain: config.controller ā logged-in user ā developer ID default
|
|
55
|
+
* Resolves environment using fallback chain: config.environment ā default 'dev'
|
|
56
|
+
* @async
|
|
57
|
+
* @param {Object} options - CLI options (for poll settings only)
|
|
58
|
+
* @param {Object} _variables - Variables from variables.yaml (unused, kept for compatibility)
|
|
59
|
+
* @returns {Promise<Object>} Extracted configuration with resolved controller URL
|
|
60
|
+
*/
|
|
61
|
+
async function extractDeploymentConfig(options, _variables) {
|
|
62
|
+
const { resolveEnvironment } = require('../core/config');
|
|
63
|
+
|
|
64
|
+
const controllerUrl = await resolveControllerUrl();
|
|
65
|
+
const envKey = await resolveEnvironment();
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
controllerUrl,
|
|
69
|
+
envKey,
|
|
70
|
+
poll: options.poll !== false,
|
|
71
|
+
pollInterval: options.pollInterval || 5000,
|
|
72
|
+
pollMaxAttempts: options.pollMaxAttempts || 60
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Validates required deployment configuration
|
|
78
|
+
* @param {Object} deploymentConfig - Deployment configuration
|
|
79
|
+
* @throws {Error} If configuration is invalid
|
|
80
|
+
*/
|
|
81
|
+
function validateDeploymentConfig(deploymentConfig) {
|
|
82
|
+
if (!deploymentConfig.controllerUrl) {
|
|
83
|
+
throw new Error('Controller URL is required. Run "aifabrix login" to set the controller URL in config.yaml');
|
|
84
|
+
}
|
|
85
|
+
if (!deploymentConfig.auth) {
|
|
86
|
+
throw new Error('Authentication is required. Run "aifabrix login" first or ensure credentials are in secrets.local.yaml');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Configure deployment environment settings from config.yaml
|
|
92
|
+
* @async
|
|
93
|
+
* @param {Object} _options - CLI options (unused, kept for compatibility)
|
|
94
|
+
* @param {Object} deploymentConfig - Deployment configuration to update
|
|
95
|
+
* @returns {Promise<void>}
|
|
96
|
+
*/
|
|
97
|
+
async function configureDeploymentEnvironment(_options, deploymentConfig) {
|
|
98
|
+
const currentEnvironment = await config.getCurrentEnvironment();
|
|
99
|
+
deploymentConfig.envKey = deploymentConfig.envKey || currentEnvironment;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Refresh deployment token and configure authentication
|
|
104
|
+
* @async
|
|
105
|
+
* @param {string} appName - Application name
|
|
106
|
+
* @param {Object} deploymentConfig - Deployment configuration to update
|
|
107
|
+
* @returns {Promise<void>}
|
|
108
|
+
* @throws {Error} If authentication fails
|
|
109
|
+
*/
|
|
110
|
+
async function refreshDeploymentToken(appName, deploymentConfig) {
|
|
111
|
+
if (!deploymentConfig.controllerUrl) {
|
|
112
|
+
throw new Error('Controller URL is required. Run "aifabrix login" to set the controller URL in config.yaml');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
const authConfig = await getDeploymentAuth(
|
|
117
|
+
deploymentConfig.controllerUrl,
|
|
118
|
+
deploymentConfig.envKey,
|
|
119
|
+
appName
|
|
120
|
+
);
|
|
121
|
+
if (!authConfig || !authConfig.controller) {
|
|
122
|
+
throw new Error('Invalid authentication configuration: missing controller URL');
|
|
123
|
+
}
|
|
124
|
+
if (!authConfig.token) {
|
|
125
|
+
throw new Error('Authentication is required');
|
|
126
|
+
}
|
|
127
|
+
deploymentConfig.auth = authConfig;
|
|
128
|
+
deploymentConfig.controllerUrl = authConfig.controller;
|
|
129
|
+
} catch (error) {
|
|
130
|
+
throw new Error(`Failed to get authentication: ${error.message}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Loads deployment configuration from variables.yaml and gets/refreshes token
|
|
136
|
+
* @async
|
|
137
|
+
* @param {string} appName - Application name
|
|
138
|
+
* @param {Object} options - CLI options
|
|
139
|
+
* @returns {Promise<Object>} Deployment configuration with token
|
|
140
|
+
* @throws {Error} If configuration is invalid
|
|
141
|
+
*/
|
|
142
|
+
async function loadDeploymentConfig(appName, options) {
|
|
143
|
+
const { appPath } = await detectAppType(appName);
|
|
144
|
+
await validateAppDirectory(appPath, appName);
|
|
145
|
+
|
|
146
|
+
const variablesPath = path.join(appPath, 'variables.yaml');
|
|
147
|
+
const variables = await loadVariablesFile(variablesPath);
|
|
148
|
+
|
|
149
|
+
const deploymentConfig = await extractDeploymentConfig(options, variables);
|
|
150
|
+
|
|
151
|
+
await configureDeploymentEnvironment(options, deploymentConfig);
|
|
152
|
+
await refreshDeploymentToken(appName, deploymentConfig);
|
|
153
|
+
|
|
154
|
+
validateDeploymentConfig(deploymentConfig);
|
|
155
|
+
|
|
156
|
+
return deploymentConfig;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
module.exports = {
|
|
160
|
+
loadDeploymentConfig
|
|
161
|
+
};
|
package/lib/app/deploy.js
CHANGED
|
@@ -15,11 +15,9 @@ const yaml = require('js-yaml');
|
|
|
15
15
|
const chalk = require('chalk');
|
|
16
16
|
const pushUtils = require('../deployment/push');
|
|
17
17
|
const logger = require('../utils/logger');
|
|
18
|
-
const config = require('../core/config');
|
|
19
|
-
const { getDeploymentAuth } = require('../utils/token-manager');
|
|
20
18
|
const { detectAppType } = require('../utils/paths');
|
|
21
|
-
const { resolveControllerUrl } = require('../utils/controller-url');
|
|
22
19
|
const { checkApplicationExists } = require('../utils/app-existence');
|
|
20
|
+
const { loadDeploymentConfig } = require('./deploy-config');
|
|
23
21
|
|
|
24
22
|
/**
|
|
25
23
|
* Validate application name format
|
|
@@ -145,155 +143,6 @@ async function pushApp(appName, options = {}) {
|
|
|
145
143
|
}
|
|
146
144
|
}
|
|
147
145
|
|
|
148
|
-
/**
|
|
149
|
-
* Validates that the application directory exists
|
|
150
|
-
* @async
|
|
151
|
-
* @param {string} builderPath - Path to builder directory
|
|
152
|
-
* @param {string} appName - Application name
|
|
153
|
-
* @throws {Error} If directory doesn't exist
|
|
154
|
-
*/
|
|
155
|
-
async function validateAppDirectory(builderPath, appName) {
|
|
156
|
-
try {
|
|
157
|
-
await fs.access(builderPath);
|
|
158
|
-
} catch (error) {
|
|
159
|
-
if (error.code === 'ENOENT') {
|
|
160
|
-
throw new Error(`Application '${appName}' not found in builder/. Run 'aifabrix create ${appName}' first`);
|
|
161
|
-
}
|
|
162
|
-
throw error;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Loads variables.yaml file
|
|
168
|
-
* @async
|
|
169
|
-
* @param {string} variablesPath - Path to variables.yaml
|
|
170
|
-
* @returns {Promise<Object>} Variables object
|
|
171
|
-
* @throws {Error} If file cannot be loaded
|
|
172
|
-
*/
|
|
173
|
-
async function loadVariablesFile(variablesPath) {
|
|
174
|
-
try {
|
|
175
|
-
const variablesContent = await fs.readFile(variablesPath, 'utf8');
|
|
176
|
-
return yaml.load(variablesContent);
|
|
177
|
-
} catch (error) {
|
|
178
|
-
throw new Error(`Failed to load configuration from variables.yaml: ${error.message}`);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Extracts deployment configuration from config.yaml
|
|
184
|
-
* Resolves controller URL using fallback chain: config.controller ā logged-in user ā developer ID default
|
|
185
|
-
* Resolves environment using fallback chain: config.environment ā default 'dev'
|
|
186
|
-
* @async
|
|
187
|
-
* @param {Object} options - CLI options (for poll settings only)
|
|
188
|
-
* @param {Object} _variables - Variables from variables.yaml (unused, kept for compatibility)
|
|
189
|
-
* @returns {Promise<Object>} Extracted configuration with resolved controller URL
|
|
190
|
-
*/
|
|
191
|
-
async function extractDeploymentConfig(options, _variables) {
|
|
192
|
-
const { resolveEnvironment } = require('../core/config');
|
|
193
|
-
|
|
194
|
-
// Resolve controller URL from config.yaml (no flags, no options)
|
|
195
|
-
const controllerUrl = await resolveControllerUrl();
|
|
196
|
-
|
|
197
|
-
// Resolve environment from config.yaml (no flags, no options)
|
|
198
|
-
const envKey = await resolveEnvironment();
|
|
199
|
-
|
|
200
|
-
return {
|
|
201
|
-
controllerUrl,
|
|
202
|
-
envKey,
|
|
203
|
-
poll: options.poll !== false,
|
|
204
|
-
pollInterval: options.pollInterval || 5000,
|
|
205
|
-
pollMaxAttempts: options.pollMaxAttempts || 60
|
|
206
|
-
};
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Validates required deployment configuration
|
|
211
|
-
* @param {Object} deploymentConfig - Deployment configuration
|
|
212
|
-
* @throws {Error} If configuration is invalid
|
|
213
|
-
*/
|
|
214
|
-
function validateDeploymentConfig(deploymentConfig) {
|
|
215
|
-
if (!deploymentConfig.controllerUrl) {
|
|
216
|
-
throw new Error('Controller URL is required. Run "aifabrix login" to set the controller URL in config.yaml');
|
|
217
|
-
}
|
|
218
|
-
if (!deploymentConfig.auth) {
|
|
219
|
-
throw new Error('Authentication is required. Run "aifabrix login" first or ensure credentials are in secrets.local.yaml');
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* Configure deployment environment settings from config.yaml
|
|
225
|
-
* @async
|
|
226
|
-
* @param {Object} _options - CLI options (unused, kept for compatibility)
|
|
227
|
-
* @param {Object} deploymentConfig - Deployment configuration to update
|
|
228
|
-
* @returns {Promise<void>}
|
|
229
|
-
*/
|
|
230
|
-
async function configureDeploymentEnvironment(_options, deploymentConfig) {
|
|
231
|
-
// Get current environment from root-level config (already resolved in extractDeploymentConfig)
|
|
232
|
-
// This function is kept for compatibility but no longer updates environment from options
|
|
233
|
-
const currentEnvironment = await config.getCurrentEnvironment();
|
|
234
|
-
deploymentConfig.envKey = deploymentConfig.envKey || currentEnvironment;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Refresh deployment token and configure authentication
|
|
239
|
-
* @async
|
|
240
|
-
* @param {string} appName - Application name
|
|
241
|
-
* @param {Object} deploymentConfig - Deployment configuration to update
|
|
242
|
-
* @returns {Promise<void>}
|
|
243
|
-
* @throws {Error} If authentication fails
|
|
244
|
-
*/
|
|
245
|
-
async function refreshDeploymentToken(appName, deploymentConfig) {
|
|
246
|
-
// Get controller URL (should already be resolved by extractDeploymentConfig)
|
|
247
|
-
if (!deploymentConfig.controllerUrl) {
|
|
248
|
-
throw new Error('Controller URL is required. Run "aifabrix login" to set the controller URL in config.yaml');
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// Get deployment authentication (device token ā client token ā credentials)
|
|
252
|
-
try {
|
|
253
|
-
const authConfig = await getDeploymentAuth(
|
|
254
|
-
deploymentConfig.controllerUrl,
|
|
255
|
-
deploymentConfig.envKey,
|
|
256
|
-
appName
|
|
257
|
-
);
|
|
258
|
-
if (!authConfig || !authConfig.controller) {
|
|
259
|
-
throw new Error('Invalid authentication configuration: missing controller URL');
|
|
260
|
-
}
|
|
261
|
-
if (!authConfig.token) {
|
|
262
|
-
throw new Error('Authentication is required');
|
|
263
|
-
}
|
|
264
|
-
deploymentConfig.auth = authConfig;
|
|
265
|
-
deploymentConfig.controllerUrl = authConfig.controller;
|
|
266
|
-
} catch (error) {
|
|
267
|
-
throw new Error(`Failed to get authentication: ${error.message}`);
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
/**
|
|
272
|
-
* Loads deployment configuration from variables.yaml and gets/refreshes token
|
|
273
|
-
* @async
|
|
274
|
-
* @param {string} appName - Application name
|
|
275
|
-
* @param {Object} options - CLI options
|
|
276
|
-
* @returns {Promise<Object>} Deployment configuration with token
|
|
277
|
-
* @throws {Error} If configuration is invalid
|
|
278
|
-
*/
|
|
279
|
-
async function loadDeploymentConfig(appName, options) {
|
|
280
|
-
// Detect app type and get correct path (integration or builder)
|
|
281
|
-
const { appPath } = await detectAppType(appName);
|
|
282
|
-
await validateAppDirectory(appPath, appName);
|
|
283
|
-
|
|
284
|
-
const variablesPath = path.join(appPath, 'variables.yaml');
|
|
285
|
-
const variables = await loadVariablesFile(variablesPath);
|
|
286
|
-
|
|
287
|
-
const deploymentConfig = await extractDeploymentConfig(options, variables);
|
|
288
|
-
|
|
289
|
-
await configureDeploymentEnvironment(options, deploymentConfig);
|
|
290
|
-
await refreshDeploymentToken(appName, deploymentConfig);
|
|
291
|
-
|
|
292
|
-
validateDeploymentConfig(deploymentConfig);
|
|
293
|
-
|
|
294
|
-
return deploymentConfig;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
146
|
/**
|
|
298
147
|
* Generates and validates deployment manifest
|
|
299
148
|
* @async
|
|
@@ -406,6 +255,22 @@ async function handleDeploymentError(error, appName, controllerUrl, usedExternal
|
|
|
406
255
|
await deployer.handleDeploymentErrors(error, appName, url, alreadyLogged);
|
|
407
256
|
}
|
|
408
257
|
|
|
258
|
+
/**
|
|
259
|
+
* Validates that the deployment image reference is pullable (includes a registry).
|
|
260
|
+
* A local ref (name:tag) causes "docker: not found" on the controller host.
|
|
261
|
+
* @param {string} imageRef - Image reference from manifest
|
|
262
|
+
* @param {string} appName - Application name (for error hint)
|
|
263
|
+
* @throws {Error} If image is missing or has no registry
|
|
264
|
+
*/
|
|
265
|
+
function validateImageIsPullable(imageRef, appName) {
|
|
266
|
+
if (!imageRef || !imageRef.includes('/')) {
|
|
267
|
+
const hint = `Set image.registry and image.tag in builder/${appName}/variables.yaml, or pass a full image ref (e.g. --image <registry>/${appName}:<tag>) when deploying`;
|
|
268
|
+
throw new Error(
|
|
269
|
+
`Deployed image must be pullable (include a registry). Current image: "${imageRef || 'none'}". ${hint}`
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
409
274
|
/**
|
|
410
275
|
* Execute standard application deployment flow
|
|
411
276
|
* @async
|
|
@@ -422,6 +287,15 @@ async function executeStandardDeployment(appName, options) {
|
|
|
422
287
|
const appExists = await checkApplicationExists(appName, controllerUrl, config.envKey, config.auth);
|
|
423
288
|
|
|
424
289
|
const { manifest, manifestPath } = await generateAndValidateManifest(appName);
|
|
290
|
+
if (options.imageOverride || options.image) {
|
|
291
|
+
manifest.image = options.imageOverride || options.image;
|
|
292
|
+
}
|
|
293
|
+
if (options.registryMode) {
|
|
294
|
+
manifest.registryMode = options.registryMode;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
validateImageIsPullable(manifest.image, appName);
|
|
298
|
+
|
|
425
299
|
displayDeploymentInfo(manifest, manifestPath);
|
|
426
300
|
|
|
427
301
|
try {
|
|
@@ -489,6 +363,7 @@ async function deployApp(appName, options = {}) {
|
|
|
489
363
|
module.exports = {
|
|
490
364
|
pushApp,
|
|
491
365
|
deployApp,
|
|
492
|
-
validateAppName
|
|
366
|
+
validateAppName,
|
|
367
|
+
validateImageIsPullable
|
|
493
368
|
};
|
|
494
369
|
|
package/lib/app/register.js
CHANGED
|
@@ -48,17 +48,18 @@ function buildRegistrationData(appConfig, options) {
|
|
|
48
48
|
registrationData.externalIntegration = appConfig.externalIntegration;
|
|
49
49
|
}
|
|
50
50
|
} else {
|
|
51
|
-
// For non-external types: include registryMode, port, image
|
|
52
|
-
registrationData.registryMode = appConfig.registryMode;
|
|
51
|
+
// For non-external types: include registryMode, port, image (options override when provided)
|
|
52
|
+
registrationData.registryMode = options.registryMode ?? appConfig.registryMode;
|
|
53
53
|
|
|
54
54
|
// Port is required for non-external types
|
|
55
55
|
if (appConfig.port) {
|
|
56
56
|
registrationData.port = appConfig.port;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
// Image is required for non-external types
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
// Image is required for non-external types (options.imageOverride overrides appConfig.image)
|
|
60
|
+
const imageValue = options.imageOverride ?? options.image ?? appConfig.image;
|
|
61
|
+
if (imageValue) {
|
|
62
|
+
registrationData.image = imageValue;
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
// URL: always set when we have port so controller DB has it. Precedence: --url, variables (app.url, deployment.dataplaneUrl, deployment.appUrl), else http://localhost:{localPort|port}
|
package/lib/app/run-helpers.js
CHANGED
|
@@ -26,6 +26,7 @@ const { waitForHealthCheck } = require('../utils/health-check');
|
|
|
26
26
|
const composeGenerator = require('../utils/compose-generator');
|
|
27
27
|
const dockerUtils = require('../utils/docker');
|
|
28
28
|
const containerHelpers = require('../utils/app-run-containers');
|
|
29
|
+
const pathsUtil = require('../utils/paths');
|
|
29
30
|
|
|
30
31
|
const execAsync = promisify(exec);
|
|
31
32
|
|
|
@@ -41,11 +42,12 @@ const stopAndRemoveContainer = containerHelpers.stopAndRemoveContainer;
|
|
|
41
42
|
*/
|
|
42
43
|
function checkBuilderDirectory(appName) {
|
|
43
44
|
const currentDir = process.cwd();
|
|
44
|
-
const
|
|
45
|
-
const
|
|
45
|
+
const expectedAppDir = pathsUtil.getBuilderPath(appName);
|
|
46
|
+
const normalizedCurrent = path.resolve(currentDir).replace(/\\/g, '/');
|
|
47
|
+
const normalizedExpected = path.resolve(expectedAppDir).replace(/\\/g, '/');
|
|
46
48
|
|
|
47
|
-
if (
|
|
48
|
-
const projectRoot = path.resolve(
|
|
49
|
+
if (normalizedCurrent === normalizedExpected) {
|
|
50
|
+
const projectRoot = path.resolve(expectedAppDir, '../..');
|
|
49
51
|
throw new Error(
|
|
50
52
|
'You\'re running from inside the builder directory.\n' +
|
|
51
53
|
`Current directory: ${currentDir}\n` +
|
|
@@ -58,19 +60,20 @@ function checkBuilderDirectory(appName) {
|
|
|
58
60
|
|
|
59
61
|
/**
|
|
60
62
|
* Load and validate config file exists
|
|
63
|
+
* Uses paths.getBuilderPath so AIFABRIX_BUILDER_DIR (e.g. from up-miso) is respected.
|
|
61
64
|
* @param {string} appName - Application name
|
|
62
65
|
* @returns {Object} Application configuration
|
|
63
66
|
* @throws {Error} If config file not found
|
|
64
67
|
*/
|
|
65
68
|
function loadAppConfig(appName) {
|
|
66
69
|
const currentDir = process.cwd();
|
|
67
|
-
const
|
|
70
|
+
const builderPath = pathsUtil.getBuilderPath(appName);
|
|
71
|
+
const configPath = path.join(builderPath, 'variables.yaml');
|
|
68
72
|
if (!fsSync.existsSync(configPath)) {
|
|
69
|
-
const expectedDir = path.join(currentDir, 'builder', appName);
|
|
70
73
|
throw new Error(
|
|
71
74
|
`Application configuration not found: ${configPath}\n` +
|
|
72
75
|
`Current directory: ${currentDir}\n` +
|
|
73
|
-
`Expected location: ${
|
|
76
|
+
`Expected location: ${builderPath}\n` +
|
|
74
77
|
'Make sure you\'re running from the project root (where \'builder\' directory exists)\n' +
|
|
75
78
|
`Run 'aifabrix create ${appName}' first if configuration doesn't exist`
|
|
76
79
|
);
|
|
@@ -194,14 +197,15 @@ async function ensureDevDirectory(appName, developerId) {
|
|
|
194
197
|
* @async
|
|
195
198
|
* @param {string} appName - Application name
|
|
196
199
|
* @param {string} builderEnvPath - Path to builder .env file
|
|
200
|
+
* @param {boolean} [skipOutputPath=false] - When true, skip copying to envOutputPath (e.g. up-miso/up-dataplane, no local code)
|
|
197
201
|
*/
|
|
198
|
-
async function ensureEnvFile(appName, builderEnvPath) {
|
|
202
|
+
async function ensureEnvFile(appName, builderEnvPath, skipOutputPath = false) {
|
|
199
203
|
if (!fsSync.existsSync(builderEnvPath)) {
|
|
200
204
|
logger.log(chalk.yellow('Generating .env file from template...'));
|
|
201
|
-
await secrets.generateEnvFile(appName, null, 'docker');
|
|
205
|
+
await secrets.generateEnvFile(appName, null, 'docker', false, skipOutputPath);
|
|
202
206
|
} else {
|
|
203
207
|
logger.log(chalk.blue('Updating .env file for Docker environment...'));
|
|
204
|
-
await secrets.generateEnvFile(appName, null, 'docker');
|
|
208
|
+
await secrets.generateEnvFile(appName, null, 'docker', false, skipOutputPath);
|
|
205
209
|
}
|
|
206
210
|
}
|
|
207
211
|
|
|
@@ -224,9 +228,10 @@ async function copyEnvToDev(builderEnvPath, devEnvPath) {
|
|
|
224
228
|
* @param {string} variablesPath - Path to variables.yaml
|
|
225
229
|
* @param {string} builderEnvPath - Path to builder .env file
|
|
226
230
|
* @param {string} devEnvPath - Path to dev .env file
|
|
231
|
+
* @param {boolean} [skipOutputPath=false] - When true, skip (e.g. up-miso/up-dataplane, no local code)
|
|
227
232
|
*/
|
|
228
|
-
async function handleEnvOutputPath(appName, variablesPath, builderEnvPath, devEnvPath) {
|
|
229
|
-
if (!fsSync.existsSync(variablesPath)) {
|
|
233
|
+
async function handleEnvOutputPath(appName, variablesPath, builderEnvPath, devEnvPath, skipOutputPath = false) {
|
|
234
|
+
if (skipOutputPath || !fsSync.existsSync(variablesPath)) {
|
|
230
235
|
return;
|
|
231
236
|
}
|
|
232
237
|
|
|
@@ -284,18 +289,19 @@ async function generateComposeFile(appName, appConfig, composeOptions, devDir) {
|
|
|
284
289
|
async function prepareEnvironment(appName, appConfig, options) {
|
|
285
290
|
const developerId = await config.getDeveloperId();
|
|
286
291
|
const devDir = await ensureDevDirectory(appName, developerId);
|
|
292
|
+
const skipEnvOutputPath = options.skipEnvOutputPath === true;
|
|
287
293
|
|
|
288
|
-
// Generate/update .env file
|
|
289
|
-
const builderEnvPath = path.join(
|
|
290
|
-
await ensureEnvFile(appName, builderEnvPath);
|
|
294
|
+
// Generate/update .env file (respect AIFABRIX_BUILDER_DIR when set by up-miso/up-dataplane)
|
|
295
|
+
const builderEnvPath = path.join(pathsUtil.getBuilderPath(appName), '.env');
|
|
296
|
+
await ensureEnvFile(appName, builderEnvPath, skipEnvOutputPath);
|
|
291
297
|
|
|
292
298
|
// Copy .env to dev directory
|
|
293
299
|
const devEnvPath = path.join(devDir, '.env');
|
|
294
300
|
await copyEnvToDev(builderEnvPath, devEnvPath);
|
|
295
301
|
|
|
296
|
-
// Handle envOutputPath if configured
|
|
302
|
+
// Handle envOutputPath if configured (skipped when skipEnvOutputPath e.g. up-miso/up-dataplane)
|
|
297
303
|
const variablesPath = path.join(devDir, 'variables.yaml');
|
|
298
|
-
await handleEnvOutputPath(appName, variablesPath, builderEnvPath, devEnvPath);
|
|
304
|
+
await handleEnvOutputPath(appName, variablesPath, builderEnvPath, devEnvPath, skipEnvOutputPath);
|
|
299
305
|
|
|
300
306
|
// Generate Docker Compose
|
|
301
307
|
const composeOptions = { ...options };
|
package/lib/cli.js
CHANGED
|
@@ -29,6 +29,8 @@ const { handleSecretsSet } = require('./commands/secrets-set');
|
|
|
29
29
|
const { handleAuthConfig } = require('./commands/auth-config');
|
|
30
30
|
const { setupAppCommands: setupAppManagementCommands } = require('./commands/app');
|
|
31
31
|
const { setupDatasourceCommands } = require('./commands/datasource');
|
|
32
|
+
const { handleUpMiso } = require('./commands/up-miso');
|
|
33
|
+
const { handleUpDataplane } = require('./commands/up-dataplane');
|
|
32
34
|
|
|
33
35
|
/**
|
|
34
36
|
* Sets up authentication commands
|
|
@@ -152,6 +154,34 @@ function setupInfraCommands(program) {
|
|
|
152
154
|
}
|
|
153
155
|
});
|
|
154
156
|
|
|
157
|
+
program.command('up-miso')
|
|
158
|
+
.description('Install keycloak and miso-controller from images (no build). Infra must be up. Uses auto-generated secrets for testing.')
|
|
159
|
+
.option('-r, --registry <url>', 'Override registry for both apps (e.g. myacr.azurecr.io)')
|
|
160
|
+
.option('--registry-mode <mode>', 'Override registry mode (acr|external)')
|
|
161
|
+
.option('-i, --image <key>=<value>', 'Override image (e.g. keycloak=myreg/keycloak:v1, miso-controller=myreg/m:v1); can be repeated', (v, prev) => (prev || []).concat([v]))
|
|
162
|
+
.action(async(options) => {
|
|
163
|
+
try {
|
|
164
|
+
await handleUpMiso(options);
|
|
165
|
+
} catch (error) {
|
|
166
|
+
handleCommandError(error, 'up-miso');
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
program.command('up-dataplane')
|
|
172
|
+
.description('Register and deploy dataplane app in dev (requires login, environment must be dev)')
|
|
173
|
+
.option('-r, --registry <url>', 'Override registry for dataplane image')
|
|
174
|
+
.option('--registry-mode <mode>', 'Override registry mode (acr|external)')
|
|
175
|
+
.option('-i, --image <ref>', 'Override dataplane image reference (e.g. myreg/dataplane:latest)')
|
|
176
|
+
.action(async(options) => {
|
|
177
|
+
try {
|
|
178
|
+
await handleUpDataplane(options);
|
|
179
|
+
} catch (error) {
|
|
180
|
+
handleCommandError(error, 'up-dataplane');
|
|
181
|
+
process.exit(1);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
|
|
155
185
|
program.command('down [app]')
|
|
156
186
|
.description('Stop and remove local infrastructure services or a specific application')
|
|
157
187
|
.option('-v, --volumes', 'Remove volumes (deletes all data)')
|
|
@@ -397,7 +427,7 @@ function setupAppCommands(program) {
|
|
|
397
427
|
.description('Build container image (auto-detects runtime)')
|
|
398
428
|
.option('-l, --language <lang>', 'Override language detection')
|
|
399
429
|
.option('-f, --force-template', 'Force rebuild from template')
|
|
400
|
-
.option('-t, --tag <tag>', 'Image tag (default: latest)')
|
|
430
|
+
.option('-t, --tag <tag>', 'Image tag (default: latest). Set image.tag in variables.yaml to match for deploy.')
|
|
401
431
|
.action(async(appName, options) => {
|
|
402
432
|
try {
|
|
403
433
|
const imageTag = await app.buildApp(appName, options);
|
package/lib/commands/logout.js
CHANGED
|
@@ -16,11 +16,10 @@ const {
|
|
|
16
16
|
clearClientToken,
|
|
17
17
|
clearAllClientTokens,
|
|
18
18
|
clearClientTokensForEnvironment,
|
|
19
|
-
normalizeControllerUrl
|
|
19
|
+
normalizeControllerUrl,
|
|
20
|
+
CONFIG_FILE
|
|
20
21
|
} = require('../core/config');
|
|
21
22
|
const logger = require('../utils/logger');
|
|
22
|
-
const os = require('os');
|
|
23
|
-
const path = require('path');
|
|
24
23
|
|
|
25
24
|
/**
|
|
26
25
|
* Validate environment key format
|
|
@@ -144,7 +143,7 @@ async function clearClientTokens(options) {
|
|
|
144
143
|
* @throws {Error} If logout fails or options are invalid
|
|
145
144
|
*/
|
|
146
145
|
async function handleLogout(options) {
|
|
147
|
-
const configPath =
|
|
146
|
+
const configPath = CONFIG_FILE;
|
|
148
147
|
logger.log(chalk.blue('\nš Clearing authentication tokens...\n'));
|
|
149
148
|
|
|
150
149
|
// Validate options
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Fabrix Builder - Up Commands Shared Helpers
|
|
3
|
+
*
|
|
4
|
+
* Shared logic for up-miso and up-dataplane (ensure app from template).
|
|
5
|
+
*
|
|
6
|
+
* @fileoverview Shared helpers for up-miso and up-dataplane commands
|
|
7
|
+
* @author AI Fabrix Team
|
|
8
|
+
* @version 2.0.0
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const chalk = require('chalk');
|
|
14
|
+
const logger = require('../utils/logger');
|
|
15
|
+
const pathsUtil = require('../utils/paths');
|
|
16
|
+
const { copyTemplateFiles } = require('../validation/template');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Copy template to a target path if variables.yaml is missing there.
|
|
20
|
+
* @param {string} appName - Application name
|
|
21
|
+
* @param {string} targetAppPath - Target directory (e.g. builder/keycloak)
|
|
22
|
+
* @returns {Promise<boolean>} True if template was copied, false if already present
|
|
23
|
+
*/
|
|
24
|
+
async function ensureTemplateAtPath(appName, targetAppPath) {
|
|
25
|
+
const variablesPath = path.join(targetAppPath, 'variables.yaml');
|
|
26
|
+
if (fs.existsSync(variablesPath)) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
await copyTemplateFiles(appName, targetAppPath);
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Ensures builder app directory exists from template if variables.yaml is missing.
|
|
35
|
+
* If builder/<appName>/variables.yaml does not exist, copies from templates/applications/<appName>.
|
|
36
|
+
* Uses AIFABRIX_BUILDER_DIR when set (e.g. by up-miso/up-dataplane from config aifabrix-env-config).
|
|
37
|
+
* When using a custom builder dir, also populates cwd/builder/<appName> so the repo's builder/ is not empty.
|
|
38
|
+
*
|
|
39
|
+
* @async
|
|
40
|
+
* @function ensureAppFromTemplate
|
|
41
|
+
* @param {string} appName - Application name (keycloak, miso-controller, dataplane)
|
|
42
|
+
* @returns {Promise<boolean>} True if template was copied (in either location), false if both already existed
|
|
43
|
+
* @throws {Error} If template copy fails
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* await ensureAppFromTemplate('keycloak');
|
|
47
|
+
*/
|
|
48
|
+
async function ensureAppFromTemplate(appName) {
|
|
49
|
+
if (!appName || typeof appName !== 'string') {
|
|
50
|
+
throw new Error('Application name is required and must be a string');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const appPath = pathsUtil.getBuilderPath(appName);
|
|
54
|
+
const primaryCopied = await ensureTemplateAtPath(appName, appPath);
|
|
55
|
+
if (primaryCopied) {
|
|
56
|
+
logger.log(chalk.blue(`Creating builder/${appName} from template...`));
|
|
57
|
+
logger.log(chalk.green(`ā Copied template for ${appName}`));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const cwdBuilderPath = path.join(process.cwd(), 'builder', appName);
|
|
61
|
+
if (path.resolve(cwdBuilderPath) !== path.resolve(appPath)) {
|
|
62
|
+
const cwdCopied = await ensureTemplateAtPath(appName, cwdBuilderPath);
|
|
63
|
+
if (cwdCopied) {
|
|
64
|
+
logger.log(chalk.blue(`Creating builder/${appName} in project (from template)...`));
|
|
65
|
+
logger.log(chalk.green(`ā Copied template for ${appName} into builder/`));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return primaryCopied;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = { ensureAppFromTemplate };
|