@uofx/cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (238) hide show
  1. package/LICENSE +40 -0
  2. package/README.md +444 -0
  3. package/THIRD-PARTY-NOTICES.txt +894 -0
  4. package/dist/application/dtos/index.js +24 -0
  5. package/dist/application/dtos/request/delete-instance.request.dto.js +3 -0
  6. package/dist/application/dtos/request/get-config.request.dto.js +3 -0
  7. package/dist/application/dtos/request/get-credentials.request.dto.js +3 -0
  8. package/dist/application/dtos/request/index.js +27 -0
  9. package/dist/application/dtos/request/install-instance.request.dto.js +3 -0
  10. package/dist/application/dtos/request/list-charts.request.dto.js +3 -0
  11. package/dist/application/dtos/request/set-config.request.dto.js +16 -0
  12. package/dist/application/dtos/request/setup-environment.request.dto.js +16 -0
  13. package/dist/application/dtos/request/show-logs.request.dto.js +19 -0
  14. package/dist/application/dtos/request/start-instance.request.dto.js +3 -0
  15. package/dist/application/dtos/request/stop-instance.request.dto.js +3 -0
  16. package/dist/application/dtos/response/credentials.response.dto.js +3 -0
  17. package/dist/application/dtos/response/delete-instance.response.dto.js +3 -0
  18. package/dist/application/dtos/response/index.js +26 -0
  19. package/dist/application/dtos/response/install-instance.response.dto.js +3 -0
  20. package/dist/application/dtos/response/instance-list.response.dto.js +3 -0
  21. package/dist/application/dtos/response/instance-status.response.dto.js +3 -0
  22. package/dist/application/dtos/response/setup-result.response.dto.js +3 -0
  23. package/dist/application/dtos/response/show-logs.response.dto.js +3 -0
  24. package/dist/application/dtos/response/start-instance.response.dto.js +3 -0
  25. package/dist/application/dtos/response/stop-instance.response.dto.js +3 -0
  26. package/dist/application/index.js +25 -0
  27. package/dist/application/interfaces/index.js +24 -0
  28. package/dist/application/interfaces/use-case.interface.js +3 -0
  29. package/dist/application/use-cases/config/get-config.use-case.js +66 -0
  30. package/dist/application/use-cases/config/set-config.use-case.js +49 -0
  31. package/dist/application/use-cases/credentials/get-credentials.use-case.js +57 -0
  32. package/dist/application/use-cases/index.js +28 -0
  33. package/dist/application/use-cases/instance/delete-instance.use-case.js +81 -0
  34. package/dist/application/use-cases/instance/index.js +23 -0
  35. package/dist/application/use-cases/instance/install-instance.use-case.js +424 -0
  36. package/dist/application/use-cases/instance/list-charts.use-case.js +43 -0
  37. package/dist/application/use-cases/instance/list-instances.use-case.js +62 -0
  38. package/dist/application/use-cases/instance/start-instance.use-case.js +154 -0
  39. package/dist/application/use-cases/instance/stop-instance.use-case.js +55 -0
  40. package/dist/application/use-cases/logs/show-logs.use-case.js +66 -0
  41. package/dist/application/use-cases/setup/setup-environment.use-case.js +53 -0
  42. package/dist/cli.js +286 -0
  43. package/dist/constants/config-defaults.js +23 -0
  44. package/dist/constants/defaults.js +89 -0
  45. package/dist/constants/deployment.js +39 -0
  46. package/dist/constants/environments.js +93 -0
  47. package/dist/constants/index.js +53 -0
  48. package/dist/constants/oci-artifacts.js +25 -0
  49. package/dist/constants/paths.js +92 -0
  50. package/dist/constants/timeouts.js +60 -0
  51. package/dist/di/container.js +34 -0
  52. package/dist/di/index.js +22 -0
  53. package/dist/di/modules/application.module.js +54 -0
  54. package/dist/di/modules/infrastructure.module.js +206 -0
  55. package/dist/di/modules/interceptor.module.js +68 -0
  56. package/dist/di/modules/presentation.module.js +31 -0
  57. package/dist/di/tokens.js +149 -0
  58. package/dist/domain/decorators/sensitive.decorator.js +39 -0
  59. package/dist/domain/entities/credentials-resolver.entity.js +127 -0
  60. package/dist/domain/entities/credentials.entity.js +65 -0
  61. package/dist/domain/entities/delete-instance-validation.entity.js +100 -0
  62. package/dist/domain/entities/deployment-parameters.entity.js +120 -0
  63. package/dist/domain/entities/environment-validation.entity.js +125 -0
  64. package/dist/domain/entities/index.js +29 -0
  65. package/dist/domain/entities/instance-lifecycle-state.entity.js +100 -0
  66. package/dist/domain/entities/instance-list-aggregator.entity.js +104 -0
  67. package/dist/domain/entities/instance-metadata.entity.js +86 -0
  68. package/dist/domain/entities/instance-status.entity.js +79 -0
  69. package/dist/domain/entities/instance.entity.js +128 -0
  70. package/dist/domain/entities/log-filter.entity.js +141 -0
  71. package/dist/domain/index.js +29 -0
  72. package/dist/domain/interfaces/safe-loggable.interface.js +3 -0
  73. package/dist/domain/ports/app-config.port.js +3 -0
  74. package/dist/domain/ports/base-image.port.js +3 -0
  75. package/dist/domain/ports/chart-version.port.js +3 -0
  76. package/dist/domain/ports/correlation-id.port.js +3 -0
  77. package/dist/domain/ports/credentials.port.js +3 -0
  78. package/dist/domain/ports/deployment.port.js +3 -0
  79. package/dist/domain/ports/error-handler.port.js +3 -0
  80. package/dist/domain/ports/index.js +46 -0
  81. package/dist/domain/ports/instance-manager.port.js +3 -0
  82. package/dist/domain/ports/instance-metadata.port.js +8 -0
  83. package/dist/domain/ports/instance-storage.port.js +3 -0
  84. package/dist/domain/ports/k8s-deployer.port.js +3 -0
  85. package/dist/domain/ports/logger.port.js +14 -0
  86. package/dist/domain/ports/output.port.js +3 -0
  87. package/dist/domain/ports/runtime-environment.port.js +6 -0
  88. package/dist/domain/ports/user-interaction.port.js +9 -0
  89. package/dist/domain/ports/user-settings.port.js +3 -0
  90. package/dist/domain/types/index.js +22 -0
  91. package/dist/domain/types/logger.types.js +29 -0
  92. package/dist/domain/types/validation.types.js +9 -0
  93. package/dist/domain/value-objects/acr-credentials.value-object.js +92 -0
  94. package/dist/domain/value-objects/chart-version.value-object.js +124 -0
  95. package/dist/domain/value-objects/config-log-level.value-object.js +84 -0
  96. package/dist/domain/value-objects/connection-info.value-object.js +65 -0
  97. package/dist/domain/value-objects/index.js +25 -0
  98. package/dist/domain/value-objects/instance-name.value-object.js +91 -0
  99. package/dist/domain/value-objects/jwt-key.value-object.js +97 -0
  100. package/dist/domain/value-objects/mssql-password.value-object.js +140 -0
  101. package/dist/domain/value-objects/rsa-key-pair.value-object.js +181 -0
  102. package/dist/index.js +6 -0
  103. package/dist/infrastructure/config/app-config.interface.js +3 -0
  104. package/dist/infrastructure/config/app-config.service.js +280 -0
  105. package/dist/infrastructure/config/config-validator.js +31 -0
  106. package/dist/infrastructure/config/crypto.service.js +125 -0
  107. package/dist/infrastructure/deployment/deployment.adapter.js +118 -0
  108. package/dist/infrastructure/deployment/interfaces/acr-credential-manager.interface.js +3 -0
  109. package/dist/infrastructure/deployment/interfaces/app-manager.interface.js +3 -0
  110. package/dist/infrastructure/deployment/interfaces/helm-registry.interface.js +3 -0
  111. package/dist/infrastructure/deployment/interfaces/infra-manager.interface.js +3 -0
  112. package/dist/infrastructure/deployment/interfaces/k8s-job-runner.interface.js +3 -0
  113. package/dist/infrastructure/deployment/interfaces/mssql-database-init.interface.js +3 -0
  114. package/dist/infrastructure/deployment/interfaces/mssql-helm-deployment.interface.js +3 -0
  115. package/dist/infrastructure/deployment/interfaces/mssql-storage.interface.js +3 -0
  116. package/dist/infrastructure/deployment/interfaces/mssql-user-manager.interface.js +3 -0
  117. package/dist/infrastructure/deployment/interfaces/oci-artifact.interface.js +3 -0
  118. package/dist/infrastructure/deployment/interfaces/secret-manager.interface.js +3 -0
  119. package/dist/infrastructure/deployment/interfaces/service-manager.interface.js +3 -0
  120. package/dist/infrastructure/deployment/interfaces/version-compatibility.interface.js +3 -0
  121. package/dist/infrastructure/deployment/services/acr-credential-manager.service.js +144 -0
  122. package/dist/infrastructure/deployment/services/app-manager.service.js +193 -0
  123. package/dist/infrastructure/deployment/services/base-helm-deployment.service.js +163 -0
  124. package/dist/infrastructure/deployment/services/helm-registry.service.js +126 -0
  125. package/dist/infrastructure/deployment/services/infra-manager.service.js +130 -0
  126. package/dist/infrastructure/deployment/services/k8s-job-runner.service.js +194 -0
  127. package/dist/infrastructure/deployment/services/mssql-database-init.service.js +139 -0
  128. package/dist/infrastructure/deployment/services/mssql-helm-deployment.service.js +100 -0
  129. package/dist/infrastructure/deployment/services/mssql-storage.service.js +54 -0
  130. package/dist/infrastructure/deployment/services/mssql-user-manager.service.js +66 -0
  131. package/dist/infrastructure/deployment/services/oci-artifact.service.js +289 -0
  132. package/dist/infrastructure/deployment/services/secret-manager.service.js +179 -0
  133. package/dist/infrastructure/deployment/services/service-manager.service.js +82 -0
  134. package/dist/infrastructure/deployment/services/version-compatibility.service.js +291 -0
  135. package/dist/infrastructure/environment/interfaces/hardware-info.interface.js +3 -0
  136. package/dist/infrastructure/environment/interfaces/network-checker.interface.js +3 -0
  137. package/dist/infrastructure/environment/services/hardware-info.service.js +135 -0
  138. package/dist/infrastructure/environment/services/network-checker.service.js +142 -0
  139. package/dist/infrastructure/environment/windows-environment.adapter.js +162 -0
  140. package/dist/infrastructure/errors/app-error.js +73 -0
  141. package/dist/infrastructure/errors/error-handler.interface.js +3 -0
  142. package/dist/infrastructure/errors/error-handler.js +218 -0
  143. package/dist/infrastructure/errors/exit-codes.js +27 -0
  144. package/dist/infrastructure/errors/index.js +25 -0
  145. package/dist/infrastructure/execution/builders/base-command.builder.js +122 -0
  146. package/dist/infrastructure/execution/builders/host-command.builder.js +58 -0
  147. package/dist/infrastructure/execution/builders/windows-host-command.builder.js +50 -0
  148. package/dist/infrastructure/execution/builders/wsl-command.builder.js +29 -0
  149. package/dist/infrastructure/execution/command-builder.js +252 -0
  150. package/dist/infrastructure/execution/command-executor.service.js +230 -0
  151. package/dist/infrastructure/execution/environments/wsl-execution.environment.js +70 -0
  152. package/dist/infrastructure/execution/execution-environment.factory.js +53 -0
  153. package/dist/infrastructure/execution/index.js +25 -0
  154. package/dist/infrastructure/execution/interfaces/command-builder.interface.js +3 -0
  155. package/dist/infrastructure/execution/interfaces/command-executor.interface.js +3 -0
  156. package/dist/infrastructure/execution/interfaces/execution-environment-factory.interface.js +3 -0
  157. package/dist/infrastructure/execution/interfaces/execution-environment.interface.js +7 -0
  158. package/dist/infrastructure/execution/interfaces/host-command-builder.interface.js +3 -0
  159. package/dist/infrastructure/execution/interfaces/index.js +23 -0
  160. package/dist/infrastructure/execution/interfaces/script-executor.interface.js +3 -0
  161. package/dist/infrastructure/execution/script-executor.service.js +171 -0
  162. package/dist/infrastructure/http/http-client.service.js +176 -0
  163. package/dist/infrastructure/http/index.js +18 -0
  164. package/dist/infrastructure/http/interfaces/http-client.interface.js +3 -0
  165. package/dist/infrastructure/interceptors/index.js +8 -0
  166. package/dist/infrastructure/interceptors/interceptor.factory.js +44 -0
  167. package/dist/infrastructure/interceptors/interceptor.interface.js +3 -0
  168. package/dist/infrastructure/interceptors/logging.interceptor.js +171 -0
  169. package/dist/infrastructure/logger/correlation-id.adapter.js +68 -0
  170. package/dist/infrastructure/logger/index.js +23 -0
  171. package/dist/infrastructure/logger/interfaces/index.js +22 -0
  172. package/dist/infrastructure/logger/interfaces/log-reader.repository.interface.js +7 -0
  173. package/dist/infrastructure/logger/interfaces/log-writer.repository.interface.js +7 -0
  174. package/dist/infrastructure/logger/logger.adapter.js +274 -0
  175. package/dist/infrastructure/logger/services/file-log-reader.repository.js +148 -0
  176. package/dist/infrastructure/logger/services/file-log-writer.repository.js +307 -0
  177. package/dist/infrastructure/logger/services/index.js +22 -0
  178. package/dist/infrastructure/persistence/index.js +25 -0
  179. package/dist/infrastructure/persistence/instance-metadata.adapter.js +100 -0
  180. package/dist/infrastructure/persistence/instance-storage.adapter.js +64 -0
  181. package/dist/infrastructure/persistence/interfaces/config.repository.interface.js +3 -0
  182. package/dist/infrastructure/persistence/interfaces/index.js +22 -0
  183. package/dist/infrastructure/persistence/interfaces/instance.repository.interface.js +3 -0
  184. package/dist/infrastructure/persistence/services/file-system-config.repository.js +168 -0
  185. package/dist/infrastructure/persistence/services/file-system-instance.repository.js +170 -0
  186. package/dist/infrastructure/persistence/services/index.js +22 -0
  187. package/dist/infrastructure/persistence/user-settings.adapter.js +55 -0
  188. package/dist/infrastructure/platform-detector.js +71 -0
  189. package/dist/infrastructure/platforms/windows/interfaces/microk8s.interface.js +3 -0
  190. package/dist/infrastructure/platforms/windows/interfaces/rootfs-manager.interface.js +3 -0
  191. package/dist/infrastructure/platforms/windows/interfaces/windows-features.interface.js +3 -0
  192. package/dist/infrastructure/platforms/windows/interfaces/windows-info.interface.js +3 -0
  193. package/dist/infrastructure/platforms/windows/interfaces/wsl-config.interface.js +3 -0
  194. package/dist/infrastructure/platforms/windows/interfaces/wsl-info.interface.js +3 -0
  195. package/dist/infrastructure/platforms/windows/interfaces/wsl-instance-inspection.interface.js +3 -0
  196. package/dist/infrastructure/platforms/windows/interfaces/wsl-instance-lifecycle.interface.js +3 -0
  197. package/dist/infrastructure/platforms/windows/interfaces/wsl-instance-naming.interface.js +3 -0
  198. package/dist/infrastructure/platforms/windows/interfaces/wsl-manager.interface.js +3 -0
  199. package/dist/infrastructure/platforms/windows/interfaces/wsl-resources.interface.js +8 -0
  200. package/dist/infrastructure/platforms/windows/interfaces/wsl-updater.interface.js +3 -0
  201. package/dist/infrastructure/platforms/windows/interfaces/wslconfig-parser.interface.js +3 -0
  202. package/dist/infrastructure/platforms/windows/parsers/wsl-version.parser.js +133 -0
  203. package/dist/infrastructure/platforms/windows/services/microk8s.service.js +168 -0
  204. package/dist/infrastructure/platforms/windows/services/rootfs-manager.service.js +336 -0
  205. package/dist/infrastructure/platforms/windows/services/windows-features.service.js +191 -0
  206. package/dist/infrastructure/platforms/windows/services/windows-info.service.js +138 -0
  207. package/dist/infrastructure/platforms/windows/services/wsl-config.service.js +171 -0
  208. package/dist/infrastructure/platforms/windows/services/wsl-info.service.js +226 -0
  209. package/dist/infrastructure/platforms/windows/services/wsl-instance-inspection.service.js +325 -0
  210. package/dist/infrastructure/platforms/windows/services/wsl-instance-lifecycle.service.js +442 -0
  211. package/dist/infrastructure/platforms/windows/services/wsl-instance-naming.service.js +93 -0
  212. package/dist/infrastructure/platforms/windows/services/wsl-updater.service.js +273 -0
  213. package/dist/infrastructure/platforms/windows/services/wslconfig-parser.service.js +222 -0
  214. package/dist/infrastructure/platforms/windows/wsl-base-image.adapter.js +41 -0
  215. package/dist/infrastructure/platforms/windows/wsl-instance-manager.adapter.js +150 -0
  216. package/dist/infrastructure/utils/error-formatter.util.js +29 -0
  217. package/dist/infrastructure/utils/file-operations.util.js +201 -0
  218. package/dist/infrastructure/utils/input-validator.util.js +152 -0
  219. package/dist/infrastructure/utils/retry.util.js +98 -0
  220. package/dist/presentation/controllers/config.controller.js +146 -0
  221. package/dist/presentation/controllers/credentials.controller.js +105 -0
  222. package/dist/presentation/controllers/index.js +25 -0
  223. package/dist/presentation/controllers/instance.controller.js +363 -0
  224. package/dist/presentation/controllers/logs.controller.js +103 -0
  225. package/dist/presentation/controllers/setup.controller.js +175 -0
  226. package/dist/presentation/interfaces/cli-options.interface.js +8 -0
  227. package/dist/presentation/prompts/acr-credentials.prompt.js +76 -0
  228. package/dist/presentation/prompts/index.js +21 -0
  229. package/dist/presentation/ui/cli-progress.service.js +193 -0
  230. package/dist/presentation/ui/constants/output-symbols.js +42 -0
  231. package/dist/presentation/ui/index.js +27 -0
  232. package/dist/presentation/ui/interaction.service.js +276 -0
  233. package/dist/presentation/ui/interfaces/cli-progress.interface.js +9 -0
  234. package/dist/presentation/ui/interfaces/output-formatter.interface.js +23 -0
  235. package/dist/presentation/ui/log-level.enum.js +66 -0
  236. package/dist/presentation/ui/output-builder.service.js +378 -0
  237. package/dist/presentation/ui/output-formatter.service.js +393 -0
  238. package/package.json +65 -0
