@bifos/nhncloud-cli 0.2.0 → 0.3.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 +10 -0
- package/dist/index.js +42 -3
- package/package.json +1 -1
- package/skills/nhncloud-cli/SKILL.md +11 -0
package/README.md
CHANGED
|
@@ -195,6 +195,16 @@ nhncloud instance create \
|
|
|
195
195
|
--boot-volume-size 30 \
|
|
196
196
|
--wait
|
|
197
197
|
|
|
198
|
+
# cloud-init user-data 주입 (부팅 시 자동 셋업 — NVIDIA 드라이버·docker 등)
|
|
199
|
+
nhncloud instance create \
|
|
200
|
+
--name gpu-runner \
|
|
201
|
+
--flavor <gpu-flavor-id> \
|
|
202
|
+
--image <image-id> \
|
|
203
|
+
--network <network-uuid> \
|
|
204
|
+
--boot-volume-size 30 \
|
|
205
|
+
--user-data ./cloud-init.yaml \
|
|
206
|
+
--wait
|
|
207
|
+
|
|
198
208
|
# --quiet --wait: 첫 IP 한 줄만 stdout (CI 파이프용)
|
|
199
209
|
IP=$(nhncloud instance create --name ci-runner \
|
|
200
210
|
--flavor <flavor-id> --image <image-id> --network <network-uuid> \
|
package/dist/index.js
CHANGED
|
@@ -1310,6 +1310,7 @@ var InstanceClient = class {
|
|
|
1310
1310
|
/**
|
|
1311
1311
|
* 인스턴스를 생성한다 (POST /servers).
|
|
1312
1312
|
* NHN 확장 필드(ephemeralDiskSize / protect)는 정의됐을 때만 payload 에 포함한다.
|
|
1313
|
+
* userDataBase64 도 정의됐을 때만 `user_data` 로 포함한다 (인코딩은 command 단에서 완료).
|
|
1313
1314
|
*/
|
|
1314
1315
|
async create(params) {
|
|
1315
1316
|
const url = `${this.computeEndpoint}/servers`;
|
|
@@ -1344,6 +1345,9 @@ var InstanceClient = class {
|
|
|
1344
1345
|
if (params.protect !== void 0) {
|
|
1345
1346
|
serverBody["NHN-EXT-ATTR:protect"] = params.protect;
|
|
1346
1347
|
}
|
|
1348
|
+
if (params.userDataBase64 !== void 0) {
|
|
1349
|
+
serverBody["user_data"] = params.userDataBase64;
|
|
1350
|
+
}
|
|
1347
1351
|
let raw;
|
|
1348
1352
|
try {
|
|
1349
1353
|
raw = await import_ky6.default.post(url, {
|
|
@@ -1480,6 +1484,7 @@ var getCommand = new import_commander8.Command("get").description("\uB2E8\uC77C
|
|
|
1480
1484
|
|
|
1481
1485
|
// src/commands/instance/create.ts
|
|
1482
1486
|
var import_commander9 = require("commander");
|
|
1487
|
+
var import_node_fs = require("fs");
|
|
1483
1488
|
var import_chalk2 = __toESM(require("chalk"));
|
|
1484
1489
|
function getFirstIp(server) {
|
|
1485
1490
|
for (const list of Object.values(server.addresses)) {
|
|
@@ -1495,12 +1500,45 @@ function getIps3(server) {
|
|
|
1495
1500
|
function getImageId2(server) {
|
|
1496
1501
|
return typeof server.image === "object" ? server.image.id : "";
|
|
1497
1502
|
}
|
|
1498
|
-
var createCommand = new import_commander9.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("--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) => {
|
|
1503
|
+
var createCommand = new import_commander9.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) => {
|
|
1499
1504
|
const opts = cmd.optsWithGlobals();
|
|
1500
1505
|
const networks = opts.network ?? [];
|
|
1501
1506
|
if (networks.length === 0) {
|
|
1502
1507
|
throw new NhnCloudCliError("--network \uB294 \uCD5C\uC18C 1\uAC1C \uD544\uC694\uD569\uB2C8\uB2E4.", EXIT_PARAM_ERROR);
|
|
1503
1508
|
}
|
|
1509
|
+
let userDataBase64;
|
|
1510
|
+
if (opts.userData !== void 0) {
|
|
1511
|
+
let stat;
|
|
1512
|
+
try {
|
|
1513
|
+
stat = (0, import_node_fs.statSync)(opts.userData);
|
|
1514
|
+
} catch (e) {
|
|
1515
|
+
const reason = e.code ?? (e instanceof Error ? e.message : String(e));
|
|
1516
|
+
throw new NhnCloudCliError(
|
|
1517
|
+
`--user-data \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${opts.userData} (${reason})`,
|
|
1518
|
+
EXIT_PARAM_ERROR
|
|
1519
|
+
);
|
|
1520
|
+
}
|
|
1521
|
+
if (!stat.isFile()) {
|
|
1522
|
+
throw new NhnCloudCliError(
|
|
1523
|
+
`--user-data \uAC00 \uC77C\uBC18 \uD30C\uC77C\uC774 \uC544\uB2D9\uB2C8\uB2E4: ${opts.userData}`,
|
|
1524
|
+
EXIT_PARAM_ERROR
|
|
1525
|
+
);
|
|
1526
|
+
}
|
|
1527
|
+
if (stat.size > 49149) {
|
|
1528
|
+
throw new NhnCloudCliError(
|
|
1529
|
+
`--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.`,
|
|
1530
|
+
EXIT_PARAM_ERROR
|
|
1531
|
+
);
|
|
1532
|
+
}
|
|
1533
|
+
const raw = (0, import_node_fs.readFileSync)(opts.userData);
|
|
1534
|
+
userDataBase64 = raw.toString("base64");
|
|
1535
|
+
if (userDataBase64.length > 65535) {
|
|
1536
|
+
throw new NhnCloudCliError(
|
|
1537
|
+
`--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.`,
|
|
1538
|
+
EXIT_PARAM_ERROR
|
|
1539
|
+
);
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1504
1542
|
const timeoutMs = parseInt(opts.timeout ?? "300", 10) * 1e3;
|
|
1505
1543
|
const { client } = await resolveInstanceClient(opts);
|
|
1506
1544
|
startSpinner("\uC778\uC2A4\uD134\uC2A4 \uC0DD\uC131 \uC911...");
|
|
@@ -1515,7 +1553,8 @@ var createCommand = new import_commander9.Command("create").description("\uC778\
|
|
|
1515
1553
|
keyName: opts.keyName,
|
|
1516
1554
|
securityGroups: opts.securityGroup && opts.securityGroup.length > 0 ? opts.securityGroup : void 0,
|
|
1517
1555
|
ephemeralDiskSize: opts.ephemeralDiskSize !== void 0 ? parseInt(opts.ephemeralDiskSize, 10) : void 0,
|
|
1518
|
-
protect: opts.protect
|
|
1556
|
+
protect: opts.protect,
|
|
1557
|
+
userDataBase64
|
|
1519
1558
|
});
|
|
1520
1559
|
} catch (err) {
|
|
1521
1560
|
stopSpinner(false);
|
|
@@ -1595,7 +1634,7 @@ var deleteCommand = new import_commander10.Command("delete").description("\uC778
|
|
|
1595
1634
|
|
|
1596
1635
|
// src/index.ts
|
|
1597
1636
|
var program = new import_commander11.Command();
|
|
1598
|
-
program.name("nhncloud").description("NHN Cloud CLI \u2014 AI agent & terminal friendly").version("0.
|
|
1637
|
+
program.name("nhncloud").description("NHN Cloud CLI \u2014 AI agent & terminal friendly").version("0.3.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");
|
|
1599
1638
|
program.hook("preAction", () => {
|
|
1600
1639
|
const opts = program.opts();
|
|
1601
1640
|
if (!opts.color || process.env["NO_COLOR"]) {
|
package/package.json
CHANGED
|
@@ -326,6 +326,7 @@ NHNCLOUD_IAAS_PASSWORD=<api-password> nhncloud configure \
|
|
|
326
326
|
| `--security-group <name>` | 아니오 | 보안 그룹 이름 (반복 지정) |
|
|
327
327
|
| `--ephemeral-disk-size <gb>` | 아니오 | 임시 디스크 크기(GB, NHN 확장) |
|
|
328
328
|
| `--protect` | 아니오 | 삭제 방지 설정 (NHN 확장) |
|
|
329
|
+
| `--user-data <path>` | 아니오 | cloud-init user-data 파일 경로. base64 인코딩해 `user_data` 주입 (인코딩 후 65535 바이트 한도, 초과 시 입력 오류). 부팅 시 드라이버·패키지 자동 셋업에 사용 |
|
|
329
330
|
| `--wait` | 아니오 | ACTIVE 상태 + IP 할당까지 대기 |
|
|
330
331
|
| `--timeout <sec>` | 아니오 | wait 타임아웃 (초, 기본 300) |
|
|
331
332
|
| `--region <region>` | 아니오 | region override (kr1/kr2/kr3/jp1) |
|
|
@@ -356,6 +357,16 @@ nhncloud instance delete "$INSTANCE_ID" --yes
|
|
|
356
357
|
|
|
357
358
|
# 4. 목록에서 id 만 추출
|
|
358
359
|
nhncloud instance list --quiet
|
|
360
|
+
|
|
361
|
+
# 5. cloud-init 으로 부팅 시 셋업 자동화 (일회성 GPU CI 러너)
|
|
362
|
+
nhncloud instance create \
|
|
363
|
+
--name gpu-ci \
|
|
364
|
+
--flavor <gpu-flavor-id> \
|
|
365
|
+
--image <image-id> \
|
|
366
|
+
--network <network-uuid> \
|
|
367
|
+
--boot-volume-size 30 \
|
|
368
|
+
--user-data ./setup-nvidia-docker.yaml \
|
|
369
|
+
--wait --quiet
|
|
359
370
|
```
|
|
360
371
|
|
|
361
372
|
### instance 에러 코드
|