hcs-cli 0.1.330__py3-none-any.whl → 0.1.332__py3-none-any.whl

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.
hcs_cli/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
- __version__ = "0.1.330"
1
+ __version__ = "0.1.332"
2
2
 
3
3
  from . import service as service # noqa: F401
@@ -0,0 +1,327 @@
1
+ """
2
+ Copyright © 2025 Omnissa, LLC.
3
+ """
4
+
5
+ import json
6
+ import re
7
+ import sys
8
+ from datetime import timezone
9
+ from typing import Optional
10
+
11
+ import click
12
+ import yumako
13
+ from hcs_core.ctxp import util
14
+ from hcs_core.sglib import cli_options as cli
15
+
16
+ from hcs_cli.service import hoc
17
+ from hcs_cli.support import predefined_payload
18
+
19
+ _hdcs = ["US", "EU", "JP"]
20
+
21
+ _regions = [
22
+ "eastus2",
23
+ "westus2",
24
+ "northcentralus",
25
+ "northeurope",
26
+ "germanywestcentral",
27
+ "uksouth",
28
+ "japaneast",
29
+ "australiaeast",
30
+ "centralindia",
31
+ ]
32
+ _stacks = ["prod", "staging", "stress"]
33
+
34
+
35
+ @click.command()
36
+ @cli.org_id
37
+ @click.option(
38
+ "--from",
39
+ "from_param",
40
+ type=str,
41
+ required=False,
42
+ default="-24h",
43
+ help="Sepcify the from date. E.g. '-1d', or '-1h35m', or '-1w', or '2023-12-04T00:19:22.854Z'.",
44
+ )
45
+ @click.option(
46
+ "--to",
47
+ type=str,
48
+ required=False,
49
+ default="now",
50
+ help="Sepcify the to date. E.g. 'now', or '-1d', or '-1h35m', or '-1w', or '2023-12-04T00:19:22.854Z'.",
51
+ )
52
+ @click.option("--hdc", required=False, type=click.Choice(_hdcs, case_sensitive=False), help="The HDC location")
53
+ @click.option("--region", required=False, type=click.Choice(_regions, case_sensitive=False), help="The region")
54
+ @click.option("--stack", required=False, type=click.Choice(_stacks, case_sensitive=False), help="The stack")
55
+ @click.option(
56
+ "--service",
57
+ required=False,
58
+ help="Filter by kubernetes.pod_labels.app. If multiple apps, use comma to separate, e.g. --service inventory,lcm",
59
+ )
60
+ @click.option("--raw", is_flag=True, default=False, help="If raw, output the raw result.")
61
+ @click.argument("query", type=str, required=False)
62
+ def log(org: str, from_param: str, to: str, hdc: str, region: str, stack: str, service: str, raw: bool, query: str):
63
+ """Search Kibana Log."""
64
+
65
+ # org_id = cli.get_org_id(org)
66
+
67
+ if not hdc:
68
+ hdc = "US"
69
+ else:
70
+ hdc = hdc.upper()
71
+ index_map = {
72
+ "US": "prod110-hdc-westus2-cp102-*",
73
+ # "US": "prod110-hdc-centralus-cp102-*",
74
+ "EU": "hoc-logs-eu-prod-*",
75
+ "JP": "hoc-logs-jp-prod-*",
76
+ "AUS": "",
77
+ "IND": "",
78
+ "GER": "",
79
+ "UK": "",
80
+ "US_APM": "prod110-hdc-westus2-cp102-*",
81
+ "EU_APM": "",
82
+ "JP_APM": "",
83
+ }
84
+ es_logs_search_index = index_map[hdc]
85
+
86
+ text = predefined_payload.load("hoc/logs.jsont")
87
+ replacements = {
88
+ "from": int(yumako.time.of(from_param, tz=timezone.utc).timestamp() * 1000),
89
+ "to": int(yumako.time.of(to, tz=timezone.utc).timestamp() * 1000),
90
+ "query": query or "",
91
+ "es_logs_search_index": es_logs_search_index,
92
+ }
93
+ try:
94
+ text = yumako.template.replace(
95
+ text=text,
96
+ mapping=replacements,
97
+ raise_on_unresolved_vars=True,
98
+ raise_on_unused_vars=True,
99
+ )
100
+ except ValueError as e:
101
+ return util.error_details(e), 1
102
+
103
+ parts = text.split("///", 2)
104
+ try:
105
+ first_line = json.loads(parts[0])
106
+ except Exception as e:
107
+ print(text, file=sys.stderr)
108
+ print(e, file=sys.stderr)
109
+ return
110
+ try:
111
+ second_line = json.loads(parts[1])
112
+ except Exception as e:
113
+ print(text, file=sys.stderr)
114
+ print(e, file=sys.stderr)
115
+ return
116
+
117
+ _update_query(second_line, any_apps=service)
118
+
119
+ payload_jsonp = json.dumps(first_line) + "\n" + json.dumps(second_line) + "\n"
120
+
121
+ hdc_location_mapping = {
122
+ # "US", "EU", "JP", "AUS", "IND", "GER", "UK", "US_APM", "EU_APM", "JP_APM"
123
+ "US": "US_APM",
124
+ "EU": "EU_APM",
125
+ "JP": "JP_APM",
126
+ }
127
+
128
+ # print(payload_jsonp)
129
+ result = hoc.es.raw_query(payload_jsonp, hdc_location=hdc_location_mapping[hdc])
130
+ if raw:
131
+ return result
132
+
133
+ # format the result to pure message array
134
+ ret = []
135
+ for item in result:
136
+ message = item["message"]
137
+ parsed = _parse_message(message)
138
+ ret.append(parsed)
139
+ return ret
140
+
141
+
142
+ # Pattern 1: LCM / service-gateway style
143
+ # "...INFO [163053][mplateMonitor-119158] c.v.h.servicegateway.lcm.utils.KopLog..."
144
+ _PATTERN_LCM = re.compile(
145
+ r"(?P<timestamp>\S+)"
146
+ r"\s+\[trace_id=(?P<trace_id>[^\]]*)\]"
147
+ r"\s+\[transaction_id=(?P<transaction_id>[^\]]*)\]"
148
+ r"\s+(?P<level>\S+)"
149
+ r"\s+\[(?P<thread_id>[^\]]*)\]\[(?P<thread_name>[^\]]*)\]"
150
+ r"\s+(?P<logger>\S+)"
151
+ r"\s+-\s+"
152
+ r"(?P<body>.*)",
153
+ )
154
+
155
+ # Pattern 2: clouddriver style (single thread bracket after level)
156
+ # "...INFO [http-nio-8080-exec-6] c.v.horizon.sg.clouddriver..."
157
+ _PATTERN_CLOUDDRIVER = re.compile(
158
+ r"(?P<timestamp>\S+)"
159
+ r"\s+\[trace_id=(?P<trace_id>[^\]]*)\]"
160
+ r"\s+\[transaction_id=(?P<transaction_id>[^\]]*)\]"
161
+ r"\s+(?P<level>\S+)"
162
+ r"\s+\[(?P<thread_name>[^\]]*)\]"
163
+ r"\s+(?P<logger>\S+)"
164
+ r"\s+-\s+"
165
+ r"(?P<body>.*)",
166
+ )
167
+
168
+ # Pattern 3: task-service style (thread bracket before level)
169
+ # "...[transaction_id=...] [tsch:iss-32521] INFO c.v.h.s.t.service.TaskService..."
170
+ _PATTERN_TASK = re.compile(
171
+ r"(?P<timestamp>\S+)"
172
+ r"\s+\[trace_id=(?P<trace_id>[^\]]*)\]"
173
+ r"\s+\[transaction_id=(?P<transaction_id>[^\]]*)\]"
174
+ r"\s+\[(?P<thread_name>[^\]]*)\]"
175
+ r"\s+(?P<level>\S+)"
176
+ r"\s+(?P<logger>\S+)"
177
+ r"\s+-\s+"
178
+ r"(?P<body>.*)",
179
+ )
180
+
181
+ # Pattern 4
182
+ # "2026-04-10 00:24:26,559 [http-nio-8080-exec-10] WARN co.elastic.apm.agent.impl.transaction.TraceState - sample rate already set to 1.0, trying to set it to 1.0 through header will be ignored"
183
+ _PATTERN_ELASTIC = re.compile(
184
+ r"(?P<timestamp>\S+\s\S+)" r"\s+\[(?P<thread_name>[^\]]*)\]" r"\s+(?P<level>\S+)" r"\s+(?P<logger>\S+)" r"\s+-\s+" r"(?P<body>.*)",
185
+ )
186
+
187
+ # Pattern 5: JSON structured log (apm-server style)
188
+ # "{\"log.level\":\"info\",\"@timestamp\":\"2026-04-10T00:24:26.596Z\",\"log.logger\":\"request\",...}"
189
+ _PATTERN_JSON_LOG = re.compile(r"\s*\{.*\"@timestamp\".*\}")
190
+
191
+ # Pattern 6: Envoy/Istio access log
192
+ # "[2026-04-10T00:24:25.684Z] \"POST /ad-twin/v1/... HTTP/1.1\" 200 ..."
193
+ _PATTERN_ENVOY = re.compile(
194
+ r"\[(?P<timestamp>[^\]]+)\]"
195
+ r"\s+\"(?P<method>\S+)\s+(?P<path>\S+)\s+(?P<protocol>[^\"]+)\""
196
+ r"\s+(?P<status_code>\d+)"
197
+ r"\s+(?P<body>.*)",
198
+ )
199
+
200
+ # Pattern 7 & 8: portal/pool style (empty transaction_id bracket)
201
+ # "2026-04-10T00:27:57.372Z [trace_id=...] [] [kafka-consumer-listener-...] INFO c.v.h.p.handler..."
202
+ # "2026-04-10T00:27:57.376Z [trace_id=...] [] [http-nio-8080-exec-26] INFO c.v.h.portal.service..."
203
+ _PATTERN_PORTAL = re.compile(
204
+ r"(?P<timestamp>\S+)"
205
+ r"\s+\[trace_id=(?P<trace_id>[^\]]*)\]"
206
+ r"\s+\[(?P<transaction_id>[^\]]*)\]"
207
+ r"\s+\[(?P<thread_name>[^\]]*)\]"
208
+ r"\s+(?P<level>\S+)"
209
+ r"\s+(?P<logger>\S+)"
210
+ r"\s+-\s+"
211
+ r"(?P<body>.*)",
212
+ )
213
+
214
+ # Pattern 9: Spring Boot style (--- separator)
215
+ # "2026-04-10 00:29:34.037 INFO 8 --- [istenerId-0-C-1] c.v.h.a.f.kafka.KafkaConsumerService : Created ..."
216
+ _PATTERN_SPRING = re.compile(
217
+ r"(?P<timestamp>\S+\s\S+)"
218
+ r"\s+(?P<level>\S+)"
219
+ r"\s+\S+"
220
+ r"\s+---"
221
+ r"\s+\[(?P<thread_name>[^\]]*)\]"
222
+ r"\s+(?P<logger>\S+)"
223
+ r"\s+:\s+"
224
+ r"(?P<body>.*)",
225
+ )
226
+
227
+ _PATTERNS = [_PATTERN_LCM, _PATTERN_CLOUDDRIVER, _PATTERN_TASK, _PATTERN_ELASTIC, _PATTERN_ENVOY, _PATTERN_PORTAL, _PATTERN_SPRING]
228
+
229
+
230
+ def _parse_message(message: str) -> dict:
231
+ if _PATTERN_JSON_LOG.match(message):
232
+ try:
233
+ obj = json.loads(message)
234
+ return {
235
+ "timestamp": obj.get("@timestamp", ""),
236
+ "level": obj.get("log.level", ""),
237
+ "logger": obj.get("log.logger", ""),
238
+ "body": obj.get("message", ""),
239
+ }
240
+ except json.JSONDecodeError:
241
+ pass
242
+ for pattern in _PATTERNS:
243
+ m = pattern.match(message)
244
+ if m:
245
+ return m.groupdict()
246
+ return {"body": message}
247
+
248
+
249
+ def _update_query(query_object: dict, any_apps: Optional[str]) -> dict:
250
+ # Example query object: query string "KOP" and "V1-CUSFP0101I", time range, and kubernetes.pod_labels.app=lcm
251
+ # {
252
+ # "query": {
253
+ # "bool": {
254
+ # "must": [],
255
+ # "filter": [
256
+ # {
257
+ # "bool": {
258
+ # "filter": [
259
+ # {
260
+ # "multi_match": {
261
+ # "type": "phrase",
262
+ # "query": "KOP",
263
+ # "lenient": true
264
+ # }
265
+ # },
266
+ # {
267
+ # "multi_match": {
268
+ # "type": "phrase",
269
+ # "query": "V1-CUSFP0101I",
270
+ # "lenient": true
271
+ # }
272
+ # }
273
+ # ]
274
+ # }
275
+ # },
276
+ # {
277
+ # "range": {
278
+ # "timestamp": {
279
+ # "format": "strict_date_optional_time",
280
+ # "gte": "2026-04-09T22:42:54.486Z",
281
+ # "lte": "2026-04-09T22:47:54.486Z"
282
+ # }
283
+ # }
284
+ # },
285
+ # {
286
+ # "match_phrase": {
287
+ # "kubernetes.pod_labels.app": "lcm"
288
+ # }
289
+ # }
290
+ # ],
291
+ # "should": [],
292
+ # "must_not": []
293
+ # }
294
+ # }
295
+ # }
296
+
297
+ # example of the last match condition, if there are multiple app specified (is one of):
298
+ # {
299
+ # "bool": {
300
+ # "minimum_should_match": 1,
301
+ # "should": [
302
+ # {
303
+ # "match_phrase": {
304
+ # "kubernetes.pod_labels.app": "clouddriver"
305
+ # }
306
+ # },
307
+ # {
308
+ # "match_phrase": {
309
+ # "kubernetes.pod_labels.app": "lcm"
310
+ # }
311
+ # }
312
+ # ]
313
+ # }
314
+ # }
315
+
316
+ filter_array = query_object["query"]["bool"]["filter"]
317
+
318
+ if any_apps:
319
+ apps = [a.strip() for a in any_apps.split(",") if a.strip()]
320
+ if len(apps) == 1:
321
+ filter_array.append({"match_phrase": {"kubernetes.pod_labels.app": apps[0]}})
322
+ elif len(apps) > 1:
323
+ filter_array.append(
324
+ {"bool": {"minimum_should_match": 1, "should": [{"match_phrase": {"kubernetes.pod_labels.app": app}} for app in apps]}}
325
+ )
326
+
327
+ return query_object
@@ -36,6 +36,17 @@ def _formalize_query_string(input_str):
36
36
  return " ".join(parts).strip()
