@bifos/nhncloud-cli 0.4.0 → 0.5.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/README.md +55 -2
- package/dist/index.js +511 -79
- package/package.json +1 -1
- package/skills/nhncloud-cli/SKILL.md +83 -2
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# nhncloud-cli
|
|
2
2
|
|
|
3
3
|
NHN Cloud 서비스를 AWS CLI 방식으로 호출하는 통합 CLI.
|
|
4
|
-
현재 `configure`, `logncrash search/send` (Log & Crash 로그 검색·전송), `deploy` (배포·바이너리 조회·업로드·다운로드), `instance` (Compute 인스턴스 목록·발급·전원 제어·타입 변경·키페어 관리·이미지·가용성 영역 조회·볼륨 연결 포함), `network` (VPC·서브넷 목록 조회), `volume` (Block Storage 볼륨 목록·조회·생성) 명령을 지원한다.
|
|
4
|
+
현재 `configure`, `logncrash search/send` (Log & Crash 로그 검색·전송), `deploy` (배포·바이너리 조회·업로드·다운로드), `instance` (Compute 인스턴스 목록·발급·전원 제어·타입 변경·키페어 관리·이미지·가용성 영역 조회·볼륨 연결 포함), `network` (VPC·서브넷 목록 조회), `volume` (Block Storage 볼륨 목록·조회·생성), `ncr` (NHN Container Registry 레지스트리 목록·조회·이미지 목록·태그 목록) 명령을 지원한다.
|
|
5
5
|
|
|
6
6
|
## 설치
|
|
7
7
|
|
|
@@ -17,7 +17,7 @@ npm install -g @bifos/nhncloud-cli
|
|
|
17
17
|
nhncloud configure
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
- profile → UAK(id/secret) → logncrash appkey/secret → iaas 자격증명 순으로 입력한다.
|
|
20
|
+
- profile → UAK(id/secret) → logncrash appkey/secret → iaas 자격증명 → ncr appkey 순으로 입력한다.
|
|
21
21
|
- 저장 전 연결 테스트를 자동으로 수행한다 (`--no-verify` 로 생략 가능).
|
|
22
22
|
- CI/자동화는 flag 로 비대화형 설정이 가능하다.
|
|
23
23
|
|
|
@@ -437,6 +437,58 @@ nhncloud floatingip delete <floatingip-id> --yes
|
|
|
437
437
|
> **associate**: `floatingip associate <floatingip-id> <instance-id>` 는 instance→port_id 매핑 경로 미확정으로 보류 중.
|
|
438
438
|
> 실측으로 경로가 확정되면 후속 task 에서 추가한다.
|
|
439
439
|
|
|
440
|
+
### NHN Container Registry (NCR)
|
|
441
|
+
|
|
442
|
+
NCR Management API 로 레지스트리를 조회한다.
|
|
443
|
+
인증은 공통 UAK(`X-TC-AUTHENTICATION-ID/SECRET` 정적 헤더)를 재사용하며 OAuth 토큰 교환이 없다.
|
|
444
|
+
appKey 는 NHN Cloud 콘솔 → Container Registry 서비스에서 확인한다.
|
|
445
|
+
|
|
446
|
+
```bash
|
|
447
|
+
# ncr appkey 설정 (비대화형)
|
|
448
|
+
nhncloud configure --ncr-appkey <appkey>
|
|
449
|
+
|
|
450
|
+
# 레지스트리 목록 조회
|
|
451
|
+
nhncloud ncr list --app-key <appkey>
|
|
452
|
+
|
|
453
|
+
# region 지정 (기본: kr1)
|
|
454
|
+
nhncloud ncr list --region kr2 --app-key <appkey>
|
|
455
|
+
|
|
456
|
+
# JSON 출력
|
|
457
|
+
nhncloud ncr list --app-key <appkey> --json
|
|
458
|
+
|
|
459
|
+
# 단일 레지스트리 조회 (이름 또는 ID)
|
|
460
|
+
nhncloud ncr get <registry-name> --app-key <appkey>
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
appKey 를 `nhncloud configure --ncr-appkey <appkey>` 로 저장해 두면 `--app-key` 없이도 호출할 수 있다.
|
|
464
|
+
|
|
465
|
+
```bash
|
|
466
|
+
# configure 로 저장 후 --app-key 생략
|
|
467
|
+
nhncloud configure --ncr-appkey <appkey>
|
|
468
|
+
nhncloud ncr list
|
|
469
|
+
nhncloud ncr get <registry-name>
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
이미지(repository)·태그는 레지스트리 데이터플레인 Harbor REST API 를 직접 호출한다.
|
|
473
|
+
UAK 를 Basic Auth 로 사용하며 추가 설정은 없다.
|
|
474
|
+
|
|
475
|
+
```bash
|
|
476
|
+
# 이미지(repository) 목록 조회
|
|
477
|
+
nhncloud ncr images <registry>
|
|
478
|
+
|
|
479
|
+
# region 지정
|
|
480
|
+
nhncloud ncr images <registry> --region kr2
|
|
481
|
+
|
|
482
|
+
# JSON 출력
|
|
483
|
+
nhncloud ncr images <registry> --json
|
|
484
|
+
|
|
485
|
+
# 특정 이미지의 태그 목록 조회
|
|
486
|
+
nhncloud ncr tags <registry> <repository>
|
|
487
|
+
|
|
488
|
+
# JSON 출력 (태그·push_time·size)
|
|
489
|
+
nhncloud ncr tags <registry> <repository> --json
|
|
490
|
+
```
|
|
491
|
+
|
|
440
492
|
## 개발
|
|
441
493
|
|
|
442
494
|
```bash
|
|
@@ -445,4 +497,5 @@ pnpm run build # tsup 단일 번들 (dist/index.js)
|
|
|
445
497
|
pnpm tsc --noEmit # 타입 체크
|
|
446
498
|
node dist/index.js instance --help
|
|
447
499
|
node dist/index.js logncrash search --help
|
|
500
|
+
node dist/index.js ncr --help
|
|
448
501
|
```
|
package/dist/index.js
CHANGED
|
@@ -24,7 +24,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
24
24
|
));
|
|
25
25
|
|
|
26
26
|
// src/index.ts
|
|
27
|
-
var
|
|
27
|
+
var import_commander38 = require("commander");
|
|
28
28
|
var import_chalk12 = __toESM(require("chalk"));
|
|
29
29
|
|
|
30
30
|
// src/utils/spinner.ts
|
|
@@ -495,6 +495,21 @@ var BLOCKSTORAGE_HOST = {
|
|
|
495
495
|
jp1: "jp1-api-block-storage-infrastructure.nhncloudservice.com"
|
|
496
496
|
};
|
|
497
497
|
var IAAS_REGIONS = Object.keys(INSTANCE_HOST).join(", ");
|
|
498
|
+
var NCR_HOST = {
|
|
499
|
+
kr1: "kr1-ncr.api.nhncloudservice.com",
|
|
500
|
+
kr2: "kr2-ncr.api.nhncloudservice.com",
|
|
501
|
+
kr3: "kr3-ncr.api.nhncloudservice.com"
|
|
502
|
+
};
|
|
503
|
+
function ncrHost(region) {
|
|
504
|
+
const host = NCR_HOST[region];
|
|
505
|
+
if (!host) {
|
|
506
|
+
throw new NhnCloudCliError(
|
|
507
|
+
`\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 NCR region \uC785\uB2C8\uB2E4: "${region}". \uC0AC\uC6A9 \uAC00\uB2A5\uD55C region: ${Object.keys(NCR_HOST).join(", ")}`,
|
|
508
|
+
EXIT_PARAM_ERROR
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
return host;
|
|
512
|
+
}
|
|
498
513
|
function instanceHost(region) {
|
|
499
514
|
const host = INSTANCE_HOST[region];
|
|
500
515
|
if (!host) {
|
|
@@ -740,6 +755,96 @@ var LogncrashClient = class {
|
|
|
740
755
|
}
|
|
741
756
|
};
|
|
742
757
|
|
|
758
|
+
// src/services/ncr/client.ts
|
|
759
|
+
var import_ky5 = __toESM(require("ky"));
|
|
760
|
+
|
|
761
|
+
// src/services/ncr/types.ts
|
|
762
|
+
function isRegistry(val) {
|
|
763
|
+
if (typeof val !== "object" || val === null) return false;
|
|
764
|
+
const obj = val;
|
|
765
|
+
return typeof obj["name"] === "string";
|
|
766
|
+
}
|
|
767
|
+
function isRepository(val) {
|
|
768
|
+
if (typeof val !== "object" || val === null) return false;
|
|
769
|
+
const obj = val;
|
|
770
|
+
return typeof obj["name"] === "string";
|
|
771
|
+
}
|
|
772
|
+
function isArtifact(val) {
|
|
773
|
+
return typeof val === "object" && val !== null;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// src/services/ncr/client.ts
|
|
777
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
778
|
+
var NcrClient = class {
|
|
779
|
+
uakId;
|
|
780
|
+
uakSecret;
|
|
781
|
+
baseUrl;
|
|
782
|
+
constructor(uakId, uakSecret, region) {
|
|
783
|
+
this.uakId = uakId;
|
|
784
|
+
this.uakSecret = uakSecret;
|
|
785
|
+
this.baseUrl = `https://${ncrHost(region)}`;
|
|
786
|
+
}
|
|
787
|
+
authHeaders() {
|
|
788
|
+
return {
|
|
789
|
+
"X-TC-AUTHENTICATION-ID": this.uakId,
|
|
790
|
+
"X-TC-AUTHENTICATION-SECRET": this.uakSecret
|
|
791
|
+
};
|
|
792
|
+
}
|
|
793
|
+
/**
|
|
794
|
+
* 레지스트리 목록을 반환한다.
|
|
795
|
+
* GET /ncr/v2.0/appkeys/{appKey}/registries
|
|
796
|
+
* 응답: { header, registries: [...] } — body 가 아니라 named 필드.
|
|
797
|
+
*/
|
|
798
|
+
async listRegistries(appKey) {
|
|
799
|
+
const url = `${this.baseUrl}/ncr/v2.0/appkeys/${encodeURIComponent(appKey)}/registries`;
|
|
800
|
+
try {
|
|
801
|
+
const res = await import_ky5.default.get(url, {
|
|
802
|
+
headers: this.authHeaders(),
|
|
803
|
+
retry: 0,
|
|
804
|
+
timeout: DEFAULT_TIMEOUT_MS
|
|
805
|
+
}).json();
|
|
806
|
+
unwrapHeader(res);
|
|
807
|
+
if (!Array.isArray(res.registries)) {
|
|
808
|
+
if (res.registries === void 0) return [];
|
|
809
|
+
throw new NhnCloudCliError(
|
|
810
|
+
"NCR API \uC751\uB2F5 \uD615\uC2DD \uC624\uB958: registries \uAC00 \uBC30\uC5F4\uC774 \uC544\uB2D9\uB2C8\uB2E4.",
|
|
811
|
+
EXIT_API_ERROR
|
|
812
|
+
);
|
|
813
|
+
}
|
|
814
|
+
return res.registries.filter(isRegistry);
|
|
815
|
+
} catch (err) {
|
|
816
|
+
if (err instanceof NhnCloudCliError) throw err;
|
|
817
|
+
throw toNhnCloudCliError(err);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
/**
|
|
821
|
+
* 단일 레지스트리를 반환한다.
|
|
822
|
+
* GET /ncr/v2.0/appkeys/{appKey}/registries/{registryNameOrId}
|
|
823
|
+
* 응답: { header, registry: {...} } — body 가 아니라 named 필드.
|
|
824
|
+
*/
|
|
825
|
+
async getRegistry(appKey, registry) {
|
|
826
|
+
const url = `${this.baseUrl}/ncr/v2.0/appkeys/${encodeURIComponent(appKey)}/registries/${encodeURIComponent(registry)}`;
|
|
827
|
+
try {
|
|
828
|
+
const res = await import_ky5.default.get(url, {
|
|
829
|
+
headers: this.authHeaders(),
|
|
830
|
+
retry: 0,
|
|
831
|
+
timeout: DEFAULT_TIMEOUT_MS
|
|
832
|
+
}).json();
|
|
833
|
+
unwrapHeader(res);
|
|
834
|
+
if (res.registry && isRegistry(res.registry)) {
|
|
835
|
+
return res.registry;
|
|
836
|
+
}
|
|
837
|
+
throw new NhnCloudCliError(
|
|
838
|
+
"NCR API \uC751\uB2F5 \uD615\uC2DD \uC624\uB958: registry \uAC1D\uCCB4\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
839
|
+
EXIT_API_ERROR
|
|
840
|
+
);
|
|
841
|
+
} catch (err) {
|
|
842
|
+
if (err instanceof NhnCloudCliError) throw err;
|
|
843
|
+
throw toNhnCloudCliError(err);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
};
|
|
847
|
+
|
|
743
848
|
// src/commands/configure-verify.ts
|
|
744
849
|
async function verifyUserAccessKey(uak) {
|
|
745
850
|
try {
|
|
@@ -763,6 +868,19 @@ async function verifyIaas(iaas) {
|
|
|
763
868
|
throw err;
|
|
764
869
|
}
|
|
765
870
|
}
|
|
871
|
+
async function verifyNcr(uak, appkey) {
|
|
872
|
+
if (!appkey) return false;
|
|
873
|
+
const client = new NcrClient(uak.id, uak.secret, "kr1");
|
|
874
|
+
try {
|
|
875
|
+
await client.listRegistries(appkey);
|
|
876
|
+
return true;
|
|
877
|
+
} catch (err) {
|
|
878
|
+
if (err instanceof NhnCloudCliError && err.exitCode === EXIT_AUTH_ERROR) {
|
|
879
|
+
return false;
|
|
880
|
+
}
|
|
881
|
+
throw err;
|
|
882
|
+
}
|
|
883
|
+
}
|
|
766
884
|
async function verifyLogncrash(cred) {
|
|
767
885
|
if (!cred.appkey || !cred.secret) return false;
|
|
768
886
|
const client = new LogncrashClient(cred.appkey, cred.secret);
|
|
@@ -786,7 +904,7 @@ async function verifyLogncrash(cred) {
|
|
|
786
904
|
}
|
|
787
905
|
|
|
788
906
|
// src/commands/configure.ts
|
|
789
|
-
async function saveAndVerify(profileName, uak, logncrash, iaas, doVerify) {
|
|
907
|
+
async function saveAndVerify(profileName, uak, logncrash, iaas, ncr, doVerify) {
|
|
790
908
|
if (doVerify) {
|
|
791
909
|
if (uak) {
|
|
792
910
|
const ok = await verifyUserAccessKey(uak);
|
|
@@ -821,6 +939,26 @@ async function saveAndVerify(profileName, uak, logncrash, iaas, doVerify) {
|
|
|
821
939
|
);
|
|
822
940
|
}
|
|
823
941
|
}
|
|
942
|
+
if (ncr) {
|
|
943
|
+
if (uak) {
|
|
944
|
+
const appkey = ncr.appkey ?? "";
|
|
945
|
+
const ok = await verifyNcr(uak, appkey);
|
|
946
|
+
if (ok) {
|
|
947
|
+
process.stderr.write(import_chalk.default.green(" \u2713 ncr \uC5F0\uACB0 \uC131\uACF5 (kr1)\n"));
|
|
948
|
+
} else {
|
|
949
|
+
throw new NhnCloudCliError(
|
|
950
|
+
"ncr \uC778\uC99D \uC2E4\uD328 \u2014 appkey \uB610\uB294 UAK \uB97C \uD655\uC778\uD558\uC138\uC694.",
|
|
951
|
+
EXIT_AUTH_ERROR
|
|
952
|
+
);
|
|
953
|
+
}
|
|
954
|
+
} else {
|
|
955
|
+
process.stderr.write(
|
|
956
|
+
import_chalk.default.yellow(
|
|
957
|
+
" \u26A0 ncr verify \uAC74\uB108\uB700 \u2014 \uC774\uBC88 \uC124\uC815\uC5D0 UAK \uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. ncr \uBA85\uB839\uC740 \uACF5\uD1B5 UAK \uAC00 \uD544\uC694\uD558\uB2C8 \uBA3C\uC800 \uC124\uC815\uD558\uC138\uC694.\n"
|
|
958
|
+
)
|
|
959
|
+
);
|
|
960
|
+
}
|
|
961
|
+
}
|
|
824
962
|
}
|
|
825
963
|
if (uak) {
|
|
826
964
|
await setUserAccessKey(profileName, uak);
|
|
@@ -831,6 +969,9 @@ async function saveAndVerify(profileName, uak, logncrash, iaas, doVerify) {
|
|
|
831
969
|
if (iaas) {
|
|
832
970
|
await setIaasCredential(profileName, iaas);
|
|
833
971
|
}
|
|
972
|
+
if (ncr) {
|
|
973
|
+
await setServiceCredential(profileName, "ncr", ncr);
|
|
974
|
+
}
|
|
834
975
|
process.stderr.write(import_chalk.default.green(`
|
|
835
976
|
\u2713 profile "${profileName}" \uC124\uC815\uC774 \uC800\uC7A5\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
|
|
836
977
|
`));
|
|
@@ -890,12 +1031,25 @@ async function runInteractive(opts) {
|
|
|
890
1031
|
});
|
|
891
1032
|
iaas = { tenantId, username: iaasUsername, password: iaasPassword, region };
|
|
892
1033
|
}
|
|
1034
|
+
let ncr;
|
|
1035
|
+
const setupNcr = await confirm({
|
|
1036
|
+
message: "ncr \uC790\uACA9\uC99D\uBA85\uB3C4 \uC124\uC815\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?",
|
|
1037
|
+
default: false
|
|
1038
|
+
});
|
|
1039
|
+
if (setupNcr) {
|
|
1040
|
+
process.stderr.write(import_chalk.default.gray("\n\u2014 ncr (Container Registry) \uC790\uACA9\uC99D\uBA85 \u2014\n"));
|
|
1041
|
+
const ncrAppkey = await input({
|
|
1042
|
+
message: "ncr appkey",
|
|
1043
|
+
validate: (v) => v.trim().length > 0 || "ncr appkey \uB97C \uC785\uB825\uD558\uC138\uC694"
|
|
1044
|
+
});
|
|
1045
|
+
ncr = { appkey: ncrAppkey.trim() };
|
|
1046
|
+
}
|
|
893
1047
|
if (opts.verify) {
|
|
894
1048
|
process.stderr.write(import_chalk.default.gray("\n\u2014 \uC5F0\uACB0 \uD14C\uC2A4\uD2B8 \uC911\u2026 \u2014\n"));
|
|
895
1049
|
}
|
|
896
1050
|
if (opts.verify) {
|
|
897
1051
|
try {
|
|
898
|
-
await saveAndVerify(profileName, uak, logncrash, iaas, true);
|
|
1052
|
+
await saveAndVerify(profileName, uak, logncrash, iaas, ncr, true);
|
|
899
1053
|
} catch (err) {
|
|
900
1054
|
if (err instanceof NhnCloudCliError && err.exitCode === EXIT_AUTH_ERROR) {
|
|
901
1055
|
process.stderr.write(import_chalk.default.red(` \u2717 ${err.message}
|
|
@@ -908,13 +1062,13 @@ async function runInteractive(opts) {
|
|
|
908
1062
|
process.stderr.write(import_chalk.default.yellow("\uC800\uC7A5\uC774 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4.\n"));
|
|
909
1063
|
return;
|
|
910
1064
|
}
|
|
911
|
-
await saveAndVerify(profileName, uak, logncrash, iaas, false);
|
|
1065
|
+
await saveAndVerify(profileName, uak, logncrash, iaas, ncr, false);
|
|
912
1066
|
} else {
|
|
913
1067
|
throw err;
|
|
914
1068
|
}
|
|
915
1069
|
}
|
|
916
1070
|
} else {
|
|
917
|
-
await saveAndVerify(profileName, uak, logncrash, iaas, false);
|
|
1071
|
+
await saveAndVerify(profileName, uak, logncrash, iaas, ncr, false);
|
|
918
1072
|
}
|
|
919
1073
|
}
|
|
920
1074
|
async function runNonInteractive(opts) {
|
|
@@ -930,22 +1084,23 @@ async function runNonInteractive(opts) {
|
|
|
930
1084
|
password: iaasPassword,
|
|
931
1085
|
region: opts.iaasRegion ?? "kr1"
|
|
932
1086
|
} : void 0;
|
|
933
|
-
|
|
1087
|
+
const ncr = opts.ncrAppkey?.trim() ? { appkey: opts.ncrAppkey.trim() } : void 0;
|
|
1088
|
+
if (!uak && !logncrash && !iaas && !ncr) {
|
|
934
1089
|
throw new NhnCloudCliError(
|
|
935
|
-
"\uBE44\uB300\uD654\uD615 \uBAA8\uB4DC: --uak-id + UAK secret, --logncrash-appkey + logncrash secret,\n
|
|
1090
|
+
"\uBE44\uB300\uD654\uD615 \uBAA8\uB4DC: --uak-id + UAK secret, --logncrash-appkey + logncrash secret,\n--iaas-tenant-id + --iaas-username + iaas password,\n\uB610\uB294 --ncr-appkey \uC911 \uD558\uB098\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.\nsecret/password \uB294 \uB178\uCD9C \uBC29\uC9C0\uB97C \uC704\uD574 \uD658\uACBD\uBCC0\uC218 \uAD8C\uC7A5:\nNHNCLOUD_UAK_SECRET / NHNCLOUD_LOGNCRASH_SECRET / NHNCLOUD_IAAS_PASSWORD.",
|
|
936
1091
|
EXIT_PARAM_ERROR
|
|
937
1092
|
);
|
|
938
1093
|
}
|
|
939
1094
|
if (opts.verify) {
|
|
940
1095
|
process.stderr.write(import_chalk.default.gray("\uC5F0\uACB0 \uD14C\uC2A4\uD2B8 \uC911\u2026\n"));
|
|
941
1096
|
}
|
|
942
|
-
await saveAndVerify(profileName, uak, logncrash, iaas, opts.verify);
|
|
1097
|
+
await saveAndVerify(profileName, uak, logncrash, iaas, ncr, opts.verify);
|
|
943
1098
|
}
|
|
944
1099
|
var configureCommand = new import_commander.Command("configure").description("\uC790\uACA9\uC99D\uBA85 \uC124\uC815 \uB9C8\uBC95\uC0AC (\uB300\uD654\uD615 + flag)").option("--profile <name>", "\uB300\uC0C1 profile \uC774\uB984 (\uAE30\uBCF8: default)").option("--uak-id <id>", "\uAC1C\uC778 UAK ID (\uBE44\uB300\uD654\uD615)").option("--uak-secret <secret>", "\uAC1C\uC778 UAK Secret (\uBE44\uB300\uD654\uD615, \uB178\uCD9C \uBC29\uC9C0\uB85C env NHNCLOUD_UAK_SECRET \uAD8C\uC7A5)").option("--logncrash-appkey <key>", "logncrash appkey (\uBE44\uB300\uD654\uD615)").option("--logncrash-secret <secret>", "logncrash secret (\uBE44\uB300\uD654\uD615, env NHNCLOUD_LOGNCRASH_SECRET \uAD8C\uC7A5)").option("--iaas-tenant-id <id>", "iaas tenantId / \uD504\uB85C\uC81D\uD2B8 ID (\uBE44\uB300\uD654\uD615)").option("--iaas-username <user>", "iaas IAM username (\uBE44\uB300\uD654\uD615)").option(
|
|
945
1100
|
"--iaas-password <pass>",
|
|
946
1101
|
"iaas API \uBE44\uBC00\uBC88\uD638 (\uBE44\uB300\uD654\uD615, \uB178\uCD9C \uBC29\uC9C0\uB85C env NHNCLOUD_IAAS_PASSWORD \uAD8C\uC7A5)"
|
|
947
|
-
).option("--iaas-region <region>", "iaas region (\uAE30\uBCF8: kr1)", "kr1").option("--no-verify", "\uC5F0\uACB0 \uD14C\uC2A4\uD2B8 \uC0DD\uB7B5").action(async (opts) => {
|
|
948
|
-
const hasFlag = opts.uakId || opts.uakSecret || opts.logncrashAppkey || opts.logncrashSecret || opts.iaasTenantId || opts.iaasUsername || opts.iaasPassword;
|
|
1102
|
+
).option("--iaas-region <region>", "iaas region (\uAE30\uBCF8: kr1)", "kr1").option("--ncr-appkey <key>", "ncr appkey (\uBE44\uB300\uD654\uD615)").option("--no-verify", "\uC5F0\uACB0 \uD14C\uC2A4\uD2B8 \uC0DD\uB7B5").action(async (opts) => {
|
|
1103
|
+
const hasFlag = opts.uakId || opts.uakSecret || opts.logncrashAppkey || opts.logncrashSecret || opts.iaasTenantId || opts.iaasUsername || opts.iaasPassword || opts.ncrAppkey;
|
|
949
1104
|
try {
|
|
950
1105
|
if (hasFlag) {
|
|
951
1106
|
await runNonInteractive(opts);
|
|
@@ -1375,7 +1530,7 @@ async function scrollNextOrExpire(client, scrollKey) {
|
|
|
1375
1530
|
var import_commander5 = require("commander");
|
|
1376
1531
|
|
|
1377
1532
|
// src/services/deploy/client.ts
|
|
1378
|
-
var
|
|
1533
|
+
var import_ky6 = __toESM(require("ky"));
|
|
1379
1534
|
function isBinaryGroup(val) {
|
|
1380
1535
|
if (typeof val !== "object" || val === null) return false;
|
|
1381
1536
|
const obj = val;
|
|
@@ -1390,7 +1545,7 @@ function isBinary(val) {
|
|
|
1390
1545
|
return (binaryKeyType === "number" || binaryKeyType === "string") && (binarySizeType === "number" || binarySizeType === "string");
|
|
1391
1546
|
}
|
|
1392
1547
|
var SYNC_TIMEOUT_MS = 6e5;
|
|
1393
|
-
var
|
|
1548
|
+
var DEFAULT_TIMEOUT_MS2 = 3e4;
|
|
1394
1549
|
var DeployClient = class {
|
|
1395
1550
|
accessToken;
|
|
1396
1551
|
baseUrl;
|
|
@@ -1422,14 +1577,14 @@ var DeployClient = class {
|
|
|
1422
1577
|
payload["targetServerHostnames"] = params.targetHosts;
|
|
1423
1578
|
}
|
|
1424
1579
|
try {
|
|
1425
|
-
const res = await
|
|
1580
|
+
const res = await import_ky6.default.post(url, {
|
|
1426
1581
|
headers: {
|
|
1427
1582
|
...this.authHeaders(),
|
|
1428
1583
|
"Content-Type": "application/json"
|
|
1429
1584
|
},
|
|
1430
1585
|
json: payload,
|
|
1431
1586
|
retry: 0,
|
|
1432
|
-
timeout: isAsync ?
|
|
1587
|
+
timeout: isAsync ? DEFAULT_TIMEOUT_MS2 : SYNC_TIMEOUT_MS
|
|
1433
1588
|
}).json();
|
|
1434
1589
|
return unwrap(res);
|
|
1435
1590
|
} catch (err) {
|
|
@@ -1442,10 +1597,10 @@ var DeployClient = class {
|
|
|
1442
1597
|
async artifacts(appKey) {
|
|
1443
1598
|
const url = `${this.baseUrl}/api/v2.1/projects/${encodeURIComponent(appKey)}/artifacts`;
|
|
1444
1599
|
try {
|
|
1445
|
-
const res = await
|
|
1600
|
+
const res = await import_ky6.default.get(url, {
|
|
1446
1601
|
headers: this.authHeaders(),
|
|
1447
1602
|
retry: 0,
|
|
1448
|
-
timeout:
|
|
1603
|
+
timeout: DEFAULT_TIMEOUT_MS2
|
|
1449
1604
|
}).json();
|
|
1450
1605
|
return unwrap(res);
|
|
1451
1606
|
} catch (err) {
|
|
@@ -1458,10 +1613,10 @@ var DeployClient = class {
|
|
|
1458
1613
|
async serverGroups(appKey, artifactId) {
|
|
1459
1614
|
const url = `${this.baseUrl}/api/v2.1/projects/${encodeURIComponent(appKey)}/artifacts/${encodeURIComponent(artifactId)}/server-groups`;
|
|
1460
1615
|
try {
|
|
1461
|
-
const res = await
|
|
1616
|
+
const res = await import_ky6.default.get(url, {
|
|
1462
1617
|
headers: this.authHeaders(),
|
|
1463
1618
|
retry: 0,
|
|
1464
|
-
timeout:
|
|
1619
|
+
timeout: DEFAULT_TIMEOUT_MS2
|
|
1465
1620
|
}).json();
|
|
1466
1621
|
return unwrap(res);
|
|
1467
1622
|
} catch (err) {
|
|
@@ -1474,10 +1629,10 @@ var DeployClient = class {
|
|
|
1474
1629
|
async histories(appKey, artifactId) {
|
|
1475
1630
|
const url = `${this.baseUrl}/api/v2.1/projects/${encodeURIComponent(appKey)}/artifacts/${encodeURIComponent(artifactId)}/deploy-histories`;
|
|
1476
1631
|
try {
|
|
1477
|
-
const res = await
|
|
1632
|
+
const res = await import_ky6.default.get(url, {
|
|
1478
1633
|
headers: this.authHeaders(),
|
|
1479
1634
|
retry: 0,
|
|
1480
|
-
timeout:
|
|
1635
|
+
timeout: DEFAULT_TIMEOUT_MS2
|
|
1481
1636
|
}).json();
|
|
1482
1637
|
return unwrap(res);
|
|
1483
1638
|
} catch (err) {
|
|
@@ -1490,10 +1645,10 @@ var DeployClient = class {
|
|
|
1490
1645
|
async binaryGroups(appKey, artifactId) {
|
|
1491
1646
|
const url = `${this.baseUrl}/api/v2.1/projects/${encodeURIComponent(appKey)}/artifacts/${encodeURIComponent(artifactId)}/binary-groups`;
|
|
1492
1647
|
try {
|
|
1493
|
-
const res = await
|
|
1648
|
+
const res = await import_ky6.default.get(url, {
|
|
1494
1649
|
headers: this.authHeaders(),
|
|
1495
1650
|
retry: 0,
|
|
1496
|
-
timeout:
|
|
1651
|
+
timeout: DEFAULT_TIMEOUT_MS2
|
|
1497
1652
|
}).json();
|
|
1498
1653
|
const body = unwrap(res);
|
|
1499
1654
|
const list = body.binaryGroups;
|
|
@@ -1520,11 +1675,11 @@ var DeployClient = class {
|
|
|
1520
1675
|
if (params.sortKey !== void 0) searchParams["sortKey"] = params.sortKey;
|
|
1521
1676
|
if (params.sortDirection !== void 0) searchParams["sortDirection"] = params.sortDirection;
|
|
1522
1677
|
try {
|
|
1523
|
-
const res = await
|
|
1678
|
+
const res = await import_ky6.default.get(url, {
|
|
1524
1679
|
headers: this.authHeaders(),
|
|
1525
1680
|
searchParams,
|
|
1526
1681
|
retry: 0,
|
|
1527
|
-
timeout:
|
|
1682
|
+
timeout: DEFAULT_TIMEOUT_MS2
|
|
1528
1683
|
}).json();
|
|
1529
1684
|
const body = unwrap(res);
|
|
1530
1685
|
const list = body.binaries;
|
|
@@ -1562,7 +1717,7 @@ var DeployClient = class {
|
|
|
1562
1717
|
form.append("description", params.description);
|
|
1563
1718
|
}
|
|
1564
1719
|
try {
|
|
1565
|
-
const res = await
|
|
1720
|
+
const res = await import_ky6.default.post(url, {
|
|
1566
1721
|
headers: this.authHeaders(),
|
|
1567
1722
|
// 인증 헤더만 — multipart boundary 는 ky 가 자동 설정
|
|
1568
1723
|
body: form,
|
|
@@ -1598,7 +1753,7 @@ var DeployClient = class {
|
|
|
1598
1753
|
async downloadBinary(appKey, artifactId, binaryGroupKey, binaryKey) {
|
|
1599
1754
|
const url = `${this.baseUrl}/api/v2.1/projects/${encodeURIComponent(appKey)}/artifacts/${encodeURIComponent(artifactId)}/binary-group/${binaryGroupKey}/binaries/${binaryKey}`;
|
|
1600
1755
|
try {
|
|
1601
|
-
const ab = await
|
|
1756
|
+
const ab = await import_ky6.default.get(url, {
|
|
1602
1757
|
headers: this.authHeaders(),
|
|
1603
1758
|
retry: 0,
|
|
1604
1759
|
timeout: SYNC_TIMEOUT_MS
|
|
@@ -1974,8 +2129,8 @@ var downloadCommand = new import_commander12.Command("download").description("\u
|
|
|
1974
2129
|
var import_commander13 = require("commander");
|
|
1975
2130
|
|
|
1976
2131
|
// src/services/instance/client.ts
|
|
1977
|
-
var
|
|
1978
|
-
var
|
|
2132
|
+
var import_ky7 = __toESM(require("ky"));
|
|
2133
|
+
var DEFAULT_TIMEOUT_MS3 = 3e4;
|
|
1979
2134
|
var DEFAULT_POLL_INTERVAL_MS = 5e3;
|
|
1980
2135
|
function isServer(val) {
|
|
1981
2136
|
if (typeof val !== "object" || val === null) return false;
|
|
@@ -2104,10 +2259,10 @@ var InstanceClient = class {
|
|
|
2104
2259
|
async list() {
|
|
2105
2260
|
const url = `${this.computeEndpoint}/servers/detail`;
|
|
2106
2261
|
try {
|
|
2107
|
-
const raw = await
|
|
2262
|
+
const raw = await import_ky7.default.get(url, {
|
|
2108
2263
|
headers: this.authHeaders(),
|
|
2109
2264
|
retry: 0,
|
|
2110
|
-
timeout:
|
|
2265
|
+
timeout: DEFAULT_TIMEOUT_MS3
|
|
2111
2266
|
}).json();
|
|
2112
2267
|
if (!isServersResponse(raw)) {
|
|
2113
2268
|
throw new NhnCloudCliError(
|
|
@@ -2126,10 +2281,10 @@ var InstanceClient = class {
|
|
|
2126
2281
|
async get(id) {
|
|
2127
2282
|
const url = `${this.computeEndpoint}/servers/${encodeURIComponent(id)}`;
|
|
2128
2283
|
try {
|
|
2129
|
-
const raw = await
|
|
2284
|
+
const raw = await import_ky7.default.get(url, {
|
|
2130
2285
|
headers: this.authHeaders(),
|
|
2131
2286
|
retry: 0,
|
|
2132
|
-
timeout:
|
|
2287
|
+
timeout: DEFAULT_TIMEOUT_MS3
|
|
2133
2288
|
}).json();
|
|
2134
2289
|
if (!isServerResponse(raw)) {
|
|
2135
2290
|
throw new NhnCloudCliError(
|
|
@@ -2185,11 +2340,11 @@ var InstanceClient = class {
|
|
|
2185
2340
|
}
|
|
2186
2341
|
let raw;
|
|
2187
2342
|
try {
|
|
2188
|
-
raw = await
|
|
2343
|
+
raw = await import_ky7.default.post(url, {
|
|
2189
2344
|
headers: this.authHeaders(),
|
|
2190
2345
|
json: { server: serverBody },
|
|
2191
2346
|
retry: 0,
|
|
2192
|
-
timeout:
|
|
2347
|
+
timeout: DEFAULT_TIMEOUT_MS3
|
|
2193
2348
|
}).json();
|
|
2194
2349
|
} catch (err) {
|
|
2195
2350
|
throw toNhnCloudCliError(err);
|
|
@@ -2208,10 +2363,10 @@ var InstanceClient = class {
|
|
|
2208
2363
|
async delete(id) {
|
|
2209
2364
|
const url = `${this.computeEndpoint}/servers/${encodeURIComponent(id)}`;
|
|
2210
2365
|
try {
|
|
2211
|
-
await
|
|
2366
|
+
await import_ky7.default.delete(url, {
|
|
2212
2367
|
headers: this.authHeaders(),
|
|
2213
2368
|
retry: 0,
|
|
2214
|
-
timeout:
|
|
2369
|
+
timeout: DEFAULT_TIMEOUT_MS3
|
|
2215
2370
|
});
|
|
2216
2371
|
} catch (err) {
|
|
2217
2372
|
throw toNhnCloudCliError(err);
|
|
@@ -2226,11 +2381,11 @@ var InstanceClient = class {
|
|
|
2226
2381
|
async serverAction(id, payload) {
|
|
2227
2382
|
const url = `${this.computeEndpoint}/servers/${encodeURIComponent(id)}/action`;
|
|
2228
2383
|
try {
|
|
2229
|
-
await
|
|
2384
|
+
await import_ky7.default.post(url, {
|
|
2230
2385
|
headers: this.authHeaders(),
|
|
2231
2386
|
json: payload,
|
|
2232
2387
|
retry: 0,
|
|
2233
|
-
timeout:
|
|
2388
|
+
timeout: DEFAULT_TIMEOUT_MS3
|
|
2234
2389
|
});
|
|
2235
2390
|
} catch (err) {
|
|
2236
2391
|
throw toNhnCloudCliError(err);
|
|
@@ -2271,11 +2426,11 @@ var InstanceClient = class {
|
|
|
2271
2426
|
if (params.minDisk !== void 0) searchParams["minDisk"] = params.minDisk;
|
|
2272
2427
|
if (params.minRam !== void 0) searchParams["minRam"] = params.minRam;
|
|
2273
2428
|
try {
|
|
2274
|
-
const raw = await
|
|
2429
|
+
const raw = await import_ky7.default.get(url, {
|
|
2275
2430
|
headers: this.authHeaders(),
|
|
2276
2431
|
searchParams,
|
|
2277
2432
|
retry: 0,
|
|
2278
|
-
timeout:
|
|
2433
|
+
timeout: DEFAULT_TIMEOUT_MS3
|
|
2279
2434
|
}).json();
|
|
2280
2435
|
if (params.detail) {
|
|
2281
2436
|
if (!isFlavorDetailsResponse(raw)) {
|
|
@@ -2304,10 +2459,10 @@ var InstanceClient = class {
|
|
|
2304
2459
|
async listAvailabilityZones() {
|
|
2305
2460
|
const url = `${this.computeEndpoint}/os-availability-zone`;
|
|
2306
2461
|
try {
|
|
2307
|
-
const raw = await
|
|
2462
|
+
const raw = await import_ky7.default.get(url, {
|
|
2308
2463
|
headers: this.authHeaders(),
|
|
2309
2464
|
retry: 0,
|
|
2310
|
-
timeout:
|
|
2465
|
+
timeout: DEFAULT_TIMEOUT_MS3
|
|
2311
2466
|
}).json();
|
|
2312
2467
|
if (!isAvailabilityZonesResponse(raw)) {
|
|
2313
2468
|
throw new NhnCloudCliError(
|
|
@@ -2324,7 +2479,7 @@ var InstanceClient = class {
|
|
|
2324
2479
|
async listKeypairs() {
|
|
2325
2480
|
const url = `${this.computeEndpoint}/os-keypairs`;
|
|
2326
2481
|
try {
|
|
2327
|
-
const raw = await
|
|
2482
|
+
const raw = await import_ky7.default.get(url, { headers: this.authHeaders(), retry: 0, timeout: DEFAULT_TIMEOUT_MS3 }).json();
|
|
2328
2483
|
if (!isKeypairsResponse(raw)) {
|
|
2329
2484
|
throw new NhnCloudCliError(
|
|
2330
2485
|
"instance keypairs \uC751\uB2F5 \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 \u2014 keypairs \uBC30\uC5F4\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
@@ -2340,7 +2495,7 @@ var InstanceClient = class {
|
|
|
2340
2495
|
async getKeypair(name) {
|
|
2341
2496
|
const url = `${this.computeEndpoint}/os-keypairs/${encodeURIComponent(name)}`;
|
|
2342
2497
|
try {
|
|
2343
|
-
const raw = await
|
|
2498
|
+
const raw = await import_ky7.default.get(url, { headers: this.authHeaders(), retry: 0, timeout: DEFAULT_TIMEOUT_MS3 }).json();
|
|
2344
2499
|
if (!isKeypairDetailResponse(raw)) {
|
|
2345
2500
|
throw new NhnCloudCliError(
|
|
2346
2501
|
`instance keypair get(${name}) \uC751\uB2F5 \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 \u2014 keypair \uC0C1\uC138 \uD544\uB4DC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.`,
|
|
@@ -2365,11 +2520,11 @@ var InstanceClient = class {
|
|
|
2365
2520
|
}
|
|
2366
2521
|
let raw;
|
|
2367
2522
|
try {
|
|
2368
|
-
raw = await
|
|
2523
|
+
raw = await import_ky7.default.post(url, {
|
|
2369
2524
|
headers: this.authHeaders(),
|
|
2370
2525
|
json: { keypair: keypairBody },
|
|
2371
2526
|
retry: 0,
|
|
2372
|
-
timeout:
|
|
2527
|
+
timeout: DEFAULT_TIMEOUT_MS3
|
|
2373
2528
|
}).json();
|
|
2374
2529
|
} catch (err) {
|
|
2375
2530
|
throw toNhnCloudCliError(err);
|
|
@@ -2394,7 +2549,7 @@ var InstanceClient = class {
|
|
|
2394
2549
|
async deleteKeypair(name) {
|
|
2395
2550
|
const url = `${this.computeEndpoint}/os-keypairs/${encodeURIComponent(name)}`;
|
|
2396
2551
|
try {
|
|
2397
|
-
await
|
|
2552
|
+
await import_ky7.default.delete(url, { headers: this.authHeaders(), retry: 0, timeout: DEFAULT_TIMEOUT_MS3 });
|
|
2398
2553
|
} catch (err) {
|
|
2399
2554
|
throw toNhnCloudCliError(err);
|
|
2400
2555
|
}
|
|
@@ -2414,11 +2569,11 @@ var InstanceClient = class {
|
|
|
2414
2569
|
if (params.owner !== void 0) searchParams["owner"] = params.owner;
|
|
2415
2570
|
if (params.status !== void 0) searchParams["status"] = params.status;
|
|
2416
2571
|
try {
|
|
2417
|
-
const raw = await
|
|
2572
|
+
const raw = await import_ky7.default.get(url, {
|
|
2418
2573
|
headers: this.authHeaders(),
|
|
2419
2574
|
searchParams,
|
|
2420
2575
|
retry: 0,
|
|
2421
|
-
timeout:
|
|
2576
|
+
timeout: DEFAULT_TIMEOUT_MS3
|
|
2422
2577
|
}).json();
|
|
2423
2578
|
if (!isImagesResponse(raw)) {
|
|
2424
2579
|
throw new NhnCloudCliError(
|
|
@@ -2438,7 +2593,7 @@ var InstanceClient = class {
|
|
|
2438
2593
|
async listVolumeAttachments(serverId) {
|
|
2439
2594
|
const url = `${this.computeEndpoint}/servers/${encodeURIComponent(serverId)}/os-volume_attachments`;
|
|
2440
2595
|
try {
|
|
2441
|
-
const raw = await
|
|
2596
|
+
const raw = await import_ky7.default.get(url, { headers: this.authHeaders(), retry: 0, timeout: DEFAULT_TIMEOUT_MS3 }).json();
|
|
2442
2597
|
if (!isVolumeAttachmentsResponse(raw)) {
|
|
2443
2598
|
throw new NhnCloudCliError(
|
|
2444
2599
|
"instance volumes \uC751\uB2F5 \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 \u2014 volumeAttachments \uBC30\uC5F4\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
@@ -2457,11 +2612,11 @@ var InstanceClient = class {
|
|
|
2457
2612
|
async attachVolume(serverId, volumeId) {
|
|
2458
2613
|
const url = `${this.computeEndpoint}/servers/${encodeURIComponent(serverId)}/os-volume_attachments`;
|
|
2459
2614
|
try {
|
|
2460
|
-
const res = await
|
|
2615
|
+
const res = await import_ky7.default.post(url, {
|
|
2461
2616
|
headers: this.authHeaders(),
|
|
2462
2617
|
json: { volumeAttachment: { volumeId } },
|
|
2463
2618
|
retry: 0,
|
|
2464
|
-
timeout:
|
|
2619
|
+
timeout: DEFAULT_TIMEOUT_MS3
|
|
2465
2620
|
});
|
|
2466
2621
|
if (res.status === 202 || res.headers.get("content-length") === "0") {
|
|
2467
2622
|
return { id: volumeId, volumeId, serverId, device: "" };
|
|
@@ -2485,7 +2640,7 @@ var InstanceClient = class {
|
|
|
2485
2640
|
async detachVolume(serverId, volumeId) {
|
|
2486
2641
|
const url = `${this.computeEndpoint}/servers/${encodeURIComponent(serverId)}/os-volume_attachments/${encodeURIComponent(volumeId)}`;
|
|
2487
2642
|
try {
|
|
2488
|
-
await
|
|
2643
|
+
await import_ky7.default.delete(url, { headers: this.authHeaders(), retry: 0, timeout: DEFAULT_TIMEOUT_MS3 });
|
|
2489
2644
|
} catch (err) {
|
|
2490
2645
|
throw toNhnCloudCliError(err);
|
|
2491
2646
|
}
|
|
@@ -2555,8 +2710,8 @@ var listCommand = new import_commander13.Command("list").description("\uC778\uC2
|
|
|
2555
2710
|
var import_commander14 = require("commander");
|
|
2556
2711
|
|
|
2557
2712
|
// src/services/blockstorage/client.ts
|
|
2558
|
-
var
|
|
2559
|
-
var
|
|
2713
|
+
var import_ky8 = __toESM(require("ky"));
|
|
2714
|
+
var DEFAULT_TIMEOUT_MS4 = 3e4;
|
|
2560
2715
|
function isVolume(val) {
|
|
2561
2716
|
if (typeof val !== "object" || val === null) return false;
|
|
2562
2717
|
const obj = val;
|
|
@@ -2592,7 +2747,7 @@ var BlockStorageClient = class {
|
|
|
2592
2747
|
if (params?.offset !== void 0) searchParams["offset"] = params.offset;
|
|
2593
2748
|
if (params?.marker !== void 0) searchParams["marker"] = params.marker;
|
|
2594
2749
|
try {
|
|
2595
|
-
const raw = await
|
|
2750
|
+
const raw = await import_ky8.default.get(url, { headers: this.authHeaders(), searchParams, retry: 0, timeout: DEFAULT_TIMEOUT_MS4 }).json();
|
|
2596
2751
|
if (!isVolumesResponse(raw)) {
|
|
2597
2752
|
throw new NhnCloudCliError(
|
|
2598
2753
|
"volume list \uC751\uB2F5 \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 \u2014 volumes \uBC30\uC5F4\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
@@ -2607,7 +2762,7 @@ var BlockStorageClient = class {
|
|
|
2607
2762
|
async get(id) {
|
|
2608
2763
|
const url = `${this.endpoint}/volumes/${encodeURIComponent(id)}`;
|
|
2609
2764
|
try {
|
|
2610
|
-
const raw = await
|
|
2765
|
+
const raw = await import_ky8.default.get(url, { headers: this.authHeaders(), retry: 0, timeout: DEFAULT_TIMEOUT_MS4 }).json();
|
|
2611
2766
|
if (!isVolumeResponse(raw)) {
|
|
2612
2767
|
throw new NhnCloudCliError(
|
|
2613
2768
|
"volume get \uC751\uB2F5 \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 \u2014 volume \uAC1D\uCCB4\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
@@ -2627,11 +2782,11 @@ var BlockStorageClient = class {
|
|
|
2627
2782
|
if (params.volume_type !== void 0) volumeBody["volume_type"] = params.volume_type;
|
|
2628
2783
|
if (params.snapshot_id !== void 0) volumeBody["snapshot_id"] = params.snapshot_id;
|
|
2629
2784
|
try {
|
|
2630
|
-
const raw = await
|
|
2785
|
+
const raw = await import_ky8.default.post(url, {
|
|
2631
2786
|
headers: this.authHeaders(),
|
|
2632
2787
|
json: { volume: volumeBody },
|
|
2633
2788
|
retry: 0,
|
|
2634
|
-
timeout:
|
|
2789
|
+
timeout: DEFAULT_TIMEOUT_MS4
|
|
2635
2790
|
}).json();
|
|
2636
2791
|
if (!isVolumeResponse(raw)) {
|
|
2637
2792
|
throw new NhnCloudCliError(
|
|
@@ -2780,8 +2935,8 @@ var createCommand = new import_commander16.Command("create").description("\uBCFC
|
|
|
2780
2935
|
var import_commander17 = require("commander");
|
|
2781
2936
|
|
|
2782
2937
|
// src/services/network/client.ts
|
|
2783
|
-
var
|
|
2784
|
-
var
|
|
2938
|
+
var import_ky9 = __toESM(require("ky"));
|
|
2939
|
+
var DEFAULT_TIMEOUT_MS5 = 3e4;
|
|
2785
2940
|
function isVpc(val) {
|
|
2786
2941
|
if (typeof val !== "object" || val === null) return false;
|
|
2787
2942
|
const obj = val;
|
|
@@ -2834,10 +2989,10 @@ var NetworkClient = class {
|
|
|
2834
2989
|
async listVpcs() {
|
|
2835
2990
|
const url = `${this.networkEndpoint}/vpcs`;
|
|
2836
2991
|
try {
|
|
2837
|
-
const raw = await
|
|
2992
|
+
const raw = await import_ky9.default.get(url, {
|
|
2838
2993
|
headers: this.authHeaders(),
|
|
2839
2994
|
retry: 0,
|
|
2840
|
-
timeout:
|
|
2995
|
+
timeout: DEFAULT_TIMEOUT_MS5
|
|
2841
2996
|
}).json();
|
|
2842
2997
|
if (!isVpcsResponse(raw)) {
|
|
2843
2998
|
throw new NhnCloudCliError(
|
|
@@ -2856,10 +3011,10 @@ var NetworkClient = class {
|
|
|
2856
3011
|
async listSubnets() {
|
|
2857
3012
|
const url = `${this.networkEndpoint}/vpcsubnets`;
|
|
2858
3013
|
try {
|
|
2859
|
-
const raw = await
|
|
3014
|
+
const raw = await import_ky9.default.get(url, {
|
|
2860
3015
|
headers: this.authHeaders(),
|
|
2861
3016
|
retry: 0,
|
|
2862
|
-
timeout:
|
|
3017
|
+
timeout: DEFAULT_TIMEOUT_MS5
|
|
2863
3018
|
}).json();
|
|
2864
3019
|
if (!isSubnetsResponse(raw)) {
|
|
2865
3020
|
throw new NhnCloudCliError(
|
|
@@ -2876,7 +3031,7 @@ var NetworkClient = class {
|
|
|
2876
3031
|
async listFloatingIps() {
|
|
2877
3032
|
const url = `${this.networkEndpoint}/floatingips`;
|
|
2878
3033
|
try {
|
|
2879
|
-
const raw = await
|
|
3034
|
+
const raw = await import_ky9.default.get(url, { headers: this.authHeaders(), retry: 0, timeout: DEFAULT_TIMEOUT_MS5 }).json();
|
|
2880
3035
|
if (!isFloatingIpsResponse(raw)) {
|
|
2881
3036
|
throw new NhnCloudCliError(
|
|
2882
3037
|
"floatingip list \uC751\uB2F5 \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 \u2014 floatingips \uBC30\uC5F4\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
@@ -2892,11 +3047,11 @@ var NetworkClient = class {
|
|
|
2892
3047
|
async createFloatingIp(params) {
|
|
2893
3048
|
const url = `${this.networkEndpoint}/floatingips`;
|
|
2894
3049
|
try {
|
|
2895
|
-
const raw = await
|
|
3050
|
+
const raw = await import_ky9.default.post(url, {
|
|
2896
3051
|
headers: this.authHeaders(),
|
|
2897
3052
|
json: { floatingip: { floating_network_id: params.floating_network_id } },
|
|
2898
3053
|
retry: 0,
|
|
2899
|
-
timeout:
|
|
3054
|
+
timeout: DEFAULT_TIMEOUT_MS5
|
|
2900
3055
|
}).json();
|
|
2901
3056
|
if (!isFloatingIpResponse(raw)) {
|
|
2902
3057
|
throw new NhnCloudCliError(
|
|
@@ -2913,7 +3068,7 @@ var NetworkClient = class {
|
|
|
2913
3068
|
async deleteFloatingIp(id) {
|
|
2914
3069
|
const url = `${this.networkEndpoint}/floatingips/${encodeURIComponent(id)}`;
|
|
2915
3070
|
try {
|
|
2916
|
-
await
|
|
3071
|
+
await import_ky9.default.delete(url, { headers: this.authHeaders(), retry: 0, timeout: DEFAULT_TIMEOUT_MS5 });
|
|
2917
3072
|
} catch (err) {
|
|
2918
3073
|
throw toNhnCloudCliError(err);
|
|
2919
3074
|
}
|
|
@@ -2927,11 +3082,11 @@ var NetworkClient = class {
|
|
|
2927
3082
|
async findExternalNetworkId() {
|
|
2928
3083
|
const url = `${this.networkEndpoint}/vpcs`;
|
|
2929
3084
|
try {
|
|
2930
|
-
const raw = await
|
|
3085
|
+
const raw = await import_ky9.default.get(url, {
|
|
2931
3086
|
headers: this.authHeaders(),
|
|
2932
3087
|
searchParams: { "router:external": "true" },
|
|
2933
3088
|
retry: 0,
|
|
2934
|
-
timeout:
|
|
3089
|
+
timeout: DEFAULT_TIMEOUT_MS5
|
|
2935
3090
|
}).json();
|
|
2936
3091
|
if (typeof raw !== "object" || raw === null) return null;
|
|
2937
3092
|
const vpcs = raw["vpcs"];
|
|
@@ -3779,9 +3934,280 @@ var volumesCommand = new import_commander33.Command("volumes").description("\uC7
|
|
|
3779
3934
|
});
|
|
3780
3935
|
});
|
|
3781
3936
|
|
|
3937
|
+
// src/commands/ncr/list.ts
|
|
3938
|
+
var import_commander34 = require("commander");
|
|
3939
|
+
|
|
3940
|
+
// src/services/ncr/harbor-client.ts
|
|
3941
|
+
var import_ky10 = __toESM(require("ky"));
|
|
3942
|
+
var DEFAULT_TIMEOUT_MS6 = 3e4;
|
|
3943
|
+
var PAGE_SIZE = 100;
|
|
3944
|
+
var MAX_PAGES = 1e3;
|
|
3945
|
+
var HarborClient = class {
|
|
3946
|
+
uakId;
|
|
3947
|
+
uakSecret;
|
|
3948
|
+
host;
|
|
3949
|
+
constructor(uakId, uakSecret, host) {
|
|
3950
|
+
this.uakId = uakId;
|
|
3951
|
+
this.uakSecret = uakSecret;
|
|
3952
|
+
this.host = host;
|
|
3953
|
+
}
|
|
3954
|
+
basicAuthHeaders() {
|
|
3955
|
+
const token = Buffer.from(`${this.uakId}:${this.uakSecret}`).toString("base64");
|
|
3956
|
+
return { Authorization: `Basic ${token}` };
|
|
3957
|
+
}
|
|
3958
|
+
/**
|
|
3959
|
+
* Harbor REST 페이지네이션 전수 수집 (ADR-017 — silent truncation 방지).
|
|
3960
|
+
*
|
|
3961
|
+
* Harbor 응답 Link: <...?page=N+1...>; rel="next" 헤더가 없으면 종료.
|
|
3962
|
+
* ky.get() 이 Response 를 반환하므로 .json() 과 .headers.get("link") 를 함께 사용한다
|
|
3963
|
+
* (체이닝하면 헤더를 못 본다 — 기존 NCR client 의 .json<T>() 체이닝 패턴과 다름).
|
|
3964
|
+
*/
|
|
3965
|
+
async getAllPages(path) {
|
|
3966
|
+
const acc = [];
|
|
3967
|
+
let page = 1;
|
|
3968
|
+
try {
|
|
3969
|
+
for (; ; ) {
|
|
3970
|
+
const url = `https://${this.host}${path}?page=${page}&page_size=${PAGE_SIZE}`;
|
|
3971
|
+
const res = await import_ky10.default.get(url, {
|
|
3972
|
+
headers: this.basicAuthHeaders(),
|
|
3973
|
+
retry: 0,
|
|
3974
|
+
timeout: DEFAULT_TIMEOUT_MS6
|
|
3975
|
+
});
|
|
3976
|
+
const data = await res.json();
|
|
3977
|
+
if (!Array.isArray(data)) {
|
|
3978
|
+
throw new NhnCloudCliError(
|
|
3979
|
+
"Harbor REST \uC751\uB2F5 \uD615\uC2DD \uC624\uB958: \uBC30\uC5F4\uC774 \uC544\uB2D9\uB2C8\uB2E4.",
|
|
3980
|
+
EXIT_API_ERROR
|
|
3981
|
+
);
|
|
3982
|
+
}
|
|
3983
|
+
acc.push(...data);
|
|
3984
|
+
const link = res.headers.get("link");
|
|
3985
|
+
if (!link || !link.includes('rel="next"')) break;
|
|
3986
|
+
page++;
|
|
3987
|
+
if (page > MAX_PAGES) {
|
|
3988
|
+
throw new NhnCloudCliError(
|
|
3989
|
+
`Harbor pagination \uCD5C\uB300 \uD398\uC774\uC9C0(${MAX_PAGES}) \uCD08\uACFC \u2014 \uBE44\uC815\uC0C1 \uC751\uB2F5\uC73C\uB85C \uC911\uB2E8\uD569\uB2C8\uB2E4.`,
|
|
3990
|
+
EXIT_API_ERROR
|
|
3991
|
+
);
|
|
3992
|
+
}
|
|
3993
|
+
}
|
|
3994
|
+
} catch (err) {
|
|
3995
|
+
if (err instanceof NhnCloudCliError) throw err;
|
|
3996
|
+
throw toNhnCloudCliError(err);
|
|
3997
|
+
}
|
|
3998
|
+
return acc;
|
|
3999
|
+
}
|
|
4000
|
+
/**
|
|
4001
|
+
* 프로젝트(레지스트리)의 repository(이미지) 목록을 반환한다.
|
|
4002
|
+
* GET /api/v2.0/projects/{project}/repositories
|
|
4003
|
+
*/
|
|
4004
|
+
async listRepositories(project) {
|
|
4005
|
+
const enc = encodeURIComponent(project);
|
|
4006
|
+
const data = await this.getAllPages(`/api/v2.0/projects/${enc}/repositories`);
|
|
4007
|
+
return data.filter(isRepository);
|
|
4008
|
+
}
|
|
4009
|
+
/**
|
|
4010
|
+
* repository 의 artifact 목록을 반환한다.
|
|
4011
|
+
* GET /api/v2.0/projects/{project}/repositories/{repository}/artifacts
|
|
4012
|
+
* repository 의 '/' 는 %2F 로 인코딩(path-traversal 방지).
|
|
4013
|
+
*/
|
|
4014
|
+
async listArtifacts(project, repository) {
|
|
4015
|
+
const encProject = encodeURIComponent(project);
|
|
4016
|
+
const encRepo = encodeURIComponent(repository);
|
|
4017
|
+
const data = await this.getAllPages(
|
|
4018
|
+
`/api/v2.0/projects/${encProject}/repositories/${encRepo}/artifacts`
|
|
4019
|
+
);
|
|
4020
|
+
return data.filter(isArtifact);
|
|
4021
|
+
}
|
|
4022
|
+
};
|
|
4023
|
+
|
|
4024
|
+
// src/commands/ncr/helpers.ts
|
|
4025
|
+
async function createNcrClient(opts) {
|
|
4026
|
+
const profileName = await resolveProfileName(opts.profile);
|
|
4027
|
+
const uak = await getUserAccessKey(profileName);
|
|
4028
|
+
const region = opts.region ?? "kr1";
|
|
4029
|
+
return { client: new NcrClient(uak.id, uak.secret, region), profileName };
|
|
4030
|
+
}
|
|
4031
|
+
async function resolveAppKey(profileName, appKeyOpt) {
|
|
4032
|
+
if (appKeyOpt) return appKeyOpt;
|
|
4033
|
+
let cred;
|
|
4034
|
+
try {
|
|
4035
|
+
cred = await getServiceCredential("ncr", profileName);
|
|
4036
|
+
} catch (err) {
|
|
4037
|
+
if (!(err instanceof NhnCloudCliError) || err.exitCode !== EXIT_CONFIG_ERROR) {
|
|
4038
|
+
throw err;
|
|
4039
|
+
}
|
|
4040
|
+
}
|
|
4041
|
+
if (!cred?.appkey) {
|
|
4042
|
+
throw new NhnCloudCliError(
|
|
4043
|
+
"NCR appKey \uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. --app-key \uC635\uC158\uC73C\uB85C \uC9C0\uC815\uD558\uAC70\uB098\nnhncloud configure --ncr-appkey <key> \uB97C \uC2E4\uD589\uD574 \uC124\uC815\uD558\uC138\uC694.",
|
|
4044
|
+
EXIT_CONFIG_ERROR
|
|
4045
|
+
);
|
|
4046
|
+
}
|
|
4047
|
+
return cred.appkey;
|
|
4048
|
+
}
|
|
4049
|
+
async function createHarborClient(opts, registryArg) {
|
|
4050
|
+
const { client: ncrClient, profileName } = await createNcrClient(opts);
|
|
4051
|
+
const appKey = await resolveAppKey(profileName, opts.appKey);
|
|
4052
|
+
const uak = await getUserAccessKey(profileName);
|
|
4053
|
+
const reg = await ncrClient.getRegistry(appKey, registryArg);
|
|
4054
|
+
const host = parseHarborHost(reg.uri);
|
|
4055
|
+
const project = typeof reg.name === "string" ? reg.name : registryArg;
|
|
4056
|
+
return { harbor: new HarborClient(uak.id, uak.secret, host), project };
|
|
4057
|
+
}
|
|
4058
|
+
function parseHarborHost(uri) {
|
|
4059
|
+
if (!uri) {
|
|
4060
|
+
throw new NhnCloudCliError(
|
|
4061
|
+
"\uB808\uC9C0\uC2A4\uD2B8\uB9AC uri \uAC00 \uC5C6\uC5B4 \uC774\uBBF8\uC9C0 host \uB97C \uD574\uC11D\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
4062
|
+
EXIT_API_ERROR
|
|
4063
|
+
);
|
|
4064
|
+
}
|
|
4065
|
+
const noScheme = uri.replace(/^https?:\/\//, "");
|
|
4066
|
+
const host = noScheme.split("/")[0];
|
|
4067
|
+
if (!host) {
|
|
4068
|
+
throw new NhnCloudCliError(
|
|
4069
|
+
"\uB808\uC9C0\uC2A4\uD2B8\uB9AC uri \uD615\uC2DD \uC624\uB958 \u2014 host \uCD94\uCD9C \uC2E4\uD328.",
|
|
4070
|
+
EXIT_API_ERROR
|
|
4071
|
+
);
|
|
4072
|
+
}
|
|
4073
|
+
return host;
|
|
4074
|
+
}
|
|
4075
|
+
|
|
4076
|
+
// src/commands/ncr/list.ts
|
|
4077
|
+
var listCommand5 = new import_commander34.Command("list").description("NCR \uB808\uC9C0\uC2A4\uD2B8\uB9AC \uBAA9\uB85D\uC744 \uC870\uD68C\uD55C\uB2E4").option("--region <region>", "NCR region (\uAE30\uBCF8: kr1)", "kr1").option("--app-key <key>", "NCR appKey (profile \uC758 ncr.appkey \uBCF4\uB2E4 \uC6B0\uC120)").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (_opts, cmd) => {
|
|
4078
|
+
const opts = cmd.optsWithGlobals();
|
|
4079
|
+
const { client, profileName } = await createNcrClient(opts);
|
|
4080
|
+
const appKey = await resolveAppKey(profileName, opts.appKey);
|
|
4081
|
+
startSpinner("\uB808\uC9C0\uC2A4\uD2B8\uB9AC \uBAA9\uB85D \uC870\uD68C \uC911...");
|
|
4082
|
+
let registries;
|
|
4083
|
+
try {
|
|
4084
|
+
registries = await client.listRegistries(appKey);
|
|
4085
|
+
} catch (err) {
|
|
4086
|
+
stopSpinner(false);
|
|
4087
|
+
throw err;
|
|
4088
|
+
}
|
|
4089
|
+
stopSpinner(true);
|
|
4090
|
+
output(opts, {
|
|
4091
|
+
headers: ["name", "repo_count", "uri"],
|
|
4092
|
+
rows: registries.map((r) => [
|
|
4093
|
+
r.name,
|
|
4094
|
+
String(r.repo_count ?? ""),
|
|
4095
|
+
r.uri ?? ""
|
|
4096
|
+
]),
|
|
4097
|
+
raw: registries,
|
|
4098
|
+
ids: registries.map((r) => r.name)
|
|
4099
|
+
});
|
|
4100
|
+
});
|
|
4101
|
+
|
|
4102
|
+
// src/commands/ncr/get.ts
|
|
4103
|
+
var import_commander35 = require("commander");
|
|
4104
|
+
var getCommand3 = new import_commander35.Command("get").description("\uB2E8\uC77C NCR \uB808\uC9C0\uC2A4\uD2B8\uB9AC\uB97C \uC870\uD68C\uD55C\uB2E4").argument("<registry>", "\uB808\uC9C0\uC2A4\uD2B8\uB9AC \uC774\uB984 \uB610\uB294 ID").option("--region <region>", "NCR region (\uAE30\uBCF8: kr1)", "kr1").option("--app-key <key>", "NCR appKey (profile \uC758 ncr.appkey \uBCF4\uB2E4 \uC6B0\uC120)").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (registry, _opts, cmd) => {
|
|
4105
|
+
const opts = cmd.optsWithGlobals();
|
|
4106
|
+
if (!registry.trim()) {
|
|
4107
|
+
throw new NhnCloudCliError(
|
|
4108
|
+
"registry \uC778\uC218\uAC00 \uBE44\uC5B4\uC788\uC2B5\uB2C8\uB2E4. \uB808\uC9C0\uC2A4\uD2B8\uB9AC \uC774\uB984 \uB610\uB294 ID \uB97C \uC9C0\uC815\uD558\uC138\uC694.",
|
|
4109
|
+
EXIT_PARAM_ERROR
|
|
4110
|
+
);
|
|
4111
|
+
}
|
|
4112
|
+
const { client, profileName } = await createNcrClient(opts);
|
|
4113
|
+
const appKey = await resolveAppKey(profileName, opts.appKey);
|
|
4114
|
+
startSpinner(`\uB808\uC9C0\uC2A4\uD2B8\uB9AC "${registry}" \uC870\uD68C \uC911...`);
|
|
4115
|
+
let reg;
|
|
4116
|
+
try {
|
|
4117
|
+
reg = await client.getRegistry(appKey, registry);
|
|
4118
|
+
} catch (err) {
|
|
4119
|
+
stopSpinner(false);
|
|
4120
|
+
throw err;
|
|
4121
|
+
}
|
|
4122
|
+
stopSpinner(true);
|
|
4123
|
+
output(opts, {
|
|
4124
|
+
headers: ["name", "repo_count", "uri", "private_uri"],
|
|
4125
|
+
rows: [[reg.name, String(reg.repo_count ?? ""), reg.uri ?? "", reg.private_uri ?? ""]],
|
|
4126
|
+
raw: reg,
|
|
4127
|
+
ids: [reg.name]
|
|
4128
|
+
});
|
|
4129
|
+
});
|
|
4130
|
+
|
|
4131
|
+
// src/commands/ncr/images.ts
|
|
4132
|
+
var import_commander36 = require("commander");
|
|
4133
|
+
var imagesCommand2 = new import_commander36.Command("images").description("\uB808\uC9C0\uC2A4\uD2B8\uB9AC\uC758 \uC774\uBBF8\uC9C0(repository) \uBAA9\uB85D\uC744 \uC870\uD68C\uD55C\uB2E4").argument("<registry>", "\uB808\uC9C0\uC2A4\uD2B8\uB9AC \uC774\uB984").option("--region <region>", "NCR region (\uAE30\uBCF8: kr1)", "kr1").option("--app-key <key>", "NCR appKey (profile \uC758 ncr.appkey \uBCF4\uB2E4 \uC6B0\uC120)").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (registry, _opts, cmd) => {
|
|
4134
|
+
const opts = cmd.optsWithGlobals();
|
|
4135
|
+
if (!registry.trim()) {
|
|
4136
|
+
throw new NhnCloudCliError(
|
|
4137
|
+
"registry \uC778\uC218\uAC00 \uBE44\uC5B4\uC788\uC2B5\uB2C8\uB2E4. \uB808\uC9C0\uC2A4\uD2B8\uB9AC \uC774\uB984\uC744 \uC9C0\uC815\uD558\uC138\uC694.",
|
|
4138
|
+
EXIT_PARAM_ERROR
|
|
4139
|
+
);
|
|
4140
|
+
}
|
|
4141
|
+
const { harbor, project } = await createHarborClient(opts, registry);
|
|
4142
|
+
startSpinner(`"${registry}" \uC774\uBBF8\uC9C0 \uBAA9\uB85D \uC870\uD68C \uC911...`);
|
|
4143
|
+
let repos;
|
|
4144
|
+
try {
|
|
4145
|
+
repos = await harbor.listRepositories(project);
|
|
4146
|
+
} catch (err) {
|
|
4147
|
+
stopSpinner(false);
|
|
4148
|
+
throw err;
|
|
4149
|
+
}
|
|
4150
|
+
stopSpinner(true);
|
|
4151
|
+
const rows = repos.map((r) => {
|
|
4152
|
+
const short = r.name.startsWith(project + "/") ? r.name.slice(project.length + 1) : r.name;
|
|
4153
|
+
return [short, String(r.artifact_count ?? ""), String(r.pull_count ?? "")];
|
|
4154
|
+
});
|
|
4155
|
+
const ids = repos.map(
|
|
4156
|
+
(r) => r.name.startsWith(project + "/") ? r.name.slice(project.length + 1) : r.name
|
|
4157
|
+
);
|
|
4158
|
+
output(opts, {
|
|
4159
|
+
headers: ["repository", "artifact_count", "pull_count"],
|
|
4160
|
+
rows,
|
|
4161
|
+
raw: repos,
|
|
4162
|
+
ids
|
|
4163
|
+
});
|
|
4164
|
+
});
|
|
4165
|
+
|
|
4166
|
+
// src/commands/ncr/tags.ts
|
|
4167
|
+
var import_commander37 = require("commander");
|
|
4168
|
+
var tagsCommand = new import_commander37.Command("tags").description("\uD2B9\uC815 \uC774\uBBF8\uC9C0\uC758 \uD0DC\uADF8 \uBAA9\uB85D\uC744 \uC870\uD68C\uD55C\uB2E4").argument("<registry>", "\uB808\uC9C0\uC2A4\uD2B8\uB9AC \uC774\uB984").argument("<repository>", "\uC774\uBBF8\uC9C0(repository) \uC774\uB984 (\uC9E7\uC740 \uC774\uB984 \uB610\uB294 {project}/{repo})").option("--region <region>", "NCR region (\uAE30\uBCF8: kr1)", "kr1").option("--app-key <key>", "NCR appKey (profile \uC758 ncr.appkey \uBCF4\uB2E4 \uC6B0\uC120)").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (registry, repository, _opts, cmd) => {
|
|
4169
|
+
const opts = cmd.optsWithGlobals();
|
|
4170
|
+
if (!registry.trim()) {
|
|
4171
|
+
throw new NhnCloudCliError(
|
|
4172
|
+
"registry \uC778\uC218\uAC00 \uBE44\uC5B4\uC788\uC2B5\uB2C8\uB2E4. \uB808\uC9C0\uC2A4\uD2B8\uB9AC \uC774\uB984\uC744 \uC9C0\uC815\uD558\uC138\uC694.",
|
|
4173
|
+
EXIT_PARAM_ERROR
|
|
4174
|
+
);
|
|
4175
|
+
}
|
|
4176
|
+
if (!repository.trim()) {
|
|
4177
|
+
throw new NhnCloudCliError(
|
|
4178
|
+
"repository \uC778\uC218\uAC00 \uBE44\uC5B4\uC788\uC2B5\uB2C8\uB2E4. \uC774\uBBF8\uC9C0 \uC774\uB984\uC744 \uC9C0\uC815\uD558\uC138\uC694.",
|
|
4179
|
+
EXIT_PARAM_ERROR
|
|
4180
|
+
);
|
|
4181
|
+
}
|
|
4182
|
+
const { harbor, project } = await createHarborClient(opts, registry);
|
|
4183
|
+
const repo = repository.startsWith(project + "/") ? repository.slice(project.length + 1) : repository;
|
|
4184
|
+
startSpinner(`"${registry}/${repo}" \uD0DC\uADF8 \uBAA9\uB85D \uC870\uD68C \uC911...`);
|
|
4185
|
+
let tagRows;
|
|
4186
|
+
try {
|
|
4187
|
+
const artifacts = await harbor.listArtifacts(project, repo);
|
|
4188
|
+
tagRows = artifacts.flatMap(
|
|
4189
|
+
(a) => (a.tags ?? []).map((t) => ({
|
|
4190
|
+
tag: t.name,
|
|
4191
|
+
push_time: t.push_time ?? a.push_time,
|
|
4192
|
+
size: String(a.size ?? "")
|
|
4193
|
+
}))
|
|
4194
|
+
);
|
|
4195
|
+
} catch (err) {
|
|
4196
|
+
stopSpinner(false);
|
|
4197
|
+
throw err;
|
|
4198
|
+
}
|
|
4199
|
+
stopSpinner(true);
|
|
4200
|
+
output(opts, {
|
|
4201
|
+
headers: ["tag", "push_time", "size"],
|
|
4202
|
+
rows: tagRows.map((r) => [r.tag, r.push_time ?? "", r.size]),
|
|
4203
|
+
raw: tagRows,
|
|
4204
|
+
ids: tagRows.map((r) => r.tag)
|
|
4205
|
+
});
|
|
4206
|
+
});
|
|
4207
|
+
|
|
3782
4208
|
// src/index.ts
|
|
3783
|
-
var program = new
|
|
3784
|
-
program.name("nhncloud").description("NHN Cloud CLI \u2014 AI agent & terminal friendly").version("0.
|
|
4209
|
+
var program = new import_commander38.Command();
|
|
4210
|
+
program.name("nhncloud").description("NHN Cloud CLI \u2014 AI agent & terminal friendly").version("0.5.0").option("--json", "JSON \uD615\uC2DD\uC73C\uB85C \uCD9C\uB825").option("--quiet", "\uCD5C\uC18C \uCD9C\uB825 (\uC790\uB3D9\uD654\uC6A9)").option("--no-color", "\uC0C9\uC0C1 \uBE44\uD65C\uC131\uD654");
|
|
3785
4211
|
program.hook("preAction", () => {
|
|
3786
4212
|
const opts = program.opts();
|
|
3787
4213
|
if (!opts.color || process.env["NO_COLOR"]) {
|
|
@@ -3792,12 +4218,12 @@ program.hook("preAction", () => {
|
|
|
3792
4218
|
}
|
|
3793
4219
|
});
|
|
3794
4220
|
program.addCommand(configureCommand);
|
|
3795
|
-
var logncrashCommand = new
|
|
4221
|
+
var logncrashCommand = new import_commander38.Command("logncrash").description("Log & Crash \uAD00\uB828 \uBA85\uB839");
|
|
3796
4222
|
logncrashCommand.addCommand(searchCommand);
|
|
3797
4223
|
logncrashCommand.addCommand(sendCommand);
|
|
3798
4224
|
logncrashCommand.addCommand(exportCommand);
|
|
3799
4225
|
program.addCommand(logncrashCommand);
|
|
3800
|
-
var deployCommand = new
|
|
4226
|
+
var deployCommand = new import_commander38.Command("deploy").description("NHN Cloud Deploy \uAD00\uB828 \uBA85\uB839");
|
|
3801
4227
|
deployCommand.addCommand(runCommand);
|
|
3802
4228
|
deployCommand.addCommand(artifactsCommand);
|
|
3803
4229
|
deployCommand.addCommand(serverGroupsCommand);
|
|
@@ -3807,7 +4233,7 @@ deployCommand.addCommand(binariesCommand);
|
|
|
3807
4233
|
deployCommand.addCommand(uploadCommand);
|
|
3808
4234
|
deployCommand.addCommand(downloadCommand);
|
|
3809
4235
|
program.addCommand(deployCommand);
|
|
3810
|
-
var instanceCommand = new
|
|
4236
|
+
var instanceCommand = new import_commander38.Command("instance").description("Compute \uC778\uC2A4\uD134\uC2A4 \uAD00\uB828 \uBA85\uB839");
|
|
3811
4237
|
instanceCommand.addCommand(listCommand);
|
|
3812
4238
|
instanceCommand.addCommand(flavorsCommand);
|
|
3813
4239
|
instanceCommand.addCommand(availabilityZonesCommand);
|
|
@@ -3826,22 +4252,28 @@ instanceCommand.addCommand(keypairCommand);
|
|
|
3826
4252
|
instanceCommand.addCommand(volumeCommand);
|
|
3827
4253
|
instanceCommand.addCommand(volumesCommand);
|
|
3828
4254
|
program.addCommand(instanceCommand);
|
|
3829
|
-
var networkCommand = new
|
|
4255
|
+
var networkCommand = new import_commander38.Command("network").description("VPC\xB7\uC11C\uBE0C\uB137 \uC870\uD68C");
|
|
3830
4256
|
networkCommand.addCommand(listCommand3);
|
|
3831
4257
|
networkCommand.addCommand(subnetCommand);
|
|
3832
4258
|
program.addCommand(networkCommand);
|
|
3833
|
-
var volumeCommand2 = new
|
|
4259
|
+
var volumeCommand2 = new import_commander38.Command("volume").description("Block Storage \uBCFC\uB968 \uAD00\uB828 \uBA85\uB839");
|
|
3834
4260
|
volumeCommand2.addCommand(listCommand2);
|
|
3835
4261
|
volumeCommand2.addCommand(getCommand);
|
|
3836
4262
|
volumeCommand2.addCommand(createCommand);
|
|
3837
4263
|
program.addCommand(volumeCommand2);
|
|
3838
|
-
var floatingipCommand = new
|
|
4264
|
+
var floatingipCommand = new import_commander38.Command("floatingip").description(
|
|
3839
4265
|
"Floating IP(\uC778\uC2A4\uD134\uC2A4 \uACF5\uC778 IP) \uAD00\uB9AC"
|
|
3840
4266
|
);
|
|
3841
4267
|
floatingipCommand.addCommand(listCommand4);
|
|
3842
4268
|
floatingipCommand.addCommand(createCommand2);
|
|
3843
4269
|
floatingipCommand.addCommand(deleteCommand);
|
|
3844
4270
|
program.addCommand(floatingipCommand);
|
|
4271
|
+
var ncrCommand = new import_commander38.Command("ncr").description("NHN Container Registry \uAD00\uB828 \uBA85\uB839");
|
|
4272
|
+
ncrCommand.addCommand(listCommand5);
|
|
4273
|
+
ncrCommand.addCommand(getCommand3);
|
|
4274
|
+
ncrCommand.addCommand(imagesCommand2);
|
|
4275
|
+
ncrCommand.addCommand(tagsCommand);
|
|
4276
|
+
program.addCommand(ncrCommand);
|
|
3845
4277
|
program.parseAsync().catch((err) => {
|
|
3846
4278
|
const message = err instanceof Error ? err.message : String(err);
|
|
3847
4279
|
const exitCode = err instanceof NhnCloudCliError ? err.exitCode : 1;
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: nhncloud-cli
|
|
3
|
-
description: NHN Cloud 서비스 CLI. 자격증명 설정(configure), Log & Crash 로그 검색·전송(logncrash search/send), Deploy 배포 실행·바이너리 그룹·바이너리 목록 조회·업로드·다운로드(deploy upload/download), Compute 인스턴스 관리(instance — 목록·발급·전원 제어·타입 변경(resize/resize-confirm/resize-revert)·키페어·이미지·가용성 영역 조회·볼륨 연결(instance volume attach/detach, instance volumes)), VPC·서브넷 조회(network list/subnet list), Block Storage 볼륨 관리(volume list/get/create), Floating IP 관리(floatingip list/create/delete) 등 NHN Cloud PaaS API 를 터미널·AI 에이전트에서 호출한다.
|
|
3
|
+
description: NHN Cloud 서비스 CLI. 자격증명 설정(configure), Log & Crash 로그 검색·전송(logncrash search/send), Deploy 배포 실행·바이너리 그룹·바이너리 목록 조회·업로드·다운로드(deploy upload/download), Compute 인스턴스 관리(instance — 목록·발급·전원 제어·타입 변경(resize/resize-confirm/resize-revert)·키페어·이미지·가용성 영역 조회·볼륨 연결(instance volume attach/detach, instance volumes)), VPC·서브넷 조회(network list/subnet list), Block Storage 볼륨 관리(volume list/get/create), Floating IP 관리(floatingip list/create/delete), NHN Container Registry 레지스트리·이미지·태그 조회(ncr list/get/images/tags) 등 NHN Cloud PaaS API 를 터미널·AI 에이전트에서 호출한다.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# nhncloud-cli
|
|
7
7
|
|
|
8
8
|
NHN Cloud PaaS 서비스를 AWS CLI 방식으로 호출하는 TypeScript CLI.
|
|
9
|
-
`configure`, `logncrash search/send`, `deploy`, `instance` (전원 제어·keypair·볼륨 연결 포함), `network` (VPC·서브넷 조회), `volume` (Block Storage 볼륨 목록·조회·생성), `floatingip` (공인 IP 관리) 명령을 지원한다.
|
|
9
|
+
`configure`, `logncrash search/send`, `deploy`, `instance` (전원 제어·keypair·볼륨 연결 포함), `network` (VPC·서브넷 조회), `volume` (Block Storage 볼륨 목록·조회·생성), `floatingip` (공인 IP 관리), `ncr` (NHN Container Registry 레지스트리·이미지·태그 조회) 명령을 지원한다.
|
|
10
10
|
|
|
11
11
|
## 설치
|
|
12
12
|
|
|
@@ -42,6 +42,7 @@ nhncloud configure \
|
|
|
42
42
|
| `--uak-secret <secret>` | 개인 UAK Secret |
|
|
43
43
|
| `--logncrash-appkey <key>` | logncrash appkey |
|
|
44
44
|
| `--logncrash-secret <secret>` | logncrash secret |
|
|
45
|
+
| `--ncr-appkey <key>` | NCR(Container Registry) appkey |
|
|
45
46
|
| `--no-verify` | 연결 테스트 생략 |
|
|
46
47
|
|
|
47
48
|
저장 파일 구조 (`~/.nhncloud/credentials.json`, mode 0600):
|
|
@@ -101,6 +102,13 @@ logncrash appkey 와 secret 은 콘솔 → Log & Crash Search → 프로젝트
|
|
|
101
102
|
| 다른 profile 사용 | `nhncloud logncrash search --query '*' --from 1h --to now --profile staging` |
|
|
102
103
|
| 로그 대량 추출 (파일로) | `nhncloud logncrash export --query '<lucene>' --from 1h --to now --output logs.jsonl` |
|
|
103
104
|
| Log & Crash 로그 전송 | `nhncloud logncrash send --body "<메시지>" --level INFO` |
|
|
105
|
+
| NCR 레지스트리 목록 조회 | `nhncloud ncr list --app-key <appkey>` |
|
|
106
|
+
| NCR 레지스트리 목록 (JSON 파싱용) | `nhncloud ncr list --app-key <appkey> --json` |
|
|
107
|
+
| NCR 단일 레지스트리 조회 | `nhncloud ncr get <registry-name> --app-key <appkey>` |
|
|
108
|
+
| NCR 이미지(repository) 목록 조회 | `nhncloud ncr images <registry>` |
|
|
109
|
+
| NCR 이미지 목록 (JSON) | `nhncloud ncr images <registry> --json` |
|
|
110
|
+
| NCR 태그 목록 조회 | `nhncloud ncr tags <registry> <repository>` |
|
|
111
|
+
| NCR 태그 목록 (JSON) | `nhncloud ncr tags <registry> <repository> --json` |
|
|
104
112
|
|
|
105
113
|
## logncrash search 옵션
|
|
106
114
|
|
|
@@ -624,3 +632,76 @@ nhncloud floatingip delete <floatingip-id> --yes
|
|
|
624
632
|
| 외부 네트워크 미발견 (create --network 미지정) | 3 (PARAM_ERROR) |
|
|
625
633
|
| 비대화형 delete --yes 누락 | 3 (PARAM_ERROR) |
|
|
626
634
|
| Floating IP API 오류 | 1 (API_ERROR) |
|
|
635
|
+
|
|
636
|
+
## ncr — NHN Container Registry 레지스트리·이미지·태그 조회
|
|
637
|
+
|
|
638
|
+
NCR Management API 로 레지스트리(Harbor 프로젝트)를 조회한다.
|
|
639
|
+
이미지(repository)·태그는 레지스트리 데이터플레인 Harbor REST API 를 직접 호출한다.
|
|
640
|
+
두 경로 모두 공통 UAK 를 사용하므로 추가 설정은 없다.
|
|
641
|
+
appKey 는 `--app-key` 옵션 또는 `nhncloud configure --ncr-appkey <appkey>` 로 profile 에 저장한 값을 자동 사용한다.
|
|
642
|
+
|
|
643
|
+
### 의도 → 커맨드 매핑
|
|
644
|
+
|
|
645
|
+
| 의도 | 커맨드 |
|
|
646
|
+
|------|--------|
|
|
647
|
+
| 레지스트리 목록 조회 | `nhncloud ncr list --app-key <appkey>` |
|
|
648
|
+
| 레지스트리 목록 (JSON) | `nhncloud ncr list --app-key <appkey> --json` |
|
|
649
|
+
| 다른 region 조회 (기본 kr1) | `nhncloud ncr list --region kr2 --app-key <appkey>` |
|
|
650
|
+
| 단일 레지스트리 조회 | `nhncloud ncr get <registry> --app-key <appkey>` |
|
|
651
|
+
| appkey 저장 후 생략 | `nhncloud configure --ncr-appkey <appkey>` → `nhncloud ncr list` |
|
|
652
|
+
| 이미지(repository) 목록 조회 | `nhncloud ncr images <registry>` |
|
|
653
|
+
| 이미지 목록 (JSON) | `nhncloud ncr images <registry> --json` |
|
|
654
|
+
| 특정 이미지의 태그 목록 조회 | `nhncloud ncr tags <registry> <repository>` |
|
|
655
|
+
| 태그 목록 (JSON) | `nhncloud ncr tags <registry> <repository> --json` |
|
|
656
|
+
|
|
657
|
+
### ncr list / get 옵션
|
|
658
|
+
|
|
659
|
+
| 옵션 | 설명 |
|
|
660
|
+
|------|------|
|
|
661
|
+
| `--region <region>` | NCR region (기본: `kr1`). 지원: `kr1`, `kr2`, `kr3` |
|
|
662
|
+
| `--app-key <key>` | NCR appKey (profile 의 `ncr.appkey` 보다 우선) |
|
|
663
|
+
| `--profile <name>` | 사용할 profile 이름 |
|
|
664
|
+
|
|
665
|
+
### ncr images / ncr tags 옵션
|
|
666
|
+
|
|
667
|
+
| 옵션 | 설명 |
|
|
668
|
+
|------|------|
|
|
669
|
+
| `--region <region>` | NCR region (기본: `kr1`) |
|
|
670
|
+
| `--app-key <key>` | NCR appKey (profile 의 `ncr.appkey` 보다 우선) |
|
|
671
|
+
| `--profile <name>` | 사용할 profile 이름 |
|
|
672
|
+
|
|
673
|
+
### 체이닝 예시
|
|
674
|
+
|
|
675
|
+
```bash
|
|
676
|
+
# 레지스트리 목록 (이름만 추출)
|
|
677
|
+
nhncloud ncr list --app-key <appkey> --json | jq -r '.[].name'
|
|
678
|
+
|
|
679
|
+
# 레지스트리 수 확인
|
|
680
|
+
nhncloud ncr list --app-key <appkey> --json | jq length
|
|
681
|
+
|
|
682
|
+
# 단일 레지스트리 상세 조회
|
|
683
|
+
nhncloud ncr get <registry> --app-key <appkey> --json
|
|
684
|
+
|
|
685
|
+
# 이미지 목록 (이름만 추출)
|
|
686
|
+
nhncloud ncr images <registry> --json | jq -r '.[].repository'
|
|
687
|
+
|
|
688
|
+
# 이미지별 artifact 수 확인
|
|
689
|
+
nhncloud ncr images <registry> --json | jq '.[] | {repository, artifact_count}'
|
|
690
|
+
|
|
691
|
+
# 특정 이미지의 태그 목록
|
|
692
|
+
nhncloud ncr tags <registry> <repository> --json | jq -r '.[].tag'
|
|
693
|
+
|
|
694
|
+
# 가장 최근 push 태그 확인
|
|
695
|
+
nhncloud ncr tags <registry> <repository> --json | jq 'sort_by(.push_time) | last | .tag'
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
### ncr 에러 코드
|
|
699
|
+
|
|
700
|
+
| 상황 | exit code |
|
|
701
|
+
|------|-----------|
|
|
702
|
+
| UAK 누락·불완전 | 4 (CONFIG_ERROR) |
|
|
703
|
+
| NCR appkey 미설정 (`--app-key` 미지정 + configure 미설정) | 4 (CONFIG_ERROR) |
|
|
704
|
+
| 지원하지 않는 region | 3 (PARAM_ERROR) |
|
|
705
|
+
| registry / repository 인수 공백·빈값 | 3 (PARAM_ERROR) |
|
|
706
|
+
| UAK 인증 실패 (401/403) | 2 (AUTH_ERROR) |
|
|
707
|
+
| NCR API 오류 | 1 (API_ERROR) |
|