@bifos/nhncloud-cli 0.3.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 +276 -2
- package/dist/index.js +2916 -302
- package/package.json +1 -1
- package/skills/nhncloud-cli/SKILL.md +329 -2
package/dist/index.js
CHANGED
|
@@ -24,8 +24,8 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
24
24
|
));
|
|
25
25
|
|
|
26
26
|
// src/index.ts
|
|
27
|
-
var
|
|
28
|
-
var
|
|
27
|
+
var import_commander38 = require("commander");
|
|
28
|
+
var import_chalk12 = __toESM(require("chalk"));
|
|
29
29
|
|
|
30
30
|
// src/utils/spinner.ts
|
|
31
31
|
var import_ora = __toESM(require("ora"));
|
|
@@ -317,7 +317,7 @@ function iaasCachePath(profile, region) {
|
|
|
317
317
|
function isIaasTokenCache(val) {
|
|
318
318
|
if (typeof val !== "object" || val === null) return false;
|
|
319
319
|
const obj = val;
|
|
320
|
-
return typeof obj["tokenId"] === "string" && typeof obj["expiresAt"] === "string" && typeof obj["computeEndpoint"] === "string";
|
|
320
|
+
return typeof obj["tokenId"] === "string" && typeof obj["expiresAt"] === "string" && typeof obj["computeEndpoint"] === "string" && typeof obj["imageEndpoint"] === "string" && typeof obj["networkEndpoint"] === "string" && typeof obj["blockStorageEndpoint"] === "string";
|
|
321
321
|
}
|
|
322
322
|
async function readIaasToken(profile, region) {
|
|
323
323
|
const filePath = iaasCachePath(profile, region);
|
|
@@ -331,7 +331,10 @@ async function readIaasToken(profile, region) {
|
|
|
331
331
|
return {
|
|
332
332
|
tokenId: parsed.tokenId,
|
|
333
333
|
expiresAt: parsed.expiresAt,
|
|
334
|
-
computeEndpoint: parsed.computeEndpoint
|
|
334
|
+
computeEndpoint: parsed.computeEndpoint,
|
|
335
|
+
imageEndpoint: parsed.imageEndpoint,
|
|
336
|
+
networkEndpoint: parsed.networkEndpoint,
|
|
337
|
+
blockStorageEndpoint: parsed.blockStorageEndpoint
|
|
335
338
|
};
|
|
336
339
|
} catch {
|
|
337
340
|
return null;
|
|
@@ -343,7 +346,10 @@ async function writeIaasToken(profile, region, data) {
|
|
|
343
346
|
const cache = {
|
|
344
347
|
tokenId: data.tokenId,
|
|
345
348
|
expiresAt: data.expiresAt,
|
|
346
|
-
computeEndpoint: data.computeEndpoint
|
|
349
|
+
computeEndpoint: data.computeEndpoint,
|
|
350
|
+
imageEndpoint: data.imageEndpoint,
|
|
351
|
+
networkEndpoint: data.networkEndpoint,
|
|
352
|
+
blockStorageEndpoint: data.blockStorageEndpoint
|
|
347
353
|
};
|
|
348
354
|
const tmp = filePath + "." + (0, import_node_crypto.randomBytes)(4).toString("hex") + ".tmp";
|
|
349
355
|
await (0, import_promises2.writeFile)(tmp, JSON.stringify(cache, null, 2), { encoding: "utf-8", mode: 384 });
|
|
@@ -448,6 +454,7 @@ var import_ky3 = __toESM(require("ky"));
|
|
|
448
454
|
// src/api/endpoints.ts
|
|
449
455
|
var ENDPOINTS = {
|
|
450
456
|
logncrash: "https://api-lncs-search.nhncloudservice.com",
|
|
457
|
+
"logncrash-collector": "https://api-logncrash.nhncloudservice.com",
|
|
451
458
|
deploy: "https://api-deploy.nhncloudservice.com"
|
|
452
459
|
};
|
|
453
460
|
function endpointFor(service) {
|
|
@@ -469,11 +476,75 @@ var INSTANCE_HOST = {
|
|
|
469
476
|
kr3: "kr3-api-instance-infrastructure.nhncloudservice.com",
|
|
470
477
|
jp1: "jp1-api-instance-infrastructure.nhncloudservice.com"
|
|
471
478
|
};
|
|
479
|
+
var IMAGE_HOST = {
|
|
480
|
+
kr1: "kr1-api-image-infrastructure.nhncloudservice.com",
|
|
481
|
+
kr2: "kr2-api-image-infrastructure.nhncloudservice.com",
|
|
482
|
+
kr3: "kr3-api-image-infrastructure.nhncloudservice.com",
|
|
483
|
+
jp1: "jp1-api-image-infrastructure.nhncloudservice.com"
|
|
484
|
+
};
|
|
485
|
+
var NETWORK_HOST = {
|
|
486
|
+
kr1: "kr1-api-network-infrastructure.nhncloudservice.com",
|
|
487
|
+
kr2: "kr2-api-network-infrastructure.nhncloudservice.com",
|
|
488
|
+
kr3: "kr3-api-network-infrastructure.nhncloudservice.com",
|
|
489
|
+
jp1: "jp1-api-network-infrastructure.nhncloudservice.com"
|
|
490
|
+
};
|
|
491
|
+
var BLOCKSTORAGE_HOST = {
|
|
492
|
+
kr1: "kr1-api-block-storage-infrastructure.nhncloudservice.com",
|
|
493
|
+
kr2: "kr2-api-block-storage-infrastructure.nhncloudservice.com",
|
|
494
|
+
kr3: "kr3-api-block-storage-infrastructure.nhncloudservice.com",
|
|
495
|
+
jp1: "jp1-api-block-storage-infrastructure.nhncloudservice.com"
|
|
496
|
+
};
|
|
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
|
+
}
|
|
472
513
|
function instanceHost(region) {
|
|
473
514
|
const host = INSTANCE_HOST[region];
|
|
474
515
|
if (!host) {
|
|
475
516
|
throw new NhnCloudCliError(
|
|
476
|
-
`\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 region \uC785\uB2C8\uB2E4: "${region}". \uC0AC\uC6A9 \uAC00\uB2A5\uD55C region: ${
|
|
517
|
+
`\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 region \uC785\uB2C8\uB2E4: "${region}". \uC0AC\uC6A9 \uAC00\uB2A5\uD55C region: ${IAAS_REGIONS}`,
|
|
518
|
+
EXIT_PARAM_ERROR
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
return host;
|
|
522
|
+
}
|
|
523
|
+
function imageHost(region) {
|
|
524
|
+
const host = IMAGE_HOST[region];
|
|
525
|
+
if (!host) {
|
|
526
|
+
throw new NhnCloudCliError(
|
|
527
|
+
`\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 region \uC785\uB2C8\uB2E4: "${region}". \uC0AC\uC6A9 \uAC00\uB2A5\uD55C region: ${IAAS_REGIONS}`,
|
|
528
|
+
EXIT_PARAM_ERROR
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
return host;
|
|
532
|
+
}
|
|
533
|
+
function networkHost(region) {
|
|
534
|
+
const host = NETWORK_HOST[region];
|
|
535
|
+
if (!host) {
|
|
536
|
+
throw new NhnCloudCliError(
|
|
537
|
+
`\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 region \uC785\uB2C8\uB2E4: "${region}". \uC0AC\uC6A9 \uAC00\uB2A5\uD55C region: ${IAAS_REGIONS}`,
|
|
538
|
+
EXIT_PARAM_ERROR
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
return host;
|
|
542
|
+
}
|
|
543
|
+
function blockStorageHost(region) {
|
|
544
|
+
const host = BLOCKSTORAGE_HOST[region];
|
|
545
|
+
if (!host) {
|
|
546
|
+
throw new NhnCloudCliError(
|
|
547
|
+
`\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 region \uC785\uB2C8\uB2E4: "${region}". \uC0AC\uC6A9 \uAC00\uB2A5\uD55C region: ${IAAS_REGIONS}`,
|
|
477
548
|
EXIT_PARAM_ERROR
|
|
478
549
|
);
|
|
479
550
|
}
|
|
@@ -494,11 +565,20 @@ async function getIaasToken(profile, iaas, forceRefresh = false) {
|
|
|
494
565
|
if (!forceRefresh) {
|
|
495
566
|
const cached = await readIaasToken(profile, iaas.region);
|
|
496
567
|
if (cached !== null) {
|
|
497
|
-
return {
|
|
568
|
+
return {
|
|
569
|
+
tokenId: cached.tokenId,
|
|
570
|
+
computeEndpoint: cached.computeEndpoint,
|
|
571
|
+
imageEndpoint: cached.imageEndpoint,
|
|
572
|
+
networkEndpoint: cached.networkEndpoint,
|
|
573
|
+
blockStorageEndpoint: cached.blockStorageEndpoint
|
|
574
|
+
};
|
|
498
575
|
}
|
|
499
576
|
}
|
|
500
577
|
const host = instanceHost(iaas.region);
|
|
501
578
|
const computeEndpoint = `https://${host}/v2/${encodeURIComponent(iaas.tenantId)}`;
|
|
579
|
+
const imageEndpoint = `https://${imageHost(iaas.region)}/v2`;
|
|
580
|
+
const networkEndpoint = `https://${networkHost(iaas.region)}/v2.0`;
|
|
581
|
+
const blockStorageEndpoint = `https://${blockStorageHost(iaas.region)}/v2/${encodeURIComponent(iaas.tenantId)}`;
|
|
502
582
|
let raw;
|
|
503
583
|
try {
|
|
504
584
|
raw = await import_ky3.default.post(keystoneIdentityUrl(), {
|
|
@@ -525,22 +605,25 @@ async function getIaasToken(profile, iaas, forceRefresh = false) {
|
|
|
525
605
|
const tokenId = raw.access.token.id;
|
|
526
606
|
const expiresAt = raw.access.token.expires;
|
|
527
607
|
if (!forceRefresh) {
|
|
528
|
-
await writeIaasToken(profile, iaas.region, { tokenId, expiresAt, computeEndpoint });
|
|
608
|
+
await writeIaasToken(profile, iaas.region, { tokenId, expiresAt, computeEndpoint, imageEndpoint, networkEndpoint, blockStorageEndpoint });
|
|
529
609
|
}
|
|
530
|
-
return { tokenId, computeEndpoint };
|
|
610
|
+
return { tokenId, computeEndpoint, imageEndpoint, networkEndpoint, blockStorageEndpoint };
|
|
531
611
|
}
|
|
532
612
|
|
|
533
613
|
// src/services/logncrash/client.ts
|
|
534
614
|
var import_ky4 = __toESM(require("ky"));
|
|
535
615
|
|
|
536
616
|
// src/api/envelope.ts
|
|
537
|
-
function
|
|
617
|
+
function unwrapHeader(res) {
|
|
538
618
|
if (!res.header.isSuccessful) {
|
|
539
619
|
throw new NhnCloudCliError(
|
|
540
620
|
`API \uC624\uB958: ${res.header.resultMessage}`,
|
|
541
621
|
EXIT_API_ERROR
|
|
542
622
|
);
|
|
543
623
|
}
|
|
624
|
+
}
|
|
625
|
+
function unwrap(res) {
|
|
626
|
+
unwrapHeader(res);
|
|
544
627
|
if (res.body === void 0) {
|
|
545
628
|
throw new NhnCloudCliError("API \uC751\uB2F5\uC5D0 body \uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.", EXIT_API_ERROR);
|
|
546
629
|
}
|
|
@@ -550,12 +633,19 @@ function unwrap(res) {
|
|
|
550
633
|
// src/services/logncrash/client.ts
|
|
551
634
|
var LogncrashClient = class {
|
|
552
635
|
appkey;
|
|
636
|
+
/** 검색(X-LNCS-SECRET)에만 필요. collector send 는 secret 을 쓰지 않으므로 옵셔널 (ADR-014). */
|
|
553
637
|
secret;
|
|
554
638
|
constructor(appkey, secret) {
|
|
555
639
|
this.appkey = appkey;
|
|
556
640
|
this.secret = secret;
|
|
557
641
|
}
|
|
558
642
|
async search(params) {
|
|
643
|
+
if (!this.secret) {
|
|
644
|
+
throw new NhnCloudCliError(
|
|
645
|
+
"logncrash search \uC5D0\uB294 secret \uC774 \uD544\uC694\uD569\uB2C8\uB2E4. configure \uB85C logncrash secret \uC744 \uC124\uC815\uD558\uC138\uC694.",
|
|
646
|
+
EXIT_CONFIG_ERROR
|
|
647
|
+
);
|
|
648
|
+
}
|
|
559
649
|
const endpoint = endpointFor("logncrash");
|
|
560
650
|
const url = `${endpoint}/api/v2/search/${encodeURIComponent(this.appkey)}`;
|
|
561
651
|
try {
|
|
@@ -577,6 +667,182 @@ var LogncrashClient = class {
|
|
|
577
667
|
throw toNhnCloudCliError(err);
|
|
578
668
|
}
|
|
579
669
|
}
|
|
670
|
+
/**
|
|
671
|
+
* scroll 검색을 시작한다. POST /api/v2/search/scroll/{appkey}.
|
|
672
|
+
* body 는 search 와 동일(query/from/to/pageSize). 응답 scrollKey 로 scrollNext 를 이어 호출한다.
|
|
673
|
+
*/
|
|
674
|
+
async scrollStart(params) {
|
|
675
|
+
if (!this.secret) {
|
|
676
|
+
throw new NhnCloudCliError(
|
|
677
|
+
"logncrash scroll \uC5D0\uB294 secret \uC774 \uD544\uC694\uD569\uB2C8\uB2E4. configure \uB85C logncrash secret \uC744 \uC124\uC815\uD558\uC138\uC694.",
|
|
678
|
+
EXIT_CONFIG_ERROR
|
|
679
|
+
);
|
|
680
|
+
}
|
|
681
|
+
const endpoint = endpointFor("logncrash");
|
|
682
|
+
const url = `${endpoint}/api/v2/search/scroll/${encodeURIComponent(this.appkey)}`;
|
|
683
|
+
try {
|
|
684
|
+
const res = await import_ky4.default.post(url, {
|
|
685
|
+
headers: {
|
|
686
|
+
"X-LNCS-SECRET": this.secret,
|
|
687
|
+
"Content-Type": "application/json"
|
|
688
|
+
},
|
|
689
|
+
json: {
|
|
690
|
+
query: params.query,
|
|
691
|
+
from: params.from,
|
|
692
|
+
to: params.to,
|
|
693
|
+
pageSize: params.pageSize ?? 100
|
|
694
|
+
}
|
|
695
|
+
}).json();
|
|
696
|
+
return unwrap(res);
|
|
697
|
+
} catch (err) {
|
|
698
|
+
throw toNhnCloudCliError(err);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
/**
|
|
702
|
+
* scroll 다음 페이지를 가져온다. POST /api/v2/search/scroll/{appkey}/{scrollKey}.
|
|
703
|
+
* body 는 보내지 않는다(scrollKey 가 좌표). scrollKey 만료 시 API 가 실패 봉투를 주며,
|
|
704
|
+
* unwrap 이 EXIT_API_ERROR 로 변환한다 — 호출부에서 만료 안내 메시지로 감싼다.
|
|
705
|
+
*/
|
|
706
|
+
async scrollNext(scrollKey) {
|
|
707
|
+
if (!this.secret) {
|
|
708
|
+
throw new NhnCloudCliError(
|
|
709
|
+
"logncrash scroll \uC5D0\uB294 secret \uC774 \uD544\uC694\uD569\uB2C8\uB2E4. configure \uB85C logncrash secret \uC744 \uC124\uC815\uD558\uC138\uC694.",
|
|
710
|
+
EXIT_CONFIG_ERROR
|
|
711
|
+
);
|
|
712
|
+
}
|
|
713
|
+
const endpoint = endpointFor("logncrash");
|
|
714
|
+
const url = `${endpoint}/api/v2/search/scroll/${encodeURIComponent(this.appkey)}/${encodeURIComponent(scrollKey)}`;
|
|
715
|
+
try {
|
|
716
|
+
const res = await import_ky4.default.post(url, {
|
|
717
|
+
headers: {
|
|
718
|
+
"X-LNCS-SECRET": this.secret,
|
|
719
|
+
"Content-Type": "application/json"
|
|
720
|
+
}
|
|
721
|
+
}).json();
|
|
722
|
+
return unwrap(res);
|
|
723
|
+
} catch (err) {
|
|
724
|
+
throw toNhnCloudCliError(err);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
/**
|
|
728
|
+
* 로그 한 건을 Log & Crash collector 로 전송한다 (ADR-014).
|
|
729
|
+
* - host: api-logncrash (검색의 api-lncs-search 와 별도)
|
|
730
|
+
* - 인증: 헤더 없음 — body 의 projectName=appkey 로 식별 (secret 불요)
|
|
731
|
+
* - logVersion 은 "v2" 고정. logSource/logType 미지정 시 collector 기본값("http"/"log") 적용.
|
|
732
|
+
*/
|
|
733
|
+
async send(params) {
|
|
734
|
+
const endpoint = endpointFor("logncrash-collector");
|
|
735
|
+
const url = `${endpoint}/v2/log`;
|
|
736
|
+
const payload = {
|
|
737
|
+
projectName: this.appkey,
|
|
738
|
+
projectVersion: params.projectVersion,
|
|
739
|
+
logVersion: "v2",
|
|
740
|
+
body: params.body
|
|
741
|
+
};
|
|
742
|
+
if (params.logLevel !== void 0) payload["logLevel"] = params.logLevel;
|
|
743
|
+
if (params.logSource !== void 0) payload["logSource"] = params.logSource;
|
|
744
|
+
if (params.logType !== void 0) payload["logType"] = params.logType;
|
|
745
|
+
if (params.host !== void 0) payload["host"] = params.host;
|
|
746
|
+
try {
|
|
747
|
+
const res = await import_ky4.default.post(url, {
|
|
748
|
+
headers: { "Content-Type": "application/json" },
|
|
749
|
+
json: payload
|
|
750
|
+
}).json();
|
|
751
|
+
unwrapHeader(res);
|
|
752
|
+
} catch (err) {
|
|
753
|
+
throw toNhnCloudCliError(err);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
};
|
|
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
|
+
}
|
|
580
846
|
};
|
|
581
847
|
|
|
582
848
|
// src/commands/configure-verify.ts
|
|
@@ -602,6 +868,19 @@ async function verifyIaas(iaas) {
|
|
|
602
868
|
throw err;
|
|
603
869
|
}
|
|
604
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
|
+
}
|
|
605
884
|
async function verifyLogncrash(cred) {
|
|
606
885
|
if (!cred.appkey || !cred.secret) return false;
|
|
607
886
|
const client = new LogncrashClient(cred.appkey, cred.secret);
|
|
@@ -625,7 +904,7 @@ async function verifyLogncrash(cred) {
|
|
|
625
904
|
}
|
|
626
905
|
|
|
627
906
|
// src/commands/configure.ts
|
|
628
|
-
async function saveAndVerify(profileName, uak, logncrash, iaas, doVerify) {
|
|
907
|
+
async function saveAndVerify(profileName, uak, logncrash, iaas, ncr, doVerify) {
|
|
629
908
|
if (doVerify) {
|
|
630
909
|
if (uak) {
|
|
631
910
|
const ok = await verifyUserAccessKey(uak);
|
|
@@ -660,6 +939,26 @@ async function saveAndVerify(profileName, uak, logncrash, iaas, doVerify) {
|
|
|
660
939
|
);
|
|
661
940
|
}
|
|
662
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
|
+
}
|
|
663
962
|
}
|
|
664
963
|
if (uak) {
|
|
665
964
|
await setUserAccessKey(profileName, uak);
|
|
@@ -670,6 +969,9 @@ async function saveAndVerify(profileName, uak, logncrash, iaas, doVerify) {
|
|
|
670
969
|
if (iaas) {
|
|
671
970
|
await setIaasCredential(profileName, iaas);
|
|
672
971
|
}
|
|
972
|
+
if (ncr) {
|
|
973
|
+
await setServiceCredential(profileName, "ncr", ncr);
|
|
974
|
+
}
|
|
673
975
|
process.stderr.write(import_chalk.default.green(`
|
|
674
976
|
\u2713 profile "${profileName}" \uC124\uC815\uC774 \uC800\uC7A5\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
|
|
675
977
|
`));
|
|
@@ -729,12 +1031,25 @@ async function runInteractive(opts) {
|
|
|
729
1031
|
});
|
|
730
1032
|
iaas = { tenantId, username: iaasUsername, password: iaasPassword, region };
|
|
731
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
|
+
}
|
|
732
1047
|
if (opts.verify) {
|
|
733
1048
|
process.stderr.write(import_chalk.default.gray("\n\u2014 \uC5F0\uACB0 \uD14C\uC2A4\uD2B8 \uC911\u2026 \u2014\n"));
|
|
734
1049
|
}
|
|
735
1050
|
if (opts.verify) {
|
|
736
1051
|
try {
|
|
737
|
-
await saveAndVerify(profileName, uak, logncrash, iaas, true);
|
|
1052
|
+
await saveAndVerify(profileName, uak, logncrash, iaas, ncr, true);
|
|
738
1053
|
} catch (err) {
|
|
739
1054
|
if (err instanceof NhnCloudCliError && err.exitCode === EXIT_AUTH_ERROR) {
|
|
740
1055
|
process.stderr.write(import_chalk.default.red(` \u2717 ${err.message}
|
|
@@ -747,13 +1062,13 @@ async function runInteractive(opts) {
|
|
|
747
1062
|
process.stderr.write(import_chalk.default.yellow("\uC800\uC7A5\uC774 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4.\n"));
|
|
748
1063
|
return;
|
|
749
1064
|
}
|
|
750
|
-
await saveAndVerify(profileName, uak, logncrash, iaas, false);
|
|
1065
|
+
await saveAndVerify(profileName, uak, logncrash, iaas, ncr, false);
|
|
751
1066
|
} else {
|
|
752
1067
|
throw err;
|
|
753
1068
|
}
|
|
754
1069
|
}
|
|
755
1070
|
} else {
|
|
756
|
-
await saveAndVerify(profileName, uak, logncrash, iaas, false);
|
|
1071
|
+
await saveAndVerify(profileName, uak, logncrash, iaas, ncr, false);
|
|
757
1072
|
}
|
|
758
1073
|
}
|
|
759
1074
|
async function runNonInteractive(opts) {
|
|
@@ -769,22 +1084,23 @@ async function runNonInteractive(opts) {
|
|
|
769
1084
|
password: iaasPassword,
|
|
770
1085
|
region: opts.iaasRegion ?? "kr1"
|
|
771
1086
|
} : void 0;
|
|
772
|
-
|
|
1087
|
+
const ncr = opts.ncrAppkey?.trim() ? { appkey: opts.ncrAppkey.trim() } : void 0;
|
|
1088
|
+
if (!uak && !logncrash && !iaas && !ncr) {
|
|
773
1089
|
throw new NhnCloudCliError(
|
|
774
|
-
"\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.",
|
|
775
1091
|
EXIT_PARAM_ERROR
|
|
776
1092
|
);
|
|
777
1093
|
}
|
|
778
1094
|
if (opts.verify) {
|
|
779
1095
|
process.stderr.write(import_chalk.default.gray("\uC5F0\uACB0 \uD14C\uC2A4\uD2B8 \uC911\u2026\n"));
|
|
780
1096
|
}
|
|
781
|
-
await saveAndVerify(profileName, uak, logncrash, iaas, opts.verify);
|
|
1097
|
+
await saveAndVerify(profileName, uak, logncrash, iaas, ncr, opts.verify);
|
|
782
1098
|
}
|
|
783
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(
|
|
784
1100
|
"--iaas-password <pass>",
|
|
785
1101
|
"iaas API \uBE44\uBC00\uBC88\uD638 (\uBE44\uB300\uD654\uD615, \uB178\uCD9C \uBC29\uC9C0\uB85C env NHNCLOUD_IAAS_PASSWORD \uAD8C\uC7A5)"
|
|
786
|
-
).option("--iaas-region <region>", "iaas region (\uAE30\uBCF8: kr1)", "kr1").option("--no-verify", "\uC5F0\uACB0 \uD14C\uC2A4\uD2B8 \uC0DD\uB7B5").action(async (opts) => {
|
|
787
|
-
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;
|
|
788
1104
|
try {
|
|
789
1105
|
if (hasFlag) {
|
|
790
1106
|
await runNonInteractive(opts);
|
|
@@ -985,106 +1301,469 @@ credentials.json \uC5D0 "secret": "<secretkey>" \uB97C \uCD94\uAC00\uD558\uC138\
|
|
|
985
1301
|
});
|
|
986
1302
|
});
|
|
987
1303
|
|
|
988
|
-
// src/commands/
|
|
1304
|
+
// src/commands/logncrash/send.ts
|
|
989
1305
|
var import_commander3 = require("commander");
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
var
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
baseUrl;
|
|
998
|
-
constructor(accessToken) {
|
|
999
|
-
this.accessToken = accessToken;
|
|
1000
|
-
this.baseUrl = endpointFor("deploy");
|
|
1001
|
-
}
|
|
1002
|
-
authHeaders() {
|
|
1003
|
-
return {
|
|
1004
|
-
"X-NHN-AUTHORIZATION": `Bearer ${this.accessToken}`
|
|
1005
|
-
};
|
|
1006
|
-
}
|
|
1007
|
-
/**
|
|
1008
|
-
* 배포를 실행한다.
|
|
1009
|
-
* - targetHosts 가 비어있으면 payload 에서 targetServerHostnames 를 제외한다 (서버그룹 전체 배포).
|
|
1010
|
-
* - async=false(기본) 일 때 서버가 완료까지 응답을 보류하므로 ky timeout 을 600s 로 설정한다.
|
|
1011
|
-
*/
|
|
1012
|
-
async run(params) {
|
|
1013
|
-
const url = `${this.baseUrl}/api/v2.1/projects/${params.appKey}/artifacts/${params.artifactId}/server-group/${params.serverGroupId}/deploy`;
|
|
1014
|
-
const isAsync = params.async ?? false;
|
|
1015
|
-
const payload = {
|
|
1016
|
-
concurrentNum: params.concurrentNum ?? 1,
|
|
1017
|
-
nextWhenFail: params.nextWhenFail ?? false,
|
|
1018
|
-
scenarioIds: params.scenarioIds,
|
|
1019
|
-
deployNote: params.deployNote ?? `CLI deploy ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
1020
|
-
async: isAsync
|
|
1021
|
-
};
|
|
1022
|
-
if (params.targetHosts) {
|
|
1023
|
-
payload["targetServerHostnames"] = params.targetHosts;
|
|
1024
|
-
}
|
|
1306
|
+
var import_node_fs = require("fs");
|
|
1307
|
+
var MAX_LOG_BYTES = 8 * 1024 * 1024;
|
|
1308
|
+
var VALID_LEVELS = ["DEBUG", "INFO", "WARN", "ERROR", "FATAL"];
|
|
1309
|
+
function resolveBody(opts) {
|
|
1310
|
+
if (opts.body !== void 0) return opts.body;
|
|
1311
|
+
if (opts.file !== void 0) {
|
|
1312
|
+
let stat;
|
|
1025
1313
|
try {
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
},
|
|
1031
|
-
json: payload,
|
|
1032
|
-
retry: 0,
|
|
1033
|
-
timeout: isAsync ? DEFAULT_TIMEOUT_MS : SYNC_TIMEOUT_MS
|
|
1034
|
-
}).json();
|
|
1035
|
-
return unwrap(res);
|
|
1036
|
-
} catch (err) {
|
|
1037
|
-
throw toNhnCloudCliError(err);
|
|
1314
|
+
stat = (0, import_node_fs.statSync)(opts.file);
|
|
1315
|
+
} catch (e) {
|
|
1316
|
+
const reason = e.code ?? (e instanceof Error ? e.message : String(e));
|
|
1317
|
+
throw new NhnCloudCliError(`\uB85C\uADF8 \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${opts.file} (${reason})`, EXIT_PARAM_ERROR);
|
|
1038
1318
|
}
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
* 아티팩트 목록을 조회한다.
|
|
1042
|
-
*/
|
|
1043
|
-
async artifacts(appKey) {
|
|
1044
|
-
const url = `${this.baseUrl}/api/v2.1/projects/${appKey}/artifacts`;
|
|
1045
|
-
try {
|
|
1046
|
-
const res = await import_ky5.default.get(url, {
|
|
1047
|
-
headers: this.authHeaders(),
|
|
1048
|
-
retry: 0,
|
|
1049
|
-
timeout: DEFAULT_TIMEOUT_MS
|
|
1050
|
-
}).json();
|
|
1051
|
-
return unwrap(res);
|
|
1052
|
-
} catch (err) {
|
|
1053
|
-
throw toNhnCloudCliError(err);
|
|
1319
|
+
if (!stat.isFile()) {
|
|
1320
|
+
throw new NhnCloudCliError(`\uB85C\uADF8 \uD30C\uC77C\uC774 \uC77C\uBC18 \uD30C\uC77C\uC774 \uC544\uB2D9\uB2C8\uB2E4: ${opts.file}`, EXIT_PARAM_ERROR);
|
|
1054
1321
|
}
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
const url = `${this.baseUrl}/api/v2.1/projects/${appKey}/artifacts/${artifactId}/server-groups`;
|
|
1061
|
-
try {
|
|
1062
|
-
const res = await import_ky5.default.get(url, {
|
|
1063
|
-
headers: this.authHeaders(),
|
|
1064
|
-
retry: 0,
|
|
1065
|
-
timeout: DEFAULT_TIMEOUT_MS
|
|
1066
|
-
}).json();
|
|
1067
|
-
return unwrap(res);
|
|
1068
|
-
} catch (err) {
|
|
1069
|
-
throw toNhnCloudCliError(err);
|
|
1322
|
+
if (stat.size > MAX_LOG_BYTES) {
|
|
1323
|
+
throw new NhnCloudCliError(
|
|
1324
|
+
`\uB85C\uADF8 \uD30C\uC77C\uC774 \uB108\uBB34 \uD07D\uB2C8\uB2E4: ${stat.size} \uBC14\uC774\uD2B8 (\uD55C\uB3C4 ${MAX_LOG_BYTES} \uBC14\uC774\uD2B8).`,
|
|
1325
|
+
EXIT_PARAM_ERROR
|
|
1326
|
+
);
|
|
1070
1327
|
}
|
|
1328
|
+
return (0, import_node_fs.readFileSync)(opts.file, "utf-8");
|
|
1071
1329
|
}
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1330
|
+
if (!process.stdin.isTTY) {
|
|
1331
|
+
return (0, import_node_fs.readFileSync)(0, "utf-8");
|
|
1332
|
+
}
|
|
1333
|
+
throw new NhnCloudCliError(
|
|
1334
|
+
"\uB85C\uADF8 \uBCF8\uBB38\uC774 \uD544\uC694\uD569\uB2C8\uB2E4. --body <text> \uB610\uB294 --file <path> \uB610\uB294 \uD45C\uC900\uC785\uB825(\uD30C\uC774\uD504)\uC73C\uB85C \uC804\uB2EC\uD558\uC138\uC694.",
|
|
1335
|
+
EXIT_PARAM_ERROR
|
|
1336
|
+
);
|
|
1337
|
+
}
|
|
1338
|
+
var sendCommand = new import_commander3.Command("send").description("\uB85C\uADF8 \uD55C \uAC74\uC744 Log & Crash \uB85C \uC804\uC1A1\uD55C\uB2E4 (--body / --file / stdin)").option("--body <text>", "\uB85C\uADF8 \uBA54\uC2DC\uC9C0 \uBCF8\uBB38 (\uBBF8\uC9C0\uC815 \uC2DC --file \uB610\uB294 stdin)").option("--file <path>", "\uB85C\uADF8 \uBCF8\uBB38\uC744 \uC77D\uC744 \uD30C\uC77C \uACBD\uB85C").option("--level <level>", "\uB85C\uADF8 \uB808\uBCA8 (DEBUG/INFO/WARN/ERROR/FATAL)").option("--app-version <ver>", "projectVersion (\uBBF8\uC9C0\uC815 \uC2DC \uAE30\uBCF8 '1.0.0')").option("--source <source>", "logSource (collector \uAE30\uBCF8 'http')").option("--type <type>", "logType (collector \uAE30\uBCF8 'log')").option("--host <host>", "\uB85C\uADF8\uB97C \uBCF4\uB0B8 host \uC2DD\uBCC4").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (_opts, cmd) => {
|
|
1339
|
+
const opts = cmd.optsWithGlobals();
|
|
1340
|
+
const body = resolveBody(opts);
|
|
1341
|
+
const bytes = Buffer.byteLength(body, "utf-8");
|
|
1342
|
+
if (bytes === 0) {
|
|
1343
|
+
throw new NhnCloudCliError("\uB85C\uADF8 \uBCF8\uBB38\uC774 \uBE44\uC5B4 \uC788\uC2B5\uB2C8\uB2E4.", EXIT_PARAM_ERROR);
|
|
1344
|
+
}
|
|
1345
|
+
if (bytes > MAX_LOG_BYTES) {
|
|
1346
|
+
throw new NhnCloudCliError(
|
|
1347
|
+
`\uB85C\uADF8 \uBCF8\uBB38\uC774 \uB108\uBB34 \uD07D\uB2C8\uB2E4: ${bytes} \uBC14\uC774\uD2B8 (\uD55C\uB3C4 ${MAX_LOG_BYTES} \uBC14\uC774\uD2B8).`,
|
|
1348
|
+
EXIT_PARAM_ERROR
|
|
1349
|
+
);
|
|
1350
|
+
}
|
|
1351
|
+
let logLevel;
|
|
1352
|
+
if (opts.level !== void 0) {
|
|
1353
|
+
const upper = opts.level.toUpperCase();
|
|
1354
|
+
if (!VALID_LEVELS.includes(upper)) {
|
|
1355
|
+
throw new NhnCloudCliError(
|
|
1356
|
+
`--level \uC740 ${VALID_LEVELS.join("/")} \uC911 \uD558\uB098\uC5EC\uC57C \uD569\uB2C8\uB2E4 (\uC785\uB825: ${opts.level}).`,
|
|
1357
|
+
EXIT_PARAM_ERROR
|
|
1358
|
+
);
|
|
1359
|
+
}
|
|
1360
|
+
logLevel = upper;
|
|
1361
|
+
}
|
|
1362
|
+
const profileName = await resolveProfileName(opts.profile);
|
|
1363
|
+
const cred = await getServiceCredential("logncrash", profileName);
|
|
1364
|
+
if (!cred.appkey) {
|
|
1365
|
+
throw new NhnCloudCliError(
|
|
1366
|
+
`profile "${profileName}" \uC758 logncrash \uC790\uACA9\uC99D\uBA85\uC5D0 appkey \uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.
|
|
1367
|
+
credentials.json \uC5D0 "appkey": "<appkey>" \uB97C \uCD94\uAC00\uD558\uC138\uC694.`,
|
|
1368
|
+
EXIT_CONFIG_ERROR
|
|
1369
|
+
);
|
|
1370
|
+
}
|
|
1371
|
+
const client = new LogncrashClient(cred.appkey);
|
|
1372
|
+
startSpinner("\uB85C\uADF8 \uC804\uC1A1 \uC911...");
|
|
1373
|
+
try {
|
|
1374
|
+
await client.send({
|
|
1375
|
+
body,
|
|
1376
|
+
projectVersion: opts.appVersion ?? "1.0.0",
|
|
1377
|
+
logLevel,
|
|
1378
|
+
logSource: opts.source,
|
|
1379
|
+
logType: opts.type,
|
|
1380
|
+
host: opts.host
|
|
1381
|
+
});
|
|
1382
|
+
} catch (err) {
|
|
1383
|
+
stopSpinner(false);
|
|
1384
|
+
throw err;
|
|
1385
|
+
}
|
|
1386
|
+
stopSpinner(true, "\uB85C\uADF8\uB97C \uC804\uC1A1\uD588\uC2B5\uB2C8\uB2E4.");
|
|
1387
|
+
if (opts.json) {
|
|
1388
|
+
process.stdout.write(JSON.stringify({ ok: true, bytes }) + "\n");
|
|
1389
|
+
}
|
|
1390
|
+
});
|
|
1391
|
+
|
|
1392
|
+
// src/commands/logncrash/export.ts
|
|
1393
|
+
var import_commander4 = require("commander");
|
|
1394
|
+
var import_promises3 = require("fs/promises");
|
|
1395
|
+
var import_node_fs2 = require("fs");
|
|
1396
|
+
var import_node_crypto2 = require("crypto");
|
|
1397
|
+
var MAX_TOTAL = 1e5;
|
|
1398
|
+
function assertWritable(path, force) {
|
|
1399
|
+
if (force) return;
|
|
1400
|
+
try {
|
|
1401
|
+
(0, import_node_fs2.statSync)(path);
|
|
1402
|
+
} catch (e) {
|
|
1403
|
+
if (e.code === "ENOENT") return;
|
|
1404
|
+
const reason = e.code ?? (e instanceof Error ? e.message : String(e));
|
|
1405
|
+
throw new NhnCloudCliError(`--output \uACBD\uB85C\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${path} (${reason})`, EXIT_PARAM_ERROR);
|
|
1406
|
+
}
|
|
1407
|
+
throw new NhnCloudCliError(
|
|
1408
|
+
`--output \uB300\uC0C1\uC774 \uC774\uBBF8 \uC874\uC7AC\uD569\uB2C8\uB2E4: ${path}. \uB36E\uC5B4\uC4F0\uB824\uBA74 --force \uB97C \uC4F0\uC138\uC694.`,
|
|
1409
|
+
EXIT_PARAM_ERROR
|
|
1410
|
+
);
|
|
1411
|
+
}
|
|
1412
|
+
var exportCommand = new import_commander4.Command("export").description("Log & Crash \uB85C\uADF8\uB97C scroll \uB85C \uC804\uCCB4 \uCD94\uCD9C\uD574 \uD30C\uC77C\uB85C \uC800\uC7A5 (\uB300\uB7C9 \uCD94\uCD9C)").option("--query <lucene>", "Lucene \uC9C8\uC758 \uBB38\uC790\uC5F4 (\uD544\uC218)").option("--from <time>", "\uAC80\uC0C9 \uC2DC\uC791: ISO8601 \uB610\uB294 \uC0C1\uB300\uC2DC\uAC04 (1h/30m/2d/now) (\uD544\uC218)").option("--to <time>", "\uAC80\uC0C9 \uB05D: ISO8601 \uB610\uB294 \uC0C1\uB300\uC2DC\uAC04 (\uD544\uC218)").option("--output <file>", "\uCD9C\uB825 \uD30C\uC77C \uACBD\uB85C (\uD544\uC218)").option("--format <fmt>", "\uCD9C\uB825 \uD615\uC2DD: jsonl(\uAE30\uBCF8, \uD55C \uC904\uB2F9 \uD55C \uB85C\uADF8) \uB610\uB294 json(\uBC30\uC5F4)", "jsonl").option("--size <n>", "scroll \uD398\uC774\uC9C0 \uD06C\uAE30 (docs \uBC94\uC704 10~100, \uAE30\uBCF8 100)", "100").option("--force", "\uCD9C\uB825 \uD30C\uC77C\uC774 \uC788\uC73C\uBA74 \uB36E\uC5B4\uC4F4\uB2E4").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (_opts, cmd) => {
|
|
1413
|
+
const opts = cmd.optsWithGlobals();
|
|
1414
|
+
if (!opts.query) {
|
|
1415
|
+
throw new NhnCloudCliError(`--query \uC635\uC158\uC740 \uD544\uC218\uC785\uB2C8\uB2E4. \uC608: --query 'logType:"NORMAL"'`, EXIT_PARAM_ERROR);
|
|
1416
|
+
}
|
|
1417
|
+
if (!opts.from) {
|
|
1418
|
+
throw new NhnCloudCliError("--from \uC635\uC158\uC740 \uD544\uC218\uC785\uB2C8\uB2E4. \uC608: --from 1h", EXIT_PARAM_ERROR);
|
|
1419
|
+
}
|
|
1420
|
+
if (!opts.to) {
|
|
1421
|
+
throw new NhnCloudCliError("--to \uC635\uC158\uC740 \uD544\uC218\uC785\uB2C8\uB2E4. \uC608: --to now", EXIT_PARAM_ERROR);
|
|
1422
|
+
}
|
|
1423
|
+
if (!opts.output) {
|
|
1424
|
+
throw new NhnCloudCliError("--output \uC635\uC158\uC740 \uD544\uC218\uC785\uB2C8\uB2E4. \uC608: --output logs.jsonl", EXIT_PARAM_ERROR);
|
|
1425
|
+
}
|
|
1426
|
+
const format = opts.format ?? "jsonl";
|
|
1427
|
+
if (format !== "jsonl" && format !== "json") {
|
|
1428
|
+
throw new NhnCloudCliError("--format \uC740 jsonl \uB610\uB294 json \uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4.", EXIT_PARAM_ERROR);
|
|
1429
|
+
}
|
|
1430
|
+
const sizeRaw = opts.size ?? "100";
|
|
1431
|
+
if (!/^[1-9]\d*$/.test(sizeRaw)) {
|
|
1432
|
+
throw new NhnCloudCliError("--size \uB294 \uC591\uC758 \uC815\uC218\uC5EC\uC57C \uD569\uB2C8\uB2E4 (docs \uBC94\uC704 10~100).", EXIT_PARAM_ERROR);
|
|
1433
|
+
}
|
|
1434
|
+
const size = parseInt(sizeRaw, 10);
|
|
1435
|
+
if (size < 10 || size > 100) {
|
|
1436
|
+
throw new NhnCloudCliError("--size \uB294 10~100 \uC0AC\uC774\uC5EC\uC57C \uD569\uB2C8\uB2E4 (Log & Crash scroll pageSize \uD55C\uB3C4).", EXIT_PARAM_ERROR);
|
|
1437
|
+
}
|
|
1438
|
+
const fromIso = resolveTime(opts.from);
|
|
1439
|
+
const toIso = resolveTime(opts.to);
|
|
1440
|
+
assertSearchRange(fromIso, toIso);
|
|
1441
|
+
assertWritable(opts.output, opts.force ?? false);
|
|
1442
|
+
const profileName = await resolveProfileName(opts.profile);
|
|
1443
|
+
const cred = await getServiceCredential("logncrash", profileName);
|
|
1444
|
+
if (!cred.appkey) {
|
|
1445
|
+
throw new NhnCloudCliError(
|
|
1446
|
+
`profile "${profileName}" \uC758 logncrash \uC790\uACA9\uC99D\uBA85\uC5D0 appkey \uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.
|
|
1447
|
+
credentials.json \uC5D0 "appkey": "<appkey>" \uB97C \uCD94\uAC00\uD558\uC138\uC694.`,
|
|
1448
|
+
EXIT_CONFIG_ERROR
|
|
1449
|
+
);
|
|
1450
|
+
}
|
|
1451
|
+
if (!cred.secret) {
|
|
1452
|
+
throw new NhnCloudCliError(
|
|
1453
|
+
`profile "${profileName}" \uC758 logncrash \uC790\uACA9\uC99D\uBA85\uC5D0 secret \uC774 \uC5C6\uC2B5\uB2C8\uB2E4.
|
|
1454
|
+
credentials.json \uC5D0 "secret": "<secret>" \uB97C \uCD94\uAC00\uD558\uC138\uC694.`,
|
|
1455
|
+
EXIT_CONFIG_ERROR
|
|
1456
|
+
);
|
|
1457
|
+
}
|
|
1458
|
+
const client = new LogncrashClient(cred.appkey, cred.secret);
|
|
1459
|
+
const tmp = opts.output + "." + (0, import_node_crypto2.randomBytes)(4).toString("hex") + ".tmp";
|
|
1460
|
+
const stream = (0, import_node_fs2.createWriteStream)(tmp, { encoding: "utf-8" });
|
|
1461
|
+
const spinner = startSpinner("\uB85C\uADF8 \uCD94\uCD9C \uC911...");
|
|
1462
|
+
let count = 0;
|
|
1463
|
+
let total = 0;
|
|
1464
|
+
let first = true;
|
|
1465
|
+
const writePage = (data) => {
|
|
1466
|
+
for (const log of data) {
|
|
1467
|
+
if (count >= MAX_TOTAL) break;
|
|
1468
|
+
const json = JSON.stringify(log);
|
|
1469
|
+
stream.write(format === "json" ? first ? json : "," + json : json + "\n");
|
|
1470
|
+
first = false;
|
|
1471
|
+
count++;
|
|
1472
|
+
}
|
|
1473
|
+
};
|
|
1474
|
+
try {
|
|
1475
|
+
if (format === "json") stream.write("[");
|
|
1476
|
+
let res = await client.scrollStart({ query: opts.query, from: fromIso, to: toIso, pageSize: size });
|
|
1477
|
+
total = res.totalItems;
|
|
1478
|
+
writePage(res.data);
|
|
1479
|
+
spinner.text = `\uB85C\uADF8 \uCD94\uCD9C \uC911... ${count}/${total}`;
|
|
1480
|
+
while (res.data.length > 0 && res.scrollKey && count < Math.min(total, MAX_TOTAL)) {
|
|
1481
|
+
res = await scrollNextOrExpire(client, res.scrollKey);
|
|
1482
|
+
writePage(res.data);
|
|
1483
|
+
spinner.text = `\uB85C\uADF8 \uCD94\uCD9C \uC911... ${count}/${total}`;
|
|
1484
|
+
}
|
|
1485
|
+
if (format === "json") stream.write("]\n");
|
|
1486
|
+
await new Promise((resolve, reject) => {
|
|
1487
|
+
stream.once("error", reject);
|
|
1488
|
+
stream.end(resolve);
|
|
1489
|
+
});
|
|
1490
|
+
} catch (err) {
|
|
1491
|
+
stopSpinner(false);
|
|
1492
|
+
stream.destroy();
|
|
1493
|
+
await (0, import_promises3.rm)(tmp, { force: true }).catch(() => {
|
|
1494
|
+
});
|
|
1495
|
+
throw err;
|
|
1496
|
+
}
|
|
1497
|
+
stopSpinner(true, `${count}\uAC74 \uCD94\uCD9C \uC644\uB8CC`);
|
|
1498
|
+
try {
|
|
1499
|
+
await (0, import_promises3.rename)(tmp, opts.output);
|
|
1500
|
+
} catch (err) {
|
|
1501
|
+
await (0, import_promises3.rm)(tmp, { force: true }).catch(() => {
|
|
1502
|
+
});
|
|
1503
|
+
const reason = err.code ?? (err instanceof Error ? err.message : String(err));
|
|
1504
|
+
throw new NhnCloudCliError(`\uCD9C\uB825 \uD30C\uC77C\uC744 \uC4F8 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${opts.output} (${reason})`, EXIT_PARAM_ERROR);
|
|
1505
|
+
}
|
|
1506
|
+
if (count >= MAX_TOTAL && total > MAX_TOTAL) {
|
|
1507
|
+
process.stderr.write(
|
|
1508
|
+
`\uACBD\uACE0: \uC804\uCCB4 ${total}\uAC74 \uC911 \uC0C1\uD55C ${MAX_TOTAL}\uAC74\uAE4C\uC9C0\uB9CC \uCD94\uCD9C\uD588\uC2B5\uB2C8\uB2E4. \uAC80\uC0C9 \uBC94\uC704\uB97C \uC881\uD600 \uB098\uB220 \uCD94\uCD9C\uD558\uC138\uC694.
|
|
1509
|
+
`
|
|
1510
|
+
);
|
|
1511
|
+
}
|
|
1512
|
+
process.stderr.write(`${opts.output} \uC5D0 ${count}\uAC74 \uC800\uC7A5
|
|
1513
|
+
`);
|
|
1514
|
+
});
|
|
1515
|
+
async function scrollNextOrExpire(client, scrollKey) {
|
|
1516
|
+
try {
|
|
1517
|
+
return await client.scrollNext(scrollKey);
|
|
1518
|
+
} catch (err) {
|
|
1519
|
+
if (err instanceof NhnCloudCliError && err.exitCode === EXIT_API_ERROR) {
|
|
1520
|
+
throw new NhnCloudCliError(
|
|
1521
|
+
`scroll \uB2E4\uC74C \uD398\uC774\uC9C0 \uC694\uCCAD\uC774 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4 (\uC6D0\uC778: ${err.message}). scrollKey \uB9CC\uB8CC(\uC720\uD6A8 1\uBD84)\uC77C \uC218 \uC788\uC73C\uB2C8, \uB9CC\uB8CC\uB77C\uBA74 \uAC80\uC0C9 \uBC94\uC704\uB97C \uC881\uD788\uAC70\uB098 --size \uB97C \uD0A4\uC6CC \uD398\uC774\uC9C0 \uC218\uB97C \uC904\uC778 \uB4A4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694.`,
|
|
1522
|
+
EXIT_API_ERROR
|
|
1523
|
+
);
|
|
1524
|
+
}
|
|
1525
|
+
throw err;
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
// src/commands/deploy/run.ts
|
|
1530
|
+
var import_commander5 = require("commander");
|
|
1531
|
+
|
|
1532
|
+
// src/services/deploy/client.ts
|
|
1533
|
+
var import_ky6 = __toESM(require("ky"));
|
|
1534
|
+
function isBinaryGroup(val) {
|
|
1535
|
+
if (typeof val !== "object" || val === null) return false;
|
|
1536
|
+
const obj = val;
|
|
1537
|
+
const keyType = typeof obj["key"];
|
|
1538
|
+
return (keyType === "number" || keyType === "string") && typeof obj["name"] === "string";
|
|
1539
|
+
}
|
|
1540
|
+
function isBinary(val) {
|
|
1541
|
+
if (typeof val !== "object" || val === null) return false;
|
|
1542
|
+
const obj = val;
|
|
1543
|
+
const binaryKeyType = typeof obj["binaryKey"];
|
|
1544
|
+
const binarySizeType = typeof obj["binarySize"];
|
|
1545
|
+
return (binaryKeyType === "number" || binaryKeyType === "string") && (binarySizeType === "number" || binarySizeType === "string");
|
|
1546
|
+
}
|
|
1547
|
+
var SYNC_TIMEOUT_MS = 6e5;
|
|
1548
|
+
var DEFAULT_TIMEOUT_MS2 = 3e4;
|
|
1549
|
+
var DeployClient = class {
|
|
1550
|
+
accessToken;
|
|
1551
|
+
baseUrl;
|
|
1552
|
+
constructor(accessToken) {
|
|
1553
|
+
this.accessToken = accessToken;
|
|
1554
|
+
this.baseUrl = endpointFor("deploy");
|
|
1555
|
+
}
|
|
1556
|
+
authHeaders() {
|
|
1557
|
+
return {
|
|
1558
|
+
"X-NHN-AUTHORIZATION": `Bearer ${this.accessToken}`
|
|
1559
|
+
};
|
|
1560
|
+
}
|
|
1561
|
+
/**
|
|
1562
|
+
* 배포를 실행한다.
|
|
1563
|
+
* - targetHosts 가 비어있으면 payload 에서 targetServerHostnames 를 제외한다 (서버그룹 전체 배포).
|
|
1564
|
+
* - async=false(기본) 일 때 서버가 완료까지 응답을 보류하므로 ky timeout 을 600s 로 설정한다.
|
|
1565
|
+
*/
|
|
1566
|
+
async run(params) {
|
|
1567
|
+
const url = `${this.baseUrl}/api/v2.1/projects/${encodeURIComponent(params.appKey)}/artifacts/${encodeURIComponent(params.artifactId)}/server-group/${encodeURIComponent(params.serverGroupId)}/deploy`;
|
|
1568
|
+
const isAsync = params.async ?? false;
|
|
1569
|
+
const payload = {
|
|
1570
|
+
concurrentNum: params.concurrentNum ?? 1,
|
|
1571
|
+
nextWhenFail: params.nextWhenFail ?? false,
|
|
1572
|
+
scenarioIds: params.scenarioIds,
|
|
1573
|
+
deployNote: params.deployNote ?? `CLI deploy ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
1574
|
+
async: isAsync
|
|
1575
|
+
};
|
|
1576
|
+
if (params.targetHosts) {
|
|
1577
|
+
payload["targetServerHostnames"] = params.targetHosts;
|
|
1578
|
+
}
|
|
1579
|
+
try {
|
|
1580
|
+
const res = await import_ky6.default.post(url, {
|
|
1581
|
+
headers: {
|
|
1582
|
+
...this.authHeaders(),
|
|
1583
|
+
"Content-Type": "application/json"
|
|
1584
|
+
},
|
|
1585
|
+
json: payload,
|
|
1586
|
+
retry: 0,
|
|
1587
|
+
timeout: isAsync ? DEFAULT_TIMEOUT_MS2 : SYNC_TIMEOUT_MS
|
|
1588
|
+
}).json();
|
|
1589
|
+
return unwrap(res);
|
|
1590
|
+
} catch (err) {
|
|
1591
|
+
throw toNhnCloudCliError(err);
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
/**
|
|
1595
|
+
* 아티팩트 목록을 조회한다.
|
|
1596
|
+
*/
|
|
1597
|
+
async artifacts(appKey) {
|
|
1598
|
+
const url = `${this.baseUrl}/api/v2.1/projects/${encodeURIComponent(appKey)}/artifacts`;
|
|
1599
|
+
try {
|
|
1600
|
+
const res = await import_ky6.default.get(url, {
|
|
1601
|
+
headers: this.authHeaders(),
|
|
1602
|
+
retry: 0,
|
|
1603
|
+
timeout: DEFAULT_TIMEOUT_MS2
|
|
1604
|
+
}).json();
|
|
1605
|
+
return unwrap(res);
|
|
1606
|
+
} catch (err) {
|
|
1607
|
+
throw toNhnCloudCliError(err);
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
/**
|
|
1611
|
+
* 서버그룹 목록을 조회한다.
|
|
1612
|
+
*/
|
|
1613
|
+
async serverGroups(appKey, artifactId) {
|
|
1614
|
+
const url = `${this.baseUrl}/api/v2.1/projects/${encodeURIComponent(appKey)}/artifacts/${encodeURIComponent(artifactId)}/server-groups`;
|
|
1615
|
+
try {
|
|
1616
|
+
const res = await import_ky6.default.get(url, {
|
|
1617
|
+
headers: this.authHeaders(),
|
|
1618
|
+
retry: 0,
|
|
1619
|
+
timeout: DEFAULT_TIMEOUT_MS2
|
|
1620
|
+
}).json();
|
|
1621
|
+
return unwrap(res);
|
|
1622
|
+
} catch (err) {
|
|
1623
|
+
throw toNhnCloudCliError(err);
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
/**
|
|
1627
|
+
* 배포 이력을 조회한다.
|
|
1628
|
+
*/
|
|
1629
|
+
async histories(appKey, artifactId) {
|
|
1630
|
+
const url = `${this.baseUrl}/api/v2.1/projects/${encodeURIComponent(appKey)}/artifacts/${encodeURIComponent(artifactId)}/deploy-histories`;
|
|
1631
|
+
try {
|
|
1632
|
+
const res = await import_ky6.default.get(url, {
|
|
1633
|
+
headers: this.authHeaders(),
|
|
1634
|
+
retry: 0,
|
|
1635
|
+
timeout: DEFAULT_TIMEOUT_MS2
|
|
1636
|
+
}).json();
|
|
1083
1637
|
return unwrap(res);
|
|
1084
1638
|
} catch (err) {
|
|
1085
1639
|
throw toNhnCloudCliError(err);
|
|
1086
1640
|
}
|
|
1087
1641
|
}
|
|
1642
|
+
/**
|
|
1643
|
+
* 바이너리 그룹 목록을 조회한다.
|
|
1644
|
+
*/
|
|
1645
|
+
async binaryGroups(appKey, artifactId) {
|
|
1646
|
+
const url = `${this.baseUrl}/api/v2.1/projects/${encodeURIComponent(appKey)}/artifacts/${encodeURIComponent(artifactId)}/binary-groups`;
|
|
1647
|
+
try {
|
|
1648
|
+
const res = await import_ky6.default.get(url, {
|
|
1649
|
+
headers: this.authHeaders(),
|
|
1650
|
+
retry: 0,
|
|
1651
|
+
timeout: DEFAULT_TIMEOUT_MS2
|
|
1652
|
+
}).json();
|
|
1653
|
+
const body = unwrap(res);
|
|
1654
|
+
const list = body.binaryGroups;
|
|
1655
|
+
if (!Array.isArray(list) || !list.every(isBinaryGroup)) {
|
|
1656
|
+
throw new NhnCloudCliError(
|
|
1657
|
+
"binary-groups \uC751\uB2F5 \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 \u2014 binaryGroups \uBC30\uC5F4\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
1658
|
+
EXIT_API_ERROR
|
|
1659
|
+
);
|
|
1660
|
+
}
|
|
1661
|
+
return list;
|
|
1662
|
+
} catch (err) {
|
|
1663
|
+
throw toNhnCloudCliError(err);
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
/**
|
|
1667
|
+
* 특정 바이너리 그룹의 바이너리 목록을 조회한다.
|
|
1668
|
+
* pageNum/pageSize/sortKey/sortDirection 은 NHN docs 의 쿼리 파라미터로 그대로 전달한다.
|
|
1669
|
+
*/
|
|
1670
|
+
async binaries(appKey, artifactId, binaryGroupKey, params = {}) {
|
|
1671
|
+
const url = `${this.baseUrl}/api/v2.1/projects/${encodeURIComponent(appKey)}/artifacts/${encodeURIComponent(artifactId)}/binary-groups/${binaryGroupKey}/binaries`;
|
|
1672
|
+
const searchParams = {};
|
|
1673
|
+
if (params.pageNum !== void 0) searchParams["pageNum"] = params.pageNum;
|
|
1674
|
+
if (params.pageSize !== void 0) searchParams["pageSize"] = params.pageSize;
|
|
1675
|
+
if (params.sortKey !== void 0) searchParams["sortKey"] = params.sortKey;
|
|
1676
|
+
if (params.sortDirection !== void 0) searchParams["sortDirection"] = params.sortDirection;
|
|
1677
|
+
try {
|
|
1678
|
+
const res = await import_ky6.default.get(url, {
|
|
1679
|
+
headers: this.authHeaders(),
|
|
1680
|
+
searchParams,
|
|
1681
|
+
retry: 0,
|
|
1682
|
+
timeout: DEFAULT_TIMEOUT_MS2
|
|
1683
|
+
}).json();
|
|
1684
|
+
const body = unwrap(res);
|
|
1685
|
+
const list = body.binaries;
|
|
1686
|
+
if (!Array.isArray(list) || !list.every(isBinary)) {
|
|
1687
|
+
throw new NhnCloudCliError(
|
|
1688
|
+
"binaries \uC751\uB2F5 \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 \u2014 binaries \uBC30\uC5F4\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
1689
|
+
EXIT_API_ERROR
|
|
1690
|
+
);
|
|
1691
|
+
}
|
|
1692
|
+
const tc = body.totalCount;
|
|
1693
|
+
const totalCount = typeof tc === "number" ? tc : typeof tc === "string" && /^\d+$/.test(tc) ? Number(tc) : list.length;
|
|
1694
|
+
return { totalCount, binaries: list };
|
|
1695
|
+
} catch (err) {
|
|
1696
|
+
throw toNhnCloudCliError(err);
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
/**
|
|
1700
|
+
* 바이너리를 multipart/form-data 로 업로드한다.
|
|
1701
|
+
*
|
|
1702
|
+
* 신규 전송 경로 — 기존 메서드는 ky `json:`(JSON body) 만 쓴다 (ADR-015).
|
|
1703
|
+
* - 파일 파트(binaryFile)는 command 에서 statSync 가드 후 읽은 Buffer 를 Blob 으로 감싼다.
|
|
1704
|
+
* - Content-Type 은 수동으로 박지 않는다 — ky 가 FormData 에서 multipart boundary 를 자동 설정한다.
|
|
1705
|
+
*
|
|
1706
|
+
* ⚠️ 실측 pending — 수동 QA 로 확정 필요:
|
|
1707
|
+
* - endpoint 경로 세그먼트 단/복수(`binary-group` vs `binary-groups`) — 404 시 복수형으로 교체.
|
|
1708
|
+
* - 응답 binaryKey 타입(number|string) — 코드는 둘 다 수용 후 Number() 정규화.
|
|
1709
|
+
*/
|
|
1710
|
+
async uploadBinary(params) {
|
|
1711
|
+
const url = `${this.baseUrl}/api/v2.1/projects/${encodeURIComponent(params.appKey)}/artifacts/${encodeURIComponent(params.artifactId)}/binary-group/${params.binaryGroupKey}`;
|
|
1712
|
+
const form = new FormData();
|
|
1713
|
+
const blob = new Blob([new Uint8Array(params.fileBuffer)]);
|
|
1714
|
+
form.append("binaryFile", blob, params.fileName);
|
|
1715
|
+
form.append("applicationType", params.applicationType);
|
|
1716
|
+
if (params.description !== void 0) {
|
|
1717
|
+
form.append("description", params.description);
|
|
1718
|
+
}
|
|
1719
|
+
try {
|
|
1720
|
+
const res = await import_ky6.default.post(url, {
|
|
1721
|
+
headers: this.authHeaders(),
|
|
1722
|
+
// 인증 헤더만 — multipart boundary 는 ky 가 자동 설정
|
|
1723
|
+
body: form,
|
|
1724
|
+
retry: 0,
|
|
1725
|
+
timeout: SYNC_TIMEOUT_MS
|
|
1726
|
+
// 업로드는 파일 크기에 따라 길 수 있어 긴 timeout
|
|
1727
|
+
}).json();
|
|
1728
|
+
const body = unwrap(res);
|
|
1729
|
+
const normalizedKey = Number(body.binaryKey);
|
|
1730
|
+
if (typeof body.downloadUrl !== "string" || !Number.isFinite(normalizedKey)) {
|
|
1731
|
+
throw new NhnCloudCliError(
|
|
1732
|
+
"upload \uC751\uB2F5 \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 \u2014 downloadUrl/binaryKey \uB204\uB77D \uB610\uB294 \uBE44\uC22B\uC790.",
|
|
1733
|
+
EXIT_API_ERROR
|
|
1734
|
+
);
|
|
1735
|
+
}
|
|
1736
|
+
return { downloadUrl: body.downloadUrl, binaryKey: normalizedKey };
|
|
1737
|
+
} catch (err) {
|
|
1738
|
+
throw toNhnCloudCliError(err);
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
/**
|
|
1742
|
+
* 바이너리를 다운로드해 내용(Buffer)을 반환한다.
|
|
1743
|
+
*
|
|
1744
|
+
* 신규 수신 경로 — 응답이 봉투 JSON 이 아니라 파일 바이너리 스트림이다 (ADR-015).
|
|
1745
|
+
* 다른 메서드처럼 .json()/unwrap 을 쓰면 바이너리를 JSON 으로 파싱하다 깨진다 —
|
|
1746
|
+
* 반드시 .arrayBuffer() 로 받는다. 성공/실패는 HTTP status(ky throwHttpErrors)로만 판정.
|
|
1747
|
+
* 파일 쓰기는 command 가 담당한다 (client 는 내용만 반환 — 테스트 용이).
|
|
1748
|
+
*
|
|
1749
|
+
* ⚠️ 실측 pending — 수동 QA round-trip 으로 확정 필요:
|
|
1750
|
+
* - endpoint 단/복수(`binary-group` vs `binary-groups`) — 404 시 복수형으로 교체.
|
|
1751
|
+
* - 응답이 raw 바이너리인지 downloadUrl JSON 인지 — QA step 5 diff 로 확인.
|
|
1752
|
+
*/
|
|
1753
|
+
async downloadBinary(appKey, artifactId, binaryGroupKey, binaryKey) {
|
|
1754
|
+
const url = `${this.baseUrl}/api/v2.1/projects/${encodeURIComponent(appKey)}/artifacts/${encodeURIComponent(artifactId)}/binary-group/${binaryGroupKey}/binaries/${binaryKey}`;
|
|
1755
|
+
try {
|
|
1756
|
+
const ab = await import_ky6.default.get(url, {
|
|
1757
|
+
headers: this.authHeaders(),
|
|
1758
|
+
retry: 0,
|
|
1759
|
+
timeout: SYNC_TIMEOUT_MS
|
|
1760
|
+
// 큰 파일 다운로드 — 긴 timeout
|
|
1761
|
+
}).arrayBuffer();
|
|
1762
|
+
return Buffer.from(ab);
|
|
1763
|
+
} catch (err) {
|
|
1764
|
+
throw toNhnCloudCliError(err);
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1088
1767
|
};
|
|
1089
1768
|
|
|
1090
1769
|
// src/commands/deploy/helpers.ts
|
|
@@ -1096,7 +1775,7 @@ async function createDeployClient(profileOpt) {
|
|
|
1096
1775
|
}
|
|
1097
1776
|
|
|
1098
1777
|
// src/commands/deploy/run.ts
|
|
1099
|
-
var runCommand = new
|
|
1778
|
+
var runCommand = new import_commander5.Command("run").description("\uBC30\uD3EC\uB97C \uC2E4\uD589\uD55C\uB2E4").argument("<target>", "config.json \uC5D0 \uC815\uC758\uB41C deploy target \uC774\uB984").option("--app-key <k>", "target \uC758 appKey override").option("--artifact-id <id>", "target \uC758 artifactId override").option("--server-group-id <id>", "target \uC758 serverGroupId override").option("--scenario-ids <csv>", "target \uC758 scenarioIds override").option("--target-hosts <csv>", "\uB300\uC0C1 \uD638\uC2A4\uD2B8 (\uC0DD\uB7B5 \uC2DC \uC11C\uBC84\uADF8\uB8F9 \uC804\uCCB4)").option("--concurrent <n>", "\uBCD1\uB82C \uBC30\uD3EC \uC218 (\uAE30\uBCF8 1)", "1").option("--next-when-fail", "\uC2DC\uB098\uB9AC\uC624 \uC2E4\uD328 \uC2DC\uC5D0\uB3C4 \uC9C4\uD589").option("--note <s>", "\uBC30\uD3EC \uBA54\uBAA8").option("--async", "\uC989\uC2DC \uBC18\uD658 (\uAE30\uBCF8\uC740 \uC644\uB8CC \uB300\uAE30)").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (targetName, _opts, cmd) => {
|
|
1100
1779
|
const opts = cmd.optsWithGlobals();
|
|
1101
1780
|
const target = await getDeployTarget(targetName);
|
|
1102
1781
|
const appKey = opts.appKey ?? target.appKey;
|
|
@@ -1132,8 +1811,8 @@ var runCommand = new import_commander3.Command("run").description("\uBC30\uD3EC\
|
|
|
1132
1811
|
});
|
|
1133
1812
|
|
|
1134
1813
|
// src/commands/deploy/artifacts.ts
|
|
1135
|
-
var
|
|
1136
|
-
var artifactsCommand = new
|
|
1814
|
+
var import_commander6 = require("commander");
|
|
1815
|
+
var artifactsCommand = new import_commander6.Command("artifacts").description("\uC544\uD2F0\uD329\uD2B8 \uBAA9\uB85D\uC744 \uC870\uD68C\uD55C\uB2E4").argument("[target]", "config.json \uC5D0 \uC815\uC758\uB41C deploy target \uC774\uB984 (--app-key \uB85C \uB300\uCCB4 \uAC00\uB2A5)").option("--app-key <k>", "appKey \uC9C1\uC811 \uC9C0\uC815 (target \uC5C6\uC774 \uC0AC\uC6A9 \uAC00\uB2A5)").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (targetName, _opts, cmd) => {
|
|
1137
1816
|
const opts = cmd.optsWithGlobals();
|
|
1138
1817
|
let appKey;
|
|
1139
1818
|
if (opts.appKey) {
|
|
@@ -1173,8 +1852,8 @@ var artifactsCommand = new import_commander4.Command("artifacts").description("\
|
|
|
1173
1852
|
});
|
|
1174
1853
|
|
|
1175
1854
|
// src/commands/deploy/server-groups.ts
|
|
1176
|
-
var
|
|
1177
|
-
var serverGroupsCommand = new
|
|
1855
|
+
var import_commander7 = require("commander");
|
|
1856
|
+
var serverGroupsCommand = new import_commander7.Command("server-groups").description("\uC11C\uBC84\uADF8\uB8F9 \uBAA9\uB85D\uC744 \uC870\uD68C\uD55C\uB2E4").argument("<target>", "config.json \uC5D0 \uC815\uC758\uB41C deploy target \uC774\uB984").option("--app-key <k>", "target \uC758 appKey override").option("--artifact-id <id>", "target \uC758 artifactId override").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (targetName, _opts, cmd) => {
|
|
1178
1857
|
const opts = cmd.optsWithGlobals();
|
|
1179
1858
|
const target = await getDeployTarget(targetName);
|
|
1180
1859
|
const appKey = opts.appKey ?? target.appKey;
|
|
@@ -1198,8 +1877,8 @@ var serverGroupsCommand = new import_commander5.Command("server-groups").descrip
|
|
|
1198
1877
|
});
|
|
1199
1878
|
|
|
1200
1879
|
// src/commands/deploy/histories.ts
|
|
1201
|
-
var
|
|
1202
|
-
var historiesCommand = new
|
|
1880
|
+
var import_commander8 = require("commander");
|
|
1881
|
+
var historiesCommand = new import_commander8.Command("histories").description("\uBC30\uD3EC \uC774\uB825\uC744 \uC870\uD68C\uD55C\uB2E4").argument("<target>", "config.json \uC5D0 \uC815\uC758\uB41C deploy target \uC774\uB984").option("--app-key <k>", "target \uC758 appKey override").option("--artifact-id <id>", "target \uC758 artifactId override").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (targetName, _opts, cmd) => {
|
|
1203
1882
|
const opts = cmd.optsWithGlobals();
|
|
1204
1883
|
const target = await getDeployTarget(targetName);
|
|
1205
1884
|
const appKey = opts.appKey ?? target.appKey;
|
|
@@ -1222,12 +1901,236 @@ var historiesCommand = new import_commander6.Command("histories").description("\
|
|
|
1222
1901
|
});
|
|
1223
1902
|
});
|
|
1224
1903
|
|
|
1904
|
+
// src/commands/deploy/binary-groups.ts
|
|
1905
|
+
var import_commander9 = require("commander");
|
|
1906
|
+
var binaryGroupsCommand = new import_commander9.Command("binary-groups").description("\uBC14\uC774\uB108\uB9AC \uADF8\uB8F9 \uBAA9\uB85D\uC744 \uC870\uD68C\uD55C\uB2E4").argument("<target>", "config.json \uC5D0 \uC815\uC758\uB41C deploy target \uC774\uB984").option("--app-key <k>", "target \uC758 appKey override").option("--artifact-id <id>", "target \uC758 artifactId override").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (targetName, _opts, cmd) => {
|
|
1907
|
+
const opts = cmd.optsWithGlobals();
|
|
1908
|
+
const target = await getDeployTarget(targetName);
|
|
1909
|
+
const appKey = opts.appKey ?? target.appKey;
|
|
1910
|
+
const artifactId = opts.artifactId ?? target.artifactId;
|
|
1911
|
+
const { client } = await createDeployClient(opts.profile);
|
|
1912
|
+
startSpinner("\uBC14\uC774\uB108\uB9AC \uADF8\uB8F9 \uBAA9\uB85D \uC870\uD68C \uC911...");
|
|
1913
|
+
let groups;
|
|
1914
|
+
try {
|
|
1915
|
+
groups = await client.binaryGroups(appKey, artifactId);
|
|
1916
|
+
} catch (err) {
|
|
1917
|
+
stopSpinner(false);
|
|
1918
|
+
throw err;
|
|
1919
|
+
}
|
|
1920
|
+
stopSpinner(true);
|
|
1921
|
+
output(opts, {
|
|
1922
|
+
headers: ["key", "name", "regionCode", "createDate", "description"],
|
|
1923
|
+
// 가드는 key·name 만 검증 — 나머지 필드는 누락 시 "undefined" 가 박히지 않게 ?? "" 방어.
|
|
1924
|
+
rows: groups.map((g) => [
|
|
1925
|
+
String(g.key),
|
|
1926
|
+
g.name ?? "",
|
|
1927
|
+
g.regionCode ?? "",
|
|
1928
|
+
g.createDate ?? "",
|
|
1929
|
+
g.description ?? ""
|
|
1930
|
+
]),
|
|
1931
|
+
raw: groups,
|
|
1932
|
+
// ids 에 key 를 넣어 --quiet 시 그룹 key 만 출력 → binaries --binary-group 에 파이프 가능
|
|
1933
|
+
ids: groups.map((g) => String(g.key))
|
|
1934
|
+
});
|
|
1935
|
+
});
|
|
1936
|
+
|
|
1937
|
+
// src/commands/deploy/binaries.ts
|
|
1938
|
+
var import_commander10 = require("commander");
|
|
1939
|
+
function parsePositiveInt(value, flag) {
|
|
1940
|
+
if (value === void 0) return void 0;
|
|
1941
|
+
if (!/^[1-9]\d*$/.test(value)) {
|
|
1942
|
+
throw new NhnCloudCliError(
|
|
1943
|
+
`${flag} \uB294 1 \uC774\uC0C1\uC758 \uC815\uC218\uC5EC\uC57C \uD569\uB2C8\uB2E4 (\uC785\uB825: ${JSON.stringify(value)}).`,
|
|
1944
|
+
EXIT_PARAM_ERROR
|
|
1945
|
+
);
|
|
1946
|
+
}
|
|
1947
|
+
return Number(value);
|
|
1948
|
+
}
|
|
1949
|
+
var binariesCommand = new import_commander10.Command("binaries").description("\uD2B9\uC815 \uBC14\uC774\uB108\uB9AC \uADF8\uB8F9\uC758 \uBC14\uC774\uB108\uB9AC \uBAA9\uB85D\uC744 \uC870\uD68C\uD55C\uB2E4").argument("<target>", "config.json \uC5D0 \uC815\uC758\uB41C deploy target \uC774\uB984").requiredOption("--binary-group <key>", "\uC870\uD68C\uD560 \uBC14\uC774\uB108\uB9AC \uADF8\uB8F9 key (binary-groups \uB85C \uD655\uC778)").option("--page-num <n>", "\uD398\uC774\uC9C0 \uBC88\uD638 (1 \uC774\uC0C1)").option("--page-size <n>", "\uD398\uC774\uC9C0 \uD06C\uAE30 (1 \uC774\uC0C1)").option("--sort-key <k>", "\uC815\uB82C \uAE30\uC900 (\uC608: UPLOAD_DATE)").option("--sort-direction <d>", "\uC815\uB82C \uBC29\uD5A5 (\uC608: DESC)").option("--app-key <k>", "target \uC758 appKey override").option("--artifact-id <id>", "target \uC758 artifactId override").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (targetName, _opts, cmd) => {
|
|
1950
|
+
const opts = cmd.optsWithGlobals();
|
|
1951
|
+
const binaryGroupKey = parsePositiveInt(opts.binaryGroup, "--binary-group");
|
|
1952
|
+
if (binaryGroupKey === void 0) {
|
|
1953
|
+
throw new NhnCloudCliError("--binary-group \uC774 \uD544\uC694\uD569\uB2C8\uB2E4.", EXIT_PARAM_ERROR);
|
|
1954
|
+
}
|
|
1955
|
+
const pageNum = parsePositiveInt(opts.pageNum, "--page-num");
|
|
1956
|
+
const pageSize = parsePositiveInt(opts.pageSize, "--page-size");
|
|
1957
|
+
const target = await getDeployTarget(targetName);
|
|
1958
|
+
const appKey = opts.appKey ?? target.appKey;
|
|
1959
|
+
const artifactId = opts.artifactId ?? target.artifactId;
|
|
1960
|
+
const { client } = await createDeployClient(opts.profile);
|
|
1961
|
+
startSpinner("\uBC14\uC774\uB108\uB9AC \uBAA9\uB85D \uC870\uD68C \uC911...");
|
|
1962
|
+
let result;
|
|
1963
|
+
try {
|
|
1964
|
+
result = await client.binaries(appKey, artifactId, binaryGroupKey, {
|
|
1965
|
+
pageNum,
|
|
1966
|
+
pageSize,
|
|
1967
|
+
sortKey: opts.sortKey,
|
|
1968
|
+
sortDirection: opts.sortDirection
|
|
1969
|
+
});
|
|
1970
|
+
} catch (err) {
|
|
1971
|
+
stopSpinner(false);
|
|
1972
|
+
throw err;
|
|
1973
|
+
}
|
|
1974
|
+
stopSpinner(true);
|
|
1975
|
+
output(opts, {
|
|
1976
|
+
headers: ["binaryKey", "version", "binaryName", "size(bytes)", "uploadDate", "uploader"],
|
|
1977
|
+
// 가드는 binaryKey·binarySize 만 검증 — 나머지 필드는 응답에서 누락 시 "undefined" 가
|
|
1978
|
+
// 표에 박히지 않게 ?? "" 로 방어한다 (타입 정합성 실측은 후속 이슈).
|
|
1979
|
+
rows: result.binaries.map((b) => [
|
|
1980
|
+
String(b.binaryKey),
|
|
1981
|
+
b.version ?? "",
|
|
1982
|
+
b.binaryName ?? "",
|
|
1983
|
+
String(b.binarySize),
|
|
1984
|
+
b.uploadDate ?? "",
|
|
1985
|
+
b.uploader ?? ""
|
|
1986
|
+
]),
|
|
1987
|
+
// raw 에 totalCount 포함 → --json 으로 페이지 정보 확인 가능
|
|
1988
|
+
raw: result,
|
|
1989
|
+
ids: result.binaries.map((b) => String(b.binaryKey))
|
|
1990
|
+
});
|
|
1991
|
+
});
|
|
1992
|
+
|
|
1993
|
+
// src/commands/deploy/upload.ts
|
|
1994
|
+
var import_commander11 = require("commander");
|
|
1995
|
+
var import_node_fs3 = require("fs");
|
|
1996
|
+
var import_node_path3 = require("path");
|
|
1997
|
+
var MAX_UPLOAD_BYTES = 512 * 1024 * 1024;
|
|
1998
|
+
function parsePositiveInt2(value, flag) {
|
|
1999
|
+
if (value === void 0) return void 0;
|
|
2000
|
+
if (!/^[1-9]\d*$/.test(value)) {
|
|
2001
|
+
throw new NhnCloudCliError(
|
|
2002
|
+
`${flag} \uB294 1 \uC774\uC0C1\uC758 \uC815\uC218\uC5EC\uC57C \uD569\uB2C8\uB2E4 (\uC785\uB825: ${JSON.stringify(value)}).`,
|
|
2003
|
+
EXIT_PARAM_ERROR
|
|
2004
|
+
);
|
|
2005
|
+
}
|
|
2006
|
+
return Number(value);
|
|
2007
|
+
}
|
|
2008
|
+
var uploadCommand = new import_commander11.Command("upload").description("\uB85C\uCEEC \uD30C\uC77C\uC744 \uBC14\uC774\uB108\uB9AC \uADF8\uB8F9\uC5D0 \uC5C5\uB85C\uB4DC\uD55C\uB2E4").argument("<target>", "config.json \uC5D0 \uC815\uC758\uB41C deploy target \uC774\uB984").requiredOption("--file <path>", "\uC5C5\uB85C\uB4DC\uD560 \uD30C\uC77C \uACBD\uB85C").requiredOption("--binary-group <key>", "\uC5C5\uB85C\uB4DC \uB300\uC0C1 \uBC14\uC774\uB108\uB9AC \uADF8\uB8F9 key (binary-groups \uB85C \uD655\uC778)").option("--application-type <type>", "applicationType (\uC608: server)", "server").option("--description <text>", "\uBC14\uC774\uB108\uB9AC \uC124\uBA85").option("--app-key <k>", "target \uC758 appKey override").option("--artifact-id <id>", "target \uC758 artifactId override").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (targetName, _opts, cmd) => {
|
|
2009
|
+
const opts = cmd.optsWithGlobals();
|
|
2010
|
+
const binaryGroupKey = parsePositiveInt2(opts.binaryGroup, "--binary-group");
|
|
2011
|
+
if (binaryGroupKey === void 0) {
|
|
2012
|
+
throw new NhnCloudCliError("--binary-group \uC774 \uD544\uC694\uD569\uB2C8\uB2E4.", EXIT_PARAM_ERROR);
|
|
2013
|
+
}
|
|
2014
|
+
const filePath = opts.file;
|
|
2015
|
+
let stat;
|
|
2016
|
+
try {
|
|
2017
|
+
stat = (0, import_node_fs3.statSync)(filePath);
|
|
2018
|
+
} catch (e) {
|
|
2019
|
+
const reason = e.code ?? (e instanceof Error ? e.message : String(e));
|
|
2020
|
+
throw new NhnCloudCliError(
|
|
2021
|
+
`--file \uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${filePath} (${reason})`,
|
|
2022
|
+
EXIT_PARAM_ERROR
|
|
2023
|
+
);
|
|
2024
|
+
}
|
|
2025
|
+
if (!stat.isFile()) {
|
|
2026
|
+
throw new NhnCloudCliError(`--file \uC774 \uC77C\uBC18 \uD30C\uC77C\uC774 \uC544\uB2D9\uB2C8\uB2E4: ${filePath}`, EXIT_PARAM_ERROR);
|
|
2027
|
+
}
|
|
2028
|
+
if (stat.size > MAX_UPLOAD_BYTES) {
|
|
2029
|
+
throw new NhnCloudCliError(
|
|
2030
|
+
`--file \uC774 \uB108\uBB34 \uD07D\uB2C8\uB2E4 (${stat.size} \uBC14\uC774\uD2B8). \uC5C5\uB85C\uB4DC \uD55C\uB3C4 ${MAX_UPLOAD_BYTES} \uBC14\uC774\uD2B8.`,
|
|
2031
|
+
EXIT_PARAM_ERROR
|
|
2032
|
+
);
|
|
2033
|
+
}
|
|
2034
|
+
const fileBuffer = (0, import_node_fs3.readFileSync)(filePath);
|
|
2035
|
+
const fileName = (0, import_node_path3.basename)(filePath);
|
|
2036
|
+
const target = await getDeployTarget(targetName);
|
|
2037
|
+
const appKey = opts.appKey ?? target.appKey;
|
|
2038
|
+
const artifactId = opts.artifactId ?? target.artifactId;
|
|
2039
|
+
const { client } = await createDeployClient(opts.profile);
|
|
2040
|
+
startSpinner("\uBC14\uC774\uB108\uB9AC \uC5C5\uB85C\uB4DC \uC911...");
|
|
2041
|
+
let result;
|
|
2042
|
+
try {
|
|
2043
|
+
result = await client.uploadBinary({
|
|
2044
|
+
appKey,
|
|
2045
|
+
artifactId,
|
|
2046
|
+
binaryGroupKey,
|
|
2047
|
+
fileBuffer,
|
|
2048
|
+
fileName,
|
|
2049
|
+
applicationType: opts.applicationType,
|
|
2050
|
+
// Commander 옵션 기본값 "server" 가 SSOT — dead fallback 제거
|
|
2051
|
+
description: opts.description
|
|
2052
|
+
});
|
|
2053
|
+
} catch (err) {
|
|
2054
|
+
stopSpinner(false);
|
|
2055
|
+
throw err;
|
|
2056
|
+
}
|
|
2057
|
+
stopSpinner(true);
|
|
2058
|
+
output(opts, {
|
|
2059
|
+
headers: ["field", "value"],
|
|
2060
|
+
rows: [
|
|
2061
|
+
["binaryKey", String(result.binaryKey)],
|
|
2062
|
+
["downloadUrl", result.downloadUrl]
|
|
2063
|
+
],
|
|
2064
|
+
raw: result,
|
|
2065
|
+
ids: [String(result.binaryKey)]
|
|
2066
|
+
});
|
|
2067
|
+
});
|
|
2068
|
+
|
|
2069
|
+
// src/commands/deploy/download.ts
|
|
2070
|
+
var import_commander12 = require("commander");
|
|
2071
|
+
var import_node_fs4 = require("fs");
|
|
2072
|
+
var import_chalk2 = __toESM(require("chalk"));
|
|
2073
|
+
function parsePositiveInt3(value, flag) {
|
|
2074
|
+
if (value === void 0) return void 0;
|
|
2075
|
+
if (!/^[1-9]\d*$/.test(value)) {
|
|
2076
|
+
throw new NhnCloudCliError(
|
|
2077
|
+
`${flag} \uB294 1 \uC774\uC0C1\uC758 \uC815\uC218\uC5EC\uC57C \uD569\uB2C8\uB2E4 (\uC785\uB825: ${JSON.stringify(value)}).`,
|
|
2078
|
+
EXIT_PARAM_ERROR
|
|
2079
|
+
);
|
|
2080
|
+
}
|
|
2081
|
+
return Number(value);
|
|
2082
|
+
}
|
|
2083
|
+
function assertWritable2(path, force) {
|
|
2084
|
+
if (force) return;
|
|
2085
|
+
try {
|
|
2086
|
+
(0, import_node_fs4.statSync)(path);
|
|
2087
|
+
} catch (e) {
|
|
2088
|
+
if (e.code === "ENOENT") return;
|
|
2089
|
+
const reason = e.code ?? (e instanceof Error ? e.message : String(e));
|
|
2090
|
+
throw new NhnCloudCliError(
|
|
2091
|
+
`-o \uACBD\uB85C\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${path} (${reason})`,
|
|
2092
|
+
EXIT_PARAM_ERROR
|
|
2093
|
+
);
|
|
2094
|
+
}
|
|
2095
|
+
throw new NhnCloudCliError(
|
|
2096
|
+
`-o \uB300\uC0C1\uC774 \uC774\uBBF8 \uC874\uC7AC\uD569\uB2C8\uB2E4: ${path}. \uB36E\uC5B4\uC4F0\uB824\uBA74 --force \uB97C \uC4F0\uC138\uC694.`,
|
|
2097
|
+
EXIT_PARAM_ERROR
|
|
2098
|
+
);
|
|
2099
|
+
}
|
|
2100
|
+
var downloadCommand = new import_commander12.Command("download").description("\uBC14\uC774\uB108\uB9AC\uB97C \uB85C\uCEEC \uD30C\uC77C\uB85C \uB2E4\uC6B4\uB85C\uB4DC\uD55C\uB2E4").argument("<target>", "config.json \uC5D0 \uC815\uC758\uB41C deploy target \uC774\uB984").requiredOption("--binary-group <key>", "\uBC14\uC774\uB108\uB9AC \uADF8\uB8F9 key (binary-groups \uB85C \uD655\uC778)").requiredOption("--binary-key <key>", "\uB2E4\uC6B4\uB85C\uB4DC\uD560 \uBC14\uC774\uB108\uB9AC key (binaries \uB610\uB294 upload \uB85C \uD655\uC778)").requiredOption("-o, --output <file>", "\uC800\uC7A5\uD560 \uD30C\uC77C \uACBD\uB85C").option("--force", "\uB300\uC0C1 \uD30C\uC77C\uC774 \uC788\uC73C\uBA74 \uB36E\uC5B4\uC4F4\uB2E4").option("--app-key <k>", "target \uC758 appKey override").option("--artifact-id <id>", "target \uC758 artifactId override").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (targetName, _opts, cmd) => {
|
|
2101
|
+
const opts = cmd.optsWithGlobals();
|
|
2102
|
+
const binaryGroupKey = parsePositiveInt3(opts.binaryGroup, "--binary-group");
|
|
2103
|
+
const binaryKey = parsePositiveInt3(opts.binaryKey, "--binary-key");
|
|
2104
|
+
if (binaryGroupKey === void 0 || binaryKey === void 0) {
|
|
2105
|
+
throw new NhnCloudCliError("--binary-group / --binary-key \uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.", EXIT_PARAM_ERROR);
|
|
2106
|
+
}
|
|
2107
|
+
const outPath = opts.output;
|
|
2108
|
+
assertWritable2(outPath, opts.force ?? false);
|
|
2109
|
+
const target = await getDeployTarget(targetName);
|
|
2110
|
+
const appKey = opts.appKey ?? target.appKey;
|
|
2111
|
+
const artifactId = opts.artifactId ?? target.artifactId;
|
|
2112
|
+
const { client } = await createDeployClient(opts.profile);
|
|
2113
|
+
startSpinner("\uBC14\uC774\uB108\uB9AC \uB2E4\uC6B4\uB85C\uB4DC \uC911...");
|
|
2114
|
+
try {
|
|
2115
|
+
const buffer = await client.downloadBinary(appKey, artifactId, binaryGroupKey, binaryKey);
|
|
2116
|
+
(0, import_node_fs4.writeFileSync)(outPath, buffer);
|
|
2117
|
+
} catch (err) {
|
|
2118
|
+
stopSpinner(false);
|
|
2119
|
+
throw err;
|
|
2120
|
+
}
|
|
2121
|
+
stopSpinner(true);
|
|
2122
|
+
if (!opts.quiet) {
|
|
2123
|
+
process.stderr.write(import_chalk2.default.green(` \uC800\uC7A5\uB428: ${outPath}
|
|
2124
|
+
`));
|
|
2125
|
+
}
|
|
2126
|
+
});
|
|
2127
|
+
|
|
1225
2128
|
// src/commands/instance/list.ts
|
|
1226
|
-
var
|
|
2129
|
+
var import_commander13 = require("commander");
|
|
1227
2130
|
|
|
1228
2131
|
// src/services/instance/client.ts
|
|
1229
|
-
var
|
|
1230
|
-
var
|
|
2132
|
+
var import_ky7 = __toESM(require("ky"));
|
|
2133
|
+
var DEFAULT_TIMEOUT_MS3 = 3e4;
|
|
1231
2134
|
var DEFAULT_POLL_INTERVAL_MS = 5e3;
|
|
1232
2135
|
function isServer(val) {
|
|
1233
2136
|
if (typeof val !== "object" || val === null) return false;
|
|
@@ -1244,21 +2147,108 @@ function isServersResponse(val) {
|
|
|
1244
2147
|
const obj = val;
|
|
1245
2148
|
return Array.isArray(obj["servers"]);
|
|
1246
2149
|
}
|
|
2150
|
+
function isFlavor(val) {
|
|
2151
|
+
if (typeof val !== "object" || val === null) return false;
|
|
2152
|
+
const obj = val;
|
|
2153
|
+
return typeof obj["id"] === "string" && typeof obj["name"] === "string";
|
|
2154
|
+
}
|
|
2155
|
+
function isImage(val) {
|
|
2156
|
+
if (typeof val !== "object" || val === null) return false;
|
|
2157
|
+
const obj = val;
|
|
2158
|
+
return typeof obj["id"] === "string" && // Glance v2 스펙상 name 은 nullable — null 인 private 이미지 하나가 페이지 전체를 거부하지 않게 허용.
|
|
2159
|
+
(typeof obj["name"] === "string" || obj["name"] === null) && typeof obj["status"] === "string" && typeof obj["visibility"] === "string";
|
|
2160
|
+
}
|
|
2161
|
+
function isImagesResponse(val) {
|
|
2162
|
+
if (typeof val !== "object" || val === null) return false;
|
|
2163
|
+
const obj = val;
|
|
2164
|
+
const nextOk = obj["next"] === void 0 || typeof obj["next"] === "string";
|
|
2165
|
+
return Array.isArray(obj["images"]) && obj["images"].every(isImage) && nextOk;
|
|
2166
|
+
}
|
|
2167
|
+
function isFlavorsResponse(val) {
|
|
2168
|
+
if (typeof val !== "object" || val === null) return false;
|
|
2169
|
+
const obj = val;
|
|
2170
|
+
return Array.isArray(obj["flavors"]) && obj["flavors"].every(isFlavor);
|
|
2171
|
+
}
|
|
2172
|
+
function isFlavorDetail(val) {
|
|
2173
|
+
if (typeof val !== "object" || val === null) return false;
|
|
2174
|
+
const obj = val;
|
|
2175
|
+
return typeof obj["id"] === "string" && typeof obj["name"] === "string" && typeof obj["vcpus"] === "number" && typeof obj["ram"] === "number" && typeof obj["disk"] === "number";
|
|
2176
|
+
}
|
|
2177
|
+
function isFlavorDetailsResponse(val) {
|
|
2178
|
+
if (typeof val !== "object" || val === null) return false;
|
|
2179
|
+
const obj = val;
|
|
2180
|
+
return Array.isArray(obj["flavors"]) && obj["flavors"].every(isFlavorDetail);
|
|
2181
|
+
}
|
|
1247
2182
|
function isCreateResponse(val) {
|
|
1248
2183
|
if (typeof val !== "object" || val === null) return false;
|
|
1249
2184
|
const server = val["server"];
|
|
1250
2185
|
if (typeof server !== "object" || server === null) return false;
|
|
1251
2186
|
return typeof server["id"] === "string";
|
|
1252
2187
|
}
|
|
2188
|
+
function isKeypair(val) {
|
|
2189
|
+
if (typeof val !== "object" || val === null) return false;
|
|
2190
|
+
const obj = val;
|
|
2191
|
+
return typeof obj["name"] === "string" && typeof obj["public_key"] === "string" && typeof obj["fingerprint"] === "string";
|
|
2192
|
+
}
|
|
2193
|
+
function isKeypairsResponse(val) {
|
|
2194
|
+
if (typeof val !== "object" || val === null) return false;
|
|
2195
|
+
const obj = val;
|
|
2196
|
+
return Array.isArray(obj["keypairs"]) && obj["keypairs"].every((e) => {
|
|
2197
|
+
if (typeof e !== "object" || e === null) return false;
|
|
2198
|
+
return isKeypair(e["keypair"]);
|
|
2199
|
+
});
|
|
2200
|
+
}
|
|
2201
|
+
function isCreateKeypairResponse(val) {
|
|
2202
|
+
if (typeof val !== "object" || val === null) return false;
|
|
2203
|
+
const obj = val;
|
|
2204
|
+
return isKeypair(obj["keypair"]);
|
|
2205
|
+
}
|
|
2206
|
+
function isKeypairDetail(val) {
|
|
2207
|
+
if (typeof val !== "object" || val === null) return false;
|
|
2208
|
+
const obj = val;
|
|
2209
|
+
return typeof obj["name"] === "string" && typeof obj["public_key"] === "string" && typeof obj["fingerprint"] === "string" && typeof obj["user_id"] === "string" && typeof obj["id"] === "string" && typeof obj["created_at"] === "string";
|
|
2210
|
+
}
|
|
2211
|
+
function isKeypairDetailResponse(val) {
|
|
2212
|
+
if (typeof val !== "object" || val === null) return false;
|
|
2213
|
+
return isKeypairDetail(val["keypair"]);
|
|
2214
|
+
}
|
|
2215
|
+
function isAvailabilityZone(val) {
|
|
2216
|
+
if (typeof val !== "object" || val === null) return false;
|
|
2217
|
+
const obj = val;
|
|
2218
|
+
const state = obj["zoneState"];
|
|
2219
|
+
if (typeof state !== "object" || state === null) return false;
|
|
2220
|
+
return typeof obj["zoneName"] === "string" && typeof state["available"] === "boolean";
|
|
2221
|
+
}
|
|
2222
|
+
function isAvailabilityZonesResponse(val) {
|
|
2223
|
+
if (typeof val !== "object" || val === null) return false;
|
|
2224
|
+
const obj = val;
|
|
2225
|
+
return Array.isArray(obj["availabilityZoneInfo"]) && obj["availabilityZoneInfo"].every(isAvailabilityZone);
|
|
2226
|
+
}
|
|
2227
|
+
function isServerVolumeAttachment(val) {
|
|
2228
|
+
if (typeof val !== "object" || val === null) return false;
|
|
2229
|
+
const o = val;
|
|
2230
|
+
return typeof o["id"] === "string" && typeof o["volumeId"] === "string" && typeof o["serverId"] === "string" && typeof o["device"] === "string";
|
|
2231
|
+
}
|
|
2232
|
+
function isVolumeAttachmentsResponse(val) {
|
|
2233
|
+
if (typeof val !== "object" || val === null) return false;
|
|
2234
|
+
const arr = val["volumeAttachments"];
|
|
2235
|
+
return Array.isArray(arr) && arr.every(isServerVolumeAttachment);
|
|
2236
|
+
}
|
|
2237
|
+
function isVolumeAttachmentResponse(val) {
|
|
2238
|
+
if (typeof val !== "object" || val === null) return false;
|
|
2239
|
+
return isServerVolumeAttachment(val["volumeAttachment"]);
|
|
2240
|
+
}
|
|
1253
2241
|
function hasIpAddress(server) {
|
|
1254
2242
|
return Object.values(server.addresses).some((list) => list.length > 0);
|
|
1255
2243
|
}
|
|
1256
2244
|
var InstanceClient = class {
|
|
1257
2245
|
tokenId;
|
|
1258
2246
|
computeEndpoint;
|
|
1259
|
-
|
|
2247
|
+
imageEndpoint;
|
|
2248
|
+
constructor(tokenId, computeEndpoint, imageEndpoint) {
|
|
1260
2249
|
this.tokenId = tokenId;
|
|
1261
2250
|
this.computeEndpoint = computeEndpoint;
|
|
2251
|
+
this.imageEndpoint = imageEndpoint;
|
|
1262
2252
|
}
|
|
1263
2253
|
authHeaders() {
|
|
1264
2254
|
return { "X-Auth-Token": this.tokenId };
|
|
@@ -1269,10 +2259,10 @@ var InstanceClient = class {
|
|
|
1269
2259
|
async list() {
|
|
1270
2260
|
const url = `${this.computeEndpoint}/servers/detail`;
|
|
1271
2261
|
try {
|
|
1272
|
-
const raw = await
|
|
2262
|
+
const raw = await import_ky7.default.get(url, {
|
|
1273
2263
|
headers: this.authHeaders(),
|
|
1274
2264
|
retry: 0,
|
|
1275
|
-
timeout:
|
|
2265
|
+
timeout: DEFAULT_TIMEOUT_MS3
|
|
1276
2266
|
}).json();
|
|
1277
2267
|
if (!isServersResponse(raw)) {
|
|
1278
2268
|
throw new NhnCloudCliError(
|
|
@@ -1291,10 +2281,10 @@ var InstanceClient = class {
|
|
|
1291
2281
|
async get(id) {
|
|
1292
2282
|
const url = `${this.computeEndpoint}/servers/${encodeURIComponent(id)}`;
|
|
1293
2283
|
try {
|
|
1294
|
-
const raw = await
|
|
2284
|
+
const raw = await import_ky7.default.get(url, {
|
|
1295
2285
|
headers: this.authHeaders(),
|
|
1296
2286
|
retry: 0,
|
|
1297
|
-
timeout:
|
|
2287
|
+
timeout: DEFAULT_TIMEOUT_MS3
|
|
1298
2288
|
}).json();
|
|
1299
2289
|
if (!isServerResponse(raw)) {
|
|
1300
2290
|
throw new NhnCloudCliError(
|
|
@@ -1350,11 +2340,11 @@ var InstanceClient = class {
|
|
|
1350
2340
|
}
|
|
1351
2341
|
let raw;
|
|
1352
2342
|
try {
|
|
1353
|
-
raw = await
|
|
2343
|
+
raw = await import_ky7.default.post(url, {
|
|
1354
2344
|
headers: this.authHeaders(),
|
|
1355
2345
|
json: { server: serverBody },
|
|
1356
2346
|
retry: 0,
|
|
1357
|
-
timeout:
|
|
2347
|
+
timeout: DEFAULT_TIMEOUT_MS3
|
|
1358
2348
|
}).json();
|
|
1359
2349
|
} catch (err) {
|
|
1360
2350
|
throw toNhnCloudCliError(err);
|
|
@@ -1373,109 +2363,1023 @@ var InstanceClient = class {
|
|
|
1373
2363
|
async delete(id) {
|
|
1374
2364
|
const url = `${this.computeEndpoint}/servers/${encodeURIComponent(id)}`;
|
|
1375
2365
|
try {
|
|
1376
|
-
await
|
|
2366
|
+
await import_ky7.default.delete(url, {
|
|
1377
2367
|
headers: this.authHeaders(),
|
|
1378
2368
|
retry: 0,
|
|
1379
|
-
timeout:
|
|
2369
|
+
timeout: DEFAULT_TIMEOUT_MS3
|
|
1380
2370
|
});
|
|
1381
2371
|
} catch (err) {
|
|
1382
2372
|
throw toNhnCloudCliError(err);
|
|
1383
2373
|
}
|
|
1384
2374
|
}
|
|
1385
2375
|
/**
|
|
1386
|
-
*
|
|
1387
|
-
*
|
|
1388
|
-
*
|
|
1389
|
-
*
|
|
2376
|
+
* 서버 action 을 실행한다 (POST /servers/{id}/action, 202 무본문).
|
|
2377
|
+
* NHN Cloud(OpenStack Nova)의 모든 전원·라이프사이클 action 의 공용 경로다.
|
|
2378
|
+
* payload 는 호출자가 action 별로 구성한다 (예: { "os-start": null }).
|
|
2379
|
+
* start/stop/reboot 가 이 helper 를 재사용하며, resize/shelve 등 향후 action 도 동일.
|
|
1390
2380
|
*/
|
|
1391
|
-
async
|
|
1392
|
-
const
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
if (remaining <= 0) break;
|
|
1403
|
-
await new Promise((resolve) => setTimeout(resolve, Math.min(intervalMs, remaining)));
|
|
2381
|
+
async serverAction(id, payload) {
|
|
2382
|
+
const url = `${this.computeEndpoint}/servers/${encodeURIComponent(id)}/action`;
|
|
2383
|
+
try {
|
|
2384
|
+
await import_ky7.default.post(url, {
|
|
2385
|
+
headers: this.authHeaders(),
|
|
2386
|
+
json: payload,
|
|
2387
|
+
retry: 0,
|
|
2388
|
+
timeout: DEFAULT_TIMEOUT_MS3
|
|
2389
|
+
});
|
|
2390
|
+
} catch (err) {
|
|
2391
|
+
throw toNhnCloudCliError(err);
|
|
1404
2392
|
}
|
|
1405
|
-
const lastStatus = lastServer ? lastServer.status : "unknown";
|
|
1406
|
-
throw new NhnCloudCliError(
|
|
1407
|
-
`\uC778\uC2A4\uD134\uC2A4 ${id} \uAC00 ACTIVE \uAC00 \uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4 (\uB9C8\uC9C0\uB9C9 \uC0C1\uD0DC: ${lastStatus}). --wait \uD0C0\uC784\uC544\uC6C3(${Math.round(opts.timeoutMs / 1e3)}\uCD08) \uCD08\uACFC.`,
|
|
1408
|
-
EXIT_API_ERROR
|
|
1409
|
-
);
|
|
1410
2393
|
}
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
async function resolveInstanceClient(opts) {
|
|
1415
|
-
const profileName = await resolveProfileName(opts.profile);
|
|
1416
|
-
const iaas = await getIaasCredential(profileName);
|
|
1417
|
-
const effectiveIaas = opts.region ? { ...iaas, region: opts.region } : iaas;
|
|
1418
|
-
const { tokenId, computeEndpoint } = await getIaasToken(profileName, effectiveIaas);
|
|
1419
|
-
return { client: new InstanceClient(tokenId, computeEndpoint), profileName };
|
|
1420
|
-
}
|
|
1421
|
-
|
|
1422
|
-
// src/commands/instance/list.ts
|
|
1423
|
-
function getIps(server) {
|
|
1424
|
-
return Object.values(server.addresses).flat().map((a) => a.addr).join(", ");
|
|
1425
|
-
}
|
|
1426
|
-
var listCommand = new import_commander7.Command("list").description("\uC778\uC2A4\uD134\uC2A4 \uBAA9\uB85D\uC744 \uC870\uD68C\uD55C\uB2E4").option("--region <region>", "region override (\uAE30\uBCF8: iaas \uC790\uACA9\uC99D\uBA85\uC758 region)").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (_opts, cmd) => {
|
|
1427
|
-
const opts = cmd.optsWithGlobals();
|
|
1428
|
-
const { client } = await resolveInstanceClient(opts);
|
|
1429
|
-
startSpinner("\uC778\uC2A4\uD134\uC2A4 \uBAA9\uB85D \uC870\uD68C \uC911...");
|
|
1430
|
-
let servers;
|
|
1431
|
-
try {
|
|
1432
|
-
servers = await client.list();
|
|
1433
|
-
} catch (err) {
|
|
1434
|
-
stopSpinner(false);
|
|
1435
|
-
throw err;
|
|
2394
|
+
/** 인스턴스를 시작한다 (SHUTOFF → ACTIVE). */
|
|
2395
|
+
async start(id) {
|
|
2396
|
+
return this.serverAction(id, { "os-start": null });
|
|
1436
2397
|
}
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
rows: servers.map((s) => [s.id, s.name, s.status, getIps(s), s.flavor.id]),
|
|
1441
|
-
raw: servers,
|
|
1442
|
-
ids: servers.map((s) => s.id)
|
|
1443
|
-
});
|
|
1444
|
-
});
|
|
1445
|
-
|
|
1446
|
-
// src/commands/instance/get.ts
|
|
1447
|
-
var import_commander8 = require("commander");
|
|
1448
|
-
function getIps2(server) {
|
|
1449
|
-
return Object.values(server.addresses).flat().map((a) => a.addr).join(", ");
|
|
1450
|
-
}
|
|
1451
|
-
function getImageId(server) {
|
|
1452
|
-
return typeof server.image === "object" ? server.image.id : "";
|
|
1453
|
-
}
|
|
1454
|
-
var getCommand = new import_commander8.Command("get").description("\uB2E8\uC77C \uC778\uC2A4\uD134\uC2A4 \uC0C1\uD0DC\uB97C \uC870\uD68C\uD55C\uB2E4").argument("<id>", "\uC778\uC2A4\uD134\uC2A4 ID").option("--region <region>", "region override (\uAE30\uBCF8: iaas \uC790\uACA9\uC99D\uBA85\uC758 region)").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (id, _opts, cmd) => {
|
|
1455
|
-
const opts = cmd.optsWithGlobals();
|
|
1456
|
-
const { client } = await resolveInstanceClient(opts);
|
|
1457
|
-
startSpinner("\uC778\uC2A4\uD134\uC2A4 \uC870\uD68C \uC911...");
|
|
1458
|
-
let server;
|
|
1459
|
-
try {
|
|
1460
|
-
server = await client.get(id);
|
|
1461
|
-
} catch (err) {
|
|
1462
|
-
stopSpinner(false);
|
|
1463
|
-
throw err;
|
|
2398
|
+
/** 인스턴스를 정지한다 (ACTIVE/ERROR → SHUTOFF). */
|
|
2399
|
+
async stop(id) {
|
|
2400
|
+
return this.serverAction(id, { "os-stop": null });
|
|
1464
2401
|
}
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
2402
|
+
/** 인스턴스를 재부팅한다. type 기본 SOFT, HARD 는 강제 전원 cycle. */
|
|
2403
|
+
async reboot(id, type = "SOFT") {
|
|
2404
|
+
return this.serverAction(id, { reboot: { type } });
|
|
2405
|
+
}
|
|
2406
|
+
/**
|
|
2407
|
+
* 인스턴스 타입(flavor)을 변경한다 (resize action).
|
|
2408
|
+
* POST /servers/{id}/action body { "resize": { "flavorRef": "<flavor-id>" } }, 202 무본문.
|
|
2409
|
+
* 사전 상태는 ACTIVE 또는 SHUTOFF (ACTIVE 면 NHN 측에서 중지 후 재시작).
|
|
2410
|
+
*/
|
|
2411
|
+
async resize(id, flavorRef) {
|
|
2412
|
+
return this.serverAction(id, { resize: { flavorRef } });
|
|
2413
|
+
}
|
|
2414
|
+
/** resize 를 확정한다 (VERIFY_RESIZE → ACTIVE, 새 flavor 로 고정). */
|
|
2415
|
+
async confirmResize(id) {
|
|
2416
|
+
return this.serverAction(id, { confirmResize: null });
|
|
2417
|
+
}
|
|
2418
|
+
/** resize 를 롤백한다 (VERIFY_RESIZE → ACTIVE, 이전 flavor 로 복귀). */
|
|
2419
|
+
async revertResize(id) {
|
|
2420
|
+
return this.serverAction(id, { revertResize: null });
|
|
2421
|
+
}
|
|
2422
|
+
async listFlavors(params = {}) {
|
|
2423
|
+
const path = params.detail ? "/flavors/detail" : "/flavors";
|
|
2424
|
+
const url = `${this.computeEndpoint}${path}`;
|
|
2425
|
+
const searchParams = {};
|
|
2426
|
+
if (params.minDisk !== void 0) searchParams["minDisk"] = params.minDisk;
|
|
2427
|
+
if (params.minRam !== void 0) searchParams["minRam"] = params.minRam;
|
|
2428
|
+
try {
|
|
2429
|
+
const raw = await import_ky7.default.get(url, {
|
|
2430
|
+
headers: this.authHeaders(),
|
|
2431
|
+
searchParams,
|
|
2432
|
+
retry: 0,
|
|
2433
|
+
timeout: DEFAULT_TIMEOUT_MS3
|
|
2434
|
+
}).json();
|
|
2435
|
+
if (params.detail) {
|
|
2436
|
+
if (!isFlavorDetailsResponse(raw)) {
|
|
2437
|
+
throw new NhnCloudCliError(
|
|
2438
|
+
"instance flavors --detail \uC751\uB2F5 \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 \u2014 vcpus\xB7ram\xB7disk \uD544\uB4DC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
2439
|
+
EXIT_API_ERROR
|
|
2440
|
+
);
|
|
2441
|
+
}
|
|
2442
|
+
return raw.flavors;
|
|
2443
|
+
}
|
|
2444
|
+
if (!isFlavorsResponse(raw)) {
|
|
2445
|
+
throw new NhnCloudCliError(
|
|
2446
|
+
"instance flavors \uC751\uB2F5 \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 \u2014 flavors \uBC30\uC5F4\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
2447
|
+
EXIT_API_ERROR
|
|
2448
|
+
);
|
|
2449
|
+
}
|
|
2450
|
+
return raw.flavors;
|
|
2451
|
+
} catch (err) {
|
|
2452
|
+
throw toNhnCloudCliError(err);
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2455
|
+
/**
|
|
2456
|
+
* 가용성 영역(availability zone) 목록을 조회한다 (GET /os-availability-zone).
|
|
2457
|
+
* zoneName·가용 여부(available)를 반환하며 페이지네이션·필터 없음.
|
|
2458
|
+
*/
|
|
2459
|
+
async listAvailabilityZones() {
|
|
2460
|
+
const url = `${this.computeEndpoint}/os-availability-zone`;
|
|
2461
|
+
try {
|
|
2462
|
+
const raw = await import_ky7.default.get(url, {
|
|
2463
|
+
headers: this.authHeaders(),
|
|
2464
|
+
retry: 0,
|
|
2465
|
+
timeout: DEFAULT_TIMEOUT_MS3
|
|
2466
|
+
}).json();
|
|
2467
|
+
if (!isAvailabilityZonesResponse(raw)) {
|
|
2468
|
+
throw new NhnCloudCliError(
|
|
2469
|
+
"instance availability-zones \uC751\uB2F5 \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 \u2014 availabilityZoneInfo \uBC30\uC5F4\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
2470
|
+
EXIT_API_ERROR
|
|
2471
|
+
);
|
|
2472
|
+
}
|
|
2473
|
+
return raw.availabilityZoneInfo;
|
|
2474
|
+
} catch (err) {
|
|
2475
|
+
throw toNhnCloudCliError(err);
|
|
2476
|
+
}
|
|
2477
|
+
}
|
|
2478
|
+
/** 키페어 목록을 조회한다 (GET /os-keypairs). 응답 원소의 한겹(keypair)을 풀어 반환. */
|
|
2479
|
+
async listKeypairs() {
|
|
2480
|
+
const url = `${this.computeEndpoint}/os-keypairs`;
|
|
2481
|
+
try {
|
|
2482
|
+
const raw = await import_ky7.default.get(url, { headers: this.authHeaders(), retry: 0, timeout: DEFAULT_TIMEOUT_MS3 }).json();
|
|
2483
|
+
if (!isKeypairsResponse(raw)) {
|
|
2484
|
+
throw new NhnCloudCliError(
|
|
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.",
|
|
2486
|
+
EXIT_API_ERROR
|
|
2487
|
+
);
|
|
2488
|
+
}
|
|
2489
|
+
return raw.keypairs.map((e) => e.keypair);
|
|
2490
|
+
} catch (err) {
|
|
2491
|
+
throw toNhnCloudCliError(err);
|
|
2492
|
+
}
|
|
2493
|
+
}
|
|
2494
|
+
/** 단일 키페어를 조회한다 (GET /os-keypairs/{name}). */
|
|
2495
|
+
async getKeypair(name) {
|
|
2496
|
+
const url = `${this.computeEndpoint}/os-keypairs/${encodeURIComponent(name)}`;
|
|
2497
|
+
try {
|
|
2498
|
+
const raw = await import_ky7.default.get(url, { headers: this.authHeaders(), retry: 0, timeout: DEFAULT_TIMEOUT_MS3 }).json();
|
|
2499
|
+
if (!isKeypairDetailResponse(raw)) {
|
|
2500
|
+
throw new NhnCloudCliError(
|
|
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.`,
|
|
2502
|
+
EXIT_API_ERROR
|
|
2503
|
+
);
|
|
2504
|
+
}
|
|
2505
|
+
return raw.keypair;
|
|
2506
|
+
} catch (err) {
|
|
2507
|
+
throw toNhnCloudCliError(err);
|
|
2508
|
+
}
|
|
2509
|
+
}
|
|
2510
|
+
/**
|
|
2511
|
+
* 키페어를 생성한다 (POST /os-keypairs).
|
|
2512
|
+
* publicKey 미지정이면 NHN 이 키쌍을 생성하고 응답 keypair 에 private_key 가 1회성으로 포함된다.
|
|
2513
|
+
* publicKey 지정이면 기존 공개키를 등록하고 private_key 는 응답에 없다.
|
|
2514
|
+
*/
|
|
2515
|
+
async createKeypair(params) {
|
|
2516
|
+
const url = `${this.computeEndpoint}/os-keypairs`;
|
|
2517
|
+
const keypairBody = { name: params.name };
|
|
2518
|
+
if (params.publicKey !== void 0) {
|
|
2519
|
+
keypairBody["public_key"] = params.publicKey;
|
|
2520
|
+
}
|
|
2521
|
+
let raw;
|
|
2522
|
+
try {
|
|
2523
|
+
raw = await import_ky7.default.post(url, {
|
|
2524
|
+
headers: this.authHeaders(),
|
|
2525
|
+
json: { keypair: keypairBody },
|
|
2526
|
+
retry: 0,
|
|
2527
|
+
timeout: DEFAULT_TIMEOUT_MS3
|
|
2528
|
+
}).json();
|
|
2529
|
+
} catch (err) {
|
|
2530
|
+
throw toNhnCloudCliError(err);
|
|
2531
|
+
}
|
|
2532
|
+
if (!isCreateKeypairResponse(raw)) {
|
|
2533
|
+
throw new NhnCloudCliError(
|
|
2534
|
+
"instance keypair create \uC751\uB2F5 \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 \u2014 keypair \uAC1D\uCCB4\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
2535
|
+
EXIT_API_ERROR
|
|
2536
|
+
);
|
|
2537
|
+
}
|
|
2538
|
+
const kp = raw.keypair;
|
|
2539
|
+
return {
|
|
2540
|
+
name: kp.name,
|
|
2541
|
+
public_key: kp.public_key,
|
|
2542
|
+
fingerprint: kp.fingerprint,
|
|
2543
|
+
user_id: kp.user_id ?? "",
|
|
2544
|
+
// 빈 문자열은 정의되지 않은 것과 동일 취급 — 빈 키 파일 저장/빈 줄 출력 방지.
|
|
2545
|
+
private_key: kp.private_key !== void 0 && kp.private_key.length > 0 ? kp.private_key : void 0
|
|
2546
|
+
};
|
|
2547
|
+
}
|
|
2548
|
+
/** 키페어를 삭제한다 (DELETE /os-keypairs/{name}, 202/204 무응답). */
|
|
2549
|
+
async deleteKeypair(name) {
|
|
2550
|
+
const url = `${this.computeEndpoint}/os-keypairs/${encodeURIComponent(name)}`;
|
|
2551
|
+
try {
|
|
2552
|
+
await import_ky7.default.delete(url, { headers: this.authHeaders(), retry: 0, timeout: DEFAULT_TIMEOUT_MS3 });
|
|
2553
|
+
} catch (err) {
|
|
2554
|
+
throw toNhnCloudCliError(err);
|
|
2555
|
+
}
|
|
2556
|
+
}
|
|
2557
|
+
/**
|
|
2558
|
+
* 이미지 목록을 조회한다 (GET /v2/images, Glance v2).
|
|
2559
|
+
* compute 와 다른 host(imageEndpoint)지만 같은 Keystone 토큰을 쓴다.
|
|
2560
|
+
* 한 페이지(기본 limit 25)만 반환한다 — next 가 있으면 호출부가 marker 로 이어 받는다.
|
|
2561
|
+
*/
|
|
2562
|
+
async listImages(params = {}) {
|
|
2563
|
+
const url = `${this.imageEndpoint}/images`;
|
|
2564
|
+
const searchParams = {};
|
|
2565
|
+
if (params.limit !== void 0) searchParams["limit"] = params.limit;
|
|
2566
|
+
if (params.marker !== void 0) searchParams["marker"] = params.marker;
|
|
2567
|
+
if (params.name !== void 0) searchParams["name"] = params.name;
|
|
2568
|
+
if (params.visibility !== void 0) searchParams["visibility"] = params.visibility;
|
|
2569
|
+
if (params.owner !== void 0) searchParams["owner"] = params.owner;
|
|
2570
|
+
if (params.status !== void 0) searchParams["status"] = params.status;
|
|
2571
|
+
try {
|
|
2572
|
+
const raw = await import_ky7.default.get(url, {
|
|
2573
|
+
headers: this.authHeaders(),
|
|
2574
|
+
searchParams,
|
|
2575
|
+
retry: 0,
|
|
2576
|
+
timeout: DEFAULT_TIMEOUT_MS3
|
|
2577
|
+
}).json();
|
|
2578
|
+
if (!isImagesResponse(raw)) {
|
|
2579
|
+
throw new NhnCloudCliError(
|
|
2580
|
+
"instance images \uC751\uB2F5 \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 \u2014 images \uBC30\uC5F4\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
2581
|
+
EXIT_API_ERROR
|
|
2582
|
+
);
|
|
2583
|
+
}
|
|
2584
|
+
return { images: raw.images, next: raw.next };
|
|
2585
|
+
} catch (err) {
|
|
2586
|
+
throw toNhnCloudCliError(err);
|
|
2587
|
+
}
|
|
2588
|
+
}
|
|
2589
|
+
/**
|
|
2590
|
+
* 인스턴스에 연결된 볼륨 목록을 조회한다 (GET .../os-volume_attachments).
|
|
2591
|
+
* Nova 표준 확장 — NHN Instance(Nova v2 호환, ADR-010). 실측 200 확인 (2026-06-11).
|
|
2592
|
+
*/
|
|
2593
|
+
async listVolumeAttachments(serverId) {
|
|
2594
|
+
const url = `${this.computeEndpoint}/servers/${encodeURIComponent(serverId)}/os-volume_attachments`;
|
|
2595
|
+
try {
|
|
2596
|
+
const raw = await import_ky7.default.get(url, { headers: this.authHeaders(), retry: 0, timeout: DEFAULT_TIMEOUT_MS3 }).json();
|
|
2597
|
+
if (!isVolumeAttachmentsResponse(raw)) {
|
|
2598
|
+
throw new NhnCloudCliError(
|
|
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.",
|
|
2600
|
+
EXIT_API_ERROR
|
|
2601
|
+
);
|
|
2602
|
+
}
|
|
2603
|
+
return raw.volumeAttachments;
|
|
2604
|
+
} catch (err) {
|
|
2605
|
+
throw toNhnCloudCliError(err);
|
|
2606
|
+
}
|
|
2607
|
+
}
|
|
2608
|
+
/**
|
|
2609
|
+
* 볼륨을 인스턴스에 연결한다 (POST .../os-volume_attachments).
|
|
2610
|
+
* 요청 body: { volumeAttachment: { volumeId } }. 실제 연결은 수동 QA 확정 (1-26).
|
|
2611
|
+
*/
|
|
2612
|
+
async attachVolume(serverId, volumeId) {
|
|
2613
|
+
const url = `${this.computeEndpoint}/servers/${encodeURIComponent(serverId)}/os-volume_attachments`;
|
|
2614
|
+
try {
|
|
2615
|
+
const res = await import_ky7.default.post(url, {
|
|
2616
|
+
headers: this.authHeaders(),
|
|
2617
|
+
json: { volumeAttachment: { volumeId } },
|
|
2618
|
+
retry: 0,
|
|
2619
|
+
timeout: DEFAULT_TIMEOUT_MS3
|
|
2620
|
+
});
|
|
2621
|
+
if (res.status === 202 || res.headers.get("content-length") === "0") {
|
|
2622
|
+
return { id: volumeId, volumeId, serverId, device: "" };
|
|
2623
|
+
}
|
|
2624
|
+
const raw = await res.json();
|
|
2625
|
+
if (!isVolumeAttachmentResponse(raw)) {
|
|
2626
|
+
throw new NhnCloudCliError(
|
|
2627
|
+
"instance volume attach \uC751\uB2F5\uC5D0 volumeAttachment \uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
2628
|
+
EXIT_API_ERROR
|
|
2629
|
+
);
|
|
2630
|
+
}
|
|
2631
|
+
return raw.volumeAttachment;
|
|
2632
|
+
} catch (err) {
|
|
2633
|
+
throw toNhnCloudCliError(err);
|
|
2634
|
+
}
|
|
2635
|
+
}
|
|
2636
|
+
/**
|
|
2637
|
+
* 볼륨 연결을 해제한다 (DELETE .../os-volume_attachments/{volumeId}, 202 무응답).
|
|
2638
|
+
* 실제 해제는 수동 QA 확정 (1-26).
|
|
2639
|
+
*/
|
|
2640
|
+
async detachVolume(serverId, volumeId) {
|
|
2641
|
+
const url = `${this.computeEndpoint}/servers/${encodeURIComponent(serverId)}/os-volume_attachments/${encodeURIComponent(volumeId)}`;
|
|
2642
|
+
try {
|
|
2643
|
+
await import_ky7.default.delete(url, { headers: this.authHeaders(), retry: 0, timeout: DEFAULT_TIMEOUT_MS3 });
|
|
2644
|
+
} catch (err) {
|
|
2645
|
+
throw toNhnCloudCliError(err);
|
|
2646
|
+
}
|
|
2647
|
+
}
|
|
2648
|
+
/**
|
|
2649
|
+
* 인스턴스가 ACTIVE 상태 + IP 1개 이상이 될 때까지 폴링한다.
|
|
2650
|
+
*
|
|
2651
|
+
* - status === "ACTIVE" + addresses 에 IP 1개 이상: 즉시 반환
|
|
2652
|
+
* - timeout 초과: 마지막 status 를 포함한 NhnCloudCliError(EXIT_API_ERROR)
|
|
2653
|
+
*/
|
|
2654
|
+
async waitForActive(id, opts) {
|
|
2655
|
+
const intervalMs = opts.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
2656
|
+
const deadline = Date.now() + opts.timeoutMs;
|
|
2657
|
+
let lastServer = null;
|
|
2658
|
+
while (Date.now() < deadline) {
|
|
2659
|
+
const server = await this.get(id);
|
|
2660
|
+
lastServer = server;
|
|
2661
|
+
if (server.status === "ACTIVE" && hasIpAddress(server)) {
|
|
2662
|
+
return server;
|
|
2663
|
+
}
|
|
2664
|
+
const remaining = deadline - Date.now();
|
|
2665
|
+
if (remaining <= 0) break;
|
|
2666
|
+
await new Promise((resolve) => setTimeout(resolve, Math.min(intervalMs, remaining)));
|
|
2667
|
+
}
|
|
2668
|
+
const lastStatus = lastServer ? lastServer.status : "unknown";
|
|
2669
|
+
throw new NhnCloudCliError(
|
|
2670
|
+
`\uC778\uC2A4\uD134\uC2A4 ${id} \uAC00 ACTIVE \uAC00 \uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4 (\uB9C8\uC9C0\uB9C9 \uC0C1\uD0DC: ${lastStatus}). --wait \uD0C0\uC784\uC544\uC6C3(${Math.round(opts.timeoutMs / 1e3)}\uCD08) \uCD08\uACFC.`,
|
|
2671
|
+
EXIT_API_ERROR
|
|
2672
|
+
);
|
|
2673
|
+
}
|
|
2674
|
+
};
|
|
2675
|
+
|
|
2676
|
+
// src/commands/instance/helpers.ts
|
|
2677
|
+
async function resolveInstanceClient(opts) {
|
|
2678
|
+
const profileName = await resolveProfileName(opts.profile);
|
|
2679
|
+
const iaas = await getIaasCredential(profileName);
|
|
2680
|
+
const effectiveIaas = opts.region ? { ...iaas, region: opts.region } : iaas;
|
|
2681
|
+
const { tokenId, computeEndpoint, imageEndpoint } = await getIaasToken(profileName, effectiveIaas);
|
|
2682
|
+
return { client: new InstanceClient(tokenId, computeEndpoint, imageEndpoint), profileName };
|
|
2683
|
+
}
|
|
2684
|
+
|
|
2685
|
+
// src/commands/instance/list.ts
|
|
2686
|
+
function getIps(server) {
|
|
2687
|
+
return Object.values(server.addresses).flat().map((a) => a.addr).join(", ");
|
|
2688
|
+
}
|
|
2689
|
+
var listCommand = new import_commander13.Command("list").description("\uC778\uC2A4\uD134\uC2A4 \uBAA9\uB85D\uC744 \uC870\uD68C\uD55C\uB2E4").option("--region <region>", "region override (\uAE30\uBCF8: iaas \uC790\uACA9\uC99D\uBA85\uC758 region)").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (_opts, cmd) => {
|
|
2690
|
+
const opts = cmd.optsWithGlobals();
|
|
2691
|
+
const { client } = await resolveInstanceClient(opts);
|
|
2692
|
+
startSpinner("\uC778\uC2A4\uD134\uC2A4 \uBAA9\uB85D \uC870\uD68C \uC911...");
|
|
2693
|
+
let servers;
|
|
2694
|
+
try {
|
|
2695
|
+
servers = await client.list();
|
|
2696
|
+
} catch (err) {
|
|
2697
|
+
stopSpinner(false);
|
|
2698
|
+
throw err;
|
|
2699
|
+
}
|
|
2700
|
+
stopSpinner(true);
|
|
2701
|
+
output(opts, {
|
|
2702
|
+
headers: ["id", "name", "status", "IPs", "flavor"],
|
|
2703
|
+
rows: servers.map((s) => [s.id, s.name, s.status, getIps(s), s.flavor.id]),
|
|
2704
|
+
raw: servers,
|
|
2705
|
+
ids: servers.map((s) => s.id)
|
|
2706
|
+
});
|
|
2707
|
+
});
|
|
2708
|
+
|
|
2709
|
+
// src/commands/volume/list.ts
|
|
2710
|
+
var import_commander14 = require("commander");
|
|
2711
|
+
|
|
2712
|
+
// src/services/blockstorage/client.ts
|
|
2713
|
+
var import_ky8 = __toESM(require("ky"));
|
|
2714
|
+
var DEFAULT_TIMEOUT_MS4 = 3e4;
|
|
2715
|
+
function isVolume(val) {
|
|
2716
|
+
if (typeof val !== "object" || val === null) return false;
|
|
2717
|
+
const obj = val;
|
|
2718
|
+
return typeof obj["id"] === "string" && // Cinder 는 --name 미지정 시 null — null 인 볼륨 하나가 list 전체를 거부하지 않게 허용 (isImage 선례).
|
|
2719
|
+
(typeof obj["name"] === "string" || obj["name"] === null) && typeof obj["size"] === "number" && typeof obj["status"] === "string" && Array.isArray(obj["attachments"]);
|
|
2720
|
+
}
|
|
2721
|
+
function isVolumesResponse(val) {
|
|
2722
|
+
if (typeof val !== "object" || val === null) return false;
|
|
2723
|
+
const obj = val;
|
|
2724
|
+
return Array.isArray(obj["volumes"]) && obj["volumes"].every(isVolume);
|
|
2725
|
+
}
|
|
2726
|
+
function isVolumeResponse(val) {
|
|
2727
|
+
if (typeof val !== "object" || val === null) return false;
|
|
2728
|
+
const obj = val;
|
|
2729
|
+
return isVolume(obj["volume"]);
|
|
2730
|
+
}
|
|
2731
|
+
var BlockStorageClient = class {
|
|
2732
|
+
tokenId;
|
|
2733
|
+
endpoint;
|
|
2734
|
+
// blockStorageEndpoint (/v2/{tenantId} 까지 포함)
|
|
2735
|
+
constructor(tokenId, blockStorageEndpoint) {
|
|
2736
|
+
this.tokenId = tokenId;
|
|
2737
|
+
this.endpoint = blockStorageEndpoint;
|
|
2738
|
+
}
|
|
2739
|
+
authHeaders() {
|
|
2740
|
+
return { "X-Auth-Token": this.tokenId };
|
|
2741
|
+
}
|
|
2742
|
+
async list(params) {
|
|
2743
|
+
const url = `${this.endpoint}/volumes`;
|
|
2744
|
+
const searchParams = {};
|
|
2745
|
+
if (params?.sort !== void 0) searchParams["sort"] = params.sort;
|
|
2746
|
+
if (params?.limit !== void 0) searchParams["limit"] = params.limit;
|
|
2747
|
+
if (params?.offset !== void 0) searchParams["offset"] = params.offset;
|
|
2748
|
+
if (params?.marker !== void 0) searchParams["marker"] = params.marker;
|
|
2749
|
+
try {
|
|
2750
|
+
const raw = await import_ky8.default.get(url, { headers: this.authHeaders(), searchParams, retry: 0, timeout: DEFAULT_TIMEOUT_MS4 }).json();
|
|
2751
|
+
if (!isVolumesResponse(raw)) {
|
|
2752
|
+
throw new NhnCloudCliError(
|
|
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.",
|
|
2754
|
+
EXIT_API_ERROR
|
|
2755
|
+
);
|
|
2756
|
+
}
|
|
2757
|
+
return raw.volumes;
|
|
2758
|
+
} catch (err) {
|
|
2759
|
+
throw toNhnCloudCliError(err);
|
|
2760
|
+
}
|
|
2761
|
+
}
|
|
2762
|
+
async get(id) {
|
|
2763
|
+
const url = `${this.endpoint}/volumes/${encodeURIComponent(id)}`;
|
|
2764
|
+
try {
|
|
2765
|
+
const raw = await import_ky8.default.get(url, { headers: this.authHeaders(), retry: 0, timeout: DEFAULT_TIMEOUT_MS4 }).json();
|
|
2766
|
+
if (!isVolumeResponse(raw)) {
|
|
2767
|
+
throw new NhnCloudCliError(
|
|
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.",
|
|
2769
|
+
EXIT_API_ERROR
|
|
2770
|
+
);
|
|
2771
|
+
}
|
|
2772
|
+
return raw.volume;
|
|
2773
|
+
} catch (err) {
|
|
2774
|
+
throw toNhnCloudCliError(err);
|
|
2775
|
+
}
|
|
2776
|
+
}
|
|
2777
|
+
async create(params) {
|
|
2778
|
+
const url = `${this.endpoint}/volumes`;
|
|
2779
|
+
const volumeBody = { size: params.size };
|
|
2780
|
+
if (params.name !== void 0) volumeBody["name"] = params.name;
|
|
2781
|
+
if (params.description !== void 0) volumeBody["description"] = params.description;
|
|
2782
|
+
if (params.volume_type !== void 0) volumeBody["volume_type"] = params.volume_type;
|
|
2783
|
+
if (params.snapshot_id !== void 0) volumeBody["snapshot_id"] = params.snapshot_id;
|
|
2784
|
+
try {
|
|
2785
|
+
const raw = await import_ky8.default.post(url, {
|
|
2786
|
+
headers: this.authHeaders(),
|
|
2787
|
+
json: { volume: volumeBody },
|
|
2788
|
+
retry: 0,
|
|
2789
|
+
timeout: DEFAULT_TIMEOUT_MS4
|
|
2790
|
+
}).json();
|
|
2791
|
+
if (!isVolumeResponse(raw)) {
|
|
2792
|
+
throw new NhnCloudCliError(
|
|
2793
|
+
"volume create \uC751\uB2F5\uC5D0 volume \uAC1D\uCCB4\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
2794
|
+
EXIT_API_ERROR
|
|
2795
|
+
);
|
|
2796
|
+
}
|
|
2797
|
+
return raw.volume;
|
|
2798
|
+
} catch (err) {
|
|
2799
|
+
throw toNhnCloudCliError(err);
|
|
2800
|
+
}
|
|
2801
|
+
}
|
|
2802
|
+
};
|
|
2803
|
+
|
|
2804
|
+
// src/commands/volume/helpers.ts
|
|
2805
|
+
async function resolveVolumeClient(opts) {
|
|
2806
|
+
const profileName = await resolveProfileName(opts.profile);
|
|
2807
|
+
const iaas = await getIaasCredential(profileName);
|
|
2808
|
+
const effectiveIaas = opts.region ? { ...iaas, region: opts.region } : iaas;
|
|
2809
|
+
const { tokenId, blockStorageEndpoint } = await getIaasToken(profileName, effectiveIaas);
|
|
2810
|
+
return { client: new BlockStorageClient(tokenId, blockStorageEndpoint), profileName };
|
|
2811
|
+
}
|
|
2812
|
+
|
|
2813
|
+
// src/commands/volume/list.ts
|
|
2814
|
+
function parsePositiveInt4(value, flag) {
|
|
2815
|
+
if (!/^[1-9]\d*$/.test(value)) {
|
|
2816
|
+
throw new NhnCloudCliError(
|
|
2817
|
+
`${flag} \uB294 \uC591\uC758 \uC815\uC218\uC5EC\uC57C \uD569\uB2C8\uB2E4: ${JSON.stringify(value)}`,
|
|
2818
|
+
EXIT_PARAM_ERROR
|
|
2819
|
+
);
|
|
2820
|
+
}
|
|
2821
|
+
return Number(value);
|
|
2822
|
+
}
|
|
2823
|
+
var listCommand2 = new import_commander14.Command("list").description("\uBCFC\uB968 \uBAA9\uB85D\uC744 \uC870\uD68C\uD55C\uB2E4").option("--sort <key:dir>", "\uC815\uB82C \uAE30\uC900 (\uC608: created_at:desc)").option("--limit <n>", "\uCD5C\uB300 \uBC18\uD658 \uAC1C\uC218").option("--offset <n>", "\uD398\uC774\uC9C0 \uC2DC\uC791 \uC624\uD504\uC14B").option("--marker <id>", "\uD398\uC774\uC9C0\uB124\uC774\uC158 \uB9C8\uCEE4 \uBCFC\uB968 ID").option("--region <region>", "region override (\uAE30\uBCF8: iaas \uC790\uACA9\uC99D\uBA85\uC758 region)").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (_opts, cmd) => {
|
|
2824
|
+
const opts = cmd.optsWithGlobals();
|
|
2825
|
+
const limitNum = opts.limit !== void 0 ? parsePositiveInt4(opts.limit, "--limit") : void 0;
|
|
2826
|
+
const offsetNum = opts.offset !== void 0 ? parsePositiveInt4(opts.offset, "--offset") : void 0;
|
|
2827
|
+
const { client } = await resolveVolumeClient(opts);
|
|
2828
|
+
startSpinner("\uBCFC\uB968 \uBAA9\uB85D \uC870\uD68C \uC911...");
|
|
2829
|
+
let volumes;
|
|
2830
|
+
try {
|
|
2831
|
+
volumes = await client.list({
|
|
2832
|
+
sort: opts.sort,
|
|
2833
|
+
limit: limitNum,
|
|
2834
|
+
offset: offsetNum,
|
|
2835
|
+
marker: opts.marker
|
|
2836
|
+
});
|
|
2837
|
+
} catch (err) {
|
|
2838
|
+
stopSpinner(false);
|
|
2839
|
+
throw err;
|
|
2840
|
+
}
|
|
2841
|
+
stopSpinner(true);
|
|
2842
|
+
output(opts, {
|
|
2843
|
+
headers: ["id", "name", "size", "status", "type"],
|
|
2844
|
+
rows: volumes.map((v) => [
|
|
2845
|
+
v.id,
|
|
2846
|
+
v.name ?? "",
|
|
2847
|
+
String(v.size),
|
|
2848
|
+
v.status,
|
|
2849
|
+
v.volume_type ?? ""
|
|
2850
|
+
]),
|
|
2851
|
+
raw: volumes,
|
|
2852
|
+
ids: volumes.map((v) => v.id)
|
|
2853
|
+
});
|
|
2854
|
+
});
|
|
2855
|
+
|
|
2856
|
+
// src/commands/volume/get.ts
|
|
2857
|
+
var import_commander15 = require("commander");
|
|
2858
|
+
var getCommand = new import_commander15.Command("get").description("\uB2E8\uC77C \uBCFC\uB968 \uC0C1\uD0DC\uB97C \uC870\uD68C\uD55C\uB2E4").argument("<id>", "\uBCFC\uB968 ID").option("--region <region>", "region override (\uAE30\uBCF8: iaas \uC790\uACA9\uC99D\uBA85\uC758 region)").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (id, _opts, cmd) => {
|
|
2859
|
+
const opts = cmd.optsWithGlobals();
|
|
2860
|
+
const { client } = await resolveVolumeClient(opts);
|
|
2861
|
+
startSpinner("\uBCFC\uB968 \uC870\uD68C \uC911...");
|
|
2862
|
+
let volume;
|
|
2863
|
+
try {
|
|
2864
|
+
volume = await client.get(id);
|
|
2865
|
+
} catch (err) {
|
|
2866
|
+
stopSpinner(false);
|
|
2867
|
+
throw err;
|
|
2868
|
+
}
|
|
2869
|
+
stopSpinner(true);
|
|
2870
|
+
const attachmentSummary = Array.isArray(volume.attachments) ? volume.attachments.filter((a) => typeof a === "object" && a !== null).map((a) => String(a.server_id)).join(", ") : "";
|
|
2871
|
+
const rows = [
|
|
2872
|
+
["id", volume.id],
|
|
2873
|
+
["name", volume.name ?? ""],
|
|
2874
|
+
["size", String(volume.size)],
|
|
2875
|
+
["status", volume.status],
|
|
2876
|
+
["volume_type", volume.volume_type ?? ""],
|
|
2877
|
+
["created_at", volume.created_at],
|
|
2878
|
+
["attachments", attachmentSummary]
|
|
2879
|
+
];
|
|
2880
|
+
output(opts, {
|
|
2881
|
+
headers: ["field", "value"],
|
|
2882
|
+
rows,
|
|
2883
|
+
raw: volume,
|
|
2884
|
+
ids: [volume.id]
|
|
2885
|
+
});
|
|
2886
|
+
});
|
|
2887
|
+
|
|
2888
|
+
// src/commands/volume/create.ts
|
|
2889
|
+
var import_commander16 = require("commander");
|
|
2890
|
+
var import_chalk3 = __toESM(require("chalk"));
|
|
2891
|
+
var createCommand = new import_commander16.Command("create").description("\uBCFC\uB968\uC744 \uBC1C\uAE09\uD55C\uB2E4").requiredOption("--size <gb>", "\uBCFC\uB968 \uD06C\uAE30(GB) \u2014 \uD544\uC218").option("--name <name>", "\uBCFC\uB968 \uC774\uB984").option("--description <text>", "\uBCFC\uB968 \uC124\uBA85").option("--volume-type <type>", "\uBCFC\uB968 \uD0C0\uC785").option("--snapshot-id <id>", "\uC2A4\uB0C5\uC0F7 ID (\uC2A4\uB0C5\uC0F7\uC5D0\uC11C \uBCFC\uB968 \uC0DD\uC131)").option("--region <region>", "region override (\uAE30\uBCF8: iaas \uC790\uACA9\uC99D\uBA85\uC758 region)").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (_opts, cmd) => {
|
|
2892
|
+
const opts = cmd.optsWithGlobals();
|
|
2893
|
+
if (!/^[1-9]\d*$/.test(opts.size)) {
|
|
2894
|
+
throw new NhnCloudCliError(
|
|
2895
|
+
`--size \uB294 \uC591\uC758 \uC815\uC218(GB)\uC5EC\uC57C \uD569\uB2C8\uB2E4: ${JSON.stringify(opts.size)}`,
|
|
2896
|
+
EXIT_PARAM_ERROR
|
|
2897
|
+
);
|
|
2898
|
+
}
|
|
2899
|
+
const sizeNum = Number(opts.size);
|
|
2900
|
+
const { client } = await resolveVolumeClient(opts);
|
|
2901
|
+
startSpinner("\uBCFC\uB968 \uBC1C\uAE09 \uC911...");
|
|
2902
|
+
let volume;
|
|
2903
|
+
try {
|
|
2904
|
+
volume = await client.create({
|
|
2905
|
+
size: sizeNum,
|
|
2906
|
+
name: opts.name,
|
|
2907
|
+
description: opts.description,
|
|
2908
|
+
volume_type: opts.volumeType,
|
|
2909
|
+
snapshot_id: opts.snapshotId
|
|
2910
|
+
});
|
|
2911
|
+
} catch (err) {
|
|
2912
|
+
stopSpinner(false);
|
|
2913
|
+
throw err;
|
|
2914
|
+
}
|
|
2915
|
+
stopSpinner(true);
|
|
2916
|
+
process.stderr.write(import_chalk3.default.green(`\uBCFC\uB968 \uBC1C\uAE09 \uC694\uCCAD \uC644\uB8CC (id: ${volume.id}, status: ${volume.status})
|
|
2917
|
+
`));
|
|
2918
|
+
const rows = [
|
|
2919
|
+
["id", volume.id],
|
|
2920
|
+
["name", volume.name ?? ""],
|
|
2921
|
+
["size", String(volume.size)],
|
|
2922
|
+
["status", volume.status],
|
|
2923
|
+
["volume_type", volume.volume_type ?? ""],
|
|
2924
|
+
["created_at", volume.created_at]
|
|
2925
|
+
];
|
|
2926
|
+
output(opts, {
|
|
2927
|
+
headers: ["field", "value"],
|
|
2928
|
+
rows,
|
|
2929
|
+
raw: volume,
|
|
2930
|
+
ids: [volume.id]
|
|
2931
|
+
});
|
|
2932
|
+
});
|
|
2933
|
+
|
|
2934
|
+
// src/commands/network/list.ts
|
|
2935
|
+
var import_commander17 = require("commander");
|
|
2936
|
+
|
|
2937
|
+
// src/services/network/client.ts
|
|
2938
|
+
var import_ky9 = __toESM(require("ky"));
|
|
2939
|
+
var DEFAULT_TIMEOUT_MS5 = 3e4;
|
|
2940
|
+
function isVpc(val) {
|
|
2941
|
+
if (typeof val !== "object" || val === null) return false;
|
|
2942
|
+
const obj = val;
|
|
2943
|
+
return typeof obj["id"] === "string" && typeof obj["name"] === "string" && typeof obj["cidrv4"] === "string" && typeof obj["state"] === "string" && typeof obj["router:external"] === "boolean";
|
|
2944
|
+
}
|
|
2945
|
+
function isVpcsResponse(val) {
|
|
2946
|
+
if (typeof val !== "object" || val === null) return false;
|
|
2947
|
+
const obj = val;
|
|
2948
|
+
return Array.isArray(obj["vpcs"]) && obj["vpcs"].every(isVpc);
|
|
2949
|
+
}
|
|
2950
|
+
function isSubnet(val) {
|
|
2951
|
+
if (typeof val !== "object" || val === null) return false;
|
|
2952
|
+
const obj = val;
|
|
2953
|
+
return typeof obj["id"] === "string" && typeof obj["cidr"] === "string" && typeof obj["vpc_id"] === "string" && typeof obj["gateway"] === "string" && typeof obj["available_ip_count"] === "number";
|
|
2954
|
+
}
|
|
2955
|
+
function isSubnetsResponse(val) {
|
|
2956
|
+
if (typeof val !== "object" || val === null) return false;
|
|
2957
|
+
const obj = val;
|
|
2958
|
+
return Array.isArray(obj["vpcsubnets"]) && obj["vpcsubnets"].every(isSubnet);
|
|
2959
|
+
}
|
|
2960
|
+
function isFloatingIp(val) {
|
|
2961
|
+
if (typeof val !== "object" || val === null) return false;
|
|
2962
|
+
const obj = val;
|
|
2963
|
+
return typeof obj["id"] === "string" && typeof obj["floating_ip_address"] === "string" && typeof obj["status"] === "string" && typeof obj["floating_network_id"] === "string" && (obj["port_id"] === null || typeof obj["port_id"] === "string") && (obj["fixed_ip_address"] === null || typeof obj["fixed_ip_address"] === "string");
|
|
2964
|
+
}
|
|
2965
|
+
function isFloatingIpsResponse(val) {
|
|
2966
|
+
if (typeof val !== "object" || val === null) return false;
|
|
2967
|
+
const obj = val;
|
|
2968
|
+
return Array.isArray(obj["floatingips"]) && obj["floatingips"].every(isFloatingIp);
|
|
2969
|
+
}
|
|
2970
|
+
function isFloatingIpResponse(val) {
|
|
2971
|
+
if (typeof val !== "object" || val === null) return false;
|
|
2972
|
+
const obj = val;
|
|
2973
|
+
return isFloatingIp(obj["floatingip"]);
|
|
2974
|
+
}
|
|
2975
|
+
var NetworkClient = class {
|
|
2976
|
+
tokenId;
|
|
2977
|
+
networkEndpoint;
|
|
2978
|
+
constructor(tokenId, networkEndpoint) {
|
|
2979
|
+
this.tokenId = tokenId;
|
|
2980
|
+
this.networkEndpoint = networkEndpoint;
|
|
2981
|
+
}
|
|
2982
|
+
authHeaders() {
|
|
2983
|
+
return { "X-Auth-Token": this.tokenId };
|
|
2984
|
+
}
|
|
2985
|
+
/**
|
|
2986
|
+
* VPC 목록을 조회한다 (GET /v2.0/vpcs, NHN VPC).
|
|
2987
|
+
* instance 와 다른 host(networkEndpoint)지만 같은 Keystone 토큰을 쓴다.
|
|
2988
|
+
*/
|
|
2989
|
+
async listVpcs() {
|
|
2990
|
+
const url = `${this.networkEndpoint}/vpcs`;
|
|
2991
|
+
try {
|
|
2992
|
+
const raw = await import_ky9.default.get(url, {
|
|
2993
|
+
headers: this.authHeaders(),
|
|
2994
|
+
retry: 0,
|
|
2995
|
+
timeout: DEFAULT_TIMEOUT_MS5
|
|
2996
|
+
}).json();
|
|
2997
|
+
if (!isVpcsResponse(raw)) {
|
|
2998
|
+
throw new NhnCloudCliError(
|
|
2999
|
+
"network list \uC751\uB2F5 \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 \u2014 vpcs \uBC30\uC5F4\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
3000
|
+
EXIT_API_ERROR
|
|
3001
|
+
);
|
|
3002
|
+
}
|
|
3003
|
+
return raw.vpcs;
|
|
3004
|
+
} catch (err) {
|
|
3005
|
+
throw toNhnCloudCliError(err);
|
|
3006
|
+
}
|
|
3007
|
+
}
|
|
3008
|
+
/**
|
|
3009
|
+
* 서브넷 목록을 조회한다 (GET /v2.0/vpcsubnets, NHN VPC).
|
|
3010
|
+
*/
|
|
3011
|
+
async listSubnets() {
|
|
3012
|
+
const url = `${this.networkEndpoint}/vpcsubnets`;
|
|
3013
|
+
try {
|
|
3014
|
+
const raw = await import_ky9.default.get(url, {
|
|
3015
|
+
headers: this.authHeaders(),
|
|
3016
|
+
retry: 0,
|
|
3017
|
+
timeout: DEFAULT_TIMEOUT_MS5
|
|
3018
|
+
}).json();
|
|
3019
|
+
if (!isSubnetsResponse(raw)) {
|
|
3020
|
+
throw new NhnCloudCliError(
|
|
3021
|
+
"network subnet list \uC751\uB2F5 \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 \u2014 vpcsubnets \uBC30\uC5F4\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
3022
|
+
EXIT_API_ERROR
|
|
3023
|
+
);
|
|
3024
|
+
}
|
|
3025
|
+
return raw.vpcsubnets;
|
|
3026
|
+
} catch (err) {
|
|
3027
|
+
throw toNhnCloudCliError(err);
|
|
3028
|
+
}
|
|
3029
|
+
}
|
|
3030
|
+
/** Floating IP 목록을 조회한다 (GET /v2.0/floatingips). */
|
|
3031
|
+
async listFloatingIps() {
|
|
3032
|
+
const url = `${this.networkEndpoint}/floatingips`;
|
|
3033
|
+
try {
|
|
3034
|
+
const raw = await import_ky9.default.get(url, { headers: this.authHeaders(), retry: 0, timeout: DEFAULT_TIMEOUT_MS5 }).json();
|
|
3035
|
+
if (!isFloatingIpsResponse(raw)) {
|
|
3036
|
+
throw new NhnCloudCliError(
|
|
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.",
|
|
3038
|
+
EXIT_API_ERROR
|
|
3039
|
+
);
|
|
3040
|
+
}
|
|
3041
|
+
return raw.floatingips;
|
|
3042
|
+
} catch (err) {
|
|
3043
|
+
throw toNhnCloudCliError(err);
|
|
3044
|
+
}
|
|
3045
|
+
}
|
|
3046
|
+
/** Floating IP 를 발급한다 (POST /v2.0/floatingips). */
|
|
3047
|
+
async createFloatingIp(params) {
|
|
3048
|
+
const url = `${this.networkEndpoint}/floatingips`;
|
|
3049
|
+
try {
|
|
3050
|
+
const raw = await import_ky9.default.post(url, {
|
|
3051
|
+
headers: this.authHeaders(),
|
|
3052
|
+
json: { floatingip: { floating_network_id: params.floating_network_id } },
|
|
3053
|
+
retry: 0,
|
|
3054
|
+
timeout: DEFAULT_TIMEOUT_MS5
|
|
3055
|
+
}).json();
|
|
3056
|
+
if (!isFloatingIpResponse(raw)) {
|
|
3057
|
+
throw new NhnCloudCliError(
|
|
3058
|
+
"floatingip create \uC751\uB2F5 \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 \u2014 floatingip \uAC1D\uCCB4\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
3059
|
+
EXIT_API_ERROR
|
|
3060
|
+
);
|
|
3061
|
+
}
|
|
3062
|
+
return raw.floatingip;
|
|
3063
|
+
} catch (err) {
|
|
3064
|
+
throw toNhnCloudCliError(err);
|
|
3065
|
+
}
|
|
3066
|
+
}
|
|
3067
|
+
/** Floating IP 를 삭제한다 (DELETE /v2.0/floatingips/{id}, 무본문). */
|
|
3068
|
+
async deleteFloatingIp(id) {
|
|
3069
|
+
const url = `${this.networkEndpoint}/floatingips/${encodeURIComponent(id)}`;
|
|
3070
|
+
try {
|
|
3071
|
+
await import_ky9.default.delete(url, { headers: this.authHeaders(), retry: 0, timeout: DEFAULT_TIMEOUT_MS5 });
|
|
3072
|
+
} catch (err) {
|
|
3073
|
+
throw toNhnCloudCliError(err);
|
|
3074
|
+
}
|
|
3075
|
+
}
|
|
3076
|
+
/**
|
|
3077
|
+
* 외부(external) VPC id 를 찾는다 — create 의 floating_network_id 기본 소스.
|
|
3078
|
+
* `router:external` 은 콜론 포함 리터럴 키 — bracket 접근 필수.
|
|
3079
|
+
* external VPC 가 둘 이상이면 첫 매칭을 반환한다.
|
|
3080
|
+
* 사용자는 `--network <id>` 로 명시 지정 가능하므로 create 의 stderr 에 그 사실을 안내한다.
|
|
3081
|
+
*/
|
|
3082
|
+
async findExternalNetworkId() {
|
|
3083
|
+
const url = `${this.networkEndpoint}/vpcs`;
|
|
3084
|
+
try {
|
|
3085
|
+
const raw = await import_ky9.default.get(url, {
|
|
3086
|
+
headers: this.authHeaders(),
|
|
3087
|
+
searchParams: { "router:external": "true" },
|
|
3088
|
+
retry: 0,
|
|
3089
|
+
timeout: DEFAULT_TIMEOUT_MS5
|
|
3090
|
+
}).json();
|
|
3091
|
+
if (typeof raw !== "object" || raw === null) return null;
|
|
3092
|
+
const vpcs = raw["vpcs"];
|
|
3093
|
+
if (!Array.isArray(vpcs)) return null;
|
|
3094
|
+
for (const v of vpcs) {
|
|
3095
|
+
if (typeof v !== "object" || v === null) continue;
|
|
3096
|
+
const obj = v;
|
|
3097
|
+
if (obj["router:external"] === true && typeof obj["id"] === "string") {
|
|
3098
|
+
return obj["id"];
|
|
3099
|
+
}
|
|
3100
|
+
}
|
|
3101
|
+
return null;
|
|
3102
|
+
} catch (err) {
|
|
3103
|
+
throw toNhnCloudCliError(err);
|
|
3104
|
+
}
|
|
3105
|
+
}
|
|
3106
|
+
};
|
|
3107
|
+
|
|
3108
|
+
// src/commands/network/helpers.ts
|
|
3109
|
+
async function resolveNetworkClient(opts) {
|
|
3110
|
+
const profileName = await resolveProfileName(opts.profile);
|
|
3111
|
+
const iaas = await getIaasCredential(profileName);
|
|
3112
|
+
const effectiveIaas = opts.region ? { ...iaas, region: opts.region } : iaas;
|
|
3113
|
+
const { tokenId, networkEndpoint } = await getIaasToken(profileName, effectiveIaas);
|
|
3114
|
+
return { client: new NetworkClient(tokenId, networkEndpoint), profileName };
|
|
3115
|
+
}
|
|
3116
|
+
|
|
3117
|
+
// src/commands/network/list.ts
|
|
3118
|
+
var listCommand3 = new import_commander17.Command("list").description("VPC \uBAA9\uB85D\uC744 \uC870\uD68C\uD55C\uB2E4 (\uC804\uCCB4 \uD544\uB4DC\uB294 --json)").option("--region <region>", "region override (\uAE30\uBCF8: iaas \uC790\uACA9\uC99D\uBA85\uC758 region)").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (_opts, cmd) => {
|
|
3119
|
+
const opts = cmd.optsWithGlobals();
|
|
3120
|
+
const { client } = await resolveNetworkClient(opts);
|
|
3121
|
+
startSpinner("VPC \uBAA9\uB85D \uC870\uD68C \uC911...");
|
|
3122
|
+
let vpcs;
|
|
3123
|
+
try {
|
|
3124
|
+
vpcs = await client.listVpcs();
|
|
3125
|
+
} catch (err) {
|
|
3126
|
+
stopSpinner(false);
|
|
3127
|
+
throw err;
|
|
3128
|
+
}
|
|
3129
|
+
stopSpinner(true);
|
|
3130
|
+
output(opts, {
|
|
3131
|
+
headers: ["id", "name", "cidrv4", "state", "external"],
|
|
3132
|
+
rows: vpcs.map((v) => [v.id, v.name, v.cidrv4, v.state, String(v["router:external"])]),
|
|
3133
|
+
raw: vpcs,
|
|
3134
|
+
ids: vpcs.map((v) => v.id)
|
|
3135
|
+
});
|
|
3136
|
+
});
|
|
3137
|
+
|
|
3138
|
+
// src/commands/network/subnet.ts
|
|
3139
|
+
var import_commander18 = require("commander");
|
|
3140
|
+
var subnetListCommand = new import_commander18.Command("list").description("\uC11C\uBE0C\uB137 \uBAA9\uB85D\uC744 \uC870\uD68C\uD55C\uB2E4 (\uC804\uCCB4 \uD544\uB4DC\uB294 --json)").option("--region <region>", "region override (\uAE30\uBCF8: iaas \uC790\uACA9\uC99D\uBA85\uC758 region)").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (_opts, cmd) => {
|
|
3141
|
+
const opts = cmd.optsWithGlobals();
|
|
3142
|
+
const { client } = await resolveNetworkClient(opts);
|
|
3143
|
+
startSpinner("\uC11C\uBE0C\uB137 \uBAA9\uB85D \uC870\uD68C \uC911...");
|
|
3144
|
+
let subnets;
|
|
3145
|
+
try {
|
|
3146
|
+
subnets = await client.listSubnets();
|
|
3147
|
+
} catch (err) {
|
|
3148
|
+
stopSpinner(false);
|
|
3149
|
+
throw err;
|
|
3150
|
+
}
|
|
3151
|
+
stopSpinner(true);
|
|
3152
|
+
output(opts, {
|
|
3153
|
+
headers: ["id", "cidr", "vpc_id", "gateway", "available_ip"],
|
|
3154
|
+
rows: subnets.map((s) => [
|
|
3155
|
+
s.id,
|
|
3156
|
+
s.cidr,
|
|
3157
|
+
s.vpc_id,
|
|
3158
|
+
s.gateway,
|
|
3159
|
+
String(s.available_ip_count)
|
|
3160
|
+
]),
|
|
3161
|
+
raw: subnets,
|
|
3162
|
+
ids: subnets.map((s) => s.id)
|
|
3163
|
+
});
|
|
3164
|
+
});
|
|
3165
|
+
var subnetCommand = new import_commander18.Command("subnet").description("\uC11C\uBE0C\uB137 \uAD00\uB828 \uBA85\uB839").addCommand(subnetListCommand);
|
|
3166
|
+
|
|
3167
|
+
// src/commands/floatingip/list.ts
|
|
3168
|
+
var import_commander19 = require("commander");
|
|
3169
|
+
var listCommand4 = new import_commander19.Command("list").description("Floating IP \uBAA9\uB85D\uC744 \uC870\uD68C\uD55C\uB2E4").option("--region <region>", "region override (\uAE30\uBCF8: iaas \uC790\uACA9\uC99D\uBA85\uC758 region)").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (_opts, cmd) => {
|
|
3170
|
+
const opts = cmd.optsWithGlobals();
|
|
3171
|
+
const { client } = await resolveNetworkClient(opts);
|
|
3172
|
+
startSpinner("Floating IP \uBAA9\uB85D \uC870\uD68C \uC911...");
|
|
3173
|
+
let fips;
|
|
3174
|
+
try {
|
|
3175
|
+
fips = await client.listFloatingIps();
|
|
3176
|
+
} catch (err) {
|
|
3177
|
+
stopSpinner(false);
|
|
3178
|
+
throw err;
|
|
3179
|
+
}
|
|
3180
|
+
stopSpinner(true);
|
|
3181
|
+
output(opts, {
|
|
3182
|
+
headers: ["id", "floating_ip_address", "status", "port_id", "fixed_ip_address"],
|
|
3183
|
+
rows: fips.map((f) => [
|
|
3184
|
+
f.id,
|
|
3185
|
+
f.floating_ip_address,
|
|
3186
|
+
f.status,
|
|
3187
|
+
f.port_id ?? "-",
|
|
3188
|
+
f.fixed_ip_address ?? "-"
|
|
3189
|
+
]),
|
|
3190
|
+
raw: fips,
|
|
3191
|
+
ids: fips.map((f) => f.id)
|
|
3192
|
+
});
|
|
3193
|
+
});
|
|
3194
|
+
|
|
3195
|
+
// src/commands/floatingip/create.ts
|
|
3196
|
+
var import_commander20 = require("commander");
|
|
3197
|
+
var import_chalk4 = __toESM(require("chalk"));
|
|
3198
|
+
var createCommand2 = new import_commander20.Command("create").description("Floating IP \uB97C \uBC1C\uAE09\uD55C\uB2E4 (--network \uBBF8\uC9C0\uC815 \uC2DC \uC678\uBD80 VPC \uC790\uB3D9 \uC870\uD68C)").option("--network <id>", "\uC678\uBD80 \uB124\uD2B8\uC6CC\uD06C(VPC) id (\uBBF8\uC9C0\uC815 \uC2DC router:external=true \uC790\uB3D9 \uC870\uD68C)").option("--region <region>", "region override (\uAE30\uBCF8: iaas \uC790\uACA9\uC99D\uBA85\uC758 region)").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (_opts, cmd) => {
|
|
3199
|
+
const opts = cmd.optsWithGlobals();
|
|
3200
|
+
const { client } = await resolveNetworkClient(opts);
|
|
3201
|
+
let networkId = opts.network;
|
|
3202
|
+
if (networkId === void 0) {
|
|
3203
|
+
startSpinner("\uC678\uBD80 \uB124\uD2B8\uC6CC\uD06C \uC870\uD68C \uC911...");
|
|
3204
|
+
try {
|
|
3205
|
+
const found = await client.findExternalNetworkId();
|
|
3206
|
+
if (found === null) {
|
|
3207
|
+
throw new NhnCloudCliError(
|
|
3208
|
+
"\uC678\uBD80 \uB124\uD2B8\uC6CC\uD06C(router:external=true)\uB97C \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. --network <id> \uB85C \uC9C1\uC811 \uC9C0\uC815\uD558\uC138\uC694.",
|
|
3209
|
+
EXIT_PARAM_ERROR
|
|
3210
|
+
);
|
|
3211
|
+
}
|
|
3212
|
+
networkId = found;
|
|
3213
|
+
} catch (err) {
|
|
3214
|
+
stopSpinner(false);
|
|
3215
|
+
throw err;
|
|
3216
|
+
}
|
|
3217
|
+
stopSpinner(true);
|
|
3218
|
+
}
|
|
3219
|
+
startSpinner(`Floating IP \uBC1C\uAE09 \uC911... (network: ${networkId})`);
|
|
3220
|
+
let fip;
|
|
3221
|
+
try {
|
|
3222
|
+
fip = await client.createFloatingIp({ floating_network_id: networkId });
|
|
3223
|
+
} catch (err) {
|
|
3224
|
+
stopSpinner(false);
|
|
3225
|
+
throw err;
|
|
3226
|
+
}
|
|
3227
|
+
stopSpinner(true);
|
|
3228
|
+
process.stderr.write(
|
|
3229
|
+
import_chalk4.default.green(`\u2713 Floating IP "${fip.floating_ip_address}" \uB97C \uBC1C\uAE09\uD588\uC2B5\uB2C8\uB2E4 (id: ${fip.id}).
|
|
3230
|
+
`)
|
|
3231
|
+
);
|
|
3232
|
+
output(opts, {
|
|
3233
|
+
headers: ["id", "floating_ip_address", "status", "floating_network_id"],
|
|
3234
|
+
rows: [[fip.id, fip.floating_ip_address, fip.status, fip.floating_network_id]],
|
|
3235
|
+
raw: fip,
|
|
3236
|
+
ids: [fip.id]
|
|
3237
|
+
});
|
|
3238
|
+
});
|
|
3239
|
+
|
|
3240
|
+
// src/commands/floatingip/delete.ts
|
|
3241
|
+
var import_commander21 = require("commander");
|
|
3242
|
+
var import_chalk5 = __toESM(require("chalk"));
|
|
3243
|
+
var deleteCommand = new import_commander21.Command("delete").description("Floating IP \uB97C \uC0AD\uC81C\uD55C\uB2E4").argument("<id>", "Floating IP ID").option("--yes", "\uD655\uC778 \uD504\uB86C\uD504\uD2B8 \uC0DD\uB7B5 (CI/\uBE44\uB300\uD654\uD615 \uD544\uC218)").option("--region <region>", "region override (\uAE30\uBCF8: iaas \uC790\uACA9\uC99D\uBA85\uC758 region)").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (id, _opts, cmd) => {
|
|
3244
|
+
const opts = cmd.optsWithGlobals();
|
|
3245
|
+
const isTTY = process.stdin.isTTY;
|
|
3246
|
+
if (!isTTY && !opts.yes) {
|
|
3247
|
+
throw new NhnCloudCliError(
|
|
3248
|
+
"\uBE44\uB300\uD654\uD615 \uD658\uACBD\uC5D0\uC11C Floating IP \uC0AD\uC81C\uB294 --yes \uD50C\uB798\uADF8\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.",
|
|
3249
|
+
EXIT_PARAM_ERROR
|
|
3250
|
+
);
|
|
3251
|
+
}
|
|
3252
|
+
if (isTTY && !opts.yes) {
|
|
3253
|
+
const { confirm } = await import("@inquirer/prompts");
|
|
3254
|
+
const ok = await confirm({
|
|
3255
|
+
message: `Floating IP "${id}" \uB97C \uC0AD\uC81C\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?`,
|
|
3256
|
+
default: false
|
|
3257
|
+
});
|
|
3258
|
+
if (!ok) {
|
|
3259
|
+
process.stderr.write(import_chalk5.default.yellow("\uC0AD\uC81C\uAC00 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4.\n"));
|
|
3260
|
+
return;
|
|
3261
|
+
}
|
|
3262
|
+
}
|
|
3263
|
+
const { client } = await resolveNetworkClient(opts);
|
|
3264
|
+
startSpinner(`Floating IP \uC0AD\uC81C \uC911... (id: ${id})`);
|
|
3265
|
+
try {
|
|
3266
|
+
await client.deleteFloatingIp(id);
|
|
3267
|
+
} catch (err) {
|
|
3268
|
+
stopSpinner(false);
|
|
3269
|
+
throw err;
|
|
3270
|
+
}
|
|
3271
|
+
stopSpinner(true);
|
|
3272
|
+
process.stderr.write(import_chalk5.default.green(`\u2713 Floating IP "${id}" \uAC00 \uC0AD\uC81C\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
|
|
3273
|
+
`));
|
|
3274
|
+
});
|
|
3275
|
+
|
|
3276
|
+
// src/commands/instance/flavors.ts
|
|
3277
|
+
var import_commander22 = require("commander");
|
|
3278
|
+
function parseNonNegInt(value, flag) {
|
|
3279
|
+
if (value === void 0) return void 0;
|
|
3280
|
+
const n = Number(value);
|
|
3281
|
+
if (!Number.isInteger(n) || n < 0) {
|
|
3282
|
+
throw new NhnCloudCliError(`${flag} \uB294 0 \uC774\uC0C1\uC758 \uC815\uC218\uC5EC\uC57C \uD569\uB2C8\uB2E4 (\uC785\uB825: ${value}).`, EXIT_PARAM_ERROR);
|
|
3283
|
+
}
|
|
3284
|
+
return n;
|
|
3285
|
+
}
|
|
3286
|
+
var flavorsCommand = new import_commander22.Command("flavors").description("\uC778\uC2A4\uD134\uC2A4 \uD0C0\uC785(flavor)\uC744 \uC870\uD68C\uD55C\uB2E4 (\uAE30\uBCF8 id\xB7name, --detail \uB85C \uC2A4\uD399, \uC804\uCCB4 \uD544\uB4DC\uB294 --json)").option("--detail", "vcpus\xB7ram\xB7disk \uB4F1 \uC2A4\uD399 \uD3EC\uD568 (GET /flavors/detail)").option("--min-disk <gb>", "\uCD5C\uC18C \uBE14\uB85D \uC2A4\uD1A0\uB9AC\uC9C0 \uD06C\uAE30(GB) \uC774\uC0C1\uB9CC \uD544\uD130").option("--min-ram <mb>", "\uCD5C\uC18C RAM \uD06C\uAE30(MB) \uC774\uC0C1\uB9CC \uD544\uD130").option("--region <region>", "region override (\uAE30\uBCF8: iaas \uC790\uACA9\uC99D\uBA85\uC758 region)").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (_opts, cmd) => {
|
|
3287
|
+
const opts = cmd.optsWithGlobals();
|
|
3288
|
+
const minDisk = parseNonNegInt(opts.minDisk, "--min-disk");
|
|
3289
|
+
const minRam = parseNonNegInt(opts.minRam, "--min-ram");
|
|
3290
|
+
const { client } = await resolveInstanceClient(opts);
|
|
3291
|
+
startSpinner("\uC778\uC2A4\uD134\uC2A4 \uD0C0\uC785 \uC870\uD68C \uC911...");
|
|
3292
|
+
try {
|
|
3293
|
+
if (opts.detail) {
|
|
3294
|
+
const flavors = await client.listFlavors({ detail: true, minDisk, minRam });
|
|
3295
|
+
stopSpinner(true);
|
|
3296
|
+
printFlavors(opts, flavors);
|
|
3297
|
+
} else {
|
|
3298
|
+
const flavors = await client.listFlavors({ minDisk, minRam });
|
|
3299
|
+
stopSpinner(true);
|
|
3300
|
+
printFlavors(opts, flavors);
|
|
3301
|
+
}
|
|
3302
|
+
} catch (err) {
|
|
3303
|
+
stopSpinner(false);
|
|
3304
|
+
throw err;
|
|
3305
|
+
}
|
|
3306
|
+
});
|
|
3307
|
+
function printFlavors(opts, flavors) {
|
|
3308
|
+
if (isDetailList(flavors)) {
|
|
3309
|
+
output(opts, {
|
|
3310
|
+
headers: ["id", "name", "vcpus", "ram(MB)", "disk(GB)"],
|
|
3311
|
+
rows: flavors.map((f) => [f.id, f.name, String(f.vcpus), String(f.ram), String(f.disk)]),
|
|
3312
|
+
raw: flavors,
|
|
3313
|
+
ids: flavors.map((f) => f.id)
|
|
3314
|
+
});
|
|
3315
|
+
} else {
|
|
3316
|
+
output(opts, {
|
|
3317
|
+
headers: ["id", "name"],
|
|
3318
|
+
rows: flavors.map((f) => [f.id, f.name]),
|
|
3319
|
+
raw: flavors,
|
|
3320
|
+
ids: flavors.map((f) => f.id)
|
|
3321
|
+
});
|
|
3322
|
+
}
|
|
3323
|
+
}
|
|
3324
|
+
function isDetailList(flavors) {
|
|
3325
|
+
return flavors.length > 0 && "vcpus" in flavors[0];
|
|
3326
|
+
}
|
|
3327
|
+
|
|
3328
|
+
// src/commands/instance/availability-zones.ts
|
|
3329
|
+
var import_commander23 = require("commander");
|
|
3330
|
+
var availabilityZonesCommand = new import_commander23.Command("availability-zones").description("\uAC00\uC6A9\uC131 \uC601\uC5ED(availability zone) \uBAA9\uB85D\uC744 \uC870\uD68C\uD55C\uB2E4 (zoneName\xB7available)").option("--region <region>", "region override (\uAE30\uBCF8: iaas \uC790\uACA9\uC99D\uBA85\uC758 region)").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (_opts, cmd) => {
|
|
3331
|
+
const opts = cmd.optsWithGlobals();
|
|
3332
|
+
const { client } = await resolveInstanceClient(opts);
|
|
3333
|
+
startSpinner("\uAC00\uC6A9\uC131 \uC601\uC5ED \uC870\uD68C \uC911...");
|
|
3334
|
+
let zones;
|
|
3335
|
+
try {
|
|
3336
|
+
zones = await client.listAvailabilityZones();
|
|
3337
|
+
} catch (err) {
|
|
3338
|
+
stopSpinner(false);
|
|
3339
|
+
throw err;
|
|
3340
|
+
}
|
|
3341
|
+
stopSpinner(true);
|
|
3342
|
+
output(opts, {
|
|
3343
|
+
headers: ["zoneName", "available"],
|
|
3344
|
+
rows: zones.map((z) => [z.zoneName, String(z.zoneState.available)]),
|
|
3345
|
+
raw: zones,
|
|
3346
|
+
ids: zones.map((z) => z.zoneName)
|
|
3347
|
+
});
|
|
3348
|
+
});
|
|
3349
|
+
|
|
3350
|
+
// src/commands/instance/get.ts
|
|
3351
|
+
var import_commander24 = require("commander");
|
|
3352
|
+
function getIps2(server) {
|
|
3353
|
+
return Object.values(server.addresses).flat().map((a) => a.addr).join(", ");
|
|
3354
|
+
}
|
|
3355
|
+
function getImageId(server) {
|
|
3356
|
+
return typeof server.image === "object" ? server.image.id : "";
|
|
3357
|
+
}
|
|
3358
|
+
var getCommand2 = new import_commander24.Command("get").description("\uB2E8\uC77C \uC778\uC2A4\uD134\uC2A4 \uC0C1\uD0DC\uB97C \uC870\uD68C\uD55C\uB2E4").argument("<id>", "\uC778\uC2A4\uD134\uC2A4 ID").option("--region <region>", "region override (\uAE30\uBCF8: iaas \uC790\uACA9\uC99D\uBA85\uC758 region)").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (id, _opts, cmd) => {
|
|
3359
|
+
const opts = cmd.optsWithGlobals();
|
|
3360
|
+
const { client } = await resolveInstanceClient(opts);
|
|
3361
|
+
startSpinner("\uC778\uC2A4\uD134\uC2A4 \uC870\uD68C \uC911...");
|
|
3362
|
+
let server;
|
|
3363
|
+
try {
|
|
3364
|
+
server = await client.get(id);
|
|
3365
|
+
} catch (err) {
|
|
3366
|
+
stopSpinner(false);
|
|
3367
|
+
throw err;
|
|
3368
|
+
}
|
|
3369
|
+
stopSpinner(true);
|
|
3370
|
+
const rows = [
|
|
3371
|
+
["id", server.id],
|
|
3372
|
+
["name", server.name],
|
|
3373
|
+
["status", server.status],
|
|
3374
|
+
["IPs", getIps2(server)],
|
|
3375
|
+
["flavor", server.flavor.id],
|
|
3376
|
+
["image", getImageId(server)],
|
|
3377
|
+
["key_name", server.key_name ?? ""],
|
|
3378
|
+
["created", server.created],
|
|
3379
|
+
["updated", server.updated]
|
|
3380
|
+
];
|
|
3381
|
+
output(opts, {
|
|
3382
|
+
headers: ["field", "value"],
|
|
1479
3383
|
rows,
|
|
1480
3384
|
raw: server,
|
|
1481
3385
|
ids: [server.id]
|
|
@@ -1483,9 +3387,9 @@ var getCommand = new import_commander8.Command("get").description("\uB2E8\uC77C
|
|
|
1483
3387
|
});
|
|
1484
3388
|
|
|
1485
3389
|
// src/commands/instance/create.ts
|
|
1486
|
-
var
|
|
1487
|
-
var
|
|
1488
|
-
var
|
|
3390
|
+
var import_commander25 = require("commander");
|
|
3391
|
+
var import_node_fs5 = require("fs");
|
|
3392
|
+
var import_chalk6 = __toESM(require("chalk"));
|
|
1489
3393
|
function getFirstIp(server) {
|
|
1490
3394
|
for (const list of Object.values(server.addresses)) {
|
|
1491
3395
|
for (const addr of list) {
|
|
@@ -1500,7 +3404,7 @@ function getIps3(server) {
|
|
|
1500
3404
|
function getImageId2(server) {
|
|
1501
3405
|
return typeof server.image === "object" ? server.image.id : "";
|
|
1502
3406
|
}
|
|
1503
|
-
var
|
|
3407
|
+
var createCommand3 = new import_commander25.Command("create").description("\uC778\uC2A4\uD134\uC2A4\uB97C \uC0DD\uC131\uD55C\uB2E4").requiredOption("--name <name>", "\uC778\uC2A4\uD134\uC2A4 \uC774\uB984").requiredOption("--flavor <id>", "flavor ID").requiredOption("--image <id>", "\uC774\uBBF8\uC9C0 ID").requiredOption("--network <uuid>", "\uB124\uD2B8\uC6CC\uD06C UUID (\uC5EC\uB7EC \uAC1C: \uBC18\uBCF5 \uC9C0\uC815)", (v, prev) => [...prev, v], []).option("--boot-volume-size <gb>", "boot-from-volume root \uBCFC\uB968 \uD06C\uAE30 (GB). GPU(g2) \uB4F1 boot-from-volume \uD544\uC218 flavor \uC5D0 \uC9C0\uC815").option("--key-name <name>", "\uD0A4\uD398\uC5B4 \uC774\uB984").option("--security-group <name>", "\uBCF4\uC548 \uADF8\uB8F9 \uC774\uB984 (\uC5EC\uB7EC \uAC1C: \uBC18\uBCF5 \uC9C0\uC815)", (v, prev) => [...prev, v], []).option("--ephemeral-disk-size <gb>", "\uC784\uC2DC \uB514\uC2A4\uD06C \uD06C\uAE30 (GB, NHN \uD655\uC7A5)").option("--protect", "\uC0AD\uC81C \uBC29\uC9C0 \uC124\uC815 (NHN \uD655\uC7A5)").option("--user-data <path>", "cloud-init user-data \uD30C\uC77C \uACBD\uB85C (base64 \uC778\uCF54\uB529\uD574 \uC8FC\uC785, \uC778\uCF54\uB529 \uD6C4 65535 \uBC14\uC774\uD2B8 \uD55C\uB3C4)").option("--wait", "ACTIVE \uC0C1\uD0DC\uAC00 \uB420 \uB54C\uAE4C\uC9C0 \uB300\uAE30").option("--timeout <sec>", "wait \uD0C0\uC784\uC544\uC6C3 (\uCD08, \uAE30\uBCF8 300)", "300").option("--region <region>", "region override (\uAE30\uBCF8: iaas \uC790\uACA9\uC99D\uBA85\uC758 region)").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (_opts, cmd) => {
|
|
1504
3408
|
const opts = cmd.optsWithGlobals();
|
|
1505
3409
|
const networks = opts.network ?? [];
|
|
1506
3410
|
if (networks.length === 0) {
|
|
@@ -1510,7 +3414,7 @@ var createCommand = new import_commander9.Command("create").description("\uC778\
|
|
|
1510
3414
|
if (opts.userData !== void 0) {
|
|
1511
3415
|
let stat;
|
|
1512
3416
|
try {
|
|
1513
|
-
stat = (0,
|
|
3417
|
+
stat = (0, import_node_fs5.statSync)(opts.userData);
|
|
1514
3418
|
} catch (e) {
|
|
1515
3419
|
const reason = e.code ?? (e instanceof Error ? e.message : String(e));
|
|
1516
3420
|
throw new NhnCloudCliError(
|
|
@@ -1520,149 +3424,859 @@ var createCommand = new import_commander9.Command("create").description("\uC778\
|
|
|
1520
3424
|
}
|
|
1521
3425
|
if (!stat.isFile()) {
|
|
1522
3426
|
throw new NhnCloudCliError(
|
|
1523
|
-
`--user-data \uAC00 \uC77C\uBC18 \uD30C\uC77C\uC774 \uC544\uB2D9\uB2C8\uB2E4: ${opts.userData}`,
|
|
3427
|
+
`--user-data \uAC00 \uC77C\uBC18 \uD30C\uC77C\uC774 \uC544\uB2D9\uB2C8\uB2E4: ${opts.userData}`,
|
|
3428
|
+
EXIT_PARAM_ERROR
|
|
3429
|
+
);
|
|
3430
|
+
}
|
|
3431
|
+
if (stat.size > 49149) {
|
|
3432
|
+
throw new NhnCloudCliError(
|
|
3433
|
+
`--user-data \uAC00 \uB108\uBB34 \uD07D\uB2C8\uB2E4 (${stat.size} \uBC14\uC774\uD2B8). base64 \uC778\uCF54\uB529 \uD6C4 65535 \uBC14\uC774\uD2B8 \uD55C\uB3C4\uB77C raw 49149 \uBC14\uC774\uD2B8\uAE4C\uC9C0\uB9CC \uD5C8\uC6A9\uB429\uB2C8\uB2E4.`,
|
|
3434
|
+
EXIT_PARAM_ERROR
|
|
3435
|
+
);
|
|
3436
|
+
}
|
|
3437
|
+
const raw = (0, import_node_fs5.readFileSync)(opts.userData);
|
|
3438
|
+
userDataBase64 = raw.toString("base64");
|
|
3439
|
+
if (userDataBase64.length > 65535) {
|
|
3440
|
+
throw new NhnCloudCliError(
|
|
3441
|
+
`--user-data \uAC00 base64 \uC778\uCF54\uB529 \uD6C4 65535 \uBC14\uC774\uD2B8\uB97C \uCD08\uACFC\uD569\uB2C8\uB2E4 (${userDataBase64.length} \uBC14\uC774\uD2B8). cloud-init \uB0B4\uC6A9\uC744 \uC904\uC774\uC138\uC694.`,
|
|
3442
|
+
EXIT_PARAM_ERROR
|
|
3443
|
+
);
|
|
3444
|
+
}
|
|
3445
|
+
}
|
|
3446
|
+
const timeoutMs = parseInt(opts.timeout ?? "300", 10) * 1e3;
|
|
3447
|
+
const { client } = await resolveInstanceClient(opts);
|
|
3448
|
+
startSpinner("\uC778\uC2A4\uD134\uC2A4 \uC0DD\uC131 \uC911...");
|
|
3449
|
+
let server;
|
|
3450
|
+
try {
|
|
3451
|
+
server = await client.create({
|
|
3452
|
+
name: opts.name,
|
|
3453
|
+
flavorRef: opts.flavor,
|
|
3454
|
+
imageRef: opts.image,
|
|
3455
|
+
networks,
|
|
3456
|
+
bootVolumeSize: opts.bootVolumeSize !== void 0 ? parseInt(opts.bootVolumeSize, 10) : void 0,
|
|
3457
|
+
keyName: opts.keyName,
|
|
3458
|
+
securityGroups: opts.securityGroup && opts.securityGroup.length > 0 ? opts.securityGroup : void 0,
|
|
3459
|
+
ephemeralDiskSize: opts.ephemeralDiskSize !== void 0 ? parseInt(opts.ephemeralDiskSize, 10) : void 0,
|
|
3460
|
+
protect: opts.protect,
|
|
3461
|
+
userDataBase64
|
|
3462
|
+
});
|
|
3463
|
+
} catch (err) {
|
|
3464
|
+
stopSpinner(false);
|
|
3465
|
+
throw err;
|
|
3466
|
+
}
|
|
3467
|
+
stopSpinner(true);
|
|
3468
|
+
if (opts.wait) {
|
|
3469
|
+
startSpinner(`ACTIVE \uB300\uAE30 \uC911... (id: ${server.id})`);
|
|
3470
|
+
try {
|
|
3471
|
+
server = await client.waitForActive(server.id, { timeoutMs });
|
|
3472
|
+
} catch (err) {
|
|
3473
|
+
stopSpinner(false);
|
|
3474
|
+
throw err;
|
|
3475
|
+
}
|
|
3476
|
+
stopSpinner(true, `ACTIVE \uD655\uC778 (id: ${server.id})`);
|
|
3477
|
+
}
|
|
3478
|
+
if (opts.quiet && opts.wait) {
|
|
3479
|
+
const ip = getFirstIp(server);
|
|
3480
|
+
if (ip) process.stdout.write(ip + "\n");
|
|
3481
|
+
return;
|
|
3482
|
+
}
|
|
3483
|
+
if (opts.wait) {
|
|
3484
|
+
process.stderr.write(import_chalk6.default.green(` IP: ${getIps3(server)}
|
|
3485
|
+
`));
|
|
3486
|
+
}
|
|
3487
|
+
const rows = [
|
|
3488
|
+
["id", server.id],
|
|
3489
|
+
["name", server.name],
|
|
3490
|
+
["status", server.status],
|
|
3491
|
+
["IPs", getIps3(server)],
|
|
3492
|
+
["flavor", server.flavor.id],
|
|
3493
|
+
["image", getImageId2(server)]
|
|
3494
|
+
];
|
|
3495
|
+
output(opts, {
|
|
3496
|
+
headers: ["field", "value"],
|
|
3497
|
+
rows,
|
|
3498
|
+
raw: server,
|
|
3499
|
+
ids: [server.id]
|
|
3500
|
+
});
|
|
3501
|
+
});
|
|
3502
|
+
|
|
3503
|
+
// src/commands/instance/delete.ts
|
|
3504
|
+
var import_commander26 = require("commander");
|
|
3505
|
+
var import_chalk7 = __toESM(require("chalk"));
|
|
3506
|
+
var deleteCommand2 = new import_commander26.Command("delete").description("\uC778\uC2A4\uD134\uC2A4\uB97C \uC0AD\uC81C\uD55C\uB2E4").argument("<id>", "\uC778\uC2A4\uD134\uC2A4 ID").option("--yes", "\uD655\uC778 \uD504\uB86C\uD504\uD2B8 \uC0DD\uB7B5 (CI/\uBE44\uB300\uD654\uD615 \uD544\uC218)").option("--region <region>", "region override (\uAE30\uBCF8: iaas \uC790\uACA9\uC99D\uBA85\uC758 region)").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (id, _opts, cmd) => {
|
|
3507
|
+
const opts = cmd.optsWithGlobals();
|
|
3508
|
+
const isTTY = process.stdin.isTTY;
|
|
3509
|
+
if (!isTTY && !opts.yes) {
|
|
3510
|
+
throw new NhnCloudCliError(
|
|
3511
|
+
"\uBE44\uB300\uD654\uD615 \uD658\uACBD\uC5D0\uC11C \uC778\uC2A4\uD134\uC2A4 \uC0AD\uC81C\uB294 --yes \uD50C\uB798\uADF8\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.",
|
|
3512
|
+
EXIT_PARAM_ERROR
|
|
3513
|
+
);
|
|
3514
|
+
}
|
|
3515
|
+
if (isTTY && !opts.yes) {
|
|
3516
|
+
const { confirm } = await import("@inquirer/prompts");
|
|
3517
|
+
const ok = await confirm({
|
|
3518
|
+
message: `\uC778\uC2A4\uD134\uC2A4 "${id}" \uB97C \uC0AD\uC81C\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?`,
|
|
3519
|
+
default: false
|
|
3520
|
+
});
|
|
3521
|
+
if (!ok) {
|
|
3522
|
+
process.stderr.write(import_chalk7.default.yellow("\uC0AD\uC81C\uAC00 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4.\n"));
|
|
3523
|
+
return;
|
|
3524
|
+
}
|
|
3525
|
+
}
|
|
3526
|
+
const { client } = await resolveInstanceClient(opts);
|
|
3527
|
+
startSpinner(`\uC778\uC2A4\uD134\uC2A4 \uC0AD\uC81C \uC911... (id: ${id})`);
|
|
3528
|
+
try {
|
|
3529
|
+
await client.delete(id);
|
|
3530
|
+
} catch (err) {
|
|
3531
|
+
stopSpinner(false);
|
|
3532
|
+
throw err;
|
|
3533
|
+
}
|
|
3534
|
+
stopSpinner(true);
|
|
3535
|
+
process.stderr.write(import_chalk7.default.green(`\u2713 \uC778\uC2A4\uD134\uC2A4 "${id}" \uAC00 \uC0AD\uC81C\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
|
|
3536
|
+
`));
|
|
3537
|
+
});
|
|
3538
|
+
|
|
3539
|
+
// src/commands/instance/power.ts
|
|
3540
|
+
var import_commander27 = require("commander");
|
|
3541
|
+
var import_chalk8 = __toESM(require("chalk"));
|
|
3542
|
+
var startCommand = new import_commander27.Command("start").description("\uC778\uC2A4\uD134\uC2A4\uB97C \uC2DC\uC791\uD55C\uB2E4 (SHUTOFF \u2192 ACTIVE)").argument("<id>", "\uC778\uC2A4\uD134\uC2A4 ID").option("--region <region>", "region override (\uAE30\uBCF8: iaas \uC790\uACA9\uC99D\uBA85\uC758 region)").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (id, _opts, cmd) => {
|
|
3543
|
+
const opts = cmd.optsWithGlobals();
|
|
3544
|
+
const { client } = await resolveInstanceClient(opts);
|
|
3545
|
+
startSpinner(`\uC778\uC2A4\uD134\uC2A4 \uC2DC\uC791 \uC911... (id: ${id})`);
|
|
3546
|
+
try {
|
|
3547
|
+
await client.start(id);
|
|
3548
|
+
} catch (err) {
|
|
3549
|
+
stopSpinner(false);
|
|
3550
|
+
throw err;
|
|
3551
|
+
}
|
|
3552
|
+
stopSpinner(true);
|
|
3553
|
+
process.stderr.write(import_chalk8.default.green(`\u2713 \uC778\uC2A4\uD134\uC2A4 "${id}" \uC2DC\uC791\uC744 \uC694\uCCAD\uD588\uC2B5\uB2C8\uB2E4 (\u2192 ACTIVE).
|
|
3554
|
+
`));
|
|
3555
|
+
});
|
|
3556
|
+
var stopCommand = new import_commander27.Command("stop").description("\uC778\uC2A4\uD134\uC2A4\uB97C \uC815\uC9C0\uD55C\uB2E4 (ACTIVE/ERROR \u2192 SHUTOFF)").argument("<id>", "\uC778\uC2A4\uD134\uC2A4 ID").option("--region <region>", "region override (\uAE30\uBCF8: iaas \uC790\uACA9\uC99D\uBA85\uC758 region)").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (id, _opts, cmd) => {
|
|
3557
|
+
const opts = cmd.optsWithGlobals();
|
|
3558
|
+
const { client } = await resolveInstanceClient(opts);
|
|
3559
|
+
startSpinner(`\uC778\uC2A4\uD134\uC2A4 \uC815\uC9C0 \uC911... (id: ${id})`);
|
|
3560
|
+
try {
|
|
3561
|
+
await client.stop(id);
|
|
3562
|
+
} catch (err) {
|
|
3563
|
+
stopSpinner(false);
|
|
3564
|
+
throw err;
|
|
3565
|
+
}
|
|
3566
|
+
stopSpinner(true);
|
|
3567
|
+
process.stderr.write(import_chalk8.default.green(`\u2713 \uC778\uC2A4\uD134\uC2A4 "${id}" \uC815\uC9C0\uB97C \uC694\uCCAD\uD588\uC2B5\uB2C8\uB2E4 (\u2192 SHUTOFF).
|
|
3568
|
+
`));
|
|
3569
|
+
});
|
|
3570
|
+
var rebootCommand = new import_commander27.Command("reboot").description("\uC778\uC2A4\uD134\uC2A4\uB97C \uC7AC\uBD80\uD305\uD55C\uB2E4 (\uAE30\uBCF8 SOFT, --hard \uB85C HARD)").argument("<id>", "\uC778\uC2A4\uD134\uC2A4 ID").option("--hard", "HARD \uC7AC\uBD80\uD305 (\uAC15\uC81C \uC804\uC6D0 cycle, \uAE30\uBCF8\uC740 SOFT)").option("--region <region>", "region override (\uAE30\uBCF8: iaas \uC790\uACA9\uC99D\uBA85\uC758 region)").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (id, _opts, cmd) => {
|
|
3571
|
+
const opts = cmd.optsWithGlobals();
|
|
3572
|
+
const { client } = await resolveInstanceClient(opts);
|
|
3573
|
+
const type = opts.hard ? "HARD" : "SOFT";
|
|
3574
|
+
startSpinner(`\uC778\uC2A4\uD134\uC2A4 \uC7AC\uBD80\uD305 \uC911... (id: ${id}, ${type})`);
|
|
3575
|
+
try {
|
|
3576
|
+
await client.reboot(id, type);
|
|
3577
|
+
} catch (err) {
|
|
3578
|
+
stopSpinner(false);
|
|
3579
|
+
throw err;
|
|
3580
|
+
}
|
|
3581
|
+
stopSpinner(true);
|
|
3582
|
+
process.stderr.write(import_chalk8.default.green(`\u2713 \uC778\uC2A4\uD134\uC2A4 "${id}" ${type} \uC7AC\uBD80\uD305\uC744 \uC694\uCCAD\uD588\uC2B5\uB2C8\uB2E4.
|
|
3583
|
+
`));
|
|
3584
|
+
});
|
|
3585
|
+
|
|
3586
|
+
// src/commands/instance/resize.ts
|
|
3587
|
+
var import_commander28 = require("commander");
|
|
3588
|
+
var import_chalk9 = __toESM(require("chalk"));
|
|
3589
|
+
var resizeCommand = new import_commander28.Command("resize").description("\uC778\uC2A4\uD134\uC2A4 \uD0C0\uC785(flavor)\uC744 \uBCC0\uACBD\uD55C\uB2E4").argument("<id>", "\uC778\uC2A4\uD134\uC2A4 ID").requiredOption("--flavor <id>", "\uBCC0\uACBD\uD560 flavor ID (instance flavors \uB85C \uD6C4\uBCF4 \uC870\uD68C)").option("--region <region>", "region override (\uAE30\uBCF8: iaas \uC790\uACA9\uC99D\uBA85\uC758 region)").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (id, _opts, cmd) => {
|
|
3590
|
+
const opts = cmd.optsWithGlobals();
|
|
3591
|
+
const { client } = await resolveInstanceClient(opts);
|
|
3592
|
+
startSpinner(`\uC778\uC2A4\uD134\uC2A4 \uD0C0\uC785 \uBCC0\uACBD \uC911... (id: ${id})`);
|
|
3593
|
+
try {
|
|
3594
|
+
await client.resize(id, opts.flavor);
|
|
3595
|
+
} catch (err) {
|
|
3596
|
+
stopSpinner(false);
|
|
3597
|
+
throw err;
|
|
3598
|
+
}
|
|
3599
|
+
stopSpinner(true);
|
|
3600
|
+
process.stderr.write(
|
|
3601
|
+
import_chalk9.default.green(
|
|
3602
|
+
`\u2713 \uC778\uC2A4\uD134\uC2A4 "${id}" \uD0C0\uC785 \uBCC0\uACBD(flavor: ${opts.flavor}) \uC744 \uC694\uCCAD\uD588\uC2B5\uB2C8\uB2E4 (\u2192 VERIFY_RESIZE, instance get \uC73C\uB85C \uD655\uC778 \uD6C4 resize-confirm/resize-revert).
|
|
3603
|
+
`
|
|
3604
|
+
)
|
|
3605
|
+
);
|
|
3606
|
+
});
|
|
3607
|
+
var resizeConfirmCommand = new import_commander28.Command("resize-confirm").description("resize \uB97C \uD655\uC815\uD55C\uB2E4 (VERIFY_RESIZE \u2192 ACTIVE, \uC0C8 flavor \uB85C \uACE0\uC815)").argument("<id>", "\uC778\uC2A4\uD134\uC2A4 ID").option("--region <region>", "region override (\uAE30\uBCF8: iaas \uC790\uACA9\uC99D\uBA85\uC758 region)").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (id, _opts, cmd) => {
|
|
3608
|
+
const opts = cmd.optsWithGlobals();
|
|
3609
|
+
const { client } = await resolveInstanceClient(opts);
|
|
3610
|
+
startSpinner(`resize \uD655\uC815 \uC911... (id: ${id})`);
|
|
3611
|
+
try {
|
|
3612
|
+
await client.confirmResize(id);
|
|
3613
|
+
} catch (err) {
|
|
3614
|
+
stopSpinner(false);
|
|
3615
|
+
throw err;
|
|
3616
|
+
}
|
|
3617
|
+
stopSpinner(true);
|
|
3618
|
+
process.stderr.write(import_chalk9.default.green(`\u2713 \uC778\uC2A4\uD134\uC2A4 "${id}" resize \uD655\uC815\uC744 \uC694\uCCAD\uD588\uC2B5\uB2C8\uB2E4 (\u2192 ACTIVE).
|
|
3619
|
+
`));
|
|
3620
|
+
});
|
|
3621
|
+
var resizeRevertCommand = new import_commander28.Command("resize-revert").description("resize \uB97C \uB864\uBC31\uD55C\uB2E4 (VERIFY_RESIZE \u2192 ACTIVE, \uC774\uC804 flavor \uB85C \uBCF5\uADC0)").argument("<id>", "\uC778\uC2A4\uD134\uC2A4 ID").option("--region <region>", "region override (\uAE30\uBCF8: iaas \uC790\uACA9\uC99D\uBA85\uC758 region)").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (id, _opts, cmd) => {
|
|
3622
|
+
const opts = cmd.optsWithGlobals();
|
|
3623
|
+
const { client } = await resolveInstanceClient(opts);
|
|
3624
|
+
startSpinner(`resize \uB864\uBC31 \uC911... (id: ${id})`);
|
|
3625
|
+
try {
|
|
3626
|
+
await client.revertResize(id);
|
|
3627
|
+
} catch (err) {
|
|
3628
|
+
stopSpinner(false);
|
|
3629
|
+
throw err;
|
|
3630
|
+
}
|
|
3631
|
+
stopSpinner(true);
|
|
3632
|
+
process.stderr.write(
|
|
3633
|
+
import_chalk9.default.green(`\u2713 \uC778\uC2A4\uD134\uC2A4 "${id}" resize \uB864\uBC31\uC744 \uC694\uCCAD\uD588\uC2B5\uB2C8\uB2E4 (\uC774\uC804 flavor \uB85C \uBCF5\uADC0).
|
|
3634
|
+
`)
|
|
3635
|
+
);
|
|
3636
|
+
});
|
|
3637
|
+
|
|
3638
|
+
// src/commands/instance/images.ts
|
|
3639
|
+
var import_commander29 = require("commander");
|
|
3640
|
+
var VISIBILITY_VALUES = ["public", "private", "shared"];
|
|
3641
|
+
function parsePositiveInt5(value, flag) {
|
|
3642
|
+
if (value === void 0) return void 0;
|
|
3643
|
+
const n = Number(value);
|
|
3644
|
+
if (!Number.isInteger(n) || n < 1) {
|
|
3645
|
+
throw new NhnCloudCliError(`${flag} \uB294 1 \uC774\uC0C1\uC758 \uC815\uC218\uC5EC\uC57C \uD569\uB2C8\uB2E4 (\uC785\uB825: ${value}).`, EXIT_PARAM_ERROR);
|
|
3646
|
+
}
|
|
3647
|
+
return n;
|
|
3648
|
+
}
|
|
3649
|
+
var imagesCommand = new import_commander29.Command("images").description("\uC774\uBBF8\uC9C0 \uBAA9\uB85D\uC744 \uC870\uD68C\uD55C\uB2E4 (create --image <id> \uC18C\uC2A4, \uC804\uCCB4 \uD544\uB4DC\uB294 --json)").option("--limit <n>", "\uD55C \uD398\uC774\uC9C0 \uCD5C\uB300 \uAC1C\uC218 (\uAE30\uBCF8: \uC11C\uBC84 \uAE30\uBCF8\uAC12 25)").option("--marker <id>", "\uC774 image id \uB2E4\uC74C\uBD80\uD130 \uC870\uD68C (\uD398\uC774\uC9C0\uB124\uC774\uC158)").option("--name <name>", "\uC774\uB984\uC73C\uB85C \uD544\uD130").option("--visibility <v>", `\uB178\uCD9C \uBC94\uC704 \uD544\uD130 (${VISIBILITY_VALUES.join("|")})`).option("--owner <id>", "\uC18C\uC720\uC790(\uD504\uB85C\uC81D\uD2B8 id)\uB85C \uD544\uD130").option("--status <status>", "\uC0C1\uD0DC\uB85C \uD544\uD130 (\uC608: active)").option("--region <region>", "region override (\uAE30\uBCF8: iaas \uC790\uACA9\uC99D\uBA85\uC758 region)").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (_opts, cmd) => {
|
|
3650
|
+
const opts = cmd.optsWithGlobals();
|
|
3651
|
+
const limit = parsePositiveInt5(opts.limit, "--limit");
|
|
3652
|
+
if (opts.visibility !== void 0 && !VISIBILITY_VALUES.includes(opts.visibility)) {
|
|
3653
|
+
throw new NhnCloudCliError(
|
|
3654
|
+
`--visibility \uB294 ${VISIBILITY_VALUES.join(" | ")} \uC911 \uD558\uB098\uC5EC\uC57C \uD569\uB2C8\uB2E4 (\uC785\uB825: ${opts.visibility}).`,
|
|
3655
|
+
EXIT_PARAM_ERROR
|
|
3656
|
+
);
|
|
3657
|
+
}
|
|
3658
|
+
const { client } = await resolveInstanceClient(opts);
|
|
3659
|
+
startSpinner("\uC774\uBBF8\uC9C0 \uBAA9\uB85D \uC870\uD68C \uC911...");
|
|
3660
|
+
let result;
|
|
3661
|
+
try {
|
|
3662
|
+
result = await client.listImages({
|
|
3663
|
+
limit,
|
|
3664
|
+
marker: opts.marker,
|
|
3665
|
+
name: opts.name,
|
|
3666
|
+
visibility: opts.visibility,
|
|
3667
|
+
owner: opts.owner,
|
|
3668
|
+
status: opts.status
|
|
3669
|
+
});
|
|
3670
|
+
} catch (err) {
|
|
3671
|
+
stopSpinner(false);
|
|
3672
|
+
throw err;
|
|
3673
|
+
}
|
|
3674
|
+
stopSpinner(true);
|
|
3675
|
+
output(opts, {
|
|
3676
|
+
headers: ["id", "name", "status", "visibility", "size"],
|
|
3677
|
+
rows: result.images.map((img) => [
|
|
3678
|
+
img.id,
|
|
3679
|
+
img.name ?? "-",
|
|
3680
|
+
img.status,
|
|
3681
|
+
img.visibility,
|
|
3682
|
+
img.size === void 0 ? "-" : String(img.size)
|
|
3683
|
+
]),
|
|
3684
|
+
raw: result.images,
|
|
3685
|
+
ids: result.images.map((img) => img.id)
|
|
3686
|
+
});
|
|
3687
|
+
if (result.next && !opts.json && !opts.quiet) {
|
|
3688
|
+
const lastId = result.images.at(-1)?.id;
|
|
3689
|
+
if (lastId) {
|
|
3690
|
+
process.stderr.write(`\uB2E4\uC74C \uD398\uC774\uC9C0: --marker ${lastId}
|
|
3691
|
+
`);
|
|
3692
|
+
}
|
|
3693
|
+
}
|
|
3694
|
+
});
|
|
3695
|
+
|
|
3696
|
+
// src/commands/instance/keypairs.ts
|
|
3697
|
+
var import_commander30 = require("commander");
|
|
3698
|
+
var keypairsCommand = new import_commander30.Command("keypairs").description("\uD0A4\uD398\uC5B4 \uBAA9\uB85D\uC744 \uC870\uD68C\uD55C\uB2E4 (name\xB7fingerprint, \uC804\uCCB4 \uD544\uB4DC\uB294 --json)").option("--region <region>", "region override (\uAE30\uBCF8: iaas \uC790\uACA9\uC99D\uBA85\uC758 region)").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (_opts, cmd) => {
|
|
3699
|
+
const opts = cmd.optsWithGlobals();
|
|
3700
|
+
const { client } = await resolveInstanceClient(opts);
|
|
3701
|
+
startSpinner("\uD0A4\uD398\uC5B4 \uBAA9\uB85D \uC870\uD68C \uC911...");
|
|
3702
|
+
let keypairs;
|
|
3703
|
+
try {
|
|
3704
|
+
keypairs = await client.listKeypairs();
|
|
3705
|
+
} catch (err) {
|
|
3706
|
+
stopSpinner(false);
|
|
3707
|
+
throw err;
|
|
3708
|
+
}
|
|
3709
|
+
stopSpinner(true);
|
|
3710
|
+
output(opts, {
|
|
3711
|
+
headers: ["name", "fingerprint"],
|
|
3712
|
+
rows: keypairs.map((k) => [k.name, k.fingerprint]),
|
|
3713
|
+
raw: keypairs,
|
|
3714
|
+
ids: keypairs.map((k) => k.name)
|
|
3715
|
+
});
|
|
3716
|
+
});
|
|
3717
|
+
|
|
3718
|
+
// src/commands/instance/keypair.ts
|
|
3719
|
+
var import_commander31 = require("commander");
|
|
3720
|
+
var import_node_fs6 = require("fs");
|
|
3721
|
+
var import_node_crypto3 = require("crypto");
|
|
3722
|
+
var import_chalk10 = __toESM(require("chalk"));
|
|
3723
|
+
function resolvePublicKey(value) {
|
|
3724
|
+
let stat;
|
|
3725
|
+
try {
|
|
3726
|
+
stat = (0, import_node_fs6.statSync)(value);
|
|
3727
|
+
} catch (e) {
|
|
3728
|
+
const err = e;
|
|
3729
|
+
if (err.code && err.code !== "ENOENT") {
|
|
3730
|
+
throw new NhnCloudCliError(
|
|
3731
|
+
`--public-key \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${value} (${err.code})`,
|
|
1524
3732
|
EXIT_PARAM_ERROR
|
|
1525
3733
|
);
|
|
1526
3734
|
}
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
3735
|
+
return value;
|
|
3736
|
+
}
|
|
3737
|
+
if (!stat.isFile()) {
|
|
3738
|
+
throw new NhnCloudCliError(`--public-key \uAC00 \uC77C\uBC18 \uD30C\uC77C\uC774 \uC544\uB2D9\uB2C8\uB2E4: ${value}`, EXIT_PARAM_ERROR);
|
|
3739
|
+
}
|
|
3740
|
+
return (0, import_node_fs6.readFileSync)(value, "utf-8").trim();
|
|
3741
|
+
}
|
|
3742
|
+
function savePrivateKey(filePath, privateKey) {
|
|
3743
|
+
const tmp = `${filePath}.${(0, import_node_crypto3.randomBytes)(4).toString("hex")}.tmp`;
|
|
3744
|
+
const content = privateKey.endsWith("\n") ? privateKey : privateKey + "\n";
|
|
3745
|
+
try {
|
|
3746
|
+
(0, import_node_fs6.writeFileSync)(tmp, content, { encoding: "utf-8", mode: 384 });
|
|
3747
|
+
(0, import_node_fs6.renameSync)(tmp, filePath);
|
|
3748
|
+
} catch (e) {
|
|
3749
|
+
const reason = e.code ?? (e instanceof Error ? e.message : String(e));
|
|
3750
|
+
let tmpNote = "";
|
|
3751
|
+
try {
|
|
3752
|
+
(0, import_node_fs6.unlinkSync)(tmp);
|
|
3753
|
+
} catch {
|
|
3754
|
+
tmpNote = ` \u2014 0600 \uC784\uC2DC \uD30C\uC77C\uC774 \uB0A8\uC558\uC744 \uC218 \uC788\uC2B5\uB2C8\uB2E4: ${tmp}`;
|
|
1532
3755
|
}
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
3756
|
+
throw new NhnCloudCliError(
|
|
3757
|
+
`private_key \uD30C\uC77C\uC744 \uC800\uC7A5\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${filePath} (${reason})${tmpNote}`,
|
|
3758
|
+
EXIT_PARAM_ERROR
|
|
3759
|
+
);
|
|
3760
|
+
}
|
|
3761
|
+
}
|
|
3762
|
+
var getKeypairCmd = new import_commander31.Command("get").description("\uB2E8\uC77C \uD0A4\uD398\uC5B4\uB97C \uC870\uD68C\uD55C\uB2E4").argument("<name>", "\uD0A4\uD398\uC5B4 \uC774\uB984").option("--region <region>", "region override").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (name, _opts, cmd) => {
|
|
3763
|
+
const opts = cmd.optsWithGlobals();
|
|
3764
|
+
const { client } = await resolveInstanceClient(opts);
|
|
3765
|
+
startSpinner("\uD0A4\uD398\uC5B4 \uC870\uD68C \uC911...");
|
|
3766
|
+
let kp;
|
|
3767
|
+
try {
|
|
3768
|
+
kp = await client.getKeypair(name);
|
|
3769
|
+
} catch (err) {
|
|
3770
|
+
stopSpinner(false);
|
|
3771
|
+
throw err;
|
|
3772
|
+
}
|
|
3773
|
+
stopSpinner(true);
|
|
3774
|
+
output(opts, {
|
|
3775
|
+
headers: ["field", "value"],
|
|
3776
|
+
rows: [
|
|
3777
|
+
["name", kp.name],
|
|
3778
|
+
["fingerprint", kp.fingerprint],
|
|
3779
|
+
["user_id", kp.user_id],
|
|
3780
|
+
["created_at", kp.created_at],
|
|
3781
|
+
["public_key", kp.public_key]
|
|
3782
|
+
],
|
|
3783
|
+
raw: kp,
|
|
3784
|
+
ids: [kp.name]
|
|
3785
|
+
});
|
|
3786
|
+
});
|
|
3787
|
+
var createKeypairCmd = new import_commander31.Command("create").description("\uD0A4\uD398\uC5B4\uB97C \uC0DD\uC131\uD55C\uB2E4 (--public-key \uBBF8\uC9C0\uC815 \uC2DC NHN \uC774 \uD0A4\uC30D \uC0DD\uC131 \u2014 private_key 1\uD68C\uC131)").argument("<name>", "\uD0A4\uD398\uC5B4 \uC774\uB984").option("--public-key <path|key>", "\uAE30\uC874 \uACF5\uAC1C\uD0A4 (\uD30C\uC77C \uACBD\uB85C \uB610\uB294 \uD0A4 \uBB38\uC790\uC5F4). \uC9C0\uC815 \uC2DC private_key \uBBF8\uBC18\uD658").option("-o, --output <keyfile>", "\uC0DD\uC131\uB41C private_key \uB97C \uD30C\uC77C(mode 0600)\uB85C \uC800\uC7A5").option("--region <region>", "region override").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (name, _opts, cmd) => {
|
|
3788
|
+
const opts = cmd.optsWithGlobals();
|
|
3789
|
+
const publicKey = opts.publicKey !== void 0 ? resolvePublicKey(opts.publicKey) : void 0;
|
|
3790
|
+
if (opts.output !== void 0 && publicKey !== void 0) {
|
|
3791
|
+
throw new NhnCloudCliError(
|
|
3792
|
+
"--output \uC740 NHN \uC774 \uD0A4\uB97C \uC0DD\uC131\uD560 \uB54C\uB9CC \uC758\uBBF8\uAC00 \uC788\uC2B5\uB2C8\uB2E4. --public-key \uC640 \uD568\uAED8 \uC4F8 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
3793
|
+
EXIT_PARAM_ERROR
|
|
3794
|
+
);
|
|
3795
|
+
}
|
|
3796
|
+
if (publicKey === void 0 && opts.quiet && opts.output === void 0) {
|
|
3797
|
+
throw new NhnCloudCliError(
|
|
3798
|
+
"NHN \uC774 \uC0DD\uC131\uD558\uB294 private_key \uB294 1\uD68C\uB9CC \uBC18\uD658\uB429\uB2C8\uB2E4. --quiet \uB85C \uC0DD\uC131\uD560 \uB54C\uB294 --output <keyfile> \uB85C \uC800\uC7A5 \uACBD\uB85C\uB97C \uC9C0\uC815\uD558\uC138\uC694 (\uBBF8\uC9C0\uC815 \uC2DC \uD0A4 \uC720\uC2E4).",
|
|
3799
|
+
EXIT_PARAM_ERROR
|
|
3800
|
+
);
|
|
3801
|
+
}
|
|
3802
|
+
const { client } = await resolveInstanceClient(opts);
|
|
3803
|
+
startSpinner("\uD0A4\uD398\uC5B4 \uC0DD\uC131 \uC911...");
|
|
3804
|
+
let result;
|
|
3805
|
+
try {
|
|
3806
|
+
result = await client.createKeypair({ name, publicKey });
|
|
3807
|
+
} catch (err) {
|
|
3808
|
+
stopSpinner(false);
|
|
3809
|
+
throw err;
|
|
3810
|
+
}
|
|
3811
|
+
stopSpinner(true);
|
|
3812
|
+
if (result.private_key !== void 0) {
|
|
3813
|
+
if (opts.output !== void 0) {
|
|
3814
|
+
try {
|
|
3815
|
+
savePrivateKey(opts.output, result.private_key);
|
|
3816
|
+
process.stderr.write(import_chalk10.default.green(` private_key \uB97C ${opts.output} \uC5D0 \uC800\uC7A5\uD588\uC2B5\uB2C8\uB2E4 (mode 0600).
|
|
3817
|
+
`));
|
|
3818
|
+
} catch (saveErr) {
|
|
3819
|
+
const reason = saveErr instanceof Error ? saveErr.message : String(saveErr);
|
|
3820
|
+
process.stderr.write(
|
|
3821
|
+
import_chalk10.default.red(` \u26A0 private_key \uD30C\uC77C \uC800\uC7A5 \uC2E4\uD328 (${reason}). \uC720\uC2E4 \uBC29\uC9C0\uB97C \uC704\uD574 \uC544\uB798\uC5D0 \uCD9C\uB825\uD569\uB2C8\uB2E4 \u2014 \uC989\uC2DC \uC548\uC804\uD55C \uACF3\uC5D0 \uBCF4\uAD00\uD558\uC138\uC694.
|
|
3822
|
+
`)
|
|
3823
|
+
);
|
|
3824
|
+
process.stdout.write(result.private_key + "\n");
|
|
3825
|
+
}
|
|
3826
|
+
} else {
|
|
3827
|
+
process.stderr.write(
|
|
3828
|
+
import_chalk10.default.yellow(" \u26A0 private_key \uB294 \uC9C0\uAE08 \uD55C \uBC88\uB9CC \uD45C\uC2DC\uB429\uB2C8\uB2E4. \uBD84\uC2E4 \uC2DC \uBCF5\uAD6C \uBD88\uAC00 \u2014 \uC548\uC804\uD55C \uACF3\uC5D0 \uBCF4\uAD00\uD558\uC138\uC694.\n")
|
|
1539
3829
|
);
|
|
3830
|
+
process.stdout.write(result.private_key + "\n");
|
|
1540
3831
|
}
|
|
1541
3832
|
}
|
|
1542
|
-
const
|
|
3833
|
+
const { private_key, ...meta } = result;
|
|
3834
|
+
void private_key;
|
|
3835
|
+
output(opts, {
|
|
3836
|
+
headers: ["field", "value"],
|
|
3837
|
+
rows: [
|
|
3838
|
+
["name", meta.name],
|
|
3839
|
+
["fingerprint", meta.fingerprint],
|
|
3840
|
+
["user_id", meta.user_id]
|
|
3841
|
+
],
|
|
3842
|
+
raw: meta,
|
|
3843
|
+
ids: [meta.name]
|
|
3844
|
+
});
|
|
3845
|
+
});
|
|
3846
|
+
var deleteKeypairCmd = new import_commander31.Command("delete").description("\uD0A4\uD398\uC5B4\uB97C \uC0AD\uC81C\uD55C\uB2E4").argument("<name>", "\uD0A4\uD398\uC5B4 \uC774\uB984").option("--region <region>", "region override").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (name, _opts, cmd) => {
|
|
3847
|
+
const opts = cmd.optsWithGlobals();
|
|
1543
3848
|
const { client } = await resolveInstanceClient(opts);
|
|
1544
|
-
startSpinner("\
|
|
1545
|
-
let server;
|
|
3849
|
+
startSpinner("\uD0A4\uD398\uC5B4 \uC0AD\uC81C \uC911...");
|
|
1546
3850
|
try {
|
|
1547
|
-
|
|
1548
|
-
name: opts.name,
|
|
1549
|
-
flavorRef: opts.flavor,
|
|
1550
|
-
imageRef: opts.image,
|
|
1551
|
-
networks,
|
|
1552
|
-
bootVolumeSize: opts.bootVolumeSize !== void 0 ? parseInt(opts.bootVolumeSize, 10) : void 0,
|
|
1553
|
-
keyName: opts.keyName,
|
|
1554
|
-
securityGroups: opts.securityGroup && opts.securityGroup.length > 0 ? opts.securityGroup : void 0,
|
|
1555
|
-
ephemeralDiskSize: opts.ephemeralDiskSize !== void 0 ? parseInt(opts.ephemeralDiskSize, 10) : void 0,
|
|
1556
|
-
protect: opts.protect,
|
|
1557
|
-
userDataBase64
|
|
1558
|
-
});
|
|
3851
|
+
await client.deleteKeypair(name);
|
|
1559
3852
|
} catch (err) {
|
|
1560
3853
|
stopSpinner(false);
|
|
1561
3854
|
throw err;
|
|
1562
3855
|
}
|
|
1563
3856
|
stopSpinner(true);
|
|
1564
|
-
|
|
1565
|
-
|
|
3857
|
+
process.stderr.write(import_chalk10.default.green(`\u2713 \uD0A4\uD398\uC5B4 "${name}" \uAC00 \uC0AD\uC81C\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
|
|
3858
|
+
`));
|
|
3859
|
+
});
|
|
3860
|
+
var keypairCommand = new import_commander31.Command("keypair").description("\uD0A4\uD398\uC5B4 \uB2E8\uAC74 \uAD00\uB9AC (get / create / delete)").addCommand(getKeypairCmd).addCommand(createKeypairCmd).addCommand(deleteKeypairCmd);
|
|
3861
|
+
|
|
3862
|
+
// src/commands/instance/volume.ts
|
|
3863
|
+
var import_commander32 = require("commander");
|
|
3864
|
+
var import_chalk11 = __toESM(require("chalk"));
|
|
3865
|
+
var attachCommand = new import_commander32.Command("attach").description("\uBCFC\uB968\uC744 \uC778\uC2A4\uD134\uC2A4\uC5D0 \uC5F0\uACB0\uD55C\uB2E4").argument("<id>", "\uC778\uC2A4\uD134\uC2A4 ID").requiredOption("--volume <volumeId>", "\uC5F0\uACB0\uD560 \uBCFC\uB968 ID").option("--region <region>", "region override (\uAE30\uBCF8: iaas \uC790\uACA9\uC99D\uBA85\uC758 region)").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (id, _opts, cmd) => {
|
|
3866
|
+
const opts = cmd.optsWithGlobals();
|
|
3867
|
+
const { client } = await resolveInstanceClient(opts);
|
|
3868
|
+
startSpinner("\uBCFC\uB968 \uC5F0\uACB0 \uC911...");
|
|
3869
|
+
let att;
|
|
3870
|
+
try {
|
|
3871
|
+
att = await client.attachVolume(id, opts.volume);
|
|
3872
|
+
} catch (err) {
|
|
3873
|
+
stopSpinner(false);
|
|
3874
|
+
throw err;
|
|
3875
|
+
}
|
|
3876
|
+
stopSpinner(true);
|
|
3877
|
+
process.stderr.write(
|
|
3878
|
+
import_chalk11.default.green(`\uBCFC\uB968 \uC5F0\uACB0 \uC644\uB8CC (volumeId: ${att.volumeId}, device: ${att.device})
|
|
3879
|
+
`)
|
|
3880
|
+
);
|
|
3881
|
+
output(opts, {
|
|
3882
|
+
headers: ["field", "value"],
|
|
3883
|
+
rows: [
|
|
3884
|
+
["id", att.id],
|
|
3885
|
+
["volumeId", att.volumeId],
|
|
3886
|
+
["serverId", att.serverId],
|
|
3887
|
+
["device", att.device]
|
|
3888
|
+
],
|
|
3889
|
+
raw: att,
|
|
3890
|
+
ids: [att.id]
|
|
3891
|
+
});
|
|
3892
|
+
});
|
|
3893
|
+
var detachCommand = new import_commander32.Command("detach").description("\uBCFC\uB968 \uC5F0\uACB0\uC744 \uD574\uC81C\uD55C\uB2E4").argument("<id>", "\uC778\uC2A4\uD134\uC2A4 ID").argument("<volumeId>", "\uD574\uC81C\uD560 \uBCFC\uB968 ID").option("--region <region>", "region override (\uAE30\uBCF8: iaas \uC790\uACA9\uC99D\uBA85\uC758 region)").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (id, volumeId, _opts, cmd) => {
|
|
3894
|
+
const opts = cmd.optsWithGlobals();
|
|
3895
|
+
const { client } = await resolveInstanceClient(opts);
|
|
3896
|
+
startSpinner("\uBCFC\uB968 \uC5F0\uACB0 \uD574\uC81C \uC911...");
|
|
3897
|
+
try {
|
|
3898
|
+
await client.detachVolume(id, volumeId);
|
|
3899
|
+
} catch (err) {
|
|
3900
|
+
stopSpinner(false);
|
|
3901
|
+
throw err;
|
|
3902
|
+
}
|
|
3903
|
+
stopSpinner(true);
|
|
3904
|
+
process.stderr.write(
|
|
3905
|
+
import_chalk11.default.green(`\uBCFC\uB968 \uC5F0\uACB0 \uD574\uC81C \uC694\uCCAD \uC644\uB8CC (volumeId: ${volumeId})
|
|
3906
|
+
`)
|
|
3907
|
+
);
|
|
3908
|
+
});
|
|
3909
|
+
var volumeCommand = new import_commander32.Command("volume").description(
|
|
3910
|
+
"\uC778\uC2A4\uD134\uC2A4 \uBCFC\uB968 \uC5F0\uACB0/\uD574\uC81C"
|
|
3911
|
+
);
|
|
3912
|
+
volumeCommand.addCommand(attachCommand);
|
|
3913
|
+
volumeCommand.addCommand(detachCommand);
|
|
3914
|
+
|
|
3915
|
+
// src/commands/instance/volumes.ts
|
|
3916
|
+
var import_commander33 = require("commander");
|
|
3917
|
+
var volumesCommand = new import_commander33.Command("volumes").description("\uC778\uC2A4\uD134\uC2A4\uC5D0 \uC5F0\uACB0\uB41C \uBCFC\uB968 \uBAA9\uB85D\uC744 \uC870\uD68C\uD55C\uB2E4").argument("<id>", "\uC778\uC2A4\uD134\uC2A4 ID").option("--region <region>", "region override (\uAE30\uBCF8: iaas \uC790\uACA9\uC99D\uBA85\uC758 region)").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (id, _opts, cmd) => {
|
|
3918
|
+
const opts = cmd.optsWithGlobals();
|
|
3919
|
+
const { client } = await resolveInstanceClient(opts);
|
|
3920
|
+
startSpinner("\uBCFC\uB968 \uBAA9\uB85D \uC870\uD68C \uC911...");
|
|
3921
|
+
let attachments;
|
|
3922
|
+
try {
|
|
3923
|
+
attachments = await client.listVolumeAttachments(id);
|
|
3924
|
+
} catch (err) {
|
|
3925
|
+
stopSpinner(false);
|
|
3926
|
+
throw err;
|
|
3927
|
+
}
|
|
3928
|
+
stopSpinner(true);
|
|
3929
|
+
output(opts, {
|
|
3930
|
+
headers: ["id", "volumeId", "device"],
|
|
3931
|
+
rows: attachments.map((a) => [a.id, a.volumeId, a.device]),
|
|
3932
|
+
raw: attachments,
|
|
3933
|
+
ids: attachments.map((a) => a.id)
|
|
3934
|
+
});
|
|
3935
|
+
});
|
|
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;
|
|
1566
3968
|
try {
|
|
1567
|
-
|
|
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
|
+
}
|
|
1568
3994
|
} catch (err) {
|
|
1569
|
-
|
|
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) {
|
|
1570
4038
|
throw err;
|
|
1571
4039
|
}
|
|
1572
|
-
stopSpinner(true, `ACTIVE \uD655\uC778 (id: ${server.id})`);
|
|
1573
4040
|
}
|
|
1574
|
-
if (
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
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
|
+
);
|
|
1578
4046
|
}
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
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
|
+
);
|
|
1582
4064
|
}
|
|
1583
|
-
const
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
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);
|
|
1591
4090
|
output(opts, {
|
|
1592
|
-
headers: ["
|
|
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"],
|
|
1593
4160
|
rows,
|
|
1594
|
-
raw:
|
|
1595
|
-
ids
|
|
4161
|
+
raw: repos,
|
|
4162
|
+
ids
|
|
1596
4163
|
});
|
|
1597
4164
|
});
|
|
1598
4165
|
|
|
1599
|
-
// src/commands/
|
|
1600
|
-
var
|
|
1601
|
-
var
|
|
1602
|
-
var deleteCommand = new import_commander10.Command("delete").description("\uC778\uC2A4\uD134\uC2A4\uB97C \uC0AD\uC81C\uD55C\uB2E4").argument("<id>", "\uC778\uC2A4\uD134\uC2A4 ID").option("--yes", "\uD655\uC778 \uD504\uB86C\uD504\uD2B8 \uC0DD\uB7B5 (CI/\uBE44\uB300\uD654\uD615 \uD544\uC218)").option("--region <region>", "region override (\uAE30\uBCF8: iaas \uC790\uACA9\uC99D\uBA85\uC758 region)").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (id, _opts, cmd) => {
|
|
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) => {
|
|
1603
4169
|
const opts = cmd.optsWithGlobals();
|
|
1604
|
-
|
|
1605
|
-
if (!isTTY && !opts.yes) {
|
|
4170
|
+
if (!registry.trim()) {
|
|
1606
4171
|
throw new NhnCloudCliError(
|
|
1607
|
-
"\
|
|
4172
|
+
"registry \uC778\uC218\uAC00 \uBE44\uC5B4\uC788\uC2B5\uB2C8\uB2E4. \uB808\uC9C0\uC2A4\uD2B8\uB9AC \uC774\uB984\uC744 \uC9C0\uC815\uD558\uC138\uC694.",
|
|
1608
4173
|
EXIT_PARAM_ERROR
|
|
1609
4174
|
);
|
|
1610
4175
|
}
|
|
1611
|
-
if (
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
});
|
|
1617
|
-
if (!ok) {
|
|
1618
|
-
process.stderr.write(import_chalk3.default.yellow("\uC0AD\uC81C\uAC00 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4.\n"));
|
|
1619
|
-
return;
|
|
1620
|
-
}
|
|
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
|
+
);
|
|
1621
4181
|
}
|
|
1622
|
-
const {
|
|
1623
|
-
|
|
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;
|
|
1624
4186
|
try {
|
|
1625
|
-
await
|
|
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
|
+
);
|
|
1626
4195
|
} catch (err) {
|
|
1627
4196
|
stopSpinner(false);
|
|
1628
4197
|
throw err;
|
|
1629
4198
|
}
|
|
1630
4199
|
stopSpinner(true);
|
|
1631
|
-
|
|
1632
|
-
|
|
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
|
+
});
|
|
1633
4206
|
});
|
|
1634
4207
|
|
|
1635
4208
|
// src/index.ts
|
|
1636
|
-
var program = new
|
|
1637
|
-
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");
|
|
1638
4211
|
program.hook("preAction", () => {
|
|
1639
4212
|
const opts = program.opts();
|
|
1640
4213
|
if (!opts.color || process.env["NO_COLOR"]) {
|
|
1641
|
-
|
|
4214
|
+
import_chalk12.default.level = 0;
|
|
1642
4215
|
}
|
|
1643
4216
|
if (opts.json || opts.quiet) {
|
|
1644
4217
|
setQuiet(true);
|
|
1645
4218
|
}
|
|
1646
4219
|
});
|
|
1647
4220
|
program.addCommand(configureCommand);
|
|
1648
|
-
var logncrashCommand = new
|
|
4221
|
+
var logncrashCommand = new import_commander38.Command("logncrash").description("Log & Crash \uAD00\uB828 \uBA85\uB839");
|
|
1649
4222
|
logncrashCommand.addCommand(searchCommand);
|
|
4223
|
+
logncrashCommand.addCommand(sendCommand);
|
|
4224
|
+
logncrashCommand.addCommand(exportCommand);
|
|
1650
4225
|
program.addCommand(logncrashCommand);
|
|
1651
|
-
var deployCommand = new
|
|
4226
|
+
var deployCommand = new import_commander38.Command("deploy").description("NHN Cloud Deploy \uAD00\uB828 \uBA85\uB839");
|
|
1652
4227
|
deployCommand.addCommand(runCommand);
|
|
1653
4228
|
deployCommand.addCommand(artifactsCommand);
|
|
1654
4229
|
deployCommand.addCommand(serverGroupsCommand);
|
|
1655
4230
|
deployCommand.addCommand(historiesCommand);
|
|
4231
|
+
deployCommand.addCommand(binaryGroupsCommand);
|
|
4232
|
+
deployCommand.addCommand(binariesCommand);
|
|
4233
|
+
deployCommand.addCommand(uploadCommand);
|
|
4234
|
+
deployCommand.addCommand(downloadCommand);
|
|
1656
4235
|
program.addCommand(deployCommand);
|
|
1657
|
-
var instanceCommand = new
|
|
4236
|
+
var instanceCommand = new import_commander38.Command("instance").description("Compute \uC778\uC2A4\uD134\uC2A4 \uAD00\uB828 \uBA85\uB839");
|
|
1658
4237
|
instanceCommand.addCommand(listCommand);
|
|
1659
|
-
instanceCommand.addCommand(
|
|
1660
|
-
instanceCommand.addCommand(
|
|
1661
|
-
instanceCommand.addCommand(
|
|
4238
|
+
instanceCommand.addCommand(flavorsCommand);
|
|
4239
|
+
instanceCommand.addCommand(availabilityZonesCommand);
|
|
4240
|
+
instanceCommand.addCommand(getCommand2);
|
|
4241
|
+
instanceCommand.addCommand(createCommand3);
|
|
4242
|
+
instanceCommand.addCommand(deleteCommand2);
|
|
4243
|
+
instanceCommand.addCommand(startCommand);
|
|
4244
|
+
instanceCommand.addCommand(stopCommand);
|
|
4245
|
+
instanceCommand.addCommand(rebootCommand);
|
|
4246
|
+
instanceCommand.addCommand(resizeCommand);
|
|
4247
|
+
instanceCommand.addCommand(resizeConfirmCommand);
|
|
4248
|
+
instanceCommand.addCommand(resizeRevertCommand);
|
|
4249
|
+
instanceCommand.addCommand(imagesCommand);
|
|
4250
|
+
instanceCommand.addCommand(keypairsCommand);
|
|
4251
|
+
instanceCommand.addCommand(keypairCommand);
|
|
4252
|
+
instanceCommand.addCommand(volumeCommand);
|
|
4253
|
+
instanceCommand.addCommand(volumesCommand);
|
|
1662
4254
|
program.addCommand(instanceCommand);
|
|
4255
|
+
var networkCommand = new import_commander38.Command("network").description("VPC\xB7\uC11C\uBE0C\uB137 \uC870\uD68C");
|
|
4256
|
+
networkCommand.addCommand(listCommand3);
|
|
4257
|
+
networkCommand.addCommand(subnetCommand);
|
|
4258
|
+
program.addCommand(networkCommand);
|
|
4259
|
+
var volumeCommand2 = new import_commander38.Command("volume").description("Block Storage \uBCFC\uB968 \uAD00\uB828 \uBA85\uB839");
|
|
4260
|
+
volumeCommand2.addCommand(listCommand2);
|
|
4261
|
+
volumeCommand2.addCommand(getCommand);
|
|
4262
|
+
volumeCommand2.addCommand(createCommand);
|
|
4263
|
+
program.addCommand(volumeCommand2);
|
|
4264
|
+
var floatingipCommand = new import_commander38.Command("floatingip").description(
|
|
4265
|
+
"Floating IP(\uC778\uC2A4\uD134\uC2A4 \uACF5\uC778 IP) \uAD00\uB9AC"
|
|
4266
|
+
);
|
|
4267
|
+
floatingipCommand.addCommand(listCommand4);
|
|
4268
|
+
floatingipCommand.addCommand(createCommand2);
|
|
4269
|
+
floatingipCommand.addCommand(deleteCommand);
|
|
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);
|
|
1663
4277
|
program.parseAsync().catch((err) => {
|
|
1664
4278
|
const message = err instanceof Error ? err.message : String(err);
|
|
1665
4279
|
const exitCode = err instanceof NhnCloudCliError ? err.exitCode : 1;
|
|
1666
|
-
process.stderr.write(
|
|
4280
|
+
process.stderr.write(import_chalk12.default.red(`\uC624\uB958: ${message}`) + "\n");
|
|
1667
4281
|
process.exit(exitCode);
|
|
1668
4282
|
});
|