@kadi.build/deploy-ability 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (198) hide show
  1. package/README.md +523 -0
  2. package/dist/constants.d.ts +82 -0
  3. package/dist/constants.d.ts.map +1 -0
  4. package/dist/constants.js +82 -0
  5. package/dist/constants.js.map +1 -0
  6. package/dist/errors/certificate-error.d.ts +95 -0
  7. package/dist/errors/certificate-error.d.ts.map +1 -0
  8. package/dist/errors/certificate-error.js +111 -0
  9. package/dist/errors/certificate-error.js.map +1 -0
  10. package/dist/errors/deployment-error.d.ts +122 -0
  11. package/dist/errors/deployment-error.d.ts.map +1 -0
  12. package/dist/errors/deployment-error.js +185 -0
  13. package/dist/errors/deployment-error.js.map +1 -0
  14. package/dist/errors/index.d.ts +13 -0
  15. package/dist/errors/index.d.ts.map +1 -0
  16. package/dist/errors/index.js +18 -0
  17. package/dist/errors/index.js.map +1 -0
  18. package/dist/errors/profile-error.d.ts +106 -0
  19. package/dist/errors/profile-error.d.ts.map +1 -0
  20. package/dist/errors/profile-error.js +127 -0
  21. package/dist/errors/profile-error.js.map +1 -0
  22. package/dist/errors/provider-error.d.ts +104 -0
  23. package/dist/errors/provider-error.d.ts.map +1 -0
  24. package/dist/errors/provider-error.js +120 -0
  25. package/dist/errors/provider-error.js.map +1 -0
  26. package/dist/errors/wallet-error.d.ts +131 -0
  27. package/dist/errors/wallet-error.d.ts.map +1 -0
  28. package/dist/errors/wallet-error.js +154 -0
  29. package/dist/errors/wallet-error.js.map +1 -0
  30. package/dist/index.d.ts +49 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +53 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/targets/akash/bid-selectors.d.ts +251 -0
  35. package/dist/targets/akash/bid-selectors.d.ts.map +1 -0
  36. package/dist/targets/akash/bid-selectors.js +322 -0
  37. package/dist/targets/akash/bid-selectors.js.map +1 -0
  38. package/dist/targets/akash/bid-types.d.ts +297 -0
  39. package/dist/targets/akash/bid-types.d.ts.map +1 -0
  40. package/dist/targets/akash/bid-types.js +89 -0
  41. package/dist/targets/akash/bid-types.js.map +1 -0
  42. package/dist/targets/akash/blockchain-client.d.ts +577 -0
  43. package/dist/targets/akash/blockchain-client.d.ts.map +1 -0
  44. package/dist/targets/akash/blockchain-client.js +803 -0
  45. package/dist/targets/akash/blockchain-client.js.map +1 -0
  46. package/dist/targets/akash/certificate-manager.d.ts +228 -0
  47. package/dist/targets/akash/certificate-manager.d.ts.map +1 -0
  48. package/dist/targets/akash/certificate-manager.js +395 -0
  49. package/dist/targets/akash/certificate-manager.js.map +1 -0
  50. package/dist/targets/akash/constants.d.ts +231 -0
  51. package/dist/targets/akash/constants.d.ts.map +1 -0
  52. package/dist/targets/akash/constants.js +225 -0
  53. package/dist/targets/akash/constants.js.map +1 -0
  54. package/dist/targets/akash/deployer.d.ts +136 -0
  55. package/dist/targets/akash/deployer.d.ts.map +1 -0
  56. package/dist/targets/akash/deployer.js +599 -0
  57. package/dist/targets/akash/deployer.js.map +1 -0
  58. package/dist/targets/akash/environment.d.ts +241 -0
  59. package/dist/targets/akash/environment.d.ts.map +1 -0
  60. package/dist/targets/akash/environment.js +245 -0
  61. package/dist/targets/akash/environment.js.map +1 -0
  62. package/dist/targets/akash/index.d.ts +1113 -0
  63. package/dist/targets/akash/index.d.ts.map +1 -0
  64. package/dist/targets/akash/index.js +909 -0
  65. package/dist/targets/akash/index.js.map +1 -0
  66. package/dist/targets/akash/lease-monitor.d.ts +51 -0
  67. package/dist/targets/akash/lease-monitor.d.ts.map +1 -0
  68. package/dist/targets/akash/lease-monitor.js +110 -0
  69. package/dist/targets/akash/lease-monitor.js.map +1 -0
  70. package/dist/targets/akash/logs.d.ts +71 -0
  71. package/dist/targets/akash/logs.d.ts.map +1 -0
  72. package/dist/targets/akash/logs.js +311 -0
  73. package/dist/targets/akash/logs.js.map +1 -0
  74. package/dist/targets/akash/logs.types.d.ts +102 -0
  75. package/dist/targets/akash/logs.types.d.ts.map +1 -0
  76. package/dist/targets/akash/logs.types.js +9 -0
  77. package/dist/targets/akash/logs.types.js.map +1 -0
  78. package/dist/targets/akash/pricing.d.ts +247 -0
  79. package/dist/targets/akash/pricing.d.ts.map +1 -0
  80. package/dist/targets/akash/pricing.js +246 -0
  81. package/dist/targets/akash/pricing.js.map +1 -0
  82. package/dist/targets/akash/provider-client.d.ts +114 -0
  83. package/dist/targets/akash/provider-client.d.ts.map +1 -0
  84. package/dist/targets/akash/provider-client.js +318 -0
  85. package/dist/targets/akash/provider-client.js.map +1 -0
  86. package/dist/targets/akash/provider-metadata.d.ts +228 -0
  87. package/dist/targets/akash/provider-metadata.d.ts.map +1 -0
  88. package/dist/targets/akash/provider-metadata.js +14 -0
  89. package/dist/targets/akash/provider-metadata.js.map +1 -0
  90. package/dist/targets/akash/provider-service.d.ts +133 -0
  91. package/dist/targets/akash/provider-service.d.ts.map +1 -0
  92. package/dist/targets/akash/provider-service.js +391 -0
  93. package/dist/targets/akash/provider-service.js.map +1 -0
  94. package/dist/targets/akash/query-client.d.ts +125 -0
  95. package/dist/targets/akash/query-client.d.ts.map +1 -0
  96. package/dist/targets/akash/query-client.js +332 -0
  97. package/dist/targets/akash/query-client.js.map +1 -0
  98. package/dist/targets/akash/sdl-generator.d.ts +31 -0
  99. package/dist/targets/akash/sdl-generator.d.ts.map +1 -0
  100. package/dist/targets/akash/sdl-generator.js +279 -0
  101. package/dist/targets/akash/sdl-generator.js.map +1 -0
  102. package/dist/targets/akash/types.d.ts +285 -0
  103. package/dist/targets/akash/types.d.ts.map +1 -0
  104. package/dist/targets/akash/types.js +54 -0
  105. package/dist/targets/akash/types.js.map +1 -0
  106. package/dist/targets/akash/wallet-manager.d.ts +526 -0
  107. package/dist/targets/akash/wallet-manager.d.ts.map +1 -0
  108. package/dist/targets/akash/wallet-manager.js +953 -0
  109. package/dist/targets/akash/wallet-manager.js.map +1 -0
  110. package/dist/targets/local/compose-generator.d.ts +244 -0
  111. package/dist/targets/local/compose-generator.d.ts.map +1 -0
  112. package/dist/targets/local/compose-generator.js +324 -0
  113. package/dist/targets/local/compose-generator.js.map +1 -0
  114. package/dist/targets/local/deployer.d.ts +82 -0
  115. package/dist/targets/local/deployer.d.ts.map +1 -0
  116. package/dist/targets/local/deployer.js +367 -0
  117. package/dist/targets/local/deployer.js.map +1 -0
  118. package/dist/targets/local/engine-manager.d.ts +155 -0
  119. package/dist/targets/local/engine-manager.d.ts.map +1 -0
  120. package/dist/targets/local/engine-manager.js +250 -0
  121. package/dist/targets/local/engine-manager.js.map +1 -0
  122. package/dist/targets/local/index.d.ts +40 -0
  123. package/dist/targets/local/index.d.ts.map +1 -0
  124. package/dist/targets/local/index.js +43 -0
  125. package/dist/targets/local/index.js.map +1 -0
  126. package/dist/targets/local/network-manager.d.ts +160 -0
  127. package/dist/targets/local/network-manager.d.ts.map +1 -0
  128. package/dist/targets/local/network-manager.js +337 -0
  129. package/dist/targets/local/network-manager.js.map +1 -0
  130. package/dist/targets/local/types.d.ts +327 -0
  131. package/dist/targets/local/types.d.ts.map +1 -0
  132. package/dist/targets/local/types.js +9 -0
  133. package/dist/targets/local/types.js.map +1 -0
  134. package/dist/types/common.d.ts +585 -0
  135. package/dist/types/common.d.ts.map +1 -0
  136. package/dist/types/common.js +13 -0
  137. package/dist/types/common.js.map +1 -0
  138. package/dist/types/index.d.ts +15 -0
  139. package/dist/types/index.d.ts.map +1 -0
  140. package/dist/types/index.js +12 -0
  141. package/dist/types/index.js.map +1 -0
  142. package/dist/types/options.d.ts +329 -0
  143. package/dist/types/options.d.ts.map +1 -0
  144. package/dist/types/options.js +10 -0
  145. package/dist/types/options.js.map +1 -0
  146. package/dist/types/profiles.d.ts +329 -0
  147. package/dist/types/profiles.d.ts.map +1 -0
  148. package/dist/types/profiles.js +27 -0
  149. package/dist/types/profiles.js.map +1 -0
  150. package/dist/types/results.d.ts +443 -0
  151. package/dist/types/results.d.ts.map +1 -0
  152. package/dist/types/results.js +64 -0
  153. package/dist/types/results.js.map +1 -0
  154. package/dist/types/validators.d.ts +118 -0
  155. package/dist/types/validators.d.ts.map +1 -0
  156. package/dist/types/validators.js +198 -0
  157. package/dist/types/validators.js.map +1 -0
  158. package/dist/utils/command-runner.d.ts +128 -0
  159. package/dist/utils/command-runner.d.ts.map +1 -0
  160. package/dist/utils/command-runner.js +210 -0
  161. package/dist/utils/command-runner.js.map +1 -0
  162. package/dist/utils/index.d.ts +10 -0
  163. package/dist/utils/index.d.ts.map +1 -0
  164. package/dist/utils/index.js +10 -0
  165. package/dist/utils/index.js.map +1 -0
  166. package/dist/utils/logger.d.ts +68 -0
  167. package/dist/utils/logger.d.ts.map +1 -0
  168. package/dist/utils/logger.js +93 -0
  169. package/dist/utils/logger.js.map +1 -0
  170. package/dist/utils/profile-loader.d.ts +76 -0
  171. package/dist/utils/profile-loader.d.ts.map +1 -0
  172. package/dist/utils/profile-loader.js +194 -0
  173. package/dist/utils/profile-loader.js.map +1 -0
  174. package/dist/utils/registry/index.d.ts +27 -0
  175. package/dist/utils/registry/index.d.ts.map +1 -0
  176. package/dist/utils/registry/index.js +29 -0
  177. package/dist/utils/registry/index.js.map +1 -0
  178. package/dist/utils/registry/manager.d.ts +319 -0
  179. package/dist/utils/registry/manager.d.ts.map +1 -0
  180. package/dist/utils/registry/manager.js +671 -0
  181. package/dist/utils/registry/manager.js.map +1 -0
  182. package/dist/utils/registry/setup.d.ts +135 -0
  183. package/dist/utils/registry/setup.d.ts.map +1 -0
  184. package/dist/utils/registry/setup.js +207 -0
  185. package/dist/utils/registry/setup.js.map +1 -0
  186. package/dist/utils/registry/transformer.d.ts +92 -0
  187. package/dist/utils/registry/transformer.d.ts.map +1 -0
  188. package/dist/utils/registry/transformer.js +131 -0
  189. package/dist/utils/registry/transformer.js.map +1 -0
  190. package/dist/utils/registry/types.d.ts +241 -0
  191. package/dist/utils/registry/types.d.ts.map +1 -0
  192. package/dist/utils/registry/types.js +10 -0
  193. package/dist/utils/registry/types.js.map +1 -0
  194. package/docs/EXAMPLES.md +293 -0
  195. package/docs/PLACEMENT.md +433 -0
  196. package/docs/STORAGE.md +318 -0
  197. package/docs/building-provider-reliability-tracker.md +2581 -0
  198. package/package.json +109 -0
