@bifos/nhncloud-cli 0.11.0 → 0.12.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 +32 -10
- package/dist/index.js +143 -121
- package/package.json +1 -1
- package/skills/nhncloud-cli/SKILL.md +30 -74
- package/skills/nhncloud-cli/references/common.md +24 -3
- package/skills/nhncloud-cli/references/logncrash.md +32 -10
- package/skills/nhncloud-cli/references/troubleshooting.md +12 -3
package/README.md
CHANGED
|
@@ -25,17 +25,17 @@ npm install -g @bifos/nhncloud-cli
|
|
|
25
25
|
nhncloud configure
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
- profile → UAK(id/secret) → logncrash appkey
|
|
28
|
+
- profile → 공통 UAK(id/secret) → logncrash appkey → iaas 자격증명 → ncr appkey → ncs appkey 순으로 입력한다.
|
|
29
29
|
- profile 은 프로젝트 단위다 — 여러 NHN Cloud 프로젝트를 쓰면 profile 을 분리하되, 같은 UAK 를 쓴다면
|
|
30
30
|
대화형 마법사가 기존 profile 의 UAK 재사용 여부를 먼저 물어 재입력을 줄여준다.
|
|
31
31
|
- 저장 전 연결 테스트를 자동으로 수행한다 (`--no-verify` 로 생략 가능).
|
|
32
32
|
- CI/자동화는 flag 로 비대화형 설정이 가능하다.
|
|
33
33
|
|
|
34
34
|
```bash
|
|
35
|
-
# UAK + logncrash + ncs 비대화형 설정
|
|
35
|
+
# 공통 UAK + logncrash + ncs 비대화형 설정
|
|
36
36
|
nhncloud configure \
|
|
37
|
-
--uak-id <id> --uak-secret <secret> \
|
|
38
|
-
--logncrash-appkey <
|
|
37
|
+
--uak-id <uak-id> --uak-secret <uak-secret> \
|
|
38
|
+
--logncrash-appkey <appkey> \
|
|
39
39
|
[--ncs-appkey <appkey>] \
|
|
40
40
|
--no-verify
|
|
41
41
|
|
|
@@ -143,6 +143,11 @@ nhncloud deploy download <target> --binary-group <key> --binary-key <binary-key>
|
|
|
143
143
|
|
|
144
144
|
### 로그 검색
|
|
145
145
|
|
|
146
|
+
Log & Crash Search v3는 profile의 logncrash appkey와 공통 UAK를 사용한다.
|
|
147
|
+
CLI가 UAK를 OAuth 토큰으로 교환해 `X-NHN-Authorization: Bearer <token>`으로 인증한다.
|
|
148
|
+
기존 logncrash secret은 검색에 사용하거나 새 설정에 저장하지 않는다.
|
|
149
|
+
전환 호환 옵션 `nhncloud configure --logncrash-secret`은 경고 후 무시한다.
|
|
150
|
+
|
|
146
151
|
```bash
|
|
147
152
|
# 최근 1시간 NORMAL 로그
|
|
148
153
|
nhncloud logncrash search \
|
|
@@ -161,6 +166,15 @@ nhncloud logncrash search \
|
|
|
161
166
|
--query 'body:*request_received*' \
|
|
162
167
|
--from 1h \
|
|
163
168
|
--to now
|
|
169
|
+
|
|
170
|
+
# 다음 페이지 조회: 직전 JSON의 nextCursor를 그대로 전달
|
|
171
|
+
next_cursor="$(nhncloud logncrash search \
|
|
172
|
+
--query '*' --from 1h --to now --json | jq -r '.nextCursor // empty')"
|
|
173
|
+
if [ -n "$next_cursor" ]; then
|
|
174
|
+
nhncloud logncrash search \
|
|
175
|
+
--query '*' --from 1h --to now \
|
|
176
|
+
--cursor "$next_cursor" --json
|
|
177
|
+
fi
|
|
164
178
|
```
|
|
165
179
|
|
|
166
180
|
`--query` 는 콘솔의 간편 검색어가 아니라 Log & Crash Search API 의 Lucene 쿼리다.
|
|
@@ -170,6 +184,9 @@ body 검색 의도가 명확하면 `body:<keyword>` 또는 `body:*<keyword>*`
|
|
|
170
184
|
API 제약상 검색 시작은 최근 90일 이내, 검색 범위는 31일 이하여야 한다 (초과 시 입력 오류로 거절).
|
|
171
185
|
전송 직후에는 인덱싱 지연으로 잠시 0건이 나올 수 있다.
|
|
172
186
|
반복 검색이나 넓은 wildcard 검색은 API rate limit 에 걸릴 수 있으므로 시간 범위를 좁혀 확인한다.
|
|
187
|
+
검색 결과는 `logTime DESC`로 고정 정렬한다.
|
|
188
|
+
`--page`는 전환 호환을 위해 남아 있지만 `0`만 허용한다.
|
|
189
|
+
다음 페이지는 JSON 응답에 `nextCursor`가 있을 때 그 값을 `--cursor`로 변형 없이 전달한다.
|
|
173
190
|
|
|
174
191
|
### 로그 대량 추출 (export)
|
|
175
192
|
|
|
@@ -187,7 +204,9 @@ nhncloud logncrash export --query 'logType:"ERROR"' --from 1h --to now \
|
|
|
187
204
|
|
|
188
205
|
단발 검색(`search`)과 달리 scroll API 로 전체 결과(최대 10만 건)를 순회해 파일로 저장한다.
|
|
189
206
|
진행 상황은 stderr, 데이터는 파일에만 기록된다(stdout 비움).
|
|
190
|
-
|
|
207
|
+
중간 요청이 실패하면 원본 오류를 확인하고 검색 시간 범위를 좁혀 다시 실행한다.
|
|
208
|
+
기존 `--size`는 전환 호환 옵션이다.
|
|
209
|
+
명시하면 10~100 범위를 검증하고 폐기 예정 경고를 출력하지만 Search v3 요청에는 전달하지 않는다.
|
|
191
210
|
시간 범위 제한은 search 와 동일(90일 이내·31일 이하).
|
|
192
211
|
|
|
193
212
|
#### export 옵션
|
|
@@ -199,7 +218,7 @@ scrollKey 유효기간은 1분이므로 데이터가 많을 때는 `--size` 를
|
|
|
199
218
|
| `--to <time>` | 예 | 검색 끝 |
|
|
200
219
|
| `--output <file>` | 예 | 출력 파일 경로 |
|
|
201
220
|
| `--format <fmt>` | 아니오 | `jsonl`(기본, 한 줄당 한 로그) 또는 `json`(배열) |
|
|
202
|
-
| `--size <n>` | 아니오 |
|
|
221
|
+
| `--size <n>` | 아니오 | 폐기 예정 호환 옵션. 10~100 검증 후 경고하고 v3 요청에서는 무시 |
|
|
203
222
|
| `--force` | 아니오 | 출력 파일이 있으면 덮어쓴다 (기본 거부) |
|
|
204
223
|
| `--profile <name>` | 아니오 | 사용할 profile 이름 |
|
|
205
224
|
|
|
@@ -219,7 +238,9 @@ echo "배치 작업 종료" | nhncloud logncrash send --level INFO
|
|
|
219
238
|
nhncloud logncrash send --body "deploy 시작" --app-version 2.3.0 --source batch
|
|
220
239
|
```
|
|
221
240
|
|
|
222
|
-
> logncrash send
|
|
241
|
+
> `logncrash send`는 Search v3 전환의 영향을 받지 않는다.
|
|
242
|
+
> 검색과 다른 collector로 전송하며 appkey만 사용한다.
|
|
243
|
+
> 단일 로그 본문은 8MB까지 허용한다.
|
|
223
244
|
|
|
224
245
|
### 출력 모드
|
|
225
246
|
|
|
@@ -247,7 +268,7 @@ root와 주요 service group의 `--help`에는 짧은 agent hint가 포함된다
|
|
|
247
268
|
| 명령 | `--json` 출력 shape |
|
|
248
269
|
|------|---------------------|
|
|
249
270
|
| `commands` | `{ commands: [{ path, description, arguments, options, subcommands }] }` |
|
|
250
|
-
| `logncrash search` | `{ totalItems, pageNumber, pageSize, data }` 객체 |
|
|
271
|
+
| `logncrash search` | `{ totalItems, pageNumber, pageSize, data, nextCursor? }` 객체 |
|
|
251
272
|
| `logncrash export` | 파일 출력 전용. stdout JSON 없음 |
|
|
252
273
|
| `deploy artifacts` | Deploy API `body` 객체 |
|
|
253
274
|
| `deploy server-groups` | Deploy API `body` 객체 |
|
|
@@ -318,8 +339,9 @@ nhncloud logncrash search --query '*' --from 1d --to now --json | jq '.totalItem
|
|
|
318
339
|
| `--query <lucene>` | 예 | Lucene 질의 문자열 |
|
|
319
340
|
| `--from <time>` | 예 | 검색 시작 (ISO8601 또는 상대시간) |
|
|
320
341
|
| `--to <time>` | 예 | 검색 끝 |
|
|
321
|
-
| `--page <n>` | 아니오 | pageNumber
|
|
322
|
-
| `--size <n>` | 아니오 | pageSize (기본 10,
|
|
342
|
+
| `--page <n>` | 아니오 | 전환 호환용 pageNumber. 기본 0이며 0만 허용 |
|
|
343
|
+
| `--size <n>` | 아니오 | pageSize (기본 10, 범위 1~100) |
|
|
344
|
+
| `--cursor <value>` | 아니오 | 직전 JSON 응답의 불투명한 `nextCursor` |
|
|
323
345
|
| `--profile <name>` | 아니오 | 사용할 profile |
|
|
324
346
|
|
|
325
347
|
전역 옵션: `--json` / `--quiet` / `--no-color`.
|
package/dist/index.js
CHANGED
|
@@ -114,7 +114,7 @@ nhncloud configure \uB97C \uC2E4\uD589\uD574 \uC790\uACA9\uC99D\uBA85\uC744 \uC1
|
|
|
114
114
|
profiles: {
|
|
115
115
|
default: {
|
|
116
116
|
userAccessKey: { id: "<uak-id>", secret: "<uak-secret>" },
|
|
117
|
-
logncrash: { appkey: "<appkey>"
|
|
117
|
+
logncrash: { appkey: "<appkey>" }
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
120
|
},
|
|
@@ -215,7 +215,7 @@ ${CREDENTIALS_PATH} \uC5D0\uC11C profiles.${profileName} \uBE14\uB85D\uC744 \uCD
|
|
|
215
215
|
throw new NhnCloudCliError(
|
|
216
216
|
`profile "${profileName}" \uC5D0 "${service}" \uC790\uACA9\uC99D\uBA85\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.
|
|
217
217
|
${CREDENTIALS_PATH} \uC5D0\uC11C profiles.${profileName}.${service} \uBE14\uB85D\uC744 \uCD94\uAC00\uD558\uC138\uC694.
|
|
218
|
-
\uC608\uC2DC: { "appkey": "<appkey>"
|
|
218
|
+
\uC608\uC2DC: { "appkey": "<appkey>" }`,
|
|
219
219
|
EXIT_CONFIG_ERROR
|
|
220
220
|
);
|
|
221
221
|
}
|
|
@@ -681,33 +681,26 @@ function unwrap(res) {
|
|
|
681
681
|
// src/services/logncrash/client.ts
|
|
682
682
|
var LogncrashClient = class {
|
|
683
683
|
appkey;
|
|
684
|
-
/**
|
|
685
|
-
|
|
686
|
-
constructor(appkey,
|
|
684
|
+
/** Search v3 읽기 요청에만 필요하다. collector send 는 인증 헤더를 쓰지 않는다(ADR-014). */
|
|
685
|
+
accessToken;
|
|
686
|
+
constructor(appkey, accessToken) {
|
|
687
687
|
this.appkey = appkey;
|
|
688
|
-
this.
|
|
688
|
+
this.accessToken = accessToken;
|
|
689
689
|
}
|
|
690
|
-
async
|
|
691
|
-
|
|
692
|
-
throw new NhnCloudCliError(
|
|
693
|
-
"logncrash search \uC5D0\uB294 secret \uC774 \uD544\uC694\uD569\uB2C8\uB2E4. configure \uB85C logncrash secret \uC744 \uC124\uC815\uD558\uC138\uC694.",
|
|
694
|
-
EXIT_CONFIG_ERROR
|
|
695
|
-
);
|
|
696
|
-
}
|
|
690
|
+
async cursorSearch(params) {
|
|
691
|
+
const headers = this.readHeaders();
|
|
697
692
|
const endpoint = endpointFor("logncrash");
|
|
698
|
-
const url = `${endpoint}/
|
|
693
|
+
const url = `${endpoint}/v3/${encodeURIComponent(this.appkey)}/logs/cursor`;
|
|
699
694
|
try {
|
|
700
695
|
const res = await import_ky4.default.post(url, {
|
|
701
|
-
headers
|
|
702
|
-
"X-LNCS-SECRET": this.secret,
|
|
703
|
-
"Content-Type": "application/json"
|
|
704
|
-
},
|
|
696
|
+
headers,
|
|
705
697
|
json: {
|
|
706
698
|
query: params.query,
|
|
707
699
|
from: params.from,
|
|
708
700
|
to: params.to,
|
|
709
|
-
|
|
710
|
-
pageSize: params.pageSize
|
|
701
|
+
sort: { logTime: "DESC" },
|
|
702
|
+
...params.pageSize !== void 0 ? { pageSize: params.pageSize } : {},
|
|
703
|
+
...params.cursor !== void 0 ? { cursor: params.cursor } : {}
|
|
711
704
|
}
|
|
712
705
|
}).json();
|
|
713
706
|
return unwrap(res);
|
|
@@ -716,29 +709,19 @@ var LogncrashClient = class {
|
|
|
716
709
|
}
|
|
717
710
|
}
|
|
718
711
|
/**
|
|
719
|
-
* scroll 검색을 시작한다.
|
|
720
|
-
* body 는 search 와 동일(query/from/to/pageSize). 응답 scrollKey 로 scrollNext 를 이어 호출한다.
|
|
712
|
+
* v3 scroll 검색을 시작한다. 응답 scrollKey 로 scrollNext 를 이어 호출한다.
|
|
721
713
|
*/
|
|
722
714
|
async scrollStart(params) {
|
|
723
|
-
|
|
724
|
-
throw new NhnCloudCliError(
|
|
725
|
-
"logncrash scroll \uC5D0\uB294 secret \uC774 \uD544\uC694\uD569\uB2C8\uB2E4. configure \uB85C logncrash secret \uC744 \uC124\uC815\uD558\uC138\uC694.",
|
|
726
|
-
EXIT_CONFIG_ERROR
|
|
727
|
-
);
|
|
728
|
-
}
|
|
715
|
+
const headers = this.readHeaders();
|
|
729
716
|
const endpoint = endpointFor("logncrash");
|
|
730
|
-
const url = `${endpoint}/
|
|
717
|
+
const url = `${endpoint}/v3/${encodeURIComponent(this.appkey)}/logs/scroll`;
|
|
731
718
|
try {
|
|
732
719
|
const res = await import_ky4.default.post(url, {
|
|
733
|
-
headers
|
|
734
|
-
"X-LNCS-SECRET": this.secret,
|
|
735
|
-
"Content-Type": "application/json"
|
|
736
|
-
},
|
|
720
|
+
headers,
|
|
737
721
|
json: {
|
|
738
722
|
query: params.query,
|
|
739
723
|
from: params.from,
|
|
740
|
-
to: params.to
|
|
741
|
-
pageSize: params.pageSize ?? 100
|
|
724
|
+
to: params.to
|
|
742
725
|
}
|
|
743
726
|
}).json();
|
|
744
727
|
return unwrap(res);
|
|
@@ -747,31 +730,30 @@ var LogncrashClient = class {
|
|
|
747
730
|
}
|
|
748
731
|
}
|
|
749
732
|
/**
|
|
750
|
-
* scroll 다음 페이지를 가져온다.
|
|
751
|
-
* body 는 보내지 않는다(scrollKey 가 좌표). scrollKey 만료 시 API 가 실패 봉투를 주며,
|
|
752
|
-
* unwrap 이 EXIT_API_ERROR 로 변환한다 — 호출부에서 만료 안내 메시지로 감싼다.
|
|
733
|
+
* v3 scroll 다음 페이지를 가져온다. body 없이 scrollKey 경로만 사용한다.
|
|
753
734
|
*/
|
|
754
735
|
async scrollNext(scrollKey) {
|
|
755
|
-
|
|
756
|
-
throw new NhnCloudCliError(
|
|
757
|
-
"logncrash scroll \uC5D0\uB294 secret \uC774 \uD544\uC694\uD569\uB2C8\uB2E4. configure \uB85C logncrash secret \uC744 \uC124\uC815\uD558\uC138\uC694.",
|
|
758
|
-
EXIT_CONFIG_ERROR
|
|
759
|
-
);
|
|
760
|
-
}
|
|
736
|
+
const headers = this.readHeaders();
|
|
761
737
|
const endpoint = endpointFor("logncrash");
|
|
762
|
-
const url = `${endpoint}/
|
|
738
|
+
const url = `${endpoint}/v3/${encodeURIComponent(this.appkey)}/logs/scroll/${encodeURIComponent(scrollKey)}`;
|
|
763
739
|
try {
|
|
764
740
|
const res = await import_ky4.default.post(url, {
|
|
765
|
-
headers
|
|
766
|
-
"X-LNCS-SECRET": this.secret,
|
|
767
|
-
"Content-Type": "application/json"
|
|
768
|
-
}
|
|
741
|
+
headers
|
|
769
742
|
}).json();
|
|
770
743
|
return unwrap(res);
|
|
771
744
|
} catch (err) {
|
|
772
745
|
throw toNhnCloudCliError(err);
|
|
773
746
|
}
|
|
774
747
|
}
|
|
748
|
+
readHeaders() {
|
|
749
|
+
if (!this.accessToken) {
|
|
750
|
+
throw new NhnCloudCliError(
|
|
751
|
+
"Log & Crash Search v3 \uD638\uCD9C\uC5D0\uB294 \uACF5\uD1B5 UAK\uB85C \uBC1C\uAE09\uD55C access token\uC774 \uD544\uC694\uD569\uB2C8\uB2E4. nhncloud configure\uB85C UAK\uB97C \uC124\uC815\uD558\uC138\uC694.",
|
|
752
|
+
EXIT_CONFIG_ERROR
|
|
753
|
+
);
|
|
754
|
+
}
|
|
755
|
+
return { "X-NHN-Authorization": `Bearer ${this.accessToken}` };
|
|
756
|
+
}
|
|
775
757
|
/**
|
|
776
758
|
* 로그 한 건을 Log & Crash collector 로 전송한다 (ADR-014).
|
|
777
759
|
* - host: api-logncrash (검색의 api-lncs-search 와 별도)
|
|
@@ -1766,17 +1748,17 @@ async function verifyNcs(uak, appkey) {
|
|
|
1766
1748
|
throw err;
|
|
1767
1749
|
}
|
|
1768
1750
|
}
|
|
1769
|
-
async function verifyLogncrash(
|
|
1770
|
-
if (!
|
|
1771
|
-
const client = new LogncrashClient(cred.appkey, cred.secret);
|
|
1751
|
+
async function verifyLogncrash(uak, appkey) {
|
|
1752
|
+
if (!appkey) return false;
|
|
1772
1753
|
const now = /* @__PURE__ */ new Date();
|
|
1773
1754
|
const oneMinuteAgo = new Date(now.getTime() - 6e4);
|
|
1774
1755
|
try {
|
|
1775
|
-
await
|
|
1756
|
+
const token = await getAccessToken("__verify__", uak.id, uak.secret, true);
|
|
1757
|
+
const client = new LogncrashClient(appkey, token);
|
|
1758
|
+
await client.cursorSearch({
|
|
1776
1759
|
query: "*",
|
|
1777
1760
|
from: oneMinuteAgo.toISOString(),
|
|
1778
1761
|
to: now.toISOString(),
|
|
1779
|
-
pageNumber: 0,
|
|
1780
1762
|
pageSize: 1
|
|
1781
1763
|
});
|
|
1782
1764
|
return true;
|
|
@@ -1789,7 +1771,7 @@ async function verifyLogncrash(cred) {
|
|
|
1789
1771
|
}
|
|
1790
1772
|
|
|
1791
1773
|
// src/commands/configure.ts
|
|
1792
|
-
async function saveAndVerify(profileName, uak, logncrash, iaas, ncr, ncs, doVerify) {
|
|
1774
|
+
async function saveAndVerify(profileName, uak, logncrash, iaas, ncr, ncs, doVerify, logncrashUak = uak) {
|
|
1793
1775
|
if (doVerify) {
|
|
1794
1776
|
if (uak) {
|
|
1795
1777
|
const ok = await verifyUserAccessKey(uak);
|
|
@@ -1803,12 +1785,18 @@ async function saveAndVerify(profileName, uak, logncrash, iaas, ncr, ncs, doVeri
|
|
|
1803
1785
|
}
|
|
1804
1786
|
}
|
|
1805
1787
|
if (logncrash) {
|
|
1806
|
-
|
|
1788
|
+
if (!logncrashUak) {
|
|
1789
|
+
throw new NhnCloudCliError(
|
|
1790
|
+
"logncrash Search v3 \uAC80\uC99D\uC5D0\uB294 \uACF5\uD1B5 UAK\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. --uak-id\uC640 UAK secret\uC744 \uD568\uAED8 \uC124\uC815\uD558\uAC70\uB098 \uAE30\uC874 profile\uC5D0 UAK\uB97C \uC124\uC815\uD558\uC138\uC694.",
|
|
1791
|
+
EXIT_CONFIG_ERROR
|
|
1792
|
+
);
|
|
1793
|
+
}
|
|
1794
|
+
const ok = await verifyLogncrash(logncrashUak, logncrash.appkey ?? "");
|
|
1807
1795
|
if (ok) {
|
|
1808
1796
|
process.stderr.write(import_chalk.default.green(" \u2713 logncrash \uC5F0\uACB0 \uC131\uACF5\n"));
|
|
1809
1797
|
} else {
|
|
1810
1798
|
throw new NhnCloudCliError(
|
|
1811
|
-
"logncrash \uC778\uC99D \uC2E4\uD328 \u2014 appkey
|
|
1799
|
+
"logncrash \uC778\uC99D \uC2E4\uD328 \u2014 appkey \uB610\uB294 UAK\uB97C \uD655\uC778\uD558\uC138\uC694.",
|
|
1812
1800
|
EXIT_AUTH_ERROR
|
|
1813
1801
|
);
|
|
1814
1802
|
}
|
|
@@ -1932,8 +1920,7 @@ async function runInteractive(opts) {
|
|
|
1932
1920
|
message: "logncrash appkey",
|
|
1933
1921
|
validate: (v) => v.trim().length > 0 || "logncrash appkey \uB97C \uC785\uB825\uD558\uC138\uC694"
|
|
1934
1922
|
});
|
|
1935
|
-
|
|
1936
|
-
logncrash = { appkey, secret };
|
|
1923
|
+
logncrash = { appkey: appkey.trim() };
|
|
1937
1924
|
}
|
|
1938
1925
|
let iaas;
|
|
1939
1926
|
const setupIaas = await confirm({
|
|
@@ -1994,7 +1981,7 @@ async function runInteractive(opts) {
|
|
|
1994
1981
|
}
|
|
1995
1982
|
if (opts.verify) {
|
|
1996
1983
|
try {
|
|
1997
|
-
await saveAndVerify(profileName, uak, logncrash, iaas, ncr, ncs, true);
|
|
1984
|
+
await saveAndVerify(profileName, uak, logncrash, iaas, ncr, ncs, true, uak);
|
|
1998
1985
|
} catch (err) {
|
|
1999
1986
|
if (err instanceof NhnCloudCliError && err.exitCode === EXIT_AUTH_ERROR) {
|
|
2000
1987
|
process.stderr.write(import_chalk.default.red(` \u2717 ${err.message}
|
|
@@ -2007,22 +1994,43 @@ async function runInteractive(opts) {
|
|
|
2007
1994
|
process.stderr.write(import_chalk.default.yellow("\uC800\uC7A5\uC774 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4.\n"));
|
|
2008
1995
|
return;
|
|
2009
1996
|
}
|
|
2010
|
-
await saveAndVerify(profileName, uak, logncrash, iaas, ncr, ncs, false);
|
|
1997
|
+
await saveAndVerify(profileName, uak, logncrash, iaas, ncr, ncs, false, uak);
|
|
2011
1998
|
} else {
|
|
2012
1999
|
throw err;
|
|
2013
2000
|
}
|
|
2014
2001
|
}
|
|
2015
2002
|
} else {
|
|
2016
|
-
await saveAndVerify(profileName, uak, logncrash, iaas, ncr, ncs, false);
|
|
2003
|
+
await saveAndVerify(profileName, uak, logncrash, iaas, ncr, ncs, false, uak);
|
|
2017
2004
|
}
|
|
2018
2005
|
}
|
|
2019
2006
|
async function runNonInteractive(opts) {
|
|
2020
|
-
const profileName = await resolveProfileName(opts.profile);
|
|
2021
2007
|
const uakSecret = opts.uakSecret ?? process.env["NHNCLOUD_UAK_SECRET"];
|
|
2022
|
-
const logncrashSecret = opts.logncrashSecret ?? process.env["NHNCLOUD_LOGNCRASH_SECRET"];
|
|
2023
2008
|
const iaasPassword = opts.iaasPassword ?? process.env["NHNCLOUD_IAAS_PASSWORD"];
|
|
2024
|
-
|
|
2025
|
-
|
|
2009
|
+
if (opts.uakId !== void 0 && opts.uakId.trim().length === 0) {
|
|
2010
|
+
throw new NhnCloudCliError("--uak-id \uAC12\uC740 \uBE44\uC5B4 \uC788\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.", EXIT_PARAM_ERROR);
|
|
2011
|
+
}
|
|
2012
|
+
if (uakSecret !== void 0 && uakSecret.length === 0) {
|
|
2013
|
+
throw new NhnCloudCliError("UAK secret \uAC12\uC740 \uBE44\uC5B4 \uC788\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.", EXIT_PARAM_ERROR);
|
|
2014
|
+
}
|
|
2015
|
+
if (opts.uakId !== void 0 !== (uakSecret !== void 0)) {
|
|
2016
|
+
throw new NhnCloudCliError(
|
|
2017
|
+
"\uBE44\uB300\uD654\uD615 \uBAA8\uB4DC\uC5D0\uC11C --uak-id\uC640 UAK secret\uC740 \uD568\uAED8 \uC9C0\uC815\uD574\uC57C \uD569\uB2C8\uB2E4.",
|
|
2018
|
+
EXIT_PARAM_ERROR
|
|
2019
|
+
);
|
|
2020
|
+
}
|
|
2021
|
+
if (opts.logncrashAppkey !== void 0 && opts.logncrashAppkey.trim().length === 0) {
|
|
2022
|
+
throw new NhnCloudCliError("--logncrash-appkey \uAC12\uC740 \uBE44\uC5B4 \uC788\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.", EXIT_PARAM_ERROR);
|
|
2023
|
+
}
|
|
2024
|
+
if (opts.logncrashSecret !== void 0) {
|
|
2025
|
+
process.stderr.write(
|
|
2026
|
+
import_chalk.default.yellow(
|
|
2027
|
+
"\uACBD\uACE0: --logncrash-secret\uC740 \uD3D0\uAE30 \uC608\uC815\uC774\uBA70 Search v3 \uC778\uC99D\uC5D0 \uC0AC\uC6A9\uD558\uAC70\uB098 \uC800\uC7A5\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.\n"
|
|
2028
|
+
)
|
|
2029
|
+
);
|
|
2030
|
+
}
|
|
2031
|
+
const profileName = await resolveProfileName(opts.profile);
|
|
2032
|
+
const uak = opts.uakId !== void 0 && uakSecret !== void 0 ? { id: opts.uakId.trim(), secret: uakSecret } : void 0;
|
|
2033
|
+
const logncrash = opts.logncrashAppkey?.trim() ? { appkey: opts.logncrashAppkey.trim() } : void 0;
|
|
2026
2034
|
const iaas = opts.iaasTenantId && opts.iaasUsername && iaasPassword ? {
|
|
2027
2035
|
tenantId: opts.iaasTenantId,
|
|
2028
2036
|
username: opts.iaasUsername,
|
|
@@ -2033,20 +2041,33 @@ async function runNonInteractive(opts) {
|
|
|
2033
2041
|
const ncs = opts.ncsAppkey?.trim() ? { appkey: opts.ncsAppkey.trim() } : void 0;
|
|
2034
2042
|
if (!uak && !logncrash && !iaas && !ncr && !ncs) {
|
|
2035
2043
|
throw new NhnCloudCliError(
|
|
2036
|
-
"\uBE44\uB300\uD654\uD615 \uBAA8\uB4DC: --uak-id + UAK secret, --logncrash-appkey
|
|
2044
|
+
"\uBE44\uB300\uD654\uD615 \uBAA8\uB4DC: --uak-id + UAK secret, --logncrash-appkey,\n--iaas-tenant-id + --iaas-username + iaas password,\n--ncr-appkey, \uB610\uB294 --ncs-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_IAAS_PASSWORD.",
|
|
2037
2045
|
EXIT_PARAM_ERROR
|
|
2038
2046
|
);
|
|
2039
2047
|
}
|
|
2048
|
+
let logncrashUak = uak;
|
|
2049
|
+
if (opts.verify && logncrash && !logncrashUak) {
|
|
2050
|
+
logncrashUak = await getUserAccessKey(profileName);
|
|
2051
|
+
}
|
|
2040
2052
|
if (opts.verify) {
|
|
2041
2053
|
process.stderr.write(import_chalk.default.gray("\uC5F0\uACB0 \uD14C\uC2A4\uD2B8 \uC911\u2026\n"));
|
|
2042
2054
|
}
|
|
2043
|
-
await saveAndVerify(
|
|
2055
|
+
await saveAndVerify(
|
|
2056
|
+
profileName,
|
|
2057
|
+
uak,
|
|
2058
|
+
logncrash,
|
|
2059
|
+
iaas,
|
|
2060
|
+
ncr,
|
|
2061
|
+
ncs,
|
|
2062
|
+
opts.verify,
|
|
2063
|
+
logncrashUak
|
|
2064
|
+
);
|
|
2044
2065
|
}
|
|
2045
|
-
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>", "
|
|
2066
|
+
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>", "\uD3D0\uAE30 \uC608\uC815: Search v3\uC5D0\uC11C\uB294 \uC0AC\uC6A9\xB7\uC800\uC7A5\uD558\uC9C0 \uC54A\uC74C").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(
|
|
2046
2067
|
"--iaas-password <pass>",
|
|
2047
2068
|
"iaas API \uBE44\uBC00\uBC88\uD638 (\uBE44\uB300\uD654\uD615, \uB178\uCD9C \uBC29\uC9C0\uB85C env NHNCLOUD_IAAS_PASSWORD \uAD8C\uC7A5)"
|
|
2048
2069
|
).option("--iaas-region <region>", "iaas region (\uAE30\uBCF8: kr1)", "kr1").option("--ncr-appkey <key>", "ncr appkey (\uBE44\uB300\uD654\uD615)").option("--ncs-appkey <key>", "ncs appkey (\uBE44\uB300\uD654\uD615)").option("--no-verify", "\uC5F0\uACB0 \uD14C\uC2A4\uD2B8 \uC0DD\uB7B5").action(async (opts) => {
|
|
2049
|
-
const hasFlag = opts.uakId || opts.uakSecret || opts.logncrashAppkey || opts.logncrashSecret || opts.iaasTenantId || opts.iaasUsername || opts.iaasPassword || opts.ncrAppkey || opts.ncsAppkey;
|
|
2070
|
+
const hasFlag = opts.uakId !== void 0 || opts.uakSecret !== void 0 || opts.logncrashAppkey !== void 0 || opts.logncrashSecret !== void 0 || opts.iaasTenantId !== void 0 || opts.iaasUsername !== void 0 || opts.iaasPassword !== void 0 || opts.ncrAppkey !== void 0 || opts.ncsAppkey !== void 0;
|
|
2050
2071
|
try {
|
|
2051
2072
|
if (hasFlag) {
|
|
2052
2073
|
await runNonInteractive(opts);
|
|
@@ -2400,6 +2421,21 @@ function parseNonNegativeIntegerOption(value, flag) {
|
|
|
2400
2421
|
return parseIntegerOption(value, flag, { min: 0 });
|
|
2401
2422
|
}
|
|
2402
2423
|
|
|
2424
|
+
// src/commands/logncrash/helpers.ts
|
|
2425
|
+
async function resolveLogncrashClient(profile) {
|
|
2426
|
+
const profileName = await resolveProfileName(profile);
|
|
2427
|
+
const credential = await getServiceCredential("logncrash", profileName);
|
|
2428
|
+
if (typeof credential.appkey !== "string" || credential.appkey.length === 0) {
|
|
2429
|
+
throw new NhnCloudCliError(
|
|
2430
|
+
`profile "${profileName}"\uC758 logncrash \uC790\uACA9\uC99D\uBA85\uC5D0 appkey\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. nhncloud configure\uB85C appkey\uB97C \uC124\uC815\uD558\uC138\uC694.`,
|
|
2431
|
+
EXIT_CONFIG_ERROR
|
|
2432
|
+
);
|
|
2433
|
+
}
|
|
2434
|
+
const uak = await getUserAccessKey(profileName);
|
|
2435
|
+
const accessToken = await getAccessToken(profileName, uak.id, uak.secret);
|
|
2436
|
+
return new LogncrashClient(credential.appkey, accessToken);
|
|
2437
|
+
}
|
|
2438
|
+
|
|
2403
2439
|
// src/commands/logncrash/search.ts
|
|
2404
2440
|
function getString(value) {
|
|
2405
2441
|
return typeof value === "string" ? value : String(value ?? "");
|
|
@@ -2410,7 +2446,7 @@ function formatLogRow(log) {
|
|
|
2410
2446
|
const body = truncate(getString(log["logBody"] ?? log["body"] ?? log["message"] ?? ""), 60);
|
|
2411
2447
|
return [logTime, logType, body];
|
|
2412
2448
|
}
|
|
2413
|
-
var searchCommand = new import_commander4.Command("search").description("Log & Crash \uB85C\uADF8 \uAC80\uC0C9").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("--page <n>", "pageNumber (\uAE30\uBCF8 0)", "0").option("--size <n>", "pageSize (\uAE30\uBCF8 10, \uCD5C\uB300 100)", "10").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (_opts, cmd) => {
|
|
2449
|
+
var searchCommand = new import_commander4.Command("search").description("Log & Crash \uB85C\uADF8 \uAC80\uC0C9").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("--page <n>", "pageNumber (\uAE30\uBCF8 0)", "0").option("--size <n>", "pageSize (\uAE30\uBCF8 10, \uCD5C\uB300 100)", "10").option("--cursor <value>", "\uB2E4\uC74C \uD398\uC774\uC9C0 \uC870\uD68C\uC6A9 opaque cursor").option("--profile <name>", "\uC0AC\uC6A9\uD560 profile \uC774\uB984").action(async (_opts, cmd) => {
|
|
2414
2450
|
const opts = cmd.optsWithGlobals();
|
|
2415
2451
|
if (!opts.query) {
|
|
2416
2452
|
throw new NhnCloudCliError(`--query \uC635\uC158\uC740 \uD544\uC218\uC785\uB2C8\uB2E4. \uC608: --query 'logType:"NORMAL"'`, EXIT_PARAM_ERROR);
|
|
@@ -2423,35 +2459,28 @@ var searchCommand = new import_commander4.Command("search").description("Log & C
|
|
|
2423
2459
|
}
|
|
2424
2460
|
const page = parseNonNegativeIntegerOption(opts.page ?? "0", "--page");
|
|
2425
2461
|
const size = parseIntegerOption(opts.size ?? "10", "--size", { min: 1, max: 100 });
|
|
2426
|
-
|
|
2427
|
-
const toIso = resolveTime(opts.to);
|
|
2428
|
-
assertSearchRange(fromIso, toIso);
|
|
2429
|
-
const profileName = await resolveProfileName(opts.profile);
|
|
2430
|
-
const cred = await getServiceCredential("logncrash", profileName);
|
|
2431
|
-
if (!cred.appkey) {
|
|
2462
|
+
if (page !== 0) {
|
|
2432
2463
|
throw new NhnCloudCliError(
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
EXIT_CONFIG_ERROR
|
|
2464
|
+
"Search v3\uC5D0\uC11C\uB294 --page 0\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4. \uB2E4\uC74C \uD398\uC774\uC9C0\uB294 --cursor <nextCursor>\uB97C \uC0AC\uC6A9\uD558\uC138\uC694.",
|
|
2465
|
+
EXIT_PARAM_ERROR
|
|
2436
2466
|
);
|
|
2437
2467
|
}
|
|
2438
|
-
if (
|
|
2439
|
-
throw new NhnCloudCliError(
|
|
2440
|
-
`profile "${profileName}" \uC758 logncrash \uC790\uACA9\uC99D\uBA85\uC5D0 secret \uC774 \uC5C6\uC2B5\uB2C8\uB2E4.
|
|
2441
|
-
credentials.json \uC5D0 "secret": "<secretkey>" \uB97C \uCD94\uAC00\uD558\uC138\uC694.`,
|
|
2442
|
-
EXIT_CONFIG_ERROR
|
|
2443
|
-
);
|
|
2468
|
+
if (opts.cursor !== void 0 && opts.cursor.trim().length === 0) {
|
|
2469
|
+
throw new NhnCloudCliError("--cursor \uAC12\uC740 \uBE44\uC5B4 \uC788\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.", EXIT_PARAM_ERROR);
|
|
2444
2470
|
}
|
|
2445
|
-
const
|
|
2471
|
+
const fromIso = resolveTime(opts.from);
|
|
2472
|
+
const toIso = resolveTime(opts.to);
|
|
2473
|
+
assertSearchRange(fromIso, toIso);
|
|
2474
|
+
const client = await resolveLogncrashClient(opts.profile);
|
|
2446
2475
|
startSpinner("\uB85C\uADF8 \uAC80\uC0C9 \uC911...");
|
|
2447
2476
|
let result;
|
|
2448
2477
|
try {
|
|
2449
|
-
result = await client.
|
|
2478
|
+
result = await client.cursorSearch({
|
|
2450
2479
|
query: opts.query,
|
|
2451
2480
|
from: fromIso,
|
|
2452
2481
|
to: toIso,
|
|
2453
|
-
|
|
2454
|
-
|
|
2482
|
+
pageSize: size,
|
|
2483
|
+
...opts.cursor !== void 0 ? { cursor: opts.cursor } : {}
|
|
2455
2484
|
});
|
|
2456
2485
|
} catch (err) {
|
|
2457
2486
|
stopSpinner(false);
|
|
@@ -2467,7 +2496,8 @@ credentials.json \uC5D0 "secret": "<secretkey>" \uB97C \uCD94\uAC00\uD558\uC138\
|
|
|
2467
2496
|
totalItems: result.totalItems,
|
|
2468
2497
|
pageNumber: result.pageNumber,
|
|
2469
2498
|
pageSize: result.pageSize,
|
|
2470
|
-
data: result.data
|
|
2499
|
+
data: result.data,
|
|
2500
|
+
...result.nextCursor !== void 0 ? { nextCursor: result.nextCursor } : {}
|
|
2471
2501
|
},
|
|
2472
2502
|
ids
|
|
2473
2503
|
});
|
|
@@ -2581,7 +2611,7 @@ function assertWritable(path3, force) {
|
|
|
2581
2611
|
EXIT_PARAM_ERROR
|
|
2582
2612
|
);
|
|
2583
2613
|
}
|
|
2584
|
-
var exportCommand = new import_commander6.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>", "
|
|
2614
|
+
var exportCommand = new import_commander6.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>", "\uD3D0\uAE30 \uC608\uC815: Search v3\uC5D0\uC11C\uB294 \uBB34\uC2DC\uB428 (\uD638\uD658 \uAC80\uC99D \uBC94\uC704 10~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) => {
|
|
2585
2615
|
const opts = cmd.optsWithGlobals();
|
|
2586
2616
|
if (!opts.query) {
|
|
2587
2617
|
throw new NhnCloudCliError(`--query \uC635\uC158\uC740 \uD544\uC218\uC785\uB2C8\uB2E4. \uC608: --query 'logType:"NORMAL"'`, EXIT_PARAM_ERROR);
|
|
@@ -2599,35 +2629,23 @@ var exportCommand = new import_commander6.Command("export").description("Log & C
|
|
|
2599
2629
|
if (format !== "jsonl" && format !== "json") {
|
|
2600
2630
|
throw new NhnCloudCliError("--format \uC740 jsonl \uB610\uB294 json \uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4.", EXIT_PARAM_ERROR);
|
|
2601
2631
|
}
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2632
|
+
if (opts.size !== void 0) {
|
|
2633
|
+
if (!/^[1-9]\d*$/.test(opts.size)) {
|
|
2634
|
+
throw new NhnCloudCliError("--size \uB294 \uC591\uC758 \uC815\uC218\uC5EC\uC57C \uD569\uB2C8\uB2E4 (\uD638\uD658 \uBC94\uC704 10~100).", EXIT_PARAM_ERROR);
|
|
2635
|
+
}
|
|
2636
|
+
const size = parseInt(opts.size, 10);
|
|
2637
|
+
if (size < 10 || size > 100) {
|
|
2638
|
+
throw new NhnCloudCliError("--size \uB294 10~100 \uC0AC\uC774\uC5EC\uC57C \uD569\uB2C8\uB2E4.", EXIT_PARAM_ERROR);
|
|
2639
|
+
}
|
|
2640
|
+
process.stderr.write(
|
|
2641
|
+
"\uACBD\uACE0: logncrash export --size\uB294 \uD3D0\uAE30 \uC608\uC815\uC774\uBA70 Search v3 \uC694\uCCAD\uC5D0\uC11C\uB294 \uBB34\uC2DC\uB429\uB2C8\uB2E4.\n"
|
|
2642
|
+
);
|
|
2609
2643
|
}
|
|
2610
2644
|
const fromIso = resolveTime(opts.from);
|
|
2611
2645
|
const toIso = resolveTime(opts.to);
|
|
2612
2646
|
assertSearchRange(fromIso, toIso);
|
|
2613
2647
|
assertWritable(opts.output, opts.force ?? false);
|
|
2614
|
-
const
|
|
2615
|
-
const cred = await getServiceCredential("logncrash", profileName);
|
|
2616
|
-
if (!cred.appkey) {
|
|
2617
|
-
throw new NhnCloudCliError(
|
|
2618
|
-
`profile "${profileName}" \uC758 logncrash \uC790\uACA9\uC99D\uBA85\uC5D0 appkey \uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.
|
|
2619
|
-
credentials.json \uC5D0 "appkey": "<appkey>" \uB97C \uCD94\uAC00\uD558\uC138\uC694.`,
|
|
2620
|
-
EXIT_CONFIG_ERROR
|
|
2621
|
-
);
|
|
2622
|
-
}
|
|
2623
|
-
if (!cred.secret) {
|
|
2624
|
-
throw new NhnCloudCliError(
|
|
2625
|
-
`profile "${profileName}" \uC758 logncrash \uC790\uACA9\uC99D\uBA85\uC5D0 secret \uC774 \uC5C6\uC2B5\uB2C8\uB2E4.
|
|
2626
|
-
credentials.json \uC5D0 "secret": "<secret>" \uB97C \uCD94\uAC00\uD558\uC138\uC694.`,
|
|
2627
|
-
EXIT_CONFIG_ERROR
|
|
2628
|
-
);
|
|
2629
|
-
}
|
|
2630
|
-
const client = new LogncrashClient(cred.appkey, cred.secret);
|
|
2648
|
+
const client = await resolveLogncrashClient(opts.profile);
|
|
2631
2649
|
const tmp = opts.output + "." + (0, import_node_crypto2.randomBytes)(4).toString("hex") + ".tmp";
|
|
2632
2650
|
const stream = (0, import_node_fs2.createWriteStream)(tmp, { encoding: "utf-8" });
|
|
2633
2651
|
const spinner = startSpinner("\uB85C\uADF8 \uCD94\uCD9C \uC911...");
|
|
@@ -2645,12 +2663,16 @@ credentials.json \uC5D0 "secret": "<secret>" \uB97C \uCD94\uAC00\uD558\uC138\uC6
|
|
|
2645
2663
|
};
|
|
2646
2664
|
try {
|
|
2647
2665
|
if (format === "json") stream.write("[");
|
|
2648
|
-
let res = await client.scrollStart({
|
|
2666
|
+
let res = await client.scrollStart({
|
|
2667
|
+
query: opts.query,
|
|
2668
|
+
from: fromIso,
|
|
2669
|
+
to: toIso
|
|
2670
|
+
});
|
|
2649
2671
|
total = res.totalItems;
|
|
2650
2672
|
writePage(res.data);
|
|
2651
2673
|
spinner.text = `\uB85C\uADF8 \uCD94\uCD9C \uC911... ${count}/${total}`;
|
|
2652
2674
|
while (res.data.length > 0 && res.scrollKey && count < Math.min(total, MAX_TOTAL)) {
|
|
2653
|
-
res = await
|
|
2675
|
+
res = await scrollNextWithHint(client, res.scrollKey);
|
|
2654
2676
|
writePage(res.data);
|
|
2655
2677
|
spinner.text = `\uB85C\uADF8 \uCD94\uCD9C \uC911... ${count}/${total}`;
|
|
2656
2678
|
}
|
|
@@ -2684,13 +2706,13 @@ credentials.json \uC5D0 "secret": "<secret>" \uB97C \uCD94\uAC00\uD558\uC138\uC6
|
|
|
2684
2706
|
process.stderr.write(`${opts.output} \uC5D0 ${count}\uAC74 \uC800\uC7A5
|
|
2685
2707
|
`);
|
|
2686
2708
|
});
|
|
2687
|
-
async function
|
|
2709
|
+
async function scrollNextWithHint(client, scrollKey) {
|
|
2688
2710
|
try {
|
|
2689
2711
|
return await client.scrollNext(scrollKey);
|
|
2690
2712
|
} catch (err) {
|
|
2691
2713
|
if (err instanceof NhnCloudCliError && err.exitCode === EXIT_API_ERROR) {
|
|
2692
2714
|
throw new NhnCloudCliError(
|
|
2693
|
-
`scroll \uB2E4\uC74C \uD398\uC774\uC9C0 \uC694\uCCAD\uC774 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4 (\uC6D0\uC778: ${err.message}).
|
|
2715
|
+
`scroll \uB2E4\uC74C \uD398\uC774\uC9C0 \uC694\uCCAD\uC774 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4 (\uC6D0\uC778: ${err.message}). \uAC80\uC0C9 \uBC94\uC704\uB97C \uC881\uD600 \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694.`,
|
|
2694
2716
|
EXIT_API_ERROR
|
|
2695
2717
|
);
|
|
2696
2718
|
}
|
|
@@ -8641,7 +8663,7 @@ Agent workflow:
|
|
|
8641
8663
|
3. nhncloud ncs workload get <id> --json
|
|
8642
8664
|
`;
|
|
8643
8665
|
var program = new import_commander53.Command();
|
|
8644
|
-
program.name("nhncloud").description("NHN Cloud CLI \u2014 AI agent & terminal friendly").version("0.
|
|
8666
|
+
program.name("nhncloud").description("NHN Cloud CLI \u2014 AI agent & terminal friendly").version("0.12.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").addHelpText("after", rootAgentHints);
|
|
8645
8667
|
program.hook("preAction", () => {
|
|
8646
8668
|
const opts = program.opts();
|
|
8647
8669
|
if (!opts.color || process.env["NO_COLOR"]) {
|
package/package.json
CHANGED
|
@@ -1,81 +1,37 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: nhncloud-cli
|
|
3
3
|
description: >-
|
|
4
|
-
NHN Cloud
|
|
5
|
-
|
|
6
|
-
Deploy
|
|
7
|
-
|
|
8
|
-
Load Balancer와 IP ACL(loadbalancer 조회·쓰기·재바인딩 복구), NHN Container Registry(ncr),
|
|
9
|
-
NHN Kubernetes Service(nks supports/cluster/nodegroup/addon/kubeconfig),
|
|
10
|
-
NHN Container Service(ncs template/workload 조회·시간 필터·생성·변경·실행제어,
|
|
11
|
-
malware 검사 설정·결과 조회) 등 NHN Cloud API를 터미널과 AI 에이전트에서 호출한다.
|
|
4
|
+
NHN Cloud 리소스를 nhncloud CLI로 조회·생성·변경·삭제하거나,
|
|
5
|
+
configure·profile·출력 형식·종료 코드·명령 문법을 확인할 때 사용한다.
|
|
6
|
+
Log & Crash, Deploy, Compute, Network, Block Storage, Floating IP,
|
|
7
|
+
Load Balancer, NCR, NKS, NCS 작업을 서비스별 참조와 명령 카탈로그로 안내한다.
|
|
12
8
|
---
|
|
13
9
|
|
|
14
10
|
# nhncloud-cli
|
|
15
11
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
|
32
|
-
|
|
33
|
-
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
- 데이터는 stdout, 진행 상황·운영 경고·에러는 stderr에 출력한다.
|
|
43
|
-
- 비밀값, appkey, tenantId, 실제 instance/network id는 문서나 이슈에 그대로 쓰지 않고 placeholder를 사용한다.
|
|
44
|
-
- Load Balancer IP ACL 쓰기는 `--profile`, `--region`, `--json`을 명시하고, 삭제·연결 교체·대상 변경에는 `--yes`도 명시한다.
|
|
45
|
-
- IP ACL 대상 변경은 기본 재바인딩을 유지한다.
|
|
46
|
-
- 대상 변경의 종료 코드가 1이면 stdout JSON의 `rebind.failed[]`를 확인한다.
|
|
47
|
-
- 복구에는 `retry_argv` 배열을 그대로 사용하고, `retry_command`는 사람 확인용으로만 사용한다.
|
|
48
|
-
- NCS workload logs·events 시간 필터는 시간대 포함 RFC3339, `now`, 0 이상의 정수와 `m`·`h`·`d` 단위를 사용한다.
|
|
49
|
-
- 잘못된 NCS 시간 입력은 종료 코드 3이므로 자격증명 문제로 재시도하지 말고 입력을 수정한다.
|
|
50
|
-
|
|
51
|
-
## 빠른 시작
|
|
52
|
-
|
|
53
|
-
```bash
|
|
54
|
-
npm install -g @bifos/nhncloud-cli
|
|
55
|
-
nhncloud configure
|
|
56
|
-
nhncloud logncrash search --query '*' --from 1h --to now --json
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
## 주요 명령군
|
|
60
|
-
|
|
61
|
-
| 명령군 | 용도 |
|
|
62
|
-
|--------|------|
|
|
63
|
-
| `configure` | 자격증명 설정 마법사와 비대화형 profile 저장 |
|
|
64
|
-
| `logncrash` | 로그 검색, 대량 export, collector 전송 |
|
|
65
|
-
| `deploy` | NHN Cloud Deploy 실행과 배포/바이너리 조회·전송 |
|
|
66
|
-
| `instance` | Compute 인스턴스, flavor, image, keypair, volume attach 관리 |
|
|
67
|
-
| `network` | VPC와 subnet 조회 |
|
|
68
|
-
| `volume` | Block Storage volume 조회·생성 |
|
|
69
|
-
| `floatingip` | Floating IP 조회·발급·삭제 |
|
|
70
|
-
| `loadbalancer` | Load Balancer와 IP ACL 그룹·대상 조회·쓰기·재바인딩 복구 |
|
|
71
|
-
| `ncr` | Container Registry registry/image/tag 조회 |
|
|
72
|
-
| `nks` | Kubernetes cluster/nodegroup/addon/kubeconfig 관리 |
|
|
73
|
-
| `ncs` | Container Service template/workload 조회·UTC 시간 필터·생성·변경·실행제어, malware 검사 설정·결과 조회 |
|
|
74
|
-
|
|
75
|
-
## 안전한 탐색 순서
|
|
76
|
-
|
|
77
|
-
1. [common.md](references/common.md)에서 profile과 출력 모드를 확인한다.
|
|
78
|
-
2. 대상 서비스 reference를 읽는다.
|
|
79
|
-
3. 조회/discovery 명령을 `--json`으로 먼저 실행한다.
|
|
80
|
-
4. 쓰기 명령은 대상 id, region, profile, `--yes`, payload file을 명시한다.
|
|
81
|
-
5. 실패하면 [troubleshooting.md](references/troubleshooting.md)의 exit code와 서비스별 인증 모델을 대조한다.
|
|
12
|
+
## 실행 순서
|
|
13
|
+
|
|
14
|
+
1. 아래 표에서 대상 서비스의 reference 하나만 먼저 읽는다.
|
|
15
|
+
2. 정확한 경로·인수·옵션이 필요하면 `nhncloud commands --json`에서 해당 명령을 찾는다.
|
|
16
|
+
3. 조회 명령을 `--json`으로 실행해 대상 식별자와 현재 상태를 확인한다.
|
|
17
|
+
4. 쓰기 명령은 대상, `--profile`, 필요한 `--region`을 명시하고, 명령이 지원하면 `--yes`를 API 호출 전에 전달한다.
|
|
18
|
+
5. 결과는 stdout의 구조화 데이터로 판정하고 stderr는 진행 상황과 진단에 사용한다.
|
|
19
|
+
6. 실패하면 [troubleshooting.md](references/troubleshooting.md)에서 종료 코드와 인증 모델을 확인한다.
|
|
20
|
+
|
|
21
|
+
`--quiet`는 해당 명령이 식별자 출력을 문서화한 경우에만 사용한다.
|
|
22
|
+
명령 문법이나 응답 형태를 추측하지 않는다.
|
|
23
|
+
비밀값과 실제 사용자 리소스 ID는 문서·이슈·보고서에 남기지 않고 placeholder로 바꾼다.
|
|
24
|
+
|
|
25
|
+
## 참조 라우터
|
|
26
|
+
|
|
27
|
+
| 작업 | 읽을 파일 |
|
|
28
|
+
|---|---|
|
|
29
|
+
| 설치, `configure`, profile, 출력 모드, 명령 카탈로그, 종료 코드 | [common.md](references/common.md) |
|
|
30
|
+
| Log & Crash Search v3 커서 검색·scroll 대량 추출·collector 전송 | [logncrash.md](references/logncrash.md) |
|
|
31
|
+
| Deploy 실행·조회·바이너리 전송 | [deploy.md](references/deploy.md) |
|
|
32
|
+
| Compute, VPC, Block Storage, Floating IP | [iaas.md](references/iaas.md) |
|
|
33
|
+
| Load Balancer와 IP ACL | [loadbalancer.md](references/loadbalancer.md) |
|
|
34
|
+
| Container Registry | [ncr.md](references/ncr.md) |
|
|
35
|
+
| Kubernetes Service | [nks.md](references/nks.md) |
|
|
36
|
+
| Container Service | [ncs.md](references/ncs.md) |
|
|
37
|
+
| 인증·profile·region·출력·검색 제한 문제 해결 | [troubleshooting.md](references/troubleshooting.md) |
|
|
@@ -24,18 +24,39 @@ CI나 자동화에서는 flag로 비대화형 저장을 한다.
|
|
|
24
24
|
```bash
|
|
25
25
|
nhncloud configure \
|
|
26
26
|
--uak-id <uak-id> --uak-secret <uak-secret> \
|
|
27
|
-
--logncrash-appkey <appkey>
|
|
27
|
+
--logncrash-appkey <appkey> \
|
|
28
28
|
--ncr-appkey <appkey> \
|
|
29
29
|
--no-verify
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
+
Log & Crash Search v3는 서비스 appkey와 profile 공통 UAK를 함께 사용한다.
|
|
33
|
+
CLI가 UAK를 OAuth 토큰으로 교환하므로 별도 logncrash secret은 저장하지 않는다.
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"version": 1,
|
|
38
|
+
"profiles": {
|
|
39
|
+
"default": {
|
|
40
|
+
"userAccessKey": {
|
|
41
|
+
"id": "<uak-id>",
|
|
42
|
+
"secret": "<uak-secret>"
|
|
43
|
+
},
|
|
44
|
+
"logncrash": {
|
|
45
|
+
"appkey": "<appkey>"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
32
52
|
주요 옵션:
|
|
33
53
|
|
|
34
54
|
| 옵션 | 설명 |
|
|
35
55
|
|------|------|
|
|
36
56
|
| `--profile <name>` | 대상 profile. 기본값은 `default` |
|
|
37
57
|
| `--uak-id <id>` / `--uak-secret <secret>` | 공통 User Access Key |
|
|
38
|
-
| `--logncrash-appkey <key>`
|
|
58
|
+
| `--logncrash-appkey <key>` | Log & Crash 서비스 appkey |
|
|
59
|
+
| `--logncrash-secret <secret>` | 폐기 예정 호환 옵션. 경고 후 사용·저장하지 않음 |
|
|
39
60
|
| `--ncr-appkey <key>` | NCR appkey |
|
|
40
61
|
| `--iaas-tenant-id <id>` / `--iaas-username <name>` / `--iaas-password <password>` | IaaS/NKS 자격증명 |
|
|
41
62
|
| `--iaas-region <region>` | IaaS 기본 region |
|
|
@@ -85,7 +106,7 @@ AI 에이전트는 먼저 `commands --json`으로 실제 command path와 option
|
|
|
85
106
|
| 명령 | `--json` 출력 shape |
|
|
86
107
|
|------|---------------------|
|
|
87
108
|
| `commands` | `{ commands: [{ path, description, arguments, options, subcommands }] }` |
|
|
88
|
-
| `logncrash search` | `{ totalItems, pageNumber, pageSize, data }` |
|
|
109
|
+
| `logncrash search` | `{ totalItems, pageNumber, pageSize, data, nextCursor? }` |
|
|
89
110
|
| `deploy binary-groups` | `binaryGroups` wrapper를 언랩한 배열 |
|
|
90
111
|
| `deploy binaries` | `{ totalCount, binaries }` |
|
|
91
112
|
| `instance list` | `servers` wrapper를 언랩한 server 배열 |
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# Log & Crash Reference
|
|
2
2
|
|
|
3
|
-
`logncrash` 명령군은 Log & Crash Search 검색, scroll export, collector 전송을 다룬다.
|
|
3
|
+
`logncrash` 명령군은 Log & Crash Search v3 커서 검색, scroll export, collector 전송을 다룬다.
|
|
4
|
+
|
|
5
|
+
검색과 export에는 profile의 logncrash appkey와 공통 UAK가 필요하다.
|
|
6
|
+
CLI가 UAK를 OAuth 토큰으로 교환해 Bearer 인증을 적용한다.
|
|
7
|
+
`send`는 별도 collector 계약이므로 Search v3 전환의 영향을 받지 않는다.
|
|
4
8
|
|
|
5
9
|
## 검색
|
|
6
10
|
|
|
@@ -25,10 +29,14 @@ nhncloud logncrash search \
|
|
|
25
29
|
| `--query <lucene>` | 예 | Lucene 질의 문자열. API에 그대로 전달 |
|
|
26
30
|
| `--from <time>` | 예 | 검색 시작. ISO8601 또는 상대시간 |
|
|
27
31
|
| `--to <time>` | 예 | 검색 끝. ISO8601, 상대시간, `now` |
|
|
28
|
-
| `--page <n>` | 아니오 | pageNumber. 기본 0 |
|
|
29
|
-
| `--size <n>` | 아니오 | pageSize. 기본 10,
|
|
32
|
+
| `--page <n>` | 아니오 | 전환 호환용 pageNumber. 기본 0이며 0만 허용 |
|
|
33
|
+
| `--size <n>` | 아니오 | pageSize. 기본 10, 범위 1~100 |
|
|
34
|
+
| `--cursor <value>` | 아니오 | 직전 JSON 응답의 불투명한 `nextCursor` |
|
|
30
35
|
| `--profile <name>` | 아니오 | 사용할 profile |
|
|
31
36
|
|
|
37
|
+
CLI는 REAL API 실측 계약에 따라 모든 커서 요청을 `logTime DESC`로 고정 정렬한다.
|
|
38
|
+
사용자 정렬 옵션은 제공하지 않는다.
|
|
39
|
+
|
|
32
40
|
Lucene 예시:
|
|
33
41
|
|
|
34
42
|
| 의도 | 쿼리 |
|
|
@@ -50,15 +58,28 @@ API 제약:
|
|
|
50
58
|
|
|
51
59
|
## 검색 출력
|
|
52
60
|
|
|
53
|
-
`--json` 출력은 `{ totalItems, pageNumber, pageSize, data }` 객체다.
|
|
61
|
+
`--json` 출력은 `{ totalItems, pageNumber, pageSize, data, nextCursor? }` 객체다.
|
|
62
|
+
마지막 페이지에는 `nextCursor`가 없을 수 있다.
|
|
54
63
|
|
|
55
64
|
```bash
|
|
56
65
|
nhncloud logncrash search \
|
|
57
66
|
--query '*' \
|
|
58
67
|
--from 1h --to now \
|
|
59
68
|
--json | jq '.totalItems'
|
|
69
|
+
|
|
70
|
+
# 다음 cursor가 있을 때만 순차 조회
|
|
71
|
+
next_cursor="$(nhncloud logncrash search \
|
|
72
|
+
--query '*' --from 1h --to now --json | jq -r '.nextCursor // empty')"
|
|
73
|
+
if [ -n "$next_cursor" ]; then
|
|
74
|
+
nhncloud logncrash search \
|
|
75
|
+
--query '*' --from 1h --to now \
|
|
76
|
+
--cursor "$next_cursor" --json
|
|
77
|
+
fi
|
|
60
78
|
```
|
|
61
79
|
|
|
80
|
+
cursor는 파싱하거나 인코딩을 바꾸지 않고 받은 문자열 그대로 전달한다.
|
|
81
|
+
임의 페이지 이동은 지원하지 않으므로 `--page 1` 이상은 입력 오류다.
|
|
82
|
+
|
|
62
83
|
전송 직후에는 인덱싱 지연으로 잠시 0건이 나올 수 있다.
|
|
63
84
|
반복 검색이나 넓은 wildcard 검색은 시간 범위를 좁혀 확인한다.
|
|
64
85
|
|
|
@@ -82,16 +103,17 @@ nhncloud logncrash export \
|
|
|
82
103
|
| `--output <file>` | 필수. 출력 파일 |
|
|
83
104
|
| `--format json` | JSON 배열로 저장. 기본은 JSON Lines |
|
|
84
105
|
| `--force` | 기존 파일 덮어쓰기 |
|
|
85
|
-
| `--size <n>` |
|
|
106
|
+
| `--size <n>` | 폐기 예정 호환 옵션. 10~100 검증 후 경고하고 v3 요청에는 미전달 |
|
|
86
107
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
108
|
+
scroll 계속 요청이 실패하면 CLI가 원본 오류를 보존한다.
|
|
109
|
+
오류를 확인한 뒤 검색 시간 범위를 좁혀 다시 실행한다.
|
|
110
|
+
`--size`는 v3 scroll의 페이지 크기를 제어하지 않는다.
|
|
90
111
|
|
|
91
112
|
## 로그 전송
|
|
92
113
|
|
|
93
114
|
`logncrash send`는 collector host로 로그 한 건을 전송한다.
|
|
94
|
-
|
|
115
|
+
검색의 UAK OAuth Bearer 인증을 재사용하지 않는다.
|
|
116
|
+
인증 헤더 없이 body의 `projectName`에 appkey를 넣는다.
|
|
95
117
|
|
|
96
118
|
```bash
|
|
97
119
|
nhncloud logncrash send --body "batch finished" --level INFO
|
|
@@ -120,7 +142,7 @@ nhncloud logncrash send --file ./error.log --level ERROR
|
|
|
120
142
|
|
|
121
143
|
| 상황 | exit code |
|
|
122
144
|
|------|-----------|
|
|
123
|
-
| appkey 또는
|
|
145
|
+
| 검색 appkey 또는 공통 UAK 누락 | 4 |
|
|
124
146
|
| 인증 실패 | 2 |
|
|
125
147
|
| 시간 범위 초과, 본문 없음, 8MB 초과 | 3 |
|
|
126
148
|
| API 오류 또는 봉투 실패 | 1 |
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
| 서비스 | 비밀 | 인증 방식 |
|
|
17
17
|
|--------|------|-----------|
|
|
18
|
-
| Log & Crash 검색/export | appkey + secret | `X-
|
|
18
|
+
| Log & Crash 검색/export | appkey + 공통 UAK id/secret | UAK OAuth `X-NHN-Authorization: Bearer <token>` |
|
|
19
19
|
| Log & Crash send | appkey | body `projectName=appkey`, 인증 헤더 없음 |
|
|
20
20
|
| Deploy | UAK id + secret | OAuth Bearer token |
|
|
21
21
|
| Instance/network/volume/floatingip | tenantId + username + API password | Keystone `X-Auth-Token` |
|
|
@@ -70,8 +70,17 @@ nhncloud ncr list --json | jq '.[0] | keys'
|
|
|
70
70
|
|
|
71
71
|
- `--from`은 최근 90일 이내여야 한다.
|
|
72
72
|
- `--to - --from` 범위는 31일 이하여야 한다.
|
|
73
|
-
-
|
|
74
|
-
-
|
|
73
|
+
- `--page`는 0만 허용한다. 다음 페이지는 JSON의 `nextCursor`를 `--cursor`로 그대로 전달한다.
|
|
74
|
+
- 검색 `--size`는 1~100이다.
|
|
75
|
+
- export `--size`는 폐기 예정 호환 옵션이며 10~100 검증 후 경고하고 v3 요청에서는 무시한다.
|
|
76
|
+
- export가 중간 실패하면 보존된 원본 오류를 확인하고 검색 범위를 좁혀 다시 실행한다.
|
|
77
|
+
|
|
78
|
+
검색 또는 export에서 설정 오류가 나면 profile에 다음 두 값이 모두 있는지 확인한다.
|
|
79
|
+
|
|
80
|
+
- `userAccessKey.id`와 `userAccessKey.secret`
|
|
81
|
+
- `logncrash.appkey`
|
|
82
|
+
|
|
83
|
+
기존 `logncrash.secret`은 Search v3 인증에 사용하지 않는다.
|
|
75
84
|
|
|
76
85
|
## 쓰기 명령 confirm
|
|
77
86
|
|