@@ -0,0 +1,163 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BaseHelmDeploymentService = void 0;
4
+ const retry_util_1 = require("../../utils/retry.util");
5
+ /**
6
+ * Helm 部署基類
7
+ *
8
+ * 提供 Helm Chart 部署的共通流程:
9
+ * 1. Registry 登入
10
+ * 2. 拉取 Chart
11
+ * 3. 前置處理(可選)
12
+ * 4. 安裝 Chart
13
+ * 5. 後置處理(可選)
14
+ * 6. 清理 Chart 檔案
15
+ *
16
+ * 子類需實作:
17
+ * - getDeploymentConfig(): 取得部署配置
18
+ * - installChart(): 安裝 Chart 邏輯
19
+ *
20
+ * 可選覆寫:
21
+ * - preInstall(): 安裝前處理
22
+ * - postInstall(): 安裝後處理
23
+ */
24
+ class BaseHelmDeploymentService {
25
+ constructor(helmRegistry, logger, envFactory) {
26
+ this.helmRegistry = helmRegistry;
27
+ this.logger = logger;
28
+ this.envFactory = envFactory;
29
+ }
30
+ /**
31
+ * 部署 Helm Chart(模板方法)
32
+ * @param instanceName - WSL 實例名稱
33
+ * @param params - 部署參數
34
+ * @param chartVersion - Chart 版本
35
+ * @param output - 輸出介面
36
+ */
37
+ async deployWithHelm(instanceName, params, chartVersion, output) {
38
+ const config = this.getDeploymentConfig(chartVersion);
39
+ this.logger.debug(`[${this.getServiceName()}] Starting deployment`, {
40
+ chart: config.chartName,
41
+ version: config.chartVersion,
42
+ instance: instanceName,
43
+ });
44
+ // 1. Registry 登入
45
+ await this.helmRegistry.login(instanceName, params.acrCredentials);
46
+ // 2. 拉取 Chart
47
+ const chartInfo = {
48
+ chartName: config.chartName,
49
+ version: config.chartVersion,
50
+ acrName: params.acrCredentials.name,
51
+ repository: config.repository,
52
+ };
53
+ const chartPath = await this.helmRegistry.pullChart(instanceName, chartInfo, output);
54
+ try {
55
+ // 3. 前置處理
56
+ await this.preInstall(instanceName, params, output);
57
+ // 4. 安裝 Chart
58
+ await this.installChart(instanceName, chartPath, params, output);
59
+ // 5. 後置處理
60
+ await this.postInstall(instanceName, params, output);
61
+ this.logger.debug(`[${this.getServiceName()}] Deployment completed`, {
62
+ chart: config.chartName,
63
+ instance: instanceName,
64
+ });
65
+ }
66
+ finally {
67
+ // 6. 清理 Chart 檔案
68
+ await this.helmRegistry.cleanupChart(instanceName, chartPath);
69
+ }
70
+ }
71
+ /**
72
+ * 安裝前處理(可選覆寫)
73
+ * @param instanceName - WSL 實例名稱
74
+ * @param params - 部署參數
75
+ * @param output - 輸出介面
76
+ */
77
+ async preInstall(_instanceName, _params, _output) {
78
+ // 預設不做任何處理,子類可覆寫
79
+ }
80
+ /**
81
+ * 安裝後處理(可選覆寫)
82
+ * @param instanceName - WSL 實例名稱
83
+ * @param params - 部署參數
84
+ * @param output - 輸出介面
85
+ */
86
+ async postInstall(_instanceName, _params, _output) {
87
+ // 預設不做任何處理,子類可覆寫
88
+ }
89
+ // =========================================================================
90
+ // 共享輔助方法
91
+ // =========================================================================
92
+ /**
93
+ * 建立 Helm upgrade --install 命令建構器
94
+ * @param instanceName - WSL 實例名稱
95
+ * @param options - Helm 安裝選項
96
+ * @returns ICommandBuilder 實例
97
+ */
98
+ createHelmUpgradeBuilder(instanceName, options) {
99
+ if (!this.envFactory) {
100
+ throw new Error('ExecutionEnvironmentFactory is required for createHelmUpgradeBuilder');
101
+ }
102
+ const env = this.envFactory.getForInstance(instanceName);
103
+ const builder = env.helm('upgrade')
104
+ .arg('--install')
105
+ .arg('--force');
106
+ // 添加 --wait 和 --timeout
107
+ if (options.wait) {
108
+ builder.arg('--wait');
109
+ if (options.timeout) {
110
+ builder.arg('--timeout', options.timeout);
111
+ }
112
+ }
113
+ // Release 名稱和 Chart 路徑
114
+ builder.arg(options.releaseName);
115
+ builder.arg(`"${options.chartPath}"`);
116
+ // 命名空間
117
+ const namespace = options.namespace ?? 'uofx';
118
+ builder.arg('-n', namespace);
119
+ builder.arg('--create-namespace');
120
+ // 添加 --set 參數
121
+ if (options.setArgs) {
122
+ for (const setArg of options.setArgs) {
123
+ builder.arg('--set', setArg);
124
+ }
125
+ }
126
+ // 添加敏感的 --set 參數
127
+ if (options.sensitiveSetArgs) {
128
+ for (const { key, value } of options.sensitiveSetArgs) {
129
+ builder.sensitiveArg('--set', `${key}="${value}"`);
130
+ }
131
+ }
132
+ // 從 stdin 讀取 values
133
+ if (options.sensitiveStdin) {
134
+ builder.arg('-f', '-');
135
+ builder.sensitiveStdin(options.sensitiveStdin);
136
+ }
137
+ return builder;
138
+ }
139
+ /**
140
+ * 執行 Helm 命令並帶有重試機制
141
+ * @param builder - ICommandBuilder 實例
142
+ * @param chartName - Chart 名稱(用於錯誤訊息)
143
+ * @param output - 輸出介面
144
+ */
145
+ async executeWithRetry(builder, chartName, output) {
146
+ const serviceName = this.getServiceName();
147
+ await (0, retry_util_1.retryWithBackoff)(async () => {
148
+ const result = await builder.exec();
149
+ if (result.exitCode !== 0) {
150
+ throw new Error(result.stderr);
151
+ }
152
+ }, (0, retry_util_1.createHelmRetryOptions)({
153
+ onRetry: (attempt) => {
154
+ output?.warning(`Retrying install (${attempt}/3)...`).flush();
155
+ this.logger.warn(`[${serviceName}] Retrying install (${attempt}/3)...`);
156
+ },
157
+ })).catch((error) => {
158
+ throw new Error(`Failed to install ${chartName} chart: ${error.message}`);
159
+ });
160
+ }
161
+ }
162
+ exports.BaseHelmDeploymentService = BaseHelmDeploymentService;
163
+ //# sourceMappingURL=base-helm-deployment.service.js.map
@@ -0,0 +1,126 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.HelmRegistryService = void 0;
16
+ const tsyringe_1 = require("tsyringe");
17
+ const tokens_1 = require("../../../di/tokens");
18
+ const acr_credentials_value_object_1 = require("../../../domain/value-objects/acr-credentials.value-object");
19
+ const sensitive_decorator_1 = require("../../../domain/decorators/sensitive.decorator");
20
+ /**
21
+ * Helm Registry 服務
22
+ *
23
+ * 負責管理 Helm Registry 相關操作,提供:
24
+ * - ACR (Azure Container Registry) 登入
25
+ * - Helm Chart 拉取
26
+ * - Chart 檔案清理
27
+ *
28
+ * 此服務整合了原先散落在 AppManagerService、InfraManagerService、
29
+ * ServiceManagerService 中的重複邏輯,遵循 DRY (Don't Repeat Yourself) 原則
30
+ *
31
+ * 實作 IHelmRegistry Domain Port
32
+ */
33
+ let HelmRegistryService = class HelmRegistryService {
34
+ constructor(envFactory, logger) {
35
+ this.envFactory = envFactory;
36
+ this.logger = logger;
37
+ }
38
+ /**
39
+ * 登入 Helm Registry (ACR)
40
+ *
41
+ * 使用 `helm registry login` 命令透過 stdin 傳遞密碼,避免密碼出現在命令列歷史中
42
+ *
43
+ * @param instanceName - WSL 實例名稱
44
+ * @param credentials - ACR 登入憑證
45
+ * @throws {Error} 登入失敗時拋出錯誤
46
+ */
47
+ async login(instanceName, credentials) {
48
+ const { name: acrName, account: acrAccount, password: acrPassword } = credentials;
49
+ const registry = `${acrName}.azurecr.io`;
50
+ this.logger.debug(`[HelmRegistry] Login to registry ${registry}...`);
51
+ const env = this.envFactory.getForInstance(instanceName);
52
+ const builder = env.helm('registry login')
53
+ .arg(registry)
54
+ .arg('--username', acrAccount)
55
+ .arg('--password-stdin')
56
+ .sensitiveStdin(acrPassword);
57
+ const result = await builder.exec();
58
+ if (result.exitCode !== 0) {
59
+ throw new Error(`Failed to login to helm registry: ${result.stderr}`);
60
+ }
61
+ this.logger.debug(`[HelmRegistry] Successfully logged in to ${registry}`);
62
+ }
63
+ /**
64
+ * 從 ACR 拉取 Helm Chart
65
+ *
66
+ * Chart 會被下載到 /tmp 目錄
67
+ *
68
+ * @param instanceName - WSL 實例名稱
69
+ * @param chartInfo - Chart 資訊 (名稱、版本、ACR 名稱)
70
+ * @param progress - 進度通知(可選)
71
+ * @returns Promise<string> - Chart 檔案的完整路徑 (如 '/tmp/uofx-1.0.0.tgz')
72
+ * @throws {Error} 拉取失敗時拋出錯誤
73
+ */
74
+ async pullChart(instanceName, chartInfo, output) {
75
+ const { chartName, version, acrName, repository = 'helm' } = chartInfo;
76
+ const registry = `${acrName}.azurecr.io`;
77
+ const chartUrl = `oci://${registry}/${repository}/${chartName}`;
78
+ const destDir = '/tmp';
79
+ this.logger.debug(`[HelmRegistry] Pulling chart ${chartUrl} version ${version}...`);
80
+ output?.item('arrow', `Pulling ${chartName} chart (v${version})...`).flush();
81
+ const env = this.envFactory.getForInstance(instanceName);
82
+ const builder = env.helm('pull')
83
+ .arg(chartUrl)
84
+ .arg('--version', version)
85
+ .arg('--destination', destDir);
86
+ const result = await builder.exec();
87
+ if (result.exitCode !== 0) {
88
+ throw new Error(`Failed to pull ${chartName} chart: ${result.stderr}`);
89
+ }
90
+ const chartPath = `${destDir}/${chartName}-${version}.tgz`;
91
+ this.logger.debug(`[HelmRegistry] Chart pulled successfully: ${chartPath}`);
92
+ return chartPath;
93
+ }
94
+ /**
95
+ * 清理暫存的 Chart 檔案
96
+ *
97
+ * 從 WSL 實例的 /tmp 目錄中刪除指定的 Chart 檔案
98
+ * 此操作不會拋出錯誤,即使檔案不存在也會靜默成功
99
+ *
100
+ * @param instanceName - WSL 實例名稱
101
+ * @param chartPath - Chart 檔案的完整路徑
102
+ */
103
+ async cleanupChart(instanceName, chartPath) {
104
+ this.logger.debug(`[HelmRegistry] Cleaning up chart: ${chartPath}`);
105
+ const env = this.envFactory.getForInstance(instanceName);
106
+ const builder = env.shell('rm')
107
+ .arg('-f')
108
+ .arg(chartPath);
109
+ await builder.exec();
110
+ this.logger.debug(`[HelmRegistry] Chart cleaned up: ${chartPath}`);
111
+ }
112
+ };
113
+ exports.HelmRegistryService = HelmRegistryService;
114
+ __decorate([
115
+ __param(1, (0, sensitive_decorator_1.Sensitive)()),
116
+ __metadata("design:type", Function),
117
+ __metadata("design:paramtypes", [String, acr_credentials_value_object_1.AcrCredentials]),
118
+ __metadata("design:returntype", Promise)
119
+ ], HelmRegistryService.prototype, "login", null);
120
+ exports.HelmRegistryService = HelmRegistryService = __decorate([
121
+ (0, tsyringe_1.injectable)(),
122
+ __param(0, (0, tsyringe_1.inject)(tokens_1.TOKENS.Internal.IExecutionEnvironmentFactory)),
123
+ __param(1, (0, tsyringe_1.inject)(tokens_1.TOKENS.ILoggerPort)),
124
+ __metadata("design:paramtypes", [Object, Object])
125
+ ], HelmRegistryService);
126
+ //# sourceMappingURL=helm-registry.service.js.map
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.InfraManagerService = void 0;
16
+ const tsyringe_1 = require("tsyringe");
17
+ const tokens_1 = require("../../../di/tokens");
18
+ const deployment_parameters_entity_1 = require("../../../domain/entities/deployment-parameters.entity");
19
+ const sensitive_decorator_1 = require("../../../domain/decorators/sensitive.decorator");
20
+ const base_helm_deployment_service_1 = require("./base-helm-deployment.service");
21
+ const paths_1 = require("../../../constants/paths");
22
+ /**
23
+ * 基礎設施管理服務
24
+ *
25
+ * 負責 uofx-infra Helm chart 的部署管理
26
+ * 繼承 BaseHelmDeploymentService 使用模板方法模式
27
+ */
28
+ let InfraManagerService = class InfraManagerService extends base_helm_deployment_service_1.BaseHelmDeploymentService {
29
+ constructor(envFactory, helmRegistry, logger) {
30
+ super(helmRegistry, logger, envFactory);
31
+ }
32
+ /**
33
+ * 部署 uofx-infra 基礎設施
34
+ */
35
+ async deployInfra(instanceName, params, chartVersion, output) {
36
+ output?.item('arrow', 'Deploying uofx-infra chart...').flush();
37
+ await this.deployWithHelm(instanceName, params, chartVersion, output);
38
+ }
39
+ // =========================================================================
40
+ // BaseHelmDeploymentService 抽象方法實作
41
+ // =========================================================================
42
+ getServiceName() {
43
+ return 'Infra';
44
+ }
45
+ getDeploymentConfig(chartVersion) {
46
+ return {
47
+ chartName: 'uofx-infra',
48
+ chartVersion,
49
+ };
50
+ }
51
+ /**
52
+ * 安裝前處理:建立儲存目錄
53
+ */
54
+ async preInstall(instanceName, _params, _output) {
55
+ await this.createStorageDirectories(instanceName);
56
+ }
57
+ async installChart(instanceName, chartPath, params, output) {
58
+ const { name: acrName, account: acrAccount, password: acrPassword } = params.acrCredentials;
59
+ const registry = `${acrName}.azurecr.io`;
60
+ const builder = this.createHelmUpgradeBuilder(instanceName, {
61
+ releaseName: 'uofx-infra',
62
+ chartPath,
63
+ setArgs: [
64
+ `k8sType="microk8s"`,
65
+ `traefik.web.TLS.enable=true`,
66
+ `traefik.admin.TLS.enable=true`,
67
+ `imageCredentials.registry="${registry}"`,
68
+ `imageCredentials.username="${acrAccount}"`,
69
+ `storage.type="local"`,
70
+ `storage.local.defaultPath="${paths_1.LINUX_PATHS.UOFX_STORAGE_BASE}"`,
71
+ `storage.local.mqShare.path="/rabbitmq"`,
72
+ `storage.local.lokiShare.path="/loki"`,
73
+ `storage.local.redisShare.path="/redis"`,
74
+ `storage.local.fileShare.path="/file"`,
75
+ `storage.local.syncShare.path="/system"`,
76
+ `storage.local.searchShare.path="/search"`,
77
+ `loki.retentionPeriod="365d"`,
78
+ `installType="install"`,
79
+ `enabledSearch=true`,
80
+ ],
81
+ sensitiveSetArgs: [
82
+ { key: 'imageCredentials.password', value: acrPassword },
83
+ ],
84
+ });
85
+ this.logger.debug('[Infra] Deploying infrastructure chart', {
86
+ chart: 'uofx-infra',
87
+ chartPath: chartPath,
88
+ namespace: 'uofx',
89
+ instance: instanceName,
90
+ });
91
+ output?.indent().item('arrow', 'Installing uofx-infra chart...').outdent().flush();
92
+ await this.executeWithRetry(builder, 'uofx-infra', output);
93
+ }
94
+ // =========================================================================
95
+ // 私有方法
96
+ // =========================================================================
97
+ /**
98
+ * 建立持久化存儲目錄
99
+ */
100
+ async createStorageDirectories(instanceName) {
101
+ this.logger.debug(`[Infra] Creating storage directories for ${instanceName}...`);
102
+ const baseDir = paths_1.LINUX_PATHS.UOFX_STORAGE_BASE;
103
+ const subDirs = ['rabbitmq', 'loki', 'redis', 'file', 'system', 'search'];
104
+ const directories = subDirs.map(sub => `${baseDir}/${sub}`);
105
+ const env = this.envFactory.getForInstance(instanceName);
106
+ const builder = env.shell('mkdir')
107
+ .arg('-p')
108
+ .args(...directories);
109
+ const result = await builder.exec();
110
+ if (result.exitCode !== 0) {
111
+ throw new Error(`Failed to create storage directories: ${result.stderr}`);
112
+ }
113
+ this.logger.debug('[Infra] Storage directories created');
114
+ }
115
+ };
116
+ exports.InfraManagerService = InfraManagerService;
117
+ __decorate([
118
+ __param(1, (0, sensitive_decorator_1.Sensitive)()),
119
+ __metadata("design:type", Function),
120
+ __metadata("design:paramtypes", [String, deployment_parameters_entity_1.DeploymentParameters, String, Object]),
121
+ __metadata("design:returntype", Promise)
122
+ ], InfraManagerService.prototype, "deployInfra", null);
123
+ exports.InfraManagerService = InfraManagerService = __decorate([
124
+ (0, tsyringe_1.injectable)(),
125
+ __param(0, (0, tsyringe_1.inject)(tokens_1.TOKENS.Internal.IExecutionEnvironmentFactory)),
126
+ __param(1, (0, tsyringe_1.inject)(tokens_1.TOKENS.Internal.IHelmRegistry)),
127
+ __param(2, (0, tsyringe_1.inject)(tokens_1.TOKENS.ILoggerPort)),
128
+ __metadata("design:paramtypes", [Object, Object, Object])
129
+ ], InfraManagerService);
130
+ //# sourceMappingURL=infra-manager.service.js.map
@@ -0,0 +1,194 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.K8sJobRunnerService = void 0;
16
+ const tsyringe_1 = require("tsyringe");
17
+ const tokens_1 = require("../../../di/tokens");
18
+ const deployment_parameters_entity_1 = require("../../../domain/entities/deployment-parameters.entity");
19
+ const sensitive_decorator_1 = require("../../../domain/decorators/sensitive.decorator");
20
+ /**
21
+ * Kubernetes Job 執行服務
22
+ *
23
+ * 負責執行 Kubernetes Job 的完整生命週期
24
+ */
25
+ let K8sJobRunnerService = class K8sJobRunnerService {
26
+ constructor(envFactory, userManagerService, logger) {
27
+ this.envFactory = envFactory;
28
+ this.userManagerService = userManagerService;
29
+ this.logger = logger;
30
+ }
31
+ /**
32
+ * 建立 Image Pull Secret (用於拉取 Job 映像檔)
33
+ * @param instanceName WSL 實例名稱
34
+ * @param params 部署參數
35
+ */
36
+ async createImagePullSecret(instanceName, params) {
37
+ const { name: acrName, account: acrAccount, password: acrPassword } = params.acrCredentials;
38
+ const registry = `${acrName}.azurecr.io`;
39
+ const env = this.envFactory.getForInstance(instanceName);
40
+ const builder = env.kubectl('create secret docker-registry')
41
+ .arg('image-pull-secret-test')
42
+ .arg('-n', 'mssql')
43
+ .arg(`--docker-server=${registry}`)
44
+ .arg(`--docker-username=${acrAccount}`)
45
+ .sensitiveArg('--docker-password', acrPassword);
46
+ // 忽略錯誤如果 Secret 已存在
47
+ const deleteBuilder = env.kubectl('delete')
48
+ .arg('secret')
49
+ .arg('image-pull-secret-test')
50
+ .arg('-n', 'mssql');
51
+ await deleteBuilder.exec();
52
+ const result = await builder.exec();
53
+ if (result.exitCode !== 0) {
54
+ throw new Error(`Failed to create image pull secret: ${result.stderr}`);
55
+ }
56
+ }
57
+ /**
58
+ * 刪除 Image Pull Secret
59
+ * @param instanceName WSL 實例名稱
60
+ */
61
+ async deleteImagePullSecret(instanceName) {
62
+ const env = this.envFactory.getForInstance(instanceName);
63
+ const builder = env.kubectl('delete')
64
+ .arg('secret')
65
+ .arg('image-pull-secret-test')
66
+ .arg('-n', 'mssql');
67
+ await builder.exec();
68
+ }
69
+ /**
70
+ * 執行 Kubernetes Job
71
+ * @param instanceName WSL 實例名稱
72
+ * @param jobName Job 名稱
73
+ * @param imageName 映像檔名稱
74
+ * @param version 映像檔版本
75
+ * @param params 部署參數
76
+ */
77
+ async runJob(instanceName, jobName, imageName, version, params) {
78
+ const registry = `${params.acrCredentials.name}.azurecr.io`;
79
+ const image = `${registry}/${imageName}:${version}`;
80
+ const env = this.envFactory.getForInstance(instanceName);
81
+ // Job YAML 模板
82
+ const jobYaml = {
83
+ apiVersion: 'batch/v1',
84
+ kind: 'Job',
85
+ metadata: {
86
+ name: jobName,
87
+ namespace: 'mssql'
88
+ },
89
+ spec: {
90
+ template: {
91
+ spec: {
92
+ imagePullSecrets: [{ name: 'image-pull-secret-test' }],
93
+ containers: [{
94
+ name: 'init-db',
95
+ image: image,
96
+ env: [
97
+ { name: 'SQL_HOST', value: 'database-mssql-latest.mssql' },
98
+ { name: 'SQL_USER', value: 'sa' },
99
+ { name: 'SQL_PASSWORD', value: params.saPassword.toString() },
100
+ { name: 'SQL_DATABASE', value: jobName === 'db-init' ? 'uofx_dev' : 'uofx_search' },
101
+ { name: 'ADMIN_PASSWORD', value: params.adminPassword.toString() },
102
+ { name: 'DISABLE_COLOR', value: '1' }
103
+ ],
104
+ command: ['/bin/bash', '-c', 'cd /src && ./uofxDbHelper.sh -A']
105
+ }],
106
+ restartPolicy: 'Never'
107
+ }
108
+ },
109
+ backoffLimit: 2
110
+ }
111
+ };
112
+ // 如果 Job 已存在則刪除
113
+ const deleteExistingBuilder = env.kubectl('delete')
114
+ .arg('job')
115
+ .arg(jobName)
116
+ .arg('-n', 'mssql');
117
+ await deleteExistingBuilder.exec();
118
+ // 應用 Job
119
+ const applyBuilder = env.kubectl('apply')
120
+ .arg('-f', '-')
121
+ .sensitiveStdin(JSON.stringify(jobYaml));
122
+ const applyResult = await applyBuilder.exec();
123
+ if (applyResult.exitCode !== 0) {
124
+ throw new Error(`Failed to start job ${jobName}: ${applyResult.stderr}`);
125
+ }
126
+ // 等待完成
127
+ const waitBuilder = env.kubectl('wait')
128
+ .arg('--for=condition=complete')
129
+ .arg(`job/${jobName}`)
130
+ .arg('-n', 'mssql')
131
+ .arg('--timeout=300s');
132
+ const waitResult = await waitBuilder.exec();
133
+ if (waitResult.exitCode !== 0) {
134
+ // 失敗時檢查日誌
135
+ const logsBuilder = env.kubectl('logs')
136
+ .arg(`job/${jobName}`)
137
+ .arg('-n', 'mssql');
138
+ const logs = await logsBuilder.exec();
139
+ this.logger.error(new Error(`Job ${jobName} logs:
140
+ ${logs.stdout}`));
141
+ throw new Error(`Job ${jobName} failed or timed out`);
142
+ }
143
+ // 清理 Job
144
+ const cleanupBuilder = env.kubectl('delete')
145
+ .arg('job')
146
+ .arg(jobName)
147
+ .arg('-n', 'mssql');
148
+ await cleanupBuilder.exec();
149
+ }
150
+ /**
151
+ * 執行所有 MSSQL 初始化 Jobs
152
+ * @param instanceName WSL 實例名稱
153
+ * @param params 部署參數
154
+ * @param chartVersion Chart 版本
155
+ * @param progress 進度通知回調
156
+ */
157
+ async runInitJobs(instanceName, params, chartVersion, output) {
158
+ output?.item('arrow', 'Initializing databases...').flush();
159
+ // 1. 建立臨時 Secret
160
+ output?.indent().item('arrow', 'Creating image pull secret...').outdent().flush();
161
+ await this.createImagePullSecret(instanceName, params);
162
+ try {
163
+ // 2. 執行 InitDb
164
+ output?.indent().item('arrow', 'Running db-init job...').outdent().flush();
165
+ await this.runJob(instanceName, 'db-init', 'uofx-k8s-db-helper', chartVersion, params);
166
+ // 3. 建立管理員使用者
167
+ output?.indent().item('arrow', 'Creating admin user...').outdent().flush();
168
+ await this.userManagerService.createAdminUser(instanceName, params);
169
+ // 4. 執行 InitSearchDb
170
+ output?.indent().item('arrow', 'Running search-db-init job...').outdent().flush();
171
+ await this.runJob(instanceName, 'search-db-init', 'uofx-k8s-search-db-helper', chartVersion, params);
172
+ }
173
+ finally {
174
+ // 5. 清理 Secret
175
+ output?.indent().item('arrow', 'Cleaning up resources...').outdent().flush();
176
+ await this.deleteImagePullSecret(instanceName);
177
+ }
178
+ }
179
+ };
180
+ exports.K8sJobRunnerService = K8sJobRunnerService;
181
+ __decorate([
182
+ __param(1, (0, sensitive_decorator_1.Sensitive)()),
183
+ __metadata("design:type", Function),
184
+ __metadata("design:paramtypes", [String, deployment_parameters_entity_1.DeploymentParameters, String, Object]),
185
+ __metadata("design:returntype", Promise)
186
+ ], K8sJobRunnerService.prototype, "runInitJobs", null);
187
+ exports.K8sJobRunnerService = K8sJobRunnerService = __decorate([
188
+ (0, tsyringe_1.injectable)(),
189
+ __param(0, (0, tsyringe_1.inject)(tokens_1.TOKENS.Internal.IExecutionEnvironmentFactory)),
190
+ __param(1, (0, tsyringe_1.inject)(tokens_1.TOKENS.Internal.IMssqlUserManager)),
191
+ __param(2, (0, tsyringe_1.inject)(tokens_1.TOKENS.ILoggerPort)),
192
+ __metadata("design:paramtypes", [Object, Object, Object])
193
+ ], K8sJobRunnerService);
194
+ //# sourceMappingURL=k8s-job-runner.service.js.map