37
37
 
38
38
 
39
+ def _parse_kv_args(arg_list: list[str]) -> dict[str, str]:
40
+ result = {}
41
+ for arg in arg_list:
42
+ if "=" not in arg:
43
+ raise ValueError(f"Invalid argument: {arg}. Expected format: key=value")
44
+ key, value = arg.split("=", 1)
45
+ key = key.strip()
46
+ result[key] = value.strip()
47
+ return result
48
+
49
+
39
50
  @click.command()
40
51
  @cli.org_id
41
52
  @click.option(
@@ -167,100 +178,3 @@ def es(org: str, from_param: str, to: str, file, raw: bool, predefined: str, arg
167
178
 
168
179
  print(payload_jsonp)
169
180
  return hoc.es.raw_query(payload_jsonp)
170
-
171
-
172
- _hdcs = ["US", "EU", "JP"]
173
- _regions = [
174
- "eastus2",
175
- "westus2",
176
- "northcentralus",
177
- "northeurope",
178
- "germanywestcentral",
179
- "uksouth",
180
- "japaneast",
181
- "australiaeast",
182
- "centralindia",
183
- ]
184
-
185
-
186
- @click.command()
187
- @cli.org_id
188
- @click.option(
189
- "--from",
190
- "from_param",
191
- type=str,
192
- required=False,
193
- default="-12h",
194
- help="Sepcify the from date. E.g. '-1d', or '-1h35m', or '-1w', or '2023-12-04T00:19:22.854Z'.",
195
- )
196
- @click.option(
197
- "--to",
198
- type=str,
199
- required=False,
200
- default="now",
201
- help="Sepcify the to date. E.g. 'now', or '-1d', or '-1h35m', or '-1w', or '2023-12-04T00:19:22.854Z'.",
202
- )
203
- @click.option("--hdc", required=False, type=click.Choice(_hdcs, case_sensitive=False), help="The HDC location")
204
- @click.option("--region", required=False, type=click.Choice(_regions, case_sensitive=False), help="The HDC location")
205
- @click.argument("query", type=str, required=False)
206
- def logs(org: str, from_param: str, to: str, hdc: str, region: str, query: str):
207
- """Perform a raw ES query for HCS logs."""
208
-
209
- # org_id = cli.get_org_id(org)
210
-
211
- if not hdc:
212
- hdc = "US"
213
- else:
214
- hdc = hdc.upper()
215
- index_map = {
216
- "US": "prod110-hdc-centralus-cp102-*",
217
- "EU": "hoc-logs-eu-prod-*",
218
- "JP": "hoc-logs-jp-prod-*",
219
- }
220
- es_logs_search_index = index_map[hdc]
221
-
222
- text = predefined_payload.load("hoc/logs.jsont")
223
- replacements = {
224
- "from": int(yumako.time.of(from_param, tz=timezone.utc).timestamp() * 1000),
225
- "to": int(yumako.time.of(to, tz=timezone.utc).timestamp() * 1000),
226
- "query": query if query else "",
227
- "es_logs_search_index": es_logs_search_index,
228
- }
229
- try:
230
- text = yumako.template.replace(
231
- text=text,
232
- mapping=replacements,
233
- raise_on_unresolved_vars=True,
234
- raise_on_unused_vars=True,
235
- )
236
- except ValueError as e:
237
- return util.error_details(e), 1
238
-
239
- parts = text.split("///", 2)
240
- try:
241
- first_line = json.loads(parts[0])
242
- except Exception as e:
243
- print(text, file=sys.stderr)
244
- print(e, file=sys.stderr)
245
- return
246
- try:
247
- second_line = json.loads(parts[1])
248
- except Exception as e:
249
- print(text, file=sys.stderr)
250
- print(e, file=sys.stderr)
251
- return
252
- payload_jsonp = json.dumps(first_line) + "\n" + json.dumps(second_line) + "\n"
253
-
254
- print(payload_jsonp)
255
- return hoc.es.raw_query(payload_jsonp)
256
-
257
-
258
- def _parse_kv_args(arg_list: list[str]) -> dict[str, str]:
259
- result = {}
260
- for arg in arg_list:
261
- if "=" not in arg:
262
- raise ValueError(f"Invalid argument: {arg}. Expected format: key=value")
263
- key, value = arg.split("=", 1)
264
- key = key.strip()
265
- result[key] = value.strip()
266
- return result
hcs_cli/cmds/task.py CHANGED
@@ -495,12 +495,15 @@ def list_tasks(
495
495
  @cli.org_id
496
496
  @click.option("--namespace", "-n", type=str, required=False)
497
497
  @click.option("--group", "-g", type=str, required=False)
498
+ @click.option(
499
+ "--location", hidden=True, type=str, required=False, help="Task location, only for global scheduler. E.g. US, JP, IN, AU, DE, IE."
500
+ ) # to be removed later
498
501
  @click.argument("smart_path", type=str, required=False)
499
- def get(org: str, namespace: str, group: str, smart_path: str, **kwargs):
502
+ def get(org: str, namespace: str, group: str, location: str, smart_path: str, **kwargs):
500
503
  """Get a task. E.g. 'task get [[<namespace>/]<group>/]<key>'."""
501
504
  namespace, group, key = _parse_task_param(namespace, group, smart_path)
502
505
  org_id = cli.get_org_id(org)
503
- return task.get(org_id, namespace, group, key, **kwargs)
506
+ return task.get(org_id, namespace, group, key, location=location, **kwargs)
504
507
 
505
508
 
506
509
  @task_cmd_group.command()
@@ -551,7 +554,9 @@ def get_logs(org: str, namespace: str, group: str, smart_path: str, last: bool,
551
554
  if state and last:
552
555
  return "--state cannot be used with --last.", 1
553
556
 
554
- if org == "all" or org is None:
557
+ if last and org is None:
558
+ org_id = cli.get_org_id(org)
559
+ elif org == "all" or org is None:
555
560
  org_id = None
556
561
  else:
557
562
  org_id = cli.get_org_id(org)
@@ -568,7 +573,7 @@ def get_logs(org: str, namespace: str, group: str, smart_path: str, last: bool,
568
573
  search = state_search
569
574
 
570
575
  if last:
571
- t = task.last(org_id, namespace, group, key, **kwargs)
576
+ t = task.last(org_id, namespace, group, key)
572
577
  if t:
573
578
  return t.log
574
579
  else:
hcs_cli/cmds/vm/log.py CHANGED
@@ -28,7 +28,7 @@ _DCT_FAILED = frozenset({"ERROR", "EXPIRED"})
28
28
  help="Whether to recreate the log if it already exists. By default, it will not recreate the log and just return the existing log.",
29
29
  )
30
30
  @click.option(
31
- "--silent", is_flag=True, default=False, help="Whether to print progress messages. By default, it will print progress messages."
31
+ "--verbose", is_flag=True, default=False, help="Whether to print progress messages. By default, it will print progress messages."
32
32
  )
33
33
  @click.option(
34
34
  "--save-as",
@@ -39,15 +39,13 @@ _DCT_FAILED = frozenset({"ERROR", "EXPIRED"})
39
39
  )
40
40
  @click.argument("vm_path", type=str, required=False)
41
41
  @cli.org_id
42
- def log(recreate: bool, silent: bool, save_path, vm_path: str, org: str, **kwargs):
42
+ def log(recreate: bool, verbose: bool, save_path, vm_path: str, org: str, **kwargs):
43
43
  """Download VM DCT log"""
44
44
  org_id = cli.get_org_id(org)
45
45
  template_id, vm_id = parse_vm_path(vm_path)
46
46
 
47
47
  is_ops = has_any_permission(["sgadm:supportw", "sgadm:supportlog", "sgadm:service", "sgadm:support"])
48
48
 
49
- verbose = not silent
50
-
51
49
  dct_info = dct_service.latest(org_id, template_id, vm_id, is_ops)
52
50
  if not dct_info:
53
51
  if verbose:
@@ -102,6 +100,9 @@ def log(recreate: bool, silent: bool, save_path, vm_path: str, org: str, **kwarg
102
100
  return f"Failed to download archive: {e}", 1
103
101
 
104
102
  dct_info["file_path"] = file_path
103
+
104
+ if verbose:
105
+ click.secho("----- DCT log downloaded -----", fg="bright_black")
105
106
  return dct_info
106
107
 
107
108
 
hcs_cli/service/hoc/es.py CHANGED
@@ -46,6 +46,7 @@ def query(
46
46
  to_date: Union[str, datetime] = "now",
47
47
  template_file: str = None,
48
48
  template: str = None,
49
+ hdc_location: str = None,
49
50
  **kwargs,
50
51
  ):
51
52
  if template_file:
@@ -87,8 +88,11 @@ def query(
87
88
  raise e
88
89
 
89
90
  payload_jsonp = json.dumps(payload_type) + "\n" + json.dumps(payload_query) + "\n"
90
- return raw_query(payload_jsonp)
91
+ return raw_query(payload_jsonp, hdc_location=hdc_location)
91
92
 
92
93
 
93
- def raw_query(payload_jsonp: str):
94
- return _client.post("/v1/data/query/search", text=payload_jsonp)
94
+ def raw_query(payload_jsonp: str, hdc_location: str = None):
95
+ url = "/v1/data/query/search"
96
+ if hdc_location:
97
+ url = f"/v1/data/query/search?hdcLocation={hdc_location}"
98
+ return _client.post(url, text=payload_jsonp)
hcs_cli/service/task.py CHANGED
@@ -312,7 +312,7 @@ def _get_v1(namespace: str, group: str, key: str) -> Optional[TaskModel]:
312
312
  return t
313
313
 
314
314
 
315
- def _get_v2(org_id: str, namespace: str, group: str, key: str) -> Optional[TaskModel]:
315
+ def _get_v2(org_id: str, namespace: str, group: str, key: str, location: str = None) -> Optional[TaskModel]:
316
316
  url = f"/v1/tasks/{key}?"
317
317
  if org_id:
318
318
  url += f"&orgId={org_id}"
@@ -320,6 +320,8 @@ def _get_v2(org_id: str, namespace: str, group: str, key: str) -> Optional[TaskM
320
320
  url += f"&namespace={namespace}"
321
321
  if group:
322
322
  url += f"&group={group}"
323
+ if location:
324
+ url += f"&location={location}"
323
325
  t = _scm_client.get(url, type=TaskModel)
324
326
  if not t:
325
327
  return
@@ -333,14 +335,14 @@ def get(org_id: str, namespace: str, group: str, key: str, **kwargs) -> Optional
333
335
  t = _get_v1(namespace, group, key)
334
336
  t.log = _lastlog(org_id, namespace, group, key)
335
337
  elif version == "v2":
336
- t = _get_v2(org_id, namespace, group, key)
338
+ t = _get_v2(org_id, namespace, group, key, location=kwargs.get("location"))
337
339
  else:
338
340
  t = _get_v1(namespace, group, key)
339
341
  if t:
340
342
  t.log = _lastlog(org_id, namespace, group, key)
341
343
  _task_temp_lru[_k] = "v1"
342
344
  else:
343
- t = _get_v2(org_id, namespace, group, key)
345
+ t = _get_v2(org_id, namespace, group, key, location=kwargs.get("location"))
344
346
  if t:
345
347
  _task_temp_lru[_k] = "v2"
346
348
 
@@ -2,7 +2,8 @@ import json
2
2
  import os
3
3
  import shlex
4
4
  import subprocess
5
- from typing import Any, List, Tuple, Union
5
+ import threading
6
+ from typing import Any, List, Optional, Tuple, Union
6
7
 
7
8
  import click
8
9
 
@@ -17,6 +18,13 @@ def _split_command(cmd: Union[str, List[str]]) -> Tuple[List[str], str]:
17
18
  raise TypeError("cmd must be a string or a list of strings")
18
19
 
19
20
 
21
+ # ANSI bright black / grey (same family as click.style(..., fg="bright_black"))
22
+ _STREAM_GREY = b"\x1b[90m"
23
+ # Yellow (same family as click.style(..., fg="yellow"))
24
+ _STREAM_YELLOW = b"\x1b[33m"
25
+ _STREAM_RESET = b"\x1b[0m"
26
+
27
+
20
28
  def exec(
21
29
  cmd,
22
30
  log_error=True,
@@ -102,3 +110,132 @@ def run_cli(
102
110
  input=input,
103
111
  show_command=show_command,
104
112
  )
113
+
114
+
115
+ def stream_cli(
116
+ cmd: str,
117
+ raise_on_error=True,
118
+ input: Union[str, dict[Any, Any], list[Any]] = None,
119
+ show_command: bool = True,
120
+ env: dict = None,
121
+ color: Optional[bool] = None,
122
+ ) -> str:
123
+ """Run ``hcs ...``, stream stdout and stderr as they arrive, return full stdout text.
124
+
125
+ Uses unbuffered binary reads from the pipes so data is forwarded as the OS delivers it
126
+ (``text=True`` would wrap pipes in a buffered ``TextIOWrapper`` and can delay reads).
127
+
128
+ The child ``hcs`` process is Python: ``PYTHONUNBUFFERED=1`` is set so its stdout is not
129
+ block-buffered when piped. If ``input`` is omitted, stdin is ``DEVNULL`` so the subprocess
130
+ does not inherit the parent's stdin and block waiting for input; otherwise stdin is a pipe
131
+ and the given payload is written (dict/list are JSON-encoded, same as :func:`exec`).
132
+
133
+ Output is written with :func:`click.get_binary_stream` so it matches what :func:`click.echo`
134
+ uses (not always identical to :data:`sys.stdout`).
135
+
136
+ ANSI wrapping (grey on stdout, yellow on stderr): if ``color`` is ``True`` or ``False``,
137
+ that choice always wins. If ``color`` is omitted (``None``), wrapping is applied per stream
138
+ only when that stream is a TTY (same as :meth:`~io.IOBase.isatty` on the binary stream).
139
+ """
140
+ child_env = os.environ.copy()
141
+ child_env["HCS_CLI_CHECK_UPGRADE"] = "false"
142
+ child_env["PYTHONUNBUFFERED"] = "1"
143
+ if env:
144
+ child_env.update(env)
145
+
146
+ commands, cmd_text = _split_command(cmd)
147
+ if show_command:
148
+ text = f"RUNNING: {cmd_text}"
149
+ if input:
150
+ text += " <input-redacted>"
151
+ click.echo(click.style(text, fg="bright_black"))
152
+
153
+ stdin_bytes: Union[bytes, None] = None
154
+ if input is not None:
155
+ if isinstance(input, dict) or isinstance(input, list):
156
+ stdin_bytes = json.dumps(input).encode("utf-8")
157
+ else:
158
+ stdin_bytes = str(input).encode("utf-8")
159
+
160
+ parts: list[bytes] = []
161
+ proc = subprocess.Popen(
162
+ commands,
163
+ stdout=subprocess.PIPE,
164
+ stderr=subprocess.PIPE,
165
+ stdin=subprocess.PIPE if stdin_bytes is not None else subprocess.DEVNULL,
166
+ env=child_env,
167
+ bufsize=0,
168
+ )
169
+ assert proc.stdout is not None
170
+ assert proc.stderr is not None
171
+
172
+ out_bin = click.get_binary_stream("stdout")
173
+ err_bin = click.get_binary_stream("stderr")
174
+
175
+ try:
176
+ stdout_is_tty = out_bin.isatty()
177
+ except OSError:
178
+ stdout_is_tty = False
179
+ try:
180
+ stderr_is_tty = err_bin.isatty()
181
+ except OSError:
182
+ stderr_is_tty = False
183
+
184
+ if color is None:
185
+ wrap_stdout = stdout_is_tty
186
+ wrap_stderr = stderr_is_tty
187
+ else:
188
+ wrap_stdout = color
189
+ wrap_stderr = color
190
+
191
+ def pump_stdin() -> None:
192
+ assert proc.stdin is not None
193
+ try:
194
+ if stdin_bytes is not None:
195
+ proc.stdin.write(stdin_bytes)
196
+ proc.stdin.flush()
197
+ finally:
198
+ proc.stdin.close()
199
+
200
+ def pump_stdout() -> None:
201
+ try:
202
+ while True:
203
+ chunk = proc.stdout.read(4096)
204
+ if not chunk:
205
+ break
206
+ out = (_STREAM_GREY + chunk + _STREAM_RESET) if wrap_stdout else chunk
207
+ out_bin.write(out)
208
+ out_bin.flush()
209
+ parts.append(out)
210
+ finally:
211
+ proc.stdout.close()
212
+
213
+ def pump_stderr() -> None:
214
+ try:
215
+ while True:
216
+ chunk = proc.stderr.read(4096)
217
+ if not chunk:
218
+ break
219
+ out = (_STREAM_YELLOW + chunk + _STREAM_RESET) if wrap_stderr else chunk
220
+ err_bin.write(out)
221
+ err_bin.flush()
222
+ finally:
223
+ proc.stderr.close()
224
+
225
+ threads: list[threading.Thread] = []
226
+ if stdin_bytes is not None:
227
+ threads.append(threading.Thread(target=pump_stdin))
228
+ threads.extend(
229
+ [
230
+ threading.Thread(target=pump_stdout),
231
+ threading.Thread(target=pump_stderr),
232
+ ]
233
+ )
234
+ for t in threads:
235
+ t.start()
236
+ for t in threads:
237
+ t.join()
238
+ rc = proc.wait()
239
+ if rc != 0 and raise_on_error:
240
+ raise click.ClickException(f"Command '{cmd_text}' failed with return code {rc}.")
241
+ return b"".join(parts).decode("utf-8", errors="replace")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hcs-cli
3
- Version: 0.1.330
3
+ Version: 0.1.332
4
4
  Summary: Horizon Cloud Service CLI.
5
5
  Project-URL: Homepage, https://github.com/euc-eng/hcs-cli
6
6
  Project-URL: Bug Tracker, https://github.com/euc-eng/hcs-cli/issues
@@ -14,7 +14,7 @@ Classifier: License :: OSI Approved :: MIT License
14
14
  Classifier: Operating System :: OS Independent
15
15
  Classifier: Programming Language :: Python :: 3
16
16
  Requires-Python: >=3.9
17
- Requires-Dist: hcs-core>=0.1.330
17
+ Requires-Dist: hcs-core>=0.1.332
18
18
  Requires-Dist: inquirerpy>=0.3.4
19
19
  Requires-Dist: matplotlib>=3.8.0
20
20
  Requires-Dist: paho-mqtt>=2.1.0
@@ -1,4 +1,4 @@
1
- hcs_cli/__init__.py,sha256=8nL9E5vkCwIrdUGhotKiSE8r-WSQRjCE2L7yuQAfp_I,72
1
+ hcs_cli/__init__.py,sha256=nPvtkDDB3R6Uz-e6LUQVWGRnxx8UYe4qTvGgMXtbZow,72
2
2
  hcs_cli/__main__.py,sha256=mReIxxxuvlcxzSdPfIgptZ_Qkrg0MPOgT37utukr65g,125
3
3
  hcs_cli/main.py,sha256=CzGoya6yabf5akJ3l0gEzxBmMKznIhxGRXbsngtuPWc,3014
4
4
  hcs_cli/cmds/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -7,7 +7,7 @@ hcs_cli/cmds/inspect.py,sha256=QBhBOkf2mHlGhgAhY9FXCfvViL2p4D3BudoKjyADdfE,659
7
7
  hcs_cli/cmds/login.py,sha256=0j2VA4cy2pgy6-Rm3ys9JMKHbyrdvCS8tw3LdijM9gk,10606
8
8
  hcs_cli/cmds/logout.py,sha256=FDYXnie1jytnEsFj-XuVSU4sZL9VPzaLO1KHFProX4g,185
9
9
  hcs_cli/cmds/query.py,sha256=hh1TVeSkvRiB07fqIA3GGpEBtP3XMz5XCmAGTXD7FOM,9302
10
- hcs_cli/cmds/task.py,sha256=pHyJNn6YXIW0l1KQBwwWma-YH2b455AXEuCi5h7VsZw,28770
10
+ hcs_cli/cmds/task.py,sha256=N9EYRGoTtCVF2yktD0bmSlugGykRaysdwjNoT2al9jY,29037
11
11
  hcs_cli/cmds/test.py,sha256=rBFbcpUtwxxm6thl7uZGqmZTyCyFRpMO-xa7wExEhuM,755
12
12
  hcs_cli/cmds/upgrade.py,sha256=p1QKlacY5kPIQecN6sFRDXgS-_HrsGu_0Hjb1DXbjgI,377
13
13
  hcs_cli/cmds/ad/__init__.py,sha256=oB4WrRdC96cMS2OXs-1ITr8U_3ylI3QQNKZk51KsMJI,82
@@ -113,13 +113,9 @@ hcs_cli/cmds/entitlement/delete.py,sha256=fcdpVtZKpDdEZ9TS11r6OwwbNOVeBIf0bjpK6n
113
113
  hcs_cli/cmds/entitlement/get.py,sha256=Ia4eG3Kh6plXc3f94MtrRC9g27U3Z4xHSqsB4Et3cRw,477
114
114
  hcs_cli/cmds/entitlement/list.py,sha256=xCahx0Sr4r5w2rhI9eBNmYPsc0NHEbivApLNcvuWTZY,420
115
115
  hcs_cli/cmds/hoc/__init__.py,sha256=OL0_-lSzNCCDefv2m_DIkUAvTnA2qDbLtUdPsN9lsPk,128
116
- hcs_cli/cmds/hoc/connect.py,sha256=GvXfbB1JhAZRnl1_H1oxCAhAMCP9jtJgAlhVy9ggYNc,576
117
- hcs_cli/cmds/hoc/oncall.py,sha256=yc7s94Ti21QDm6WZAbuzB21GsGwced5jLrfmvkV8J2c,575
118
- hcs_cli/cmds/hoc/org.py,sha256=fi2rjO4hLuJuLSinWG3s8-dTdAj9EIrIFcN82LM8mtI,570
119
- hcs_cli/cmds/hoc/search.py,sha256=TrmuXd0_Mr7HlQouktjdgUq9I2WJgeg2nfpB4nxXddA,7972
116
+ hcs_cli/cmds/hoc/log.py,sha256=dDMm4kqoVUCdz9zrVwE9xX1vpbejCr-m4W_IAC9yT_M,10518
117
+ hcs_cli/cmds/hoc/search.py,sha256=YNFRiNXqNQNYOC8QfocXDJTZeLOmNMgmmkGcPOGqG-E,5513
120
118
  hcs_cli/cmds/hoc/stats.py,sha256=QEnAULDIhvsEgMLJg4Ql9cI6wUqd0zQW2QjwBWwXdno,2641
121
- hcs_cli/cmds/hoc/template.py,sha256=M9bPYDuPfYQjjR8SYS4r4NknckSAfrMJGW6GeczCZvw,574
122
- hcs_cli/cmds/hoc/vm.py,sha256=YjijTxpSbnUKyCtsc9wIYDYZULM4RKo2JfROdilmNvk,664
123
119
  hcs_cli/cmds/hoc/util/__init__.py,sha256=AC-hl0F-5R4V2uejk5FzFDlvT7uKGfjQyWq0quSznQs,14
124
120
  hcs_cli/cmds/hoc/util/inv_cache.py,sha256=p1Y2MLlzdVvcmWUyBN-55X0irqTihjgGTgRGF-diw4Q,398
125
121
  hcs_cli/cmds/hst/__init__.py,sha256=tAbpunGkAG0fBOaOBQybQzv9r6Ij7kgp4SWUpBEB1e0,119
@@ -284,7 +280,7 @@ hcs_cli/cmds/vm/actions.py,sha256=PdwqGc8rjX7QabhnTZR6do4NaGjvDZotx22LeiE1HJw,33
284
280
  hcs_cli/cmds/vm/delete.py,sha256=n6e4pPc26nujJN93bwFBE4rV00hNJZEQhlRMdakJwLU,934
285
281
  hcs_cli/cmds/vm/get.py,sha256=IJqxnuIXEBTDxBS2-SbqNFU-sKKn0tzOX5blaVFpxrU,483
286
282
  hcs_cli/cmds/vm/list.py,sha256=tMFs7VcVE-7MphtiqTlwGP41JWjIsbFnQFc0wHKF-J0,6044
287
- hcs_cli/cmds/vm/log.py,sha256=NffHS8OnECePKYBtb_n-7vEr9iSP8_1phO5bqDNAYoM,5250
283
+ hcs_cli/cmds/vm/log.py,sha256=ejWr_7KGIOnbFEFCIJsO1DE42eqDJR1199FCovtb_Ws,5316
288
284
  hcs_cli/cmds/vm/pairing_info.py,sha256=Oq4bXWXj_GP_ejnK5fouc7928PDFk11-vGUjRXQnT6w,623
289
285
  hcs_cli/cmds/vm/put.py,sha256=bOqwdEVqI3_6F6C_3WH7P4PZP0iAhkJmm6Y9RHBVgl8,645
290
286
  hcs_cli/cmds/vm/use.py,sha256=EONJqWcF176tUyQ9OnkSpR92HBzTgFNwB__Dt10WAVU,1276
@@ -402,7 +398,7 @@ hcs_cli/service/edge.py,sha256=YN5uWrXKARfuOnqCKZQYCVP2BrNiAEDdS2AIIAgQdEo,3714
402
398
  hcs_cli/service/graphql.py,sha256=Vqt4wMeRSPV_2YhGvb9vs0vlac0_fMAhg1BijKX6reg,468
403
399
  hcs_cli/service/pool.py,sha256=aWdW0bRGlE58z95HvdSs7i8kwM79_dzqsrGjtnLv7lA,712
404
400
  hcs_cli/service/site.py,sha256=CMKMAhKaXZ67tFGkg5bPPBHsD3Ud76mOZKh4Vw_nx5g,1014
405
- hcs_cli/service/task.py,sha256=M1P0zwEHSmgg0VH_lxbXwWwUkiZgjBmQLlEjNQMS8Xk,16768
401
+ hcs_cli/service/task.py,sha256=uyewZ4FctrAcdEUbt_mxhcS4iCf3mXz_QAuqdAl8kCE,16912
406
402
  hcs_cli/service/template.py,sha256=qKJ09Toe5NmApgd2H0TcVIoFshz-8fh2j3hDtvMO7z4,2242
407
403
  hcs_cli/service/uag.py,sha256=HabShUiLzZN-VM_UlPbfb4cQiWcgvsn3eB4CVPOzee0,3193
408
404
  hcs_cli/service/vm.py,sha256=Aw1AKvFSPlNTh1VWCdQXf21Yu_tDDao2NhMj5muGzwc,4839
@@ -439,7 +435,7 @@ hcs_cli/service/edgehub/edgecontroller.py,sha256=O6tSOm2auxtgSgxLbmEyXTLEi2d1l_0
439
435
  hcs_cli/service/edgehub/edgetwin.py,sha256=Wk6mDzL0CldI8wASicJ8y6IQX3HKn44v8VioPD4G2mw,405
440
436
  hcs_cli/service/hoc/__init__.py,sha256=fHFaB2296HgzIKwMPkB9BDxezIYI6M4OD-woPsrtOts,48
441
437
  hcs_cli/service/hoc/diagnostic.py,sha256=3sS1Saydsy-kYuF4IQMlz4scv1O4nMjnHOM7Trqh9cY,1465
442
- hcs_cli/service/hoc/es.py,sha256=XCeYuhGMJE89uRiH49lolCnNWMEi0Ec7VRhxKbEvAO4,3491
438
+ hcs_cli/service/hoc/es.py,sha256=91eEgOMLXAucsu8BksVlE_Gj9OsxpsC_fyzi4GyLum8,3675
443
439
  hcs_cli/service/ims/__init__.py,sha256=CTtPWRJqthcWW7jMyqD-4fP09xSGEfVEj75GbrtyDsA,86
444
440
  hcs_cli/service/ims/gold_pattern.py,sha256=Q2zltOH051cii_-WJxtFouuA_okqJpzcWCuAYcNO2j4,2606
445
441
  hcs_cli/service/ims/helper.py,sha256=kruncMu5bUUA4mNCdzgdcgrSu7h6ZRjoI02_Fj6dqC0,1794
@@ -486,7 +482,7 @@ hcs_cli/service/vmm/hoc_event.py,sha256=NSU8Wj2cc8vWod-YDTEMuUXD4ToEKhgIrj3u1lTv
486
482
  hcs_cli/support/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
487
483
  hcs_cli/support/constant.py,sha256=dVOiIaiH4rhC-cZJhTruXyv5ieS1j32YUhF8wEFjbEw,171
488
484
  hcs_cli/support/debug_util.py,sha256=X8gOsXzDDPwJxLKPPW7j_j4mZ3Up9xNLu1TDVv_VBRE,2678
489
- hcs_cli/support/exec_util.py,sha256=Epa09iCouAky7NE17O5rG-q3vdw35XJzrIUitPStSsY,3098
485
+ hcs_cli/support/exec_util.py,sha256=Njqu-pgSTCexJFypYKna8ZvngHhUVkSmJOq81tXw37c,7692
490
486
  hcs_cli/support/inspect_util.py,sha256=weLylRM1_3O6nz0yUOiJUEBfR9TpUe-jpghmrQblSjE,3192
491
487
  hcs_cli/support/param_util.py,sha256=CHXWJvBF9EwFLYPn_CTOpFWdSyCP3w6iLlYByiz-nvE,1492
492
488
  hcs_cli/support/patch_util.py,sha256=r4IjpLA5yGqKer-wuSMB7d93BGPVnse_FhHcAxwYg7A,1524
@@ -501,7 +497,7 @@ hcs_cli/support/vm_table.py,sha256=hqNUKLVUuBiuDwlqcAQ7uAT1-4z6ZYOIGRcFWn8lnAo,2
501
497
  hcs_cli/support/scm/html_util.py,sha256=clgMpM90HxRRs3D9ORYYNB57AYh7y_-UzJrB4KX3dsY,458
502
498
  hcs_cli/support/scm/plan-editor.html.template,sha256=HtXMmvIvTixVEYN1gblCeqFTt_F8JF6ltDC11Eb_GMw,46975
503
499
  hcs_cli/support/scm/plan_editor.py,sha256=TxfHyyozoQ_DCyaPrYXFG9n8AnBuUD8753009FUD0GU,8722
504
- hcs_cli-0.1.330.dist-info/METADATA,sha256=UY6eU0l46KfLmhSe9wx2FhHFSkZAHfyby4z2FnGH_R8,3222
505
- hcs_cli-0.1.330.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
506
- hcs_cli-0.1.330.dist-info/entry_points.txt,sha256=5uH-af1WUETSBSer2bu4YMGQNY5RriJHsjepb8ACiX8,42
507
- hcs_cli-0.1.330.dist-info/RECORD,,
500
+ hcs_cli-0.1.332.dist-info/METADATA,sha256=HQSDAbv69tNoL5kWve6ZIWmoABWWaPiWx7Yd8zzEfw0,3222
501
+ hcs_cli-0.1.332.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
502
+ hcs_cli-0.1.332.dist-info/entry_points.txt,sha256=5uH-af1WUETSBSer2bu4YMGQNY5RriJHsjepb8ACiX8,42
503
+ hcs_cli-0.1.332.dist-info/RECORD,,
@@ -1,24 +0,0 @@
1
- import click
2
- from hcs_core.sglib import cli_options as cli
3
-
4
-
5
- @click.command()
6
- @cli.org_id
7
- @click.option(
8
- "--from",
9
- "from_param",
10
- type=str,
11
- required=False,
12
- default="-12h",
13
- help="Sepcify the from date. E.g. '-1d', or '-1h35m', or '-1w', or '2023-12-04T00:19:22.854Z'.",
14
- )
15
- @click.option(
16
- "--to",
17
- type=str,
18
- required=False,
19
- default="now",
20
- help="Sepcify the to date. E.g. 'now', or '-1d', or '-1h35m', or '-1w', or '2023-12-04T00:19:22.854Z'.",
21
- )
22
- def connect(org: str, from_param: str, to: str):
23
- """Analyse connect issue."""
24
- pass
@@ -1,24 +0,0 @@
1
- import click
2
- from hcs_core.sglib import cli_options as cli
3
-
4
-
5
- @click.command()
6
- @cli.org_id
7
- @click.option(
8
- "--from",
9
- "from_param",
10
- type=str,
11
- required=False,
12
- default="-12h",
13
- help="Sepcify the from date. E.g. '-1d', or '-1h35m', or '-1w', or '2023-12-04T00:19:22.854Z'.",
14
- )
15
- @click.option(
16
- "--to",
17
- type=str,
18
- required=False,
19
- default="now",
20
- help="Sepcify the to date. E.g. 'now', or '-1d', or '-1h35m', or '-1w', or '2023-12-04T00:19:22.854Z'.",
21
- )
22
- def oncall(org: str, from_param: str, to: str):
23
- """Perform on-call tasks."""
24
- pass
hcs_cli/cmds/hoc/org.py DELETED
@@ -1,24 +0,0 @@
1
- import click
2
- from hcs_core.sglib import cli_options as cli
3
-
4
-
5
- @click.command()
6
- @cli.org_id
7
- @click.option(
8
- "--from",
9
- "from_param",
10
- type=str,
11
- required=False,
12
- default="-12h",
13
- help="Sepcify the from date. E.g. '-1d', or '-1h35m', or '-1w', or '2023-12-04T00:19:22.854Z'.",
14
- )
15
- @click.option(
16
- "--to",
17
- type=str,
18
- required=False,
19
- default="now",
20
- help="Sepcify the to date. E.g. 'now', or '-1d', or '-1h35m', or '-1w', or '2023-12-04T00:19:22.854Z'.",
21
- )
22
- def template(org: str, from_param: str, to: str):
23
- """Analyse an org."""
24
- pass
@@ -1,24 +0,0 @@
1
- import click
2
- from hcs_core.sglib import cli_options as cli
3
-
4
-
5
- @click.command()
6
- @cli.org_id
7
- @click.option(
8
- "--from",
9
- "from_param",
10
- type=str,
11
- required=False,
12
- default="-12h",
13
- help="Sepcify the from date. E.g. '-1d', or '-1h35m', or '-1w', or '2023-12-04T00:19:22.854Z'.",
14
- )
15
- @click.option(
16
- "--to",
17
- type=str,
18
- required=False,
19
- default="now",
20
- help="Sepcify the to date. E.g. 'now', or '-1d', or '-1h35m', or '-1w', or '2023-12-04T00:19:22.854Z'.",
21
- )
22
- def template(org: str, from_param: str, to: str):
23
- """Analyse a template."""
24
- pass
hcs_cli/cmds/hoc/vm.py DELETED
@@ -1,27 +0,0 @@
1
- import click
2
- from hcs_core.sglib import cli_options as cli
3
-
4
- from hcs_cli.service import VM
5
-
6
-
7
- @click.command()
8
- @cli.org_id
9
- @click.option(
10
- "--from",
11
- "from_param",
12
- type=str,
13
- required=False,
14
- default="-12h",
15
- help="Sepcify the from date. E.g. '-1d', or '-1h35m', or '-1w', or '2023-12-04T00:19:22.854Z'.",
16
- )
17
- @click.option(
18
- "--to",
19
- type=str,
20
- required=False,
21
- default="now",
22
- help="Sepcify the to date. E.g. 'now', or '-1d', or '-1h35m', or '-1w', or '2023-12-04T00:19:22.854Z'.",
23
- )
24
- def template(org: str, from_param: str, to: str):
25
- """Analyse a VM."""
26
- vm = VM(org, "688d055e4f363c64c9c56764", "az-1-m-001")
27
- return vm