@@ -0,0 +1,671 @@
1
+ /**
2
+ * Temporary Container Registry Manager
3
+ *
4
+ * Manages a temporary container registry for making local Docker images accessible
5
+ * to Akash providers during deployment. Wraps the TunneledContainerRegistry from
6
+ * @kadi.build/container-registry-ability with deployment-specific logic.
7
+ *
8
+ * **Core Responsibilities:**
9
+ * - Start/stop temporary registry with public tunnel
10
+ * - Detect which images exist locally vs remotely
11
+ * - Push local images to temporary registry
12
+ * - Track image → registry URL mappings
13
+ * - Provide credentials for SDL generation
14
+ *
15
+ * @module utils/registry/manager
16
+ */
17
+ import path from 'node:path';
18
+ import fs from 'node:fs';
19
+ import os from 'node:os';
20
+ import { fileURLToPath } from 'node:url';
21
+ import { execSync } from 'node:child_process';
22
+ import dotenv from 'dotenv';
23
+ import debug from 'debug';
24
+ // @ts-ignore - Using existing TunneledContainerRegistry from JavaScript package
25
+ import { TunneledContainerRegistry } from '@kadi.build/container-registry-ability';
26
+ /**
27
+ * Debug logger for registry operations
28
+ *
29
+ * Enable with: DEBUG=deploy-ability:registry
30
+ */
31
+ const log = debug('deploy-ability:registry');
32
+ /**
33
+ * Temporary Container Registry Manager
34
+ *
35
+ * Manages the lifecycle of a temporary container registry that makes local images
36
+ * accessible to Akash providers. This class handles starting the registry, detecting
37
+ * local images, pushing them, and tracking URL transformations.
38
+ *
39
+ * **Typical Workflow:**
40
+ * ```typescript
41
+ * const manager = new TemporaryContainerRegistryManager(logger);
42
+ *
43
+ * // 1. Start registry
44
+ * await manager.startTemporaryRegistry({
45
+ * tunnelService: 'serveo',
46
+ * containerEngine: 'docker'
47
+ * });
48
+ *
49
+ * // 2. Add local images from profile
50
+ * await manager.addLocalImagesToTemporaryRegistry(profile, 'docker');
51
+ *
52
+ * // 3. Get transformed image URLs
53
+ * const url = manager.getPublicImageUrl('frontend', 'my-app:latest');
54
+ *
55
+ * // 4. Get credentials for SDL
56
+ * const creds = manager.getRegistryCredentials();
57
+ *
58
+ * // 5. Stop registry when done
59
+ * await manager.stopTemporaryRegistry();
60
+ * ```
61
+ */
62
+ export class TemporaryContainerRegistryManager {
63
+ logger;
64
+ registry = null;
65
+ registryInfo = null;
66
+ /**
67
+ * Maps original image names to their public registry URLs
68
+ *
69
+ * Key: Original image name (e.g., "my-app:latest")
70
+ * Value: Container mapping with registry URL and metadata
71
+ */
72
+ containerMappings = new Map();
73
+ /**
74
+ * Create a new registry manager
75
+ *
76
+ * @param logger - Logger for progress and error messages
77
+ */
78
+ constructor(logger) {
79
+ this.logger = logger;
80
+ }
81
+ /**
82
+ * Start temporary registry with public tunnel
83
+ *
84
+ * Creates a local container registry on the specified port and exposes it publicly
85
+ * via a tunnel service (ngrok, serveo, or bore). The registry is used to make
86
+ * local Docker images accessible to Akash providers during deployment.
87
+ *
88
+ * **What This Does:**
89
+ * 1. Loads environment variables (.env) for tunnel configuration
90
+ * 2. Starts local registry container on specified port
91
+ * 3. Creates public tunnel (ngrok/serveo/bore)
92
+ * 4. Generates authentication credentials
93
+ * 5. Returns when registry is accessible
94
+ *
95
+ * **Tunnel Services:**
96
+ * - **ngrok**: Most reliable, requires auth token (NGROK_AUTH_TOKEN env var)
97
+ * - **serveo**: Free SSH-based tunnel, no signup required
98
+ * - **bore**: Modern alternative using bore.pub
99
+ *
100
+ * @param options - Configuration options for registry and tunnel
101
+ * @throws Error if registry fails to start or tunnel cannot be established
102
+ *
103
+ * @example
104
+ * ```typescript
105
+ * await manager.startTemporaryRegistry({
106
+ * port: 3000,
107
+ * tunnelService: 'serveo',
108
+ * containerEngine: 'docker',
109
+ * registryDuration: 600000, // 10 minutes
110
+ * autoShutdown: false
111
+ * });
112
+ * ```
113
+ */
114
+ async startTemporaryRegistry(options) {
115
+ if (this.registry) {
116
+ log('Temporary registry already running');
117
+ return;
118
+ }
119
+ log('Starting temporary container registry...');
120
+ try {
121
+ // Load environment variables for tunnel configuration
122
+ // Checks two locations:
123
+ // 1. Module directory (dist/.env) - plugin-specific overrides
124
+ // 2. Package root (.env) - project-wide configuration
125
+ const moduleDir = path.dirname(fileURLToPath(import.meta.url));
126
+ const pluginEnvPath = path.join(moduleDir, '.env');
127
+ if (fs.existsSync(pluginEnvPath)) {
128
+ dotenv.config({ path: pluginEnvPath });
129
+ log('Loaded env from: %s', pluginEnvPath);
130
+ }
131
+ const packageRootEnvPath = path.join(moduleDir, '..', '..', '..', '.env');
132
+ if (fs.existsSync(packageRootEnvPath)) {
133
+ dotenv.config({ path: packageRootEnvPath });
134
+ log('Loaded env from: %s', packageRootEnvPath);
135
+ }
136
+ // Resolve tunnel configuration from environment variables
137
+ // CLI options take precedence over environment variables
138
+ const envNgrokToken = process.env.NGROK_AUTH_TOKEN || undefined;
139
+ const envNgrokRegion = process.env.NGROK_REGION || undefined;
140
+ const envNgrokProtocol = process.env.NGROK_PROTOCOL || undefined;
141
+ // Create TunneledContainerRegistry instance
142
+ this.registry = new TunneledContainerRegistry({
143
+ port: options.port || 3000,
144
+ tunnelService: options.tunnelService || 'serveo',
145
+ tunnelOptions: {
146
+ // Allow CLI options to override env if provided
147
+ authToken: options.tunnelAuthToken || envNgrokToken,
148
+ region: options.tunnelRegion || envNgrokRegion,
149
+ protocol: options.tunnelProtocol || envNgrokProtocol,
150
+ subdomain: options.tunnelSubdomain
151
+ },
152
+ enableMonitoring: false,
153
+ // Disable verbose logging - only show errors
154
+ // User can enable with DEBUG=deploy-ability:* environment variable if needed
155
+ enableLogging: false,
156
+ logLevel: 'error',
157
+ // Enable auto-shutdown by default to cleanup resources after deployment completes
158
+ // Can be disabled by passing options.autoShutdown = false
159
+ autoShutdown: options.autoShutdown ?? true,
160
+ containerType: options.containerEngine || 'docker',
161
+ duration: options.registryDuration
162
+ });
163
+ // Start the registry and get connection info
164
+ await this.registry.start();
165
+ this.registryInfo = this.registry.getRegistryInfo();
166
+ // Validate registry info
167
+ if (!this.registryInfo) {
168
+ throw new Error('Failed to get registry information after starting registry');
169
+ }
170
+ if (!this.registryInfo.localUrl) {
171
+ throw new Error('Registry started but no local URL available');
172
+ }
173
+ // Determine the primary registry URL (prefer tunnel over local)
174
+ const primaryRegistryUrl = this.registryInfo.tunnelUrl || this.registryInfo.localUrl;
175
+ log('Registry started at: %s', primaryRegistryUrl);
176
+ await this.displayRegistryAccessInformation();
177
+ }
178
+ catch (error) {
179
+ // Clean up on failure
180
+ await this.stopTemporaryRegistry();
181
+ throw new Error(`Failed to start temporary registry: ${error}`);
182
+ }
183
+ }
184
+ /**
185
+ * Display registry access information including domain and credentials
186
+ *
187
+ * Logs registry connection details for debugging purposes.
188
+ * Only logs if debug logging is enabled (DEBUG=deploy-ability:registry).
189
+ */
190
+ async displayRegistryAccessInformation() {
191
+ if (!this.registryInfo) {
192
+ return;
193
+ }
194
+ try {
195
+ // Try to get command help from registry (provides formatted domain)
196
+ const commandHelp = await this.registry.generateCommandHelp();
197
+ const registryDomain = commandHelp?.registry?.registryDomain ||
198
+ (this.registryInfo.tunnelUrl || this.registryInfo.localUrl).replace(/^https?:\/\//, '');
199
+ log('Registry domain: %s', registryDomain);
200
+ if (this.registryInfo.credentials) {
201
+ log('Access key: %s', this.registryInfo.credentials.accessKey);
202
+ log('Secret key: [hidden]');
203
+ }
204
+ }
205
+ catch (error) {
206
+ // Fallback display if command generation fails
207
+ if (this.registryInfo.credentials) {
208
+ log('Registry credentials available');
209
+ log('Username: %s', this.registryInfo.credentials.accessKey);
210
+ log('Password: [hidden]');
211
+ }
212
+ }
213
+ }
214
+ /**
215
+ * Check if a container image exists locally
216
+ *
217
+ * Uses `docker images -q` or `podman images -q` to check if an image exists
218
+ * in the local container engine. This is the ground truth for whether we need
219
+ * to add an image to the temporary registry.
220
+ *
221
+ * **Why This Works:**
222
+ * - Returns image hash if image exists locally
223
+ * - Returns empty string if image doesn't exist
224
+ * - Works for both tagged and untagged images
225
+ * - Handles shorthand names correctly (e.g., "nginx" vs "docker.io/library/nginx")
226
+ *
227
+ * **Reality-Based Detection:**
228
+ * Instead of guessing based on image name patterns (checking for "/" in the name),
229
+ * we check the actual state of the local container engine. This prevents false
230
+ * positives and false negatives.
231
+ *
232
+ * @param imageName - Full image name with tag (e.g., "my-app:latest")
233
+ * @param engine - Container engine to use
234
+ * @returns True if image exists locally, false otherwise
235
+ */
236
+ checkImageExistsLocally(imageName, engine) {
237
+ try {
238
+ const result = execSync(`${engine} images -q ${imageName}`, {
239
+ encoding: 'utf8',
240
+ stdio: ['pipe', 'pipe', 'pipe'] // Suppress stderr
241
+ });
242
+ return result.trim().length > 0;
243
+ }
244
+ catch (error) {
245
+ // Command failed (engine not available or other error)
246
+ return false;
247
+ }
248
+ }
249
+ /**
250
+ * Add local images from deployment profile to the temporary registry
251
+ *
252
+ * **Simplified Logic (Reality-Based Detection):**
253
+ *
254
+ * Instead of guessing if an image is "local" based on name patterns (like checking
255
+ * if it has "/" in the name), we simply check if the image actually exists locally
256
+ * using `docker images -q <image>`.
257
+ *
258
+ * **This approach:**
259
+ * 1. Is more accurate (checks reality, not heuristics)
260
+ * 2. Is simpler (one check instead of multiple conditions)
261
+ * 3. Handles edge cases automatically:
262
+ * - Docker Hub shorthand ("nginx" → exists remotely, not locally)
263
+ * - Custom registries with default namespaces
264
+ * - Images that "look local" but are actually remote
265
+ *
266
+ * **Decision Flow:**
267
+ * - Image exists locally → Add to temporary registry and make publicly accessible
268
+ * - Image doesn't exist locally → Treat as remote reference (don't add to registry)
269
+ *
270
+ * **Why This Works:**
271
+ * The temporary registry is ONLY needed for images that exist locally but need to
272
+ * be made publicly accessible to Akash providers. If an image doesn't exist locally,
273
+ * either:
274
+ * - It's a remote image (providers can pull it directly)
275
+ * - It doesn't exist anywhere (deployment will fail later with clear error)
276
+ *
277
+ * @param profile - Deployment profile containing services with images
278
+ * @param containerEngine - Container engine to use for operations
279
+ * @throws Error if a required local image cannot be added to registry
280
+ *
281
+ * @example
282
+ * ```typescript
283
+ * await manager.addLocalImagesToTemporaryRegistry(profile, 'docker');
284
+ * // Checks each service image, adds local ones to registry
285
+ * ```
286
+ */
287
+ async addLocalImagesToTemporaryRegistry(profile, containerEngine) {
288
+ log('Checking which service images exist locally...');
289
+ for (const [serviceName, serviceConfig] of Object.entries(profile.services)) {
290
+ const imageName = serviceConfig.image;
291
+ // Check if image exists locally (reality-based, not name-based)
292
+ if (this.checkImageExistsLocally(imageName, containerEngine)) {
293
+ // Image exists locally → add to temporary registry
294
+ log('Image found locally for service %s: %s', serviceName, imageName);
295
+ try {
296
+ // Use intelligent fallback strategy (tar file → container engine)
297
+ const mapping = await this.addContainerIntelligently(imageName, serviceName, containerEngine);
298
+ // Store the mapping for later SDL generation
299
+ this.containerMappings.set(imageName, mapping);
300
+ }
301
+ catch (error) {
302
+ this.logger.error(`❌ Failed to add local image '${imageName}' for service '${serviceName}': ${error.message}`);
303
+ throw error; // Stop deployment if we can't host a required local image
304
+ }
305
+ }
306
+ else {
307
+ // Image doesn't exist locally → treat as remote
308
+ log('Image not found locally, treating as remote for service %s: %s', serviceName, imageName);
309
+ }
310
+ }
311
+ log('Successfully processed %d local images', this.containerMappings.size);
312
+ }
313
+ /**
314
+ * Intelligent container addition with fallback strategies
315
+ *
316
+ * Tries multiple strategies to add a container image to the registry:
317
+ * 1. **Strategy 1**: Load from tar file (from kadi-build export cache)
318
+ * 2. **Strategy 2**: Load from container engine (docker/podman)
319
+ * 3. **Failure**: Show helpful error with suggestions
320
+ *
321
+ * **Why Multiple Strategies:**
322
+ * - Tar files are faster and don't require container engine running
323
+ * - Container engine is the fallback if tar file not available
324
+ * - Provides clear guidance if both fail
325
+ *
326
+ * @param imageName - Full image name with tag
327
+ * @param serviceName - Service name from profile
328
+ * @param containerEngine - Container engine to use
329
+ * @returns Container mapping with registry URL
330
+ * @throws Error if image cannot be added with any strategy
331
+ */
332
+ async addContainerIntelligently(imageName, serviceName, containerEngine) {
333
+ // Parse image name into repository and tag
334
+ const repoName = imageName.includes(':')
335
+ ? imageName.split(':')[0]
336
+ : imageName;
337
+ const imageTag = imageName.includes(':')
338
+ ? imageName.split(':')[1]
339
+ : 'latest';
340
+ log('Attempting to add container: %s', imageName);
341
+ // Strategy 1: Try to find and use tar file from kadi-build
342
+ const tarPath = this.findKadiBuildTarFile(imageName);
343
+ if (tarPath) {
344
+ try {
345
+ log('Loading container from tar file: %s', tarPath);
346
+ const containerInfo = await this.registry.addContainer({
347
+ type: 'tar',
348
+ name: repoName || imageName, // Fallback to imageName if repoName is undefined
349
+ path: tarPath
350
+ });
351
+ log('Container loaded from tar file with alias: %s', containerInfo.alias);
352
+ return this.createContainerMapping(imageName, serviceName, containerInfo, repoName || imageName, // Use imageName as fallback
353
+ imageTag || 'latest' // Use 'latest' as fallback
354
+ );
355
+ }
356
+ catch (error) {
357
+ log('Failed to load from tar file: %s', error.message);
358
+ log('Falling back to container engine...');
359
+ }
360
+ }
361
+ // Strategy 2: Try to add from container engine (docker/podman)
362
+ try {
363
+ log('Attempting to add from %s engine: %s', containerEngine, imageName);
364
+ const containerInfo = await this.registry.addContainer({
365
+ type: containerEngine,
366
+ name: repoName || imageName, // Fallback to imageName if repoName is undefined
367
+ image: imageName
368
+ });
369
+ log('Container loaded from %s with alias: %s', containerEngine, containerInfo.alias);
370
+ return this.createContainerMapping(imageName, serviceName, containerInfo, repoName || imageName, // Use imageName as fallback
371
+ imageTag || 'latest' // Use 'latest' as fallback
372
+ );
373
+ }
374
+ catch (error) {
375
+ log('Failed to load from %s: %s', containerEngine, error.message);
376
+ }
377
+ // Strategy 3: Show helpful error message
378
+ this.showKadiBuildSuggestion(imageName, containerEngine);
379
+ throw new Error(`Could not add container ${imageName}. See suggestions above.`);
380
+ }
381
+ /**
382
+ * Create container mapping for registry URLs
383
+ *
384
+ * Transforms a local image reference into a complete registry URL mapping
385
+ * that can be used in SDL generation. Includes service name tracking for
386
+ * better debugging and error messages.
387
+ *
388
+ * @param originalImage - Original image name from agent.json (e.g., "my-app")
389
+ * @param serviceName - Service name from profile (e.g., "frontend")
390
+ * @param containerInfo - Container info from registry with alias
391
+ * @param repoName - Repository name extracted from image
392
+ * @param imageTag - Image tag (e.g., "latest")
393
+ * @returns Complete container mapping with registry URL
394
+ */
395
+ async createContainerMapping(originalImage, serviceName, containerInfo, repoName, imageTag) {
396
+ const actualAlias = containerInfo.alias;
397
+ const registryDomain = this.getPreferredDomain();
398
+ const registryImageUrl = `${registryDomain}/${actualAlias}:${imageTag}`;
399
+ const mapping = {
400
+ originalImage,
401
+ serviceName,
402
+ registryUrl: registryImageUrl,
403
+ repoName,
404
+ imageTag,
405
+ actualAlias
406
+ };
407
+ log('Container available at: %s', registryImageUrl);
408
+ // Verify container is accessible
409
+ this.verifyContainerInRegistry(actualAlias);
410
+ return mapping;
411
+ }
412
+ /**
413
+ * Get public image URL for a specific service
414
+ *
415
+ * This is the key method that SDL generator calls to get the transformed
416
+ * registry URL for a local image.
417
+ *
418
+ * **Returns null if:**
419
+ * - Image is not local (no mapping exists)
420
+ * - Image wasn't added to registry
421
+ * - Service name doesn't match
422
+ *
423
+ * @param serviceName - Service name from profile
424
+ * @param originalImage - Original image name from profile
425
+ * @returns Public registry URL or null if not a local image
426
+ *
427
+ * @example
428
+ * ```typescript
429
+ * const url = manager.getPublicImageUrl('frontend', 'my-app:latest');
430
+ * // Returns: "abc123.serveo.net/my-app:latest" or null
431
+ * ```
432
+ */
433
+ getPublicImageUrl(serviceName, originalImage) {
434
+ const mapping = this.containerMappings.get(originalImage);
435
+ if (mapping && mapping.serviceName === serviceName) {
436
+ return mapping.registryUrl;
437
+ }
438
+ return null;
439
+ }
440
+ /**
441
+ * Get registry credentials for SDL generation
442
+ *
443
+ * Returns authentication credentials that should be added to the SDL services
444
+ * section so Akash providers can authenticate with the temporary registry.
445
+ *
446
+ * @returns Registry credentials or null if not available
447
+ *
448
+ * @example
449
+ * ```typescript
450
+ * const creds = manager.getRegistryCredentials();
451
+ * // Use in SDL: { host: "abc123.serveo.net", username: "...", password: "..." }
452
+ * ```
453
+ */
454
+ getRegistryCredentials() {
455
+ if (!this.registryInfo?.credentials) {
456
+ log('No registry credentials available');
457
+ return null;
458
+ }
459
+ log('Registry info - tunnel: %s, local: %s', this.registryInfo.tunnelUrl, this.registryInfo.localUrl);
460
+ const host = this.getRegistryDomain();
461
+ // Convert to the format expected by SDL generation
462
+ const credentials = {
463
+ host: host,
464
+ username: this.registryInfo.credentials.accessKey,
465
+ password: this.registryInfo.credentials.secretKey
466
+ };
467
+ log('Returning registry credentials - host: %s, username: %s', credentials.host, credentials.username);
468
+ return credentials;
469
+ }
470
+ /**
471
+ * Find tar file from kadi-build export directory
472
+ *
473
+ * Searches for container tar files exported by kadi-build in common locations:
474
+ * 1. ~/.kadi/tmp/container-registry-exports/containers/ (primary)
475
+ * 2. ./container-exports/ (backup)
476
+ * 3. /tmp/container-registry-exports/containers/ (fallback)
477
+ *
478
+ * **Why This Matters:**
479
+ * Tar files are faster to load and don't require the container engine to be running.
480
+ *
481
+ * @param imageName - Image name to search for (e.g., "my-app:0.0.1")
482
+ * @returns Path to tar file or null if not found
483
+ */
484
+ findKadiBuildTarFile(imageName) {
485
+ // Generate the expected filename pattern
486
+ // Example: "my-app:0.0.1" -> "my-app-0.0.1.tar"
487
+ const expectedFilename = `${imageName.replace(/[^a-zA-Z0-9.-]/g, '-')}.tar`;
488
+ // Common locations where kadi-build saves tar files
489
+ const possiblePaths = [
490
+ // User's home directory .kadi cache (primary location)
491
+ path.join(os.homedir(), '.kadi', 'tmp', 'container-registry-exports', 'containers'),
492
+ // Current working directory exports (backup location)
493
+ path.join(process.cwd(), 'container-exports'),
494
+ // Temporary directory exports (fallback location)
495
+ path.join(os.tmpdir(), 'container-registry-exports', 'containers')
496
+ ];
497
+ for (const basePath of possiblePaths) {
498
+ const fullPath = path.join(basePath, expectedFilename);
499
+ if (fs.existsSync(fullPath)) {
500
+ log('Found tar file for %s: %s', imageName, fullPath);
501
+ return fullPath;
502
+ }
503
+ log('Checked: %s - not found', fullPath);
504
+ }
505
+ return null;
506
+ }
507
+ /**
508
+ * Show helpful suggestion to run kadi-build
509
+ *
510
+ * Displays actionable suggestions when a container cannot be found or added.
511
+ * Helps users understand what went wrong and how to fix it.
512
+ *
513
+ * @param imageName - Image that couldn't be added
514
+ * @param containerType - Container engine used
515
+ */
516
+ showKadiBuildSuggestion(imageName, containerType) {
517
+ this.logger.log(`\n🔧 CONTAINER NOT FOUND: ${imageName}`);
518
+ this.logger.log(`\nThis could mean:`);
519
+ this.logger.log(` • The container hasn't been built yet`);
520
+ this.logger.log(` • The container name doesn't match what's available`);
521
+ this.logger.log(` • The container was built with a different tool`);
522
+ this.logger.log(`\n💡 SUGGESTED SOLUTIONS:`);
523
+ this.logger.log(`\n1. Build the container first:`);
524
+ this.logger.log(` ${containerType} build -t ${imageName} .`);
525
+ this.logger.log(`\n2. Check available containers:`);
526
+ this.logger.log(` ${containerType} images | grep ${imageName.split(':')[0]}`);
527
+ this.logger.log(`\n3. Verify the image name in your agent.json matches the built container`);
528
+ this.logger.log(`\n4. If using a different tag, ensure it exists:`);
529
+ this.logger.log(` ${containerType} images ${imageName.split(':')[0]}`);
530
+ }
531
+ /**
532
+ * Verify container is accessible in the registry
533
+ *
534
+ * Checks that a container with the given alias is present in the registry.
535
+ * Logs a warning if not found and shows available containers for debugging.
536
+ *
537
+ * @param actualAlias - Container alias to verify
538
+ */
539
+ verifyContainerInRegistry(actualAlias) {
540
+ try {
541
+ const containers = this.registry.listContainers();
542
+ const foundContainer = containers.find((c) => c.alias === actualAlias);
543
+ if (foundContainer) {
544
+ log('Verified container in registry with alias: %s', foundContainer.alias);
545
+ }
546
+ else {
547
+ this.logger.warn(` ⚠️ Warning: Container not found in registry with alias: ${actualAlias}`);
548
+ this.logger.log(` Available containers: ${containers.map((c) => c.alias).join(', ')}`);
549
+ }
550
+ }
551
+ catch (error) {
552
+ // Silently ignore verification errors - registry might not support listing
553
+ }
554
+ }
555
+ /**
556
+ * Get the registry domain (without protocol)
557
+ *
558
+ * Returns the domain to use in image URLs. Prefers tunnel domain over local.
559
+ *
560
+ * @returns Registry domain (e.g., "abc123.serveo.net" or "localhost:3000")
561
+ */
562
+ getRegistryDomain() {
563
+ try {
564
+ const preferredDomain = this.getPreferredDomain();
565
+ if (preferredDomain) {
566
+ log('Registry domain from getRegistryUrls: %s', preferredDomain);
567
+ return preferredDomain;
568
+ }
569
+ }
570
+ catch (error) {
571
+ log('Could not get registry URLs: %s', error);
572
+ }
573
+ // Fallback to parsing from registry info
574
+ if (this.registryInfo) {
575
+ const url = this.registryInfo.tunnelUrl || this.registryInfo.localUrl;
576
+ if (url) {
577
+ const domain = url.replace(/^https?:\/\//, '');
578
+ log('Registry domain from registryInfo: %s', domain);
579
+ return domain;
580
+ }
581
+ }
582
+ this.logger.error('❌ Could not determine registry domain!');
583
+ return '';
584
+ }
585
+ /**
586
+ * Get registry URL components including local and tunnel endpoints
587
+ *
588
+ * @returns Registry URLs with both local and tunnel information
589
+ */
590
+ getRegistryUrls() {
591
+ if (!this.registryInfo) {
592
+ throw new Error('Registry not started');
593
+ }
594
+ const localUrl = this.registryInfo.localUrl;
595
+ const tunnelUrl = this.registryInfo.tunnelUrl || null;
596
+ // Extract domains (remove protocol)
597
+ const localDomain = localUrl.replace(/^https?:\/\//, '');
598
+ const tunnelDomain = tunnelUrl
599
+ ? tunnelUrl.replace(/^https?:\/\//, '')
600
+ : null;
601
+ return {
602
+ localUrl,
603
+ localDomain,
604
+ tunnelUrl,
605
+ tunnelDomain
606
+ };
607
+ }
608
+ /**
609
+ * Get the preferred domain (tunnel if available, otherwise local)
610
+ *
611
+ * @returns Domain without protocol
612
+ */
613
+ getPreferredDomain() {
614
+ const urls = this.getRegistryUrls();
615
+ return urls.tunnelDomain || urls.localDomain;
616
+ }
617
+ /**
618
+ * Check if registry is running
619
+ *
620
+ * @returns True if registry is started and accessible
621
+ */
622
+ isRunning() {
623
+ return this.registry !== null && this.registryInfo !== null;
624
+ }
625
+ /**
626
+ * Display container mappings for debugging
627
+ *
628
+ * Shows how original image names were transformed to registry URLs.
629
+ * Useful for troubleshooting deployment issues.
630
+ */
631
+ async displayContainerMappings() {
632
+ this.logger.log('\n🔍 Container image mappings:');
633
+ if (this.containerMappings.size === 0) {
634
+ this.logger.log(' No local images processed');
635
+ return;
636
+ }
637
+ for (const [originalImage, mapping] of this.containerMappings) {
638
+ this.logger.log(` ${mapping.serviceName}:`);
639
+ this.logger.log(` Original: ${originalImage}`);
640
+ this.logger.log(` Registry: ${mapping.registryUrl}`);
641
+ this.logger.log(` Alias: ${mapping.actualAlias}`);
642
+ }
643
+ }
644
+ /**
645
+ * Stop the temporary registry and cleanup resources
646
+ *
647
+ * Shuts down the registry container and tunnel. Should be called after
648
+ * deployment completes and providers have pulled all images.
649
+ *
650
+ * **Safe to call multiple times** - idempotent operation.
651
+ */
652
+ async stopTemporaryRegistry() {
653
+ if (!this.registry) {
654
+ return;
655
+ }
656
+ log('Stopping temporary registry...');
657
+ try {
658
+ // Stop the registry instance
659
+ await this.registry.stop();
660
+ // Clear state
661
+ this.registry = null;
662
+ this.registryInfo = null;
663
+ this.containerMappings.clear();
664
+ log('Temporary registry stopped and cleaned up');
665
+ }
666
+ catch (error) {
667
+ this.logger.warn(`⚠️ Error during registry cleanup: ${error}`);
668
+ }
669
+ }
670
+ }
671
+ //# sourceMappingURL=manager.js.map