@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.
- package/LICENSE +40 -0
- package/README.md +444 -0
- package/THIRD-PARTY-NOTICES.txt +894 -0
- package/dist/application/dtos/index.js +24 -0
- package/dist/application/dtos/request/delete-instance.request.dto.js +3 -0
- package/dist/application/dtos/request/get-config.request.dto.js +3 -0
- package/dist/application/dtos/request/get-credentials.request.dto.js +3 -0
- package/dist/application/dtos/request/index.js +27 -0
- package/dist/application/dtos/request/install-instance.request.dto.js +3 -0
- package/dist/application/dtos/request/list-charts.request.dto.js +3 -0
- package/dist/application/dtos/request/set-config.request.dto.js +16 -0
- package/dist/application/dtos/request/setup-environment.request.dto.js +16 -0
- package/dist/application/dtos/request/show-logs.request.dto.js +19 -0
- package/dist/application/dtos/request/start-instance.request.dto.js +3 -0
- package/dist/application/dtos/request/stop-instance.request.dto.js +3 -0
- package/dist/application/dtos/response/credentials.response.dto.js +3 -0
- package/dist/application/dtos/response/delete-instance.response.dto.js +3 -0
- package/dist/application/dtos/response/index.js +26 -0
- package/dist/application/dtos/response/install-instance.response.dto.js +3 -0
- package/dist/application/dtos/response/instance-list.response.dto.js +3 -0
- package/dist/application/dtos/response/instance-status.response.dto.js +3 -0
- package/dist/application/dtos/response/setup-result.response.dto.js +3 -0
- package/dist/application/dtos/response/show-logs.response.dto.js +3 -0
- package/dist/application/dtos/response/start-instance.response.dto.js +3 -0
- package/dist/application/dtos/response/stop-instance.response.dto.js +3 -0
- package/dist/application/index.js +25 -0
- package/dist/application/interfaces/index.js +24 -0
- package/dist/application/interfaces/use-case.interface.js +3 -0
- package/dist/application/use-cases/config/get-config.use-case.js +66 -0
- package/dist/application/use-cases/config/set-config.use-case.js +49 -0
- package/dist/application/use-cases/credentials/get-credentials.use-case.js +57 -0
- package/dist/application/use-cases/index.js +28 -0
- package/dist/application/use-cases/instance/delete-instance.use-case.js +81 -0
- package/dist/application/use-cases/instance/index.js +23 -0
- package/dist/application/use-cases/instance/install-instance.use-case.js +424 -0
- package/dist/application/use-cases/instance/list-charts.use-case.js +43 -0
- package/dist/application/use-cases/instance/list-instances.use-case.js +62 -0
- package/dist/application/use-cases/instance/start-instance.use-case.js +154 -0
- package/dist/application/use-cases/instance/stop-instance.use-case.js +55 -0
- package/dist/application/use-cases/logs/show-logs.use-case.js +66 -0
- package/dist/application/use-cases/setup/setup-environment.use-case.js +53 -0
- package/dist/cli.js +286 -0
- package/dist/constants/config-defaults.js +23 -0
- package/dist/constants/defaults.js +89 -0
- package/dist/constants/deployment.js +39 -0
- package/dist/constants/environments.js +93 -0
- package/dist/constants/index.js +53 -0
- package/dist/constants/oci-artifacts.js +25 -0
- package/dist/constants/paths.js +92 -0
- package/dist/constants/timeouts.js +60 -0
- package/dist/di/container.js +34 -0
- package/dist/di/index.js +22 -0
- package/dist/di/modules/application.module.js +54 -0
- package/dist/di/modules/infrastructure.module.js +206 -0
- package/dist/di/modules/interceptor.module.js +68 -0
- package/dist/di/modules/presentation.module.js +31 -0
- package/dist/di/tokens.js +149 -0
- package/dist/domain/decorators/sensitive.decorator.js +39 -0
- package/dist/domain/entities/credentials-resolver.entity.js +127 -0
- package/dist/domain/entities/credentials.entity.js +65 -0
- package/dist/domain/entities/delete-instance-validation.entity.js +100 -0
- package/dist/domain/entities/deployment-parameters.entity.js +120 -0
- package/dist/domain/entities/environment-validation.entity.js +125 -0
- package/dist/domain/entities/index.js +29 -0
- package/dist/domain/entities/instance-lifecycle-state.entity.js +100 -0
- package/dist/domain/entities/instance-list-aggregator.entity.js +104 -0
- package/dist/domain/entities/instance-metadata.entity.js +86 -0
- package/dist/domain/entities/instance-status.entity.js +79 -0
- package/dist/domain/entities/instance.entity.js +128 -0
- package/dist/domain/entities/log-filter.entity.js +141 -0
- package/dist/domain/index.js +29 -0
- package/dist/domain/interfaces/safe-loggable.interface.js +3 -0
- package/dist/domain/ports/app-config.port.js +3 -0
- package/dist/domain/ports/base-image.port.js +3 -0
- package/dist/domain/ports/chart-version.port.js +3 -0
- package/dist/domain/ports/correlation-id.port.js +3 -0
- package/dist/domain/ports/credentials.port.js +3 -0
- package/dist/domain/ports/deployment.port.js +3 -0
- package/dist/domain/ports/error-handler.port.js +3 -0
- package/dist/domain/ports/index.js +46 -0
- package/dist/domain/ports/instance-manager.port.js +3 -0
- package/dist/domain/ports/instance-metadata.port.js +8 -0
- package/dist/domain/ports/instance-storage.port.js +3 -0
- package/dist/domain/ports/k8s-deployer.port.js +3 -0
- package/dist/domain/ports/logger.port.js +14 -0
- package/dist/domain/ports/output.port.js +3 -0
- package/dist/domain/ports/runtime-environment.port.js +6 -0
- package/dist/domain/ports/user-interaction.port.js +9 -0
- package/dist/domain/ports/user-settings.port.js +3 -0
- package/dist/domain/types/index.js +22 -0
- package/dist/domain/types/logger.types.js +29 -0
- package/dist/domain/types/validation.types.js +9 -0
- package/dist/domain/value-objects/acr-credentials.value-object.js +92 -0
- package/dist/domain/value-objects/chart-version.value-object.js +124 -0
- package/dist/domain/value-objects/config-log-level.value-object.js +84 -0
- package/dist/domain/value-objects/connection-info.value-object.js +65 -0
- package/dist/domain/value-objects/index.js +25 -0
- package/dist/domain/value-objects/instance-name.value-object.js +91 -0
- package/dist/domain/value-objects/jwt-key.value-object.js +97 -0
- package/dist/domain/value-objects/mssql-password.value-object.js +140 -0
- package/dist/domain/value-objects/rsa-key-pair.value-object.js +181 -0
- package/dist/index.js +6 -0
- package/dist/infrastructure/config/app-config.interface.js +3 -0
- package/dist/infrastructure/config/app-config.service.js +280 -0
- package/dist/infrastructure/config/config-validator.js +31 -0
- package/dist/infrastructure/config/crypto.service.js +125 -0
- package/dist/infrastructure/deployment/deployment.adapter.js +118 -0
- package/dist/infrastructure/deployment/interfaces/acr-credential-manager.interface.js +3 -0
- package/dist/infrastructure/deployment/interfaces/app-manager.interface.js +3 -0
- package/dist/infrastructure/deployment/interfaces/helm-registry.interface.js +3 -0
- package/dist/infrastructure/deployment/interfaces/infra-manager.interface.js +3 -0
- package/dist/infrastructure/deployment/interfaces/k8s-job-runner.interface.js +3 -0
- package/dist/infrastructure/deployment/interfaces/mssql-database-init.interface.js +3 -0
- package/dist/infrastructure/deployment/interfaces/mssql-helm-deployment.interface.js +3 -0
- package/dist/infrastructure/deployment/interfaces/mssql-storage.interface.js +3 -0
- package/dist/infrastructure/deployment/interfaces/mssql-user-manager.interface.js +3 -0
- package/dist/infrastructure/deployment/interfaces/oci-artifact.interface.js +3 -0
- package/dist/infrastructure/deployment/interfaces/secret-manager.interface.js +3 -0
- package/dist/infrastructure/deployment/interfaces/service-manager.interface.js +3 -0
- package/dist/infrastructure/deployment/interfaces/version-compatibility.interface.js +3 -0
- package/dist/infrastructure/deployment/services/acr-credential-manager.service.js +144 -0
- package/dist/infrastructure/deployment/services/app-manager.service.js +193 -0
- package/dist/infrastructure/deployment/services/base-helm-deployment.service.js +163 -0
- package/dist/infrastructure/deployment/services/helm-registry.service.js +126 -0
- package/dist/infrastructure/deployment/services/infra-manager.service.js +130 -0
- package/dist/infrastructure/deployment/services/k8s-job-runner.service.js +194 -0
- package/dist/infrastructure/deployment/services/mssql-database-init.service.js +139 -0
- package/dist/infrastructure/deployment/services/mssql-helm-deployment.service.js +100 -0
- package/dist/infrastructure/deployment/services/mssql-storage.service.js +54 -0
- package/dist/infrastructure/deployment/services/mssql-user-manager.service.js +66 -0
- package/dist/infrastructure/deployment/services/oci-artifact.service.js +289 -0
- package/dist/infrastructure/deployment/services/secret-manager.service.js +179 -0
- package/dist/infrastructure/deployment/services/service-manager.service.js +82 -0
- package/dist/infrastructure/deployment/services/version-compatibility.service.js +291 -0
- package/dist/infrastructure/environment/interfaces/hardware-info.interface.js +3 -0
- package/dist/infrastructure/environment/interfaces/network-checker.interface.js +3 -0
- package/dist/infrastructure/environment/services/hardware-info.service.js +135 -0
- package/dist/infrastructure/environment/services/network-checker.service.js +142 -0
- package/dist/infrastructure/environment/windows-environment.adapter.js +162 -0
- package/dist/infrastructure/errors/app-error.js +73 -0
- package/dist/infrastructure/errors/error-handler.interface.js +3 -0
- package/dist/infrastructure/errors/error-handler.js +218 -0
- package/dist/infrastructure/errors/exit-codes.js +27 -0
- package/dist/infrastructure/errors/index.js +25 -0
- package/dist/infrastructure/execution/builders/base-command.builder.js +122 -0
- package/dist/infrastructure/execution/builders/host-command.builder.js +58 -0
- package/dist/infrastructure/execution/builders/windows-host-command.builder.js +50 -0
- package/dist/infrastructure/execution/builders/wsl-command.builder.js +29 -0
- package/dist/infrastructure/execution/command-builder.js +252 -0
- package/dist/infrastructure/execution/command-executor.service.js +230 -0
- package/dist/infrastructure/execution/environments/wsl-execution.environment.js +70 -0
- package/dist/infrastructure/execution/execution-environment.factory.js +53 -0
- package/dist/infrastructure/execution/index.js +25 -0
- package/dist/infrastructure/execution/interfaces/command-builder.interface.js +3 -0
- package/dist/infrastructure/execution/interfaces/command-executor.interface.js +3 -0
- package/dist/infrastructure/execution/interfaces/execution-environment-factory.interface.js +3 -0
- package/dist/infrastructure/execution/interfaces/execution-environment.interface.js +7 -0
- package/dist/infrastructure/execution/interfaces/host-command-builder.interface.js +3 -0
- package/dist/infrastructure/execution/interfaces/index.js +23 -0
- package/dist/infrastructure/execution/interfaces/script-executor.interface.js +3 -0
- package/dist/infrastructure/execution/script-executor.service.js +171 -0
- package/dist/infrastructure/http/http-client.service.js +176 -0
- package/dist/infrastructure/http/index.js +18 -0
- package/dist/infrastructure/http/interfaces/http-client.interface.js +3 -0
- package/dist/infrastructure/interceptors/index.js +8 -0
- package/dist/infrastructure/interceptors/interceptor.factory.js +44 -0
- package/dist/infrastructure/interceptors/interceptor.interface.js +3 -0
- package/dist/infrastructure/interceptors/logging.interceptor.js +171 -0
- package/dist/infrastructure/logger/correlation-id.adapter.js +68 -0
- package/dist/infrastructure/logger/index.js +23 -0
- package/dist/infrastructure/logger/interfaces/index.js +22 -0
- package/dist/infrastructure/logger/interfaces/log-reader.repository.interface.js +7 -0
- package/dist/infrastructure/logger/interfaces/log-writer.repository.interface.js +7 -0
- package/dist/infrastructure/logger/logger.adapter.js +274 -0
- package/dist/infrastructure/logger/services/file-log-reader.repository.js +148 -0
- package/dist/infrastructure/logger/services/file-log-writer.repository.js +307 -0
- package/dist/infrastructure/logger/services/index.js +22 -0
- package/dist/infrastructure/persistence/index.js +25 -0
- package/dist/infrastructure/persistence/instance-metadata.adapter.js +100 -0
- package/dist/infrastructure/persistence/instance-storage.adapter.js +64 -0
- package/dist/infrastructure/persistence/interfaces/config.repository.interface.js +3 -0
- package/dist/infrastructure/persistence/interfaces/index.js +22 -0
- package/dist/infrastructure/persistence/interfaces/instance.repository.interface.js +3 -0
- package/dist/infrastructure/persistence/services/file-system-config.repository.js +168 -0
- package/dist/infrastructure/persistence/services/file-system-instance.repository.js +170 -0
- package/dist/infrastructure/persistence/services/index.js +22 -0
- package/dist/infrastructure/persistence/user-settings.adapter.js +55 -0
- package/dist/infrastructure/platform-detector.js +71 -0
- package/dist/infrastructure/platforms/windows/interfaces/microk8s.interface.js +3 -0
- package/dist/infrastructure/platforms/windows/interfaces/rootfs-manager.interface.js +3 -0
- package/dist/infrastructure/platforms/windows/interfaces/windows-features.interface.js +3 -0
- package/dist/infrastructure/platforms/windows/interfaces/windows-info.interface.js +3 -0
- package/dist/infrastructure/platforms/windows/interfaces/wsl-config.interface.js +3 -0
- package/dist/infrastructure/platforms/windows/interfaces/wsl-info.interface.js +3 -0
- package/dist/infrastructure/platforms/windows/interfaces/wsl-instance-inspection.interface.js +3 -0
- package/dist/infrastructure/platforms/windows/interfaces/wsl-instance-lifecycle.interface.js +3 -0
- package/dist/infrastructure/platforms/windows/interfaces/wsl-instance-naming.interface.js +3 -0
- package/dist/infrastructure/platforms/windows/interfaces/wsl-manager.interface.js +3 -0
- package/dist/infrastructure/platforms/windows/interfaces/wsl-resources.interface.js +8 -0
- package/dist/infrastructure/platforms/windows/interfaces/wsl-updater.interface.js +3 -0
- package/dist/infrastructure/platforms/windows/interfaces/wslconfig-parser.interface.js +3 -0
- package/dist/infrastructure/platforms/windows/parsers/wsl-version.parser.js +133 -0
- package/dist/infrastructure/platforms/windows/services/microk8s.service.js +168 -0
- package/dist/infrastructure/platforms/windows/services/rootfs-manager.service.js +336 -0
- package/dist/infrastructure/platforms/windows/services/windows-features.service.js +191 -0
- package/dist/infrastructure/platforms/windows/services/windows-info.service.js +138 -0
- package/dist/infrastructure/platforms/windows/services/wsl-config.service.js +171 -0
- package/dist/infrastructure/platforms/windows/services/wsl-info.service.js +226 -0
- package/dist/infrastructure/platforms/windows/services/wsl-instance-inspection.service.js +325 -0
- package/dist/infrastructure/platforms/windows/services/wsl-instance-lifecycle.service.js +442 -0
- package/dist/infrastructure/platforms/windows/services/wsl-instance-naming.service.js +93 -0
- package/dist/infrastructure/platforms/windows/services/wsl-updater.service.js +273 -0
- package/dist/infrastructure/platforms/windows/services/wslconfig-parser.service.js +222 -0
- package/dist/infrastructure/platforms/windows/wsl-base-image.adapter.js +41 -0
- package/dist/infrastructure/platforms/windows/wsl-instance-manager.adapter.js +150 -0
- package/dist/infrastructure/utils/error-formatter.util.js +29 -0
- package/dist/infrastructure/utils/file-operations.util.js +201 -0
- package/dist/infrastructure/utils/input-validator.util.js +152 -0
- package/dist/infrastructure/utils/retry.util.js +98 -0
- package/dist/presentation/controllers/config.controller.js +146 -0
- package/dist/presentation/controllers/credentials.controller.js +105 -0
- package/dist/presentation/controllers/index.js +25 -0
- package/dist/presentation/controllers/instance.controller.js +363 -0
- package/dist/presentation/controllers/logs.controller.js +103 -0
- package/dist/presentation/controllers/setup.controller.js +175 -0
- package/dist/presentation/interfaces/cli-options.interface.js +8 -0
- package/dist/presentation/prompts/acr-credentials.prompt.js +76 -0
- package/dist/presentation/prompts/index.js +21 -0
- package/dist/presentation/ui/cli-progress.service.js +193 -0
- package/dist/presentation/ui/constants/output-symbols.js +42 -0
- package/dist/presentation/ui/index.js +27 -0
- package/dist/presentation/ui/interaction.service.js +276 -0
- package/dist/presentation/ui/interfaces/cli-progress.interface.js +9 -0
- package/dist/presentation/ui/interfaces/output-formatter.interface.js +23 -0
- package/dist/presentation/ui/log-level.enum.js +66 -0
- package/dist/presentation/ui/output-builder.service.js +378 -0
- package/dist/presentation/ui/output-formatter.service.js +393 -0
- package/package.json +65 -0
|
@@ -0,0 +1,139 @@
|
|
|
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.MssqlDatabaseInitService = void 0;
|
|
16
|
+
const tsyringe_1 = require("tsyringe");
|
|
17
|
+
const tokens_1 = require("../../../di/tokens");
|
|
18
|
+
const paths_1 = require("../../../constants/paths");
|
|
19
|
+
/**
|
|
20
|
+
* MSSQL 資料庫初始化服務
|
|
21
|
+
*
|
|
22
|
+
* 負責等待 MSSQL Pod 就緒和建立初始資料庫
|
|
23
|
+
*/
|
|
24
|
+
let MssqlDatabaseInitService = class MssqlDatabaseInitService {
|
|
25
|
+
constructor(envFactory, logger) {
|
|
26
|
+
this.envFactory = envFactory;
|
|
27
|
+
this.logger = logger;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 等待 MSSQL Pod 完全就緒
|
|
31
|
+
* @param instanceName WSL 實例名稱
|
|
32
|
+
*/
|
|
33
|
+
async waitForMssqlReady(instanceName) {
|
|
34
|
+
const env = this.envFactory.getForInstance(instanceName);
|
|
35
|
+
const builder = env.kubectl('wait')
|
|
36
|
+
.arg('--for=condition=ready')
|
|
37
|
+
.arg('pod')
|
|
38
|
+
.arg('-l', 'app.kubernetes.io/name=mssql-latest')
|
|
39
|
+
.arg('-n', 'mssql')
|
|
40
|
+
.arg('--timeout=300s');
|
|
41
|
+
const result = await builder.exec();
|
|
42
|
+
if (result.exitCode !== 0) {
|
|
43
|
+
throw new Error(`MSSQL Pod not ready: ${result.stderr}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* 取得 MSSQL Pod 名稱
|
|
48
|
+
* @param instanceName WSL 實例名稱
|
|
49
|
+
* @returns Pod 名稱
|
|
50
|
+
*/
|
|
51
|
+
async getMssqlPodName(instanceName) {
|
|
52
|
+
const env = this.envFactory.getForInstance(instanceName);
|
|
53
|
+
const builder = env.kubectl('get')
|
|
54
|
+
.arg('pod')
|
|
55
|
+
.arg('-l', 'app.kubernetes.io/name=mssql-latest')
|
|
56
|
+
.arg('-n', 'mssql')
|
|
57
|
+
.arg('-o', "jsonpath='{.items[0].metadata.name}'");
|
|
58
|
+
const result = await builder.exec();
|
|
59
|
+
if (result.exitCode !== 0 || !result.stdout.trim()) {
|
|
60
|
+
throw new Error(`Failed to get MSSQL Pod name: ${result.stderr}`);
|
|
61
|
+
}
|
|
62
|
+
return result.stdout.trim();
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* 等待 SQL Server 服務啟動並接受連線
|
|
66
|
+
* @param instanceName WSL 實例名稱
|
|
67
|
+
* @param podName Pod 名稱
|
|
68
|
+
* @param saPassword SA 密碼
|
|
69
|
+
*/
|
|
70
|
+
async waitForSqlServerReady(instanceName, podName, saPassword) {
|
|
71
|
+
this.logger.debug('[Mssql] Waiting for SQL Server connection...');
|
|
72
|
+
await new Promise(resolve => setTimeout(resolve, 5000)); // 初始等待 5 秒
|
|
73
|
+
const maxRetries = 30; // 最多重試 30 次
|
|
74
|
+
const retryDelay = 2000; // 每次重試間隔 2 秒
|
|
75
|
+
const env = this.envFactory.getForInstance(instanceName);
|
|
76
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
77
|
+
// 嘗試連線到 SQL Server
|
|
78
|
+
const builder = env.kubectl('exec')
|
|
79
|
+
.arg('-n', 'mssql')
|
|
80
|
+
.arg(podName)
|
|
81
|
+
.arg('--')
|
|
82
|
+
.arg(paths_1.LINUX_PATHS.SQLCMD_BIN)
|
|
83
|
+
.arg('-S', 'localhost')
|
|
84
|
+
.arg('-U', 'sa')
|
|
85
|
+
.sensitiveArg('-P', `'${saPassword}'`)
|
|
86
|
+
.arg('-C')
|
|
87
|
+
.arg('-Q', '"SELECT 1"');
|
|
88
|
+
const result = await builder.exec();
|
|
89
|
+
if (result.exitCode === 0) {
|
|
90
|
+
this.logger.debug('[Mssql] SQL Server is ready');
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
// 如果不是最後一次重試,等待後再試
|
|
94
|
+
if (i < maxRetries - 1) {
|
|
95
|
+
await new Promise(resolve => setTimeout(resolve, retryDelay));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
throw new Error('SQL Server failed to start accepting connections after 60 seconds');
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* 建立初始資料庫 (uofx_dev, uofx_search)
|
|
102
|
+
* @param instanceName WSL 實例名稱
|
|
103
|
+
* @param saPassword SA 密碼
|
|
104
|
+
*/
|
|
105
|
+
async createInitialDatabases(instanceName, saPassword) {
|
|
106
|
+
this.logger.debug('[Mssql] Creating initial databases...');
|
|
107
|
+
// 等待 MSSQL Pod 完全就緒
|
|
108
|
+
await this.waitForMssqlReady(instanceName);
|
|
109
|
+
// 取得 Pod 名稱
|
|
110
|
+
const podName = await this.getMssqlPodName(instanceName);
|
|
111
|
+
// 等待 SQL Server 服務啟動並接受連線
|
|
112
|
+
await this.waitForSqlServerReady(instanceName, podName, saPassword);
|
|
113
|
+
// 建立資料庫的 SQL 命令
|
|
114
|
+
const env = this.envFactory.getForInstance(instanceName);
|
|
115
|
+
const builder = env.kubectl('exec')
|
|
116
|
+
.arg('-n', 'mssql')
|
|
117
|
+
.arg(podName)
|
|
118
|
+
.arg('--')
|
|
119
|
+
.arg(paths_1.LINUX_PATHS.SQLCMD_BIN)
|
|
120
|
+
.arg('-S', 'localhost')
|
|
121
|
+
.arg('-U', 'sa')
|
|
122
|
+
.sensitiveArg('-P', `'${saPassword}'`)
|
|
123
|
+
.arg('-C')
|
|
124
|
+
.arg('-Q', '"CREATE DATABASE uofx_dev; CREATE DATABASE uofx_search;"');
|
|
125
|
+
this.logger.debug(`[Mssql] Creating databases uofx_dev, uofx_search...`);
|
|
126
|
+
const result = await builder.exec();
|
|
127
|
+
if (result.exitCode !== 0) {
|
|
128
|
+
throw new Error(`Failed to create initial databases: ${result.stderr}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
exports.MssqlDatabaseInitService = MssqlDatabaseInitService;
|
|
133
|
+
exports.MssqlDatabaseInitService = MssqlDatabaseInitService = __decorate([
|
|
134
|
+
(0, tsyringe_1.injectable)(),
|
|
135
|
+
__param(0, (0, tsyringe_1.inject)(tokens_1.TOKENS.Internal.IExecutionEnvironmentFactory)),
|
|
136
|
+
__param(1, (0, tsyringe_1.inject)(tokens_1.TOKENS.ILoggerPort)),
|
|
137
|
+
__metadata("design:paramtypes", [Object, Object])
|
|
138
|
+
], MssqlDatabaseInitService);
|
|
139
|
+
//# sourceMappingURL=mssql-database-init.service.js.map
|
|
@@ -0,0 +1,100 @@
|
|
|
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.MssqlHelmDeploymentService = void 0;
|
|
16
|
+
const tsyringe_1 = require("tsyringe");
|
|
17
|
+
const tokens_1 = require("../../../di/tokens");
|
|
18
|
+
const base_helm_deployment_service_1 = require("./base-helm-deployment.service");
|
|
19
|
+
/**
|
|
20
|
+
* MSSQL Helm 部署服務
|
|
21
|
+
*
|
|
22
|
+
* 負責使用 Helm 部署 MSSQL,繼承 BaseHelmDeploymentService
|
|
23
|
+
*/
|
|
24
|
+
let MssqlHelmDeploymentService = class MssqlHelmDeploymentService extends base_helm_deployment_service_1.BaseHelmDeploymentService {
|
|
25
|
+
constructor(envFactory, helmRegistry, storageService, dbInitService, logger) {
|
|
26
|
+
super(helmRegistry, logger, envFactory);
|
|
27
|
+
this.storageService = storageService;
|
|
28
|
+
this.dbInitService = dbInitService;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* 部署 MSSQL
|
|
32
|
+
* @param instanceName WSL 實例名稱
|
|
33
|
+
* @param params 部署參數
|
|
34
|
+
* @param chartVersion Chart 版本 (MSSQL 使用固定版本,此參數會被忽略)
|
|
35
|
+
* @param progress 進度通知回調
|
|
36
|
+
*/
|
|
37
|
+
async deployMssql(instanceName, params, _chartVersion, output) {
|
|
38
|
+
output?.item('arrow', 'Deploying MSSQL database...').flush();
|
|
39
|
+
// MSSQL 使用固定版本,傳入空字串讓 getDeploymentConfig 決定版本
|
|
40
|
+
await this.deployWithHelm(instanceName, params, '', output);
|
|
41
|
+
}
|
|
42
|
+
// =========================================================================
|
|
43
|
+
// BaseHelmDeploymentService 抽象方法實作
|
|
44
|
+
// =========================================================================
|
|
45
|
+
getServiceName() {
|
|
46
|
+
return 'Mssql';
|
|
47
|
+
}
|
|
48
|
+
getDeploymentConfig(_chartVersion) {
|
|
49
|
+
// MSSQL 使用固定版本和特定 repository
|
|
50
|
+
return {
|
|
51
|
+
chartName: 'mssql-latest',
|
|
52
|
+
chartVersion: '0.0.0-dev',
|
|
53
|
+
repository: 'uofx-cli',
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* 安裝前處理:建立 MSSQL 所需的持久化存儲目錄
|
|
58
|
+
*/
|
|
59
|
+
async preInstall(instanceName, _params, _output) {
|
|
60
|
+
await this.storageService.createMssqlDirectories(instanceName);
|
|
61
|
+
}
|
|
62
|
+
async installChart(instanceName, chartPath, params, _output) {
|
|
63
|
+
const env = this.envFactory.getForInstance(instanceName);
|
|
64
|
+
const builder = env.helm('install')
|
|
65
|
+
.arg('--wait')
|
|
66
|
+
.arg('--timeout', '10m')
|
|
67
|
+
.arg('-n', 'mssql')
|
|
68
|
+
.arg('database')
|
|
69
|
+
.arg(`"${chartPath}"`)
|
|
70
|
+
.arg('--create-namespace')
|
|
71
|
+
.sensitiveArg('--set', `sa_password='${params.saPassword}'`);
|
|
72
|
+
this.logger.debug('[Mssql] Installing MSSQL chart', {
|
|
73
|
+
chart: 'mssql-latest',
|
|
74
|
+
chartPath: chartPath,
|
|
75
|
+
namespace: 'mssql',
|
|
76
|
+
instance: instanceName,
|
|
77
|
+
});
|
|
78
|
+
const result = await builder.exec();
|
|
79
|
+
if (result.exitCode !== 0) {
|
|
80
|
+
throw new Error(`Failed to deploy MSSQL: ${result.stderr}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* 安裝後處理:建立初始資料庫
|
|
85
|
+
*/
|
|
86
|
+
async postInstall(instanceName, params, _output) {
|
|
87
|
+
await this.dbInitService.createInitialDatabases(instanceName, params.saPassword.toString());
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
exports.MssqlHelmDeploymentService = MssqlHelmDeploymentService;
|
|
91
|
+
exports.MssqlHelmDeploymentService = MssqlHelmDeploymentService = __decorate([
|
|
92
|
+
(0, tsyringe_1.injectable)(),
|
|
93
|
+
__param(0, (0, tsyringe_1.inject)(tokens_1.TOKENS.Internal.IExecutionEnvironmentFactory)),
|
|
94
|
+
__param(1, (0, tsyringe_1.inject)(tokens_1.TOKENS.Internal.IHelmRegistry)),
|
|
95
|
+
__param(2, (0, tsyringe_1.inject)(tokens_1.TOKENS.Internal.IMssqlStorage)),
|
|
96
|
+
__param(3, (0, tsyringe_1.inject)(tokens_1.TOKENS.Internal.IMssqlDatabaseInit)),
|
|
97
|
+
__param(4, (0, tsyringe_1.inject)(tokens_1.TOKENS.ILoggerPort)),
|
|
98
|
+
__metadata("design:paramtypes", [Object, Object, Object, Object, Object])
|
|
99
|
+
], MssqlHelmDeploymentService);
|
|
100
|
+
//# sourceMappingURL=mssql-helm-deployment.service.js.map
|
|
@@ -0,0 +1,54 @@
|
|
|
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.MssqlStorageService = void 0;
|
|
16
|
+
const tsyringe_1 = require("tsyringe");
|
|
17
|
+
const tokens_1 = require("../../../di/tokens");
|
|
18
|
+
const paths_1 = require("../../../constants/paths");
|
|
19
|
+
/**
|
|
20
|
+
* MSSQL 存儲服務
|
|
21
|
+
*
|
|
22
|
+
* 負責建立 MSSQL 所需的持久化存儲目錄
|
|
23
|
+
*/
|
|
24
|
+
let MssqlStorageService = class MssqlStorageService {
|
|
25
|
+
constructor(envFactory, logger) {
|
|
26
|
+
this.envFactory = envFactory;
|
|
27
|
+
this.logger = logger;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 建立 MSSQL 所需的持久化存儲目錄
|
|
31
|
+
* @param instanceName WSL 實例名稱
|
|
32
|
+
*/
|
|
33
|
+
async createMssqlDirectories(instanceName) {
|
|
34
|
+
this.logger.debug(`[Mssql] Creating data directories for instance ${instanceName}...`);
|
|
35
|
+
const directories = (0, paths_1.getMssqlDataDirectories)();
|
|
36
|
+
const env = this.envFactory.getForInstance(instanceName);
|
|
37
|
+
const builder = env.shell('mkdir')
|
|
38
|
+
.arg('-p')
|
|
39
|
+
.args(...directories);
|
|
40
|
+
const result = await builder.exec();
|
|
41
|
+
if (result.exitCode !== 0) {
|
|
42
|
+
throw new Error(`Failed to create MSSQL directories: ${result.stderr}`);
|
|
43
|
+
}
|
|
44
|
+
this.logger.debug('[Mssql] Directories created successfully');
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
exports.MssqlStorageService = MssqlStorageService;
|
|
48
|
+
exports.MssqlStorageService = MssqlStorageService = __decorate([
|
|
49
|
+
(0, tsyringe_1.injectable)(),
|
|
50
|
+
__param(0, (0, tsyringe_1.inject)(tokens_1.TOKENS.Internal.IExecutionEnvironmentFactory)),
|
|
51
|
+
__param(1, (0, tsyringe_1.inject)(tokens_1.TOKENS.ILoggerPort)),
|
|
52
|
+
__metadata("design:paramtypes", [Object, Object])
|
|
53
|
+
], MssqlStorageService);
|
|
54
|
+
//# sourceMappingURL=mssql-storage.service.js.map
|
|
@@ -0,0 +1,66 @@
|
|
|
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.MssqlUserManagerService = void 0;
|
|
16
|
+
const tsyringe_1 = require("tsyringe");
|
|
17
|
+
const tokens_1 = require("../../../di/tokens");
|
|
18
|
+
const paths_1 = require("../../../constants/paths");
|
|
19
|
+
/**
|
|
20
|
+
* MSSQL 使用者管理服務
|
|
21
|
+
*
|
|
22
|
+
* 負責在 MSSQL 資料庫中建立管理員使用者
|
|
23
|
+
*/
|
|
24
|
+
let MssqlUserManagerService = class MssqlUserManagerService {
|
|
25
|
+
constructor(envFactory, dbInitService, logger) {
|
|
26
|
+
this.envFactory = envFactory;
|
|
27
|
+
this.dbInitService = dbInitService;
|
|
28
|
+
this.logger = logger;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* 建立 uofxadmin 管理員使用者
|
|
32
|
+
* @param instanceName WSL 實例名稱
|
|
33
|
+
* @param params 部署參數
|
|
34
|
+
*/
|
|
35
|
+
async createAdminUser(instanceName, params) {
|
|
36
|
+
this.logger.debug('[Mssql] Creating uofxadmin user...');
|
|
37
|
+
const podName = await this.dbInitService.getMssqlPodName(instanceName);
|
|
38
|
+
const email = `admin@${instanceName}.com`;
|
|
39
|
+
const query = `EXECUTE [dbo].[usp_Admin_User_Upcert_v01] 'uofxadmin', '${params.adminPassword}', '${email}'`;
|
|
40
|
+
const env = this.envFactory.getForInstance(instanceName);
|
|
41
|
+
const builder = env.kubectl('exec')
|
|
42
|
+
.arg('-n', 'mssql')
|
|
43
|
+
.arg(podName)
|
|
44
|
+
.arg('--')
|
|
45
|
+
.arg(paths_1.LINUX_PATHS.SQLCMD_BIN)
|
|
46
|
+
.arg('-S', 'localhost')
|
|
47
|
+
.arg('-U', 'sa')
|
|
48
|
+
.sensitiveArg('-P', `'${params.saPassword}'`)
|
|
49
|
+
.arg('-d', 'uofx_dev')
|
|
50
|
+
.arg('-C')
|
|
51
|
+
.sensitiveArg('-Q', `"${query}"`);
|
|
52
|
+
const result = await builder.exec();
|
|
53
|
+
if (result.exitCode !== 0) {
|
|
54
|
+
throw new Error(`Failed to create admin user: ${result.stderr}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
exports.MssqlUserManagerService = MssqlUserManagerService;
|
|
59
|
+
exports.MssqlUserManagerService = MssqlUserManagerService = __decorate([
|
|
60
|
+
(0, tsyringe_1.injectable)(),
|
|
61
|
+
__param(0, (0, tsyringe_1.inject)(tokens_1.TOKENS.Internal.IExecutionEnvironmentFactory)),
|
|
62
|
+
__param(1, (0, tsyringe_1.inject)(tokens_1.TOKENS.Internal.IMssqlDatabaseInit)),
|
|
63
|
+
__param(2, (0, tsyringe_1.inject)(tokens_1.TOKENS.ILoggerPort)),
|
|
64
|
+
__metadata("design:paramtypes", [Object, Object, Object])
|
|
65
|
+
], MssqlUserManagerService);
|
|
66
|
+
//# sourceMappingURL=mssql-user-manager.service.js.map
|
|
@@ -0,0 +1,289 @@
|
|
|
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.OciArtifactService = void 0;
|
|
16
|
+
const tsyringe_1 = require("tsyringe");
|
|
17
|
+
const tokens_1 = require("../../../di/tokens");
|
|
18
|
+
const sensitive_decorator_1 = require("../../../domain/decorators/sensitive.decorator");
|
|
19
|
+
/**
|
|
20
|
+
* OCI Artifact 下載服務
|
|
21
|
+
*
|
|
22
|
+
* 負責從 OCI registry 下載 artifacts(非 Helm Charts)
|
|
23
|
+
* 支援兩種模式:
|
|
24
|
+
* 1. Windows 側 HTTP 下載(用於 JSON 配置檔)
|
|
25
|
+
* 2. WSL 內透過 curl 下載(用於需要在 WSL 執行的腳本)
|
|
26
|
+
*/
|
|
27
|
+
let OciArtifactService = class OciArtifactService {
|
|
28
|
+
constructor(httpClient, envFactory, logger) {
|
|
29
|
+
this.httpClient = httpClient;
|
|
30
|
+
this.envFactory = envFactory;
|
|
31
|
+
this.logger = logger;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* 從 OCI registry 下載 JSON artifact 並解析
|
|
35
|
+
* 在 Windows 側執行
|
|
36
|
+
*/
|
|
37
|
+
async fetchJson(repo, tag, credentials) {
|
|
38
|
+
const registry = `${credentials.name}.azurecr.io`;
|
|
39
|
+
const scope = `repository:${repo}:pull`;
|
|
40
|
+
this.logger.debug(`Fetching OCI artifact: ${repo}:${tag}`);
|
|
41
|
+
// 1. 取得 Token
|
|
42
|
+
const token = await this.getAcrToken(registry, scope, credentials.account, credentials.password);
|
|
43
|
+
// 2. 取得 Manifest
|
|
44
|
+
const manifestUrl = `https://${registry}/v2/${repo}/manifests/${tag}`;
|
|
45
|
+
const manifestResponse = await this.httpClient.getJson(manifestUrl, {
|
|
46
|
+
Authorization: `Bearer ${token}`,
|
|
47
|
+
Accept: 'application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json'
|
|
48
|
+
});
|
|
49
|
+
// 3. 取得 Blob Digest
|
|
50
|
+
const digest = this.extractDigest(manifestResponse);
|
|
51
|
+
if (!digest) {
|
|
52
|
+
throw new Error(`Could not find digest in manifest for ${repo}:${tag}`);
|
|
53
|
+
}
|
|
54
|
+
// 4. 下載 Blob
|
|
55
|
+
const blobUrl = `https://${registry}/v2/${repo}/blobs/${digest}`;
|
|
56
|
+
const blob = await this.httpClient.getJson(blobUrl, {
|
|
57
|
+
Authorization: `Bearer ${token}`
|
|
58
|
+
});
|
|
59
|
+
this.logger.debug(`Successfully fetched OCI artifact: ${repo}:${tag}`);
|
|
60
|
+
return blob;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* 在實例內下載 OCI artifact
|
|
64
|
+
* 全程在實例內透過 curl 完成
|
|
65
|
+
* 透過 IExecutionEnvironmentFactory 自動選擇正確的執行環境
|
|
66
|
+
*/
|
|
67
|
+
async downloadToInstance(instanceName, repo, tag, targetPath, credentials) {
|
|
68
|
+
const registry = `${credentials.name}.azurecr.io`;
|
|
69
|
+
const scope = `repository:${repo}:pull`;
|
|
70
|
+
this.logger.debug(`Downloading OCI artifact to instance: ${repo}:${tag} -> ${targetPath}`);
|
|
71
|
+
// 建立在實例內執行的下載腳本
|
|
72
|
+
const downloadScript = this.buildDownloadScript(registry, repo, tag, scope, targetPath, credentials.account, credentials.password);
|
|
73
|
+
const env = this.envFactory.getForInstance(instanceName);
|
|
74
|
+
// 使用 stdin 傳遞腳本以避免引號轉義問題
|
|
75
|
+
const result = await env.shell('bash').sensitiveStdin(downloadScript).exec();
|
|
76
|
+
if (result.exitCode !== 0) {
|
|
77
|
+
// 輸出詳細錯誤信息以幫助診斷
|
|
78
|
+
const errorDetail = result.stderr || result.stdout || 'No output';
|
|
79
|
+
this.logger.error(new Error(`OCI download script failed`), {
|
|
80
|
+
exitCode: result.exitCode,
|
|
81
|
+
stderr: result.stderr,
|
|
82
|
+
stdout: result.stdout,
|
|
83
|
+
instanceName,
|
|
84
|
+
repo,
|
|
85
|
+
tag
|
|
86
|
+
});
|
|
87
|
+
throw new Error(`Failed to download OCI artifact: ${errorDetail}`);
|
|
88
|
+
}
|
|
89
|
+
this.logger.debug(`Successfully downloaded OCI artifact to instance: ${targetPath}`);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* 建立在實例內執行的下載腳本
|
|
93
|
+
*/
|
|
94
|
+
buildDownloadScript(registry, repo, tag, scope, targetPath, user, password) {
|
|
95
|
+
// 使用 base64 編碼密碼以避免特殊字符問題
|
|
96
|
+
const passwordBase64 = Buffer.from(password).toString('base64');
|
|
97
|
+
// 腳本會:
|
|
98
|
+
// 1. 解碼密碼
|
|
99
|
+
// 2. 取得 Token
|
|
100
|
+
// 3. 取得 Manifest,解析 digest
|
|
101
|
+
// 4. 下載 Blob 到目標路徑
|
|
102
|
+
// 5. 設定執行權限(如果是 .sh 檔案)
|
|
103
|
+
return `
|
|
104
|
+
set -e
|
|
105
|
+
|
|
106
|
+
REGISTRY="${registry}"
|
|
107
|
+
REPO="${repo}"
|
|
108
|
+
TAG="${tag}"
|
|
109
|
+
SCOPE="${scope}"
|
|
110
|
+
TARGET="${targetPath}"
|
|
111
|
+
USER="${user}"
|
|
112
|
+
PASS_B64="${passwordBase64}"
|
|
113
|
+
|
|
114
|
+
echo "OCI Download: Starting..."
|
|
115
|
+
|
|
116
|
+
# 解碼密碼
|
|
117
|
+
PASS=$(echo "\${PASS_B64}" | base64 -d)
|
|
118
|
+
|
|
119
|
+
echo "OCI Download: Getting token..."
|
|
120
|
+
# 1. 取得 Token
|
|
121
|
+
TOKEN_RESPONSE=$(curl -sfL -u "\${USER}:\${PASS}" \\
|
|
122
|
+
"https://\${REGISTRY}/oauth2/token?service=\${REGISTRY}&scope=\${SCOPE}")
|
|
123
|
+
|
|
124
|
+
if [ -z "\${TOKEN_RESPONSE}" ]; then
|
|
125
|
+
echo "Failed to get ACR token response" >&2
|
|
126
|
+
exit 1
|
|
127
|
+
fi
|
|
128
|
+
|
|
129
|
+
TOKEN=$(echo "\${TOKEN_RESPONSE}" | sed -n 's/.*"access_token":"\\([^"]*\\)".*/\\1/p')
|
|
130
|
+
|
|
131
|
+
if [ -z "\${TOKEN}" ]; then
|
|
132
|
+
echo "Failed to parse ACR token from response" >&2
|
|
133
|
+
echo "Response: \${TOKEN_RESPONSE}" >&2
|
|
134
|
+
exit 1
|
|
135
|
+
fi
|
|
136
|
+
|
|
137
|
+
echo "OCI Download: Getting manifest..."
|
|
138
|
+
# 2. 取得 Manifest,解析 digest
|
|
139
|
+
MANIFEST=$(curl -sfL -H "Authorization: Bearer \${TOKEN}" \\
|
|
140
|
+
-H "Accept: application/vnd.oci.image.manifest.v1+json" \\
|
|
141
|
+
"https://\${REGISTRY}/v2/\${REPO}/manifests/\${TAG}")
|
|
142
|
+
|
|
143
|
+
if [ -z "\${MANIFEST}" ]; then
|
|
144
|
+
echo "Failed to get manifest for \${REPO}:\${TAG}" >&2
|
|
145
|
+
exit 1
|
|
146
|
+
fi
|
|
147
|
+
|
|
148
|
+
# 從 layers 區塊取得第一個 digest
|
|
149
|
+
# 先找到 layers 區塊,再從中提取 digest
|
|
150
|
+
DIGEST=$(echo "\${MANIFEST}" | sed 's/.*"layers"[^[]*\\[//' | grep -o '"digest"[ ]*:[ ]*"sha256:[^"]*"' | head -1 | sed 's/.*"\\(sha256:[^"]*\\)".*/\\1/')
|
|
151
|
+
|
|
152
|
+
if [ -z "\${DIGEST}" ]; then
|
|
153
|
+
echo "Failed to extract digest from manifest" >&2
|
|
154
|
+
echo "Manifest: \${MANIFEST}" >&2
|
|
155
|
+
exit 1
|
|
156
|
+
fi
|
|
157
|
+
|
|
158
|
+
echo "OCI Download: Downloading blob \${DIGEST}..."
|
|
159
|
+
# 3. 下載 Blob(可能是 tar 或直接檔案)
|
|
160
|
+
# 注意:ACR 會返回 302 重定向到 Azure Blob Storage,需要 -L 跟隨重定向
|
|
161
|
+
TEMP_BLOB="/tmp/oci-blob-$$"
|
|
162
|
+
curl -sfL -H "Authorization: Bearer \${TOKEN}" \\
|
|
163
|
+
-o "\${TEMP_BLOB}" \\
|
|
164
|
+
"https://\${REGISTRY}/v2/\${REPO}/blobs/\${DIGEST}"
|
|
165
|
+
|
|
166
|
+
if [ ! -f "\${TEMP_BLOB}" ]; then
|
|
167
|
+
echo "Failed to download blob" >&2
|
|
168
|
+
exit 1
|
|
169
|
+
fi
|
|
170
|
+
|
|
171
|
+
# 4. 檢查是否為 tar 檔案,如果是則解壓縮
|
|
172
|
+
TARGET_DIR=$(dirname "\${TARGET}")
|
|
173
|
+
TARGET_BASENAME=$(basename "\${TARGET}")
|
|
174
|
+
|
|
175
|
+
# 嘗試 tar -tf 列出內容,如果成功則是 tar
|
|
176
|
+
if tar -tf "\${TEMP_BLOB}" >/dev/null 2>&1; then
|
|
177
|
+
# 是 tar 檔案,解壓縮
|
|
178
|
+
echo "Blob is a tar archive, extracting..."
|
|
179
|
+
|
|
180
|
+
# 取得 tar 內的檔案列表
|
|
181
|
+
TAR_CONTENTS=$(tar -tf "\${TEMP_BLOB}")
|
|
182
|
+
|
|
183
|
+
# 解壓縮到目標目錄
|
|
184
|
+
tar -xf "\${TEMP_BLOB}" -C "\${TARGET_DIR}"
|
|
185
|
+
|
|
186
|
+
# 如果目標檔案不存在,找到解壓縮的檔案並重命名
|
|
187
|
+
if [ ! -f "\${TARGET}" ]; then
|
|
188
|
+
# 取得 tar 內第一個檔案名稱
|
|
189
|
+
FIRST_FILE=$(echo "\${TAR_CONTENTS}" | head -1)
|
|
190
|
+
EXTRACTED_PATH="\${TARGET_DIR}/\${FIRST_FILE}"
|
|
191
|
+
|
|
192
|
+
if [ -f "\${EXTRACTED_PATH}" ]; then
|
|
193
|
+
echo "Renaming extracted file: \${FIRST_FILE} -> \${TARGET_BASENAME}"
|
|
194
|
+
mv "\${EXTRACTED_PATH}" "\${TARGET}"
|
|
195
|
+
else
|
|
196
|
+
# 備用:尋找目錄中最新的檔案
|
|
197
|
+
EXTRACTED=$(find "\${TARGET_DIR}" -maxdepth 1 -type f -newer "\${TEMP_BLOB}" 2>/dev/null | head -1)
|
|
198
|
+
if [ -n "\${EXTRACTED}" ] && [ "\${EXTRACTED}" != "\${TARGET}" ]; then
|
|
199
|
+
echo "Moving extracted file: \${EXTRACTED} -> \${TARGET}"
|
|
200
|
+
mv "\${EXTRACTED}" "\${TARGET}"
|
|
201
|
+
fi
|
|
202
|
+
fi
|
|
203
|
+
fi
|
|
204
|
+
else
|
|
205
|
+
# 不是 tar,直接複製
|
|
206
|
+
cp "\${TEMP_BLOB}" "\${TARGET}"
|
|
207
|
+
fi
|
|
208
|
+
|
|
209
|
+
rm -f "\${TEMP_BLOB}"
|
|
210
|
+
|
|
211
|
+
if [ ! -f "\${TARGET}" ]; then
|
|
212
|
+
echo "Failed to extract/copy blob to \${TARGET}" >&2
|
|
213
|
+
exit 1
|
|
214
|
+
fi
|
|
215
|
+
|
|
216
|
+
echo "OCI Download: Converting line endings..."
|
|
217
|
+
# 5. 轉換 Windows 換行符 (CRLF) 為 Unix 格式 (LF)
|
|
218
|
+
sed -i 's/\\r$//' "\${TARGET}"
|
|
219
|
+
|
|
220
|
+
echo "OCI Download: Setting permissions..."
|
|
221
|
+
# 6. 如果是 .sh 檔案,設定執行權限
|
|
222
|
+
case "\${TARGET}" in
|
|
223
|
+
*.sh)
|
|
224
|
+
chmod +x "\${TARGET}"
|
|
225
|
+
;;
|
|
226
|
+
esac
|
|
227
|
+
|
|
228
|
+
echo "OCI Download: Complete - \${TARGET} ($(wc -c < \${TARGET}) bytes)"
|
|
229
|
+
`;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* 從 Manifest 回應中提取 Digest
|
|
233
|
+
*/
|
|
234
|
+
extractDigest(manifest) {
|
|
235
|
+
// 優先從 layers[0] 取得
|
|
236
|
+
if (manifest.layers && manifest.layers.length > 0) {
|
|
237
|
+
return manifest.layers[0].digest;
|
|
238
|
+
}
|
|
239
|
+
// 備用:從 config 取得
|
|
240
|
+
if (manifest.config && manifest.config.digest) {
|
|
241
|
+
return manifest.config.digest;
|
|
242
|
+
}
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* 獲取 ACR 存取 Token(Windows 側)
|
|
247
|
+
*/
|
|
248
|
+
async getAcrToken(registry, scope, user, password) {
|
|
249
|
+
const auth = Buffer.from(`${user}:${password}`).toString('base64');
|
|
250
|
+
const url = `https://${registry}/oauth2/token?service=${registry}&scope=${scope}`;
|
|
251
|
+
const response = await this.httpClient.getJson(url, {
|
|
252
|
+
Authorization: `Basic ${auth}`
|
|
253
|
+
});
|
|
254
|
+
return response.access_token;
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
exports.OciArtifactService = OciArtifactService;
|
|
258
|
+
__decorate([
|
|
259
|
+
__param(2, (0, sensitive_decorator_1.Sensitive)()),
|
|
260
|
+
__metadata("design:type", Function),
|
|
261
|
+
__metadata("design:paramtypes", [String, String, Object]),
|
|
262
|
+
__metadata("design:returntype", Promise)
|
|
263
|
+
], OciArtifactService.prototype, "fetchJson", null);
|
|
264
|
+
__decorate([
|
|
265
|
+
__param(4, (0, sensitive_decorator_1.Sensitive)()),
|
|
266
|
+
__metadata("design:type", Function),
|
|
267
|
+
__metadata("design:paramtypes", [String, String, String, String, Object]),
|
|
268
|
+
__metadata("design:returntype", Promise)
|
|
269
|
+
], OciArtifactService.prototype, "downloadToInstance", null);
|
|
270
|
+
__decorate([
|
|
271
|
+
__param(6, (0, sensitive_decorator_1.Sensitive)()),
|
|
272
|
+
__metadata("design:type", Function),
|
|
273
|
+
__metadata("design:paramtypes", [String, String, String, String, String, String, String]),
|
|
274
|
+
__metadata("design:returntype", String)
|
|
275
|
+
], OciArtifactService.prototype, "buildDownloadScript", null);
|
|
276
|
+
__decorate([
|
|
277
|
+
__param(3, (0, sensitive_decorator_1.Sensitive)()),
|
|
278
|
+
__metadata("design:type", Function),
|
|
279
|
+
__metadata("design:paramtypes", [String, String, String, String]),
|
|
280
|
+
__metadata("design:returntype", Promise)
|
|
281
|
+
], OciArtifactService.prototype, "getAcrToken", null);
|
|
282
|
+
exports.OciArtifactService = OciArtifactService = __decorate([
|
|
283
|
+
(0, tsyringe_1.injectable)(),
|
|
284
|
+
__param(0, (0, tsyringe_1.inject)(tokens_1.TOKENS.Internal.IHttpClient)),
|
|
285
|
+
__param(1, (0, tsyringe_1.inject)(tokens_1.TOKENS.Internal.IExecutionEnvironmentFactory)),
|
|
286
|
+
__param(2, (0, tsyringe_1.inject)(tokens_1.TOKENS.ILoggerPort)),
|
|
287
|
+
__metadata("design:paramtypes", [Object, Object, Object])
|
|
288
|
+
], OciArtifactService);
|
|
289
|
+
//# sourceMappingURL=oci-artifact.service.js.map
|