hcs-core 0.1.290__tar.gz → 0.1.292__tar.gz

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.
Files changed (71) hide show
  1. {hcs_core-0.1.290 → hcs_core-0.1.292}/PKG-INFO +16 -16
  2. hcs_core-0.1.292/hcs_core/__init__.py +1 -0
  3. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/ctxp/_init.py +6 -3
  4. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/ctxp/built_in_cmds/profile.py +1 -3
  5. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/ctxp/cli_options.py +2 -6
  6. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/ctxp/cli_processor.py +7 -1
  7. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/ctxp/data_util.py +2 -6
  8. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/ctxp/duration.py +1 -3
  9. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/ctxp/logger.py +4 -4
  10. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/ctxp/profile.py +1 -3
  11. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/ctxp/recent.py +1 -1
  12. hcs_core-0.1.292/hcs_core/ctxp/telemetry.py +102 -0
  13. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/ctxp/util.py +5 -1
  14. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/plan/core.py +3 -11
  15. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/sglib/auth.py +16 -7
  16. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/sglib/client_util.py +27 -13
  17. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/sglib/csp.py +1 -3
  18. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/sglib/login_support.py +1 -1
  19. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/util/job_view.py +1 -3
  20. {hcs_core-0.1.290 → hcs_core-0.1.292}/pyproject.toml +16 -16
  21. hcs_core-0.1.290/hcs_core/__init__.py +0 -1
  22. {hcs_core-0.1.290 → hcs_core-0.1.292}/.gitignore +0 -0
  23. {hcs_core-0.1.290 → hcs_core-0.1.292}/README.md +0 -0
  24. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/ctxp/__init__.py +0 -0
  25. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/ctxp/built_in_cmds/__init__.py +0 -0
  26. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/ctxp/built_in_cmds/_ut.py +0 -0
  27. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/ctxp/built_in_cmds/context.py +0 -0
  28. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/ctxp/cmd_util.py +0 -0
  29. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/ctxp/config.py +0 -0
  30. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/ctxp/context.py +0 -0
  31. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/ctxp/dispatcher.py +0 -0
  32. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/ctxp/extension.py +0 -0
  33. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/ctxp/fn_util.py +0 -0
  34. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/ctxp/fstore.py +0 -0
  35. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/ctxp/jsondot.py +0 -0
  36. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/ctxp/profile_store.py +0 -0
  37. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/ctxp/state.py +0 -0
  38. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/ctxp/task_schd.py +0 -0
  39. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/ctxp/template_util.py +0 -0
  40. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/ctxp/timeutil.py +0 -0
  41. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/ctxp/var_template.py +0 -0
  42. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/plan/__init__.py +0 -0
  43. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/plan/actions.py +0 -0
  44. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/plan/base_provider.py +0 -0
  45. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/plan/context.py +0 -0
  46. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/plan/dag.py +0 -0
  47. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/plan/helper.py +0 -0
  48. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/plan/kop.py +0 -0
  49. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/plan/provider/__init__.py +0 -0
  50. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/plan/provider/dev/__init__.py +0 -0
  51. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/plan/provider/dev/_prepare.py +0 -0
  52. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/plan/provider/dev/dummy.py +0 -0
  53. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/plan/provider/dev/fibonacci.py +0 -0
  54. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/sglib/__init__.py +0 -0
  55. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/sglib/cli_options.py +0 -0
  56. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/sglib/ez_client.py +0 -0
  57. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/sglib/hcs_client.py +0 -0
  58. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/sglib/init.py +0 -0
  59. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/sglib/payload_util.py +0 -0
  60. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/sglib/requtil.py +0 -0
  61. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/sglib/utils.py +0 -0
  62. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/util/__init__.py +0 -0
  63. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/util/check_license.py +0 -0
  64. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/util/duration.py +0 -0
  65. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/util/exit.py +0 -0
  66. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/util/hcs_constants.py +0 -0
  67. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/util/pki_util.py +0 -0
  68. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/util/query_util.py +0 -0
  69. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/util/scheduler.py +0 -0
  70. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/util/ssl_util.py +0 -0
  71. {hcs_core-0.1.290 → hcs_core-0.1.292}/hcs_core/util/versions.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hcs-core
3
- Version: 0.1.290
3
+ Version: 0.1.292
4
4
  Summary: Horizon Cloud Service CLI module.
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,29 +14,29 @@ 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: authlib>=1.2.1
18
- Requires-Dist: click>=8.1.7
17
+ Requires-Dist: authlib>=1.6.0
18
+ Requires-Dist: click>=8.2.1
19
19
  Requires-Dist: coloredlogs>=15.0.1
20
- Requires-Dist: cryptography>=42.0.7
21
- Requires-Dist: graphviz>=0.20.3
22
- Requires-Dist: httpx>=0.27.0
23
- Requires-Dist: packaging>=24.0
24
- Requires-Dist: portalocker>=2.8.2
25
- Requires-Dist: psutil>=5.9.4
26
- Requires-Dist: pydantic>=2.0.0
27
- Requires-Dist: pyjwt>=2.8.0
28
- Requires-Dist: pyopenssl>=24.1.0
29
- Requires-Dist: python-dotenv>=1.1.0
20
+ Requires-Dist: cryptography>=45.0.5
21
+ Requires-Dist: graphviz>=0.21
22
+ Requires-Dist: httpx>=0.28.0
23
+ Requires-Dist: packaging>=25.0
24
+ Requires-Dist: portalocker>=3.0.0
25
+ Requires-Dist: psutil>=7.0.0
26
+ Requires-Dist: pydantic>=2.11.7
27
+ Requires-Dist: pyjwt>=2.10.1
28
+ Requires-Dist: pyopenssl>=25.1.0
29
+ Requires-Dist: python-dotenv>=1.1.1
30
30
  Requires-Dist: pyyaml>=6.0.1
31
31
  Requires-Dist: questionary>=2.0.1
32
- Requires-Dist: rel>=0.4.7
32
+ Requires-Dist: rel>=0.4.9.20
33
33
  Requires-Dist: retry>=0.9.2
34
- Requires-Dist: rich>=13.7.1
34
+ Requires-Dist: rich>=14.0.0
35
35
  Requires-Dist: schedule>=1.1.0
36
36
  Requires-Dist: setuptools>=70.0.0
37
37
  Requires-Dist: tabulate>=0.9.0
38
38
  Requires-Dist: websocket-client>=1.2.3
39
- Requires-Dist: yumako>=0.1.24
39
+ Requires-Dist: yumako>=0.1.27
40
40
  Provides-Extra: dev
41
41
  Requires-Dist: bandit; extra == 'dev'
42
42
  Requires-Dist: black; extra == 'dev'
@@ -0,0 +1 @@
1
+ __version__ = "0.1.292"
@@ -19,7 +19,7 @@ from pathlib import Path
19
19
 
20
20
  import click
21
21
 
22
- from . import cli_processor, config, profile, state
22
+ from . import cli_processor, config, profile, state, telemetry
23
23
 
24
24
 
25
25
  def _get_store_path():
@@ -79,8 +79,11 @@ def app_name():
79
79
 
80
80
  def init_cli(main_cli: click.Group, commands_dir: str = "./cmds"):
81
81
  try:
82
- return cli_processor.init(main_cli, commands_dir)
83
- except Exception as e:
82
+ ret = cli_processor.init(main_cli, commands_dir)
83
+ telemetry.end()
84
+ return ret
85
+ except BaseException as e:
86
+ telemetry.end(error=e)
84
87
  if _need_stack_trace(e):
85
88
  raise e
86
89
  else:
@@ -89,9 +89,7 @@ def get(name: str):
89
89
  if name:
90
90
  data = profile.get(name)
91
91
  if data is None:
92
- panic(
93
- "Profile not found. Use 'hcs profile list' to show available profiles, or 'hcs profile init' to create one."
94
- )
92
+ panic("Profile not found. Use 'hcs profile list' to show available profiles, or 'hcs profile init' to create one.")
95
93
  else:
96
94
  data = profile.current()
97
95
  if data is None:
@@ -74,9 +74,7 @@ sort = click.option(
74
74
  help="Ascending/Descending. Format is property,{asc|desc} and default is ascending",
75
75
  )
76
76
 
77
- limit = click.option(
78
- "--limit", "-l", type=int, required=False, default=100, help="Optionally, specify the number of records to fetch."
79
- )
77
+ limit = click.option("--limit", "-l", type=int, required=False, default=100, help="Optionally, specify the number of records to fetch.")
80
78
 
81
79
  ids = click.option(
82
80
  "--ids",
@@ -101,9 +99,7 @@ first = click.option(
101
99
 
102
100
  force = click.option("--force/--grace", type=bool, default=True, help="Specify deletion mode: forceful, or graceful.")
103
101
 
104
- confirm = click.option(
105
- "--confirm/--prompt", "-y", type=bool, default=False, help="Confirm the operation without prompt."
106
- )
102
+ confirm = click.option("--confirm/--prompt", "-y", type=bool, default=False, help="Confirm the operation without prompt.")
107
103
 
108
104
 
109
105
  def formatter(custom_fn):
@@ -24,7 +24,6 @@ from pathlib import Path
24
24
  import click
25
25
  from click.core import Group
26
26
 
27
- from .extension import ensure_extension
28
27
  from .util import avoid_trace_for_ctrl_c, print_error, print_output, validate_error_return
29
28
 
30
29
  _eager_loading = os.environ.get("_CTXP_EAGER_LOAD")
@@ -61,6 +60,8 @@ class LazyGroup(click.Group):
61
60
 
62
61
  def _ensure_extension(self):
63
62
  if self._extension:
63
+ from .extension import ensure_extension
64
+
64
65
  ensure_extension(self._extension)
65
66
 
66
67
 
@@ -186,6 +187,11 @@ def _default_io(cmd: click.Command):
186
187
  "first": kwargs.pop("first"),
187
188
  "exclude_field": kwargs.pop("exclude_field"),
188
189
  }
190
+ ctx = click.get_current_context()
191
+ from .telemetry import start as telemetry_start
192
+
193
+ telemetry_start(ctx.command_path, ctx.params)
194
+
189
195
  if io_args["output"] == "table":
190
196
 
191
197
  def _format(data):
@@ -100,9 +100,7 @@ def strict_dict_to_class(data: dict, class_type):
100
100
  value = strict_dict_to_class(value, field_type)
101
101
  setattr(inst, field_name, value)
102
102
  continue
103
- raise ValueError(
104
- f"Field '{class_type.__name__}.{field_name}' has an incorrect type. Declared: {field_type}, actual: {type(value)}"
105
- )
103
+ raise ValueError(f"Field '{class_type.__name__}.{field_name}' has an incorrect type. Declared: {field_type}, actual: {type(value)}")
106
104
  return inst
107
105
 
108
106
 
@@ -341,9 +339,7 @@ def resolve_expression(expr, fn_get_value, referencing_attr_path) -> Tuple[Any,
341
339
  f"Invalid variable value for expression. Expect list, actual {type(target_value).__name__}. attr_path={referencing_attr_path}, src_var_name={src_var_name}"
342
340
  )
343
341
  if not mapped_value.startswith(tmp_var_name + "."):
344
- raise CtxpException(
345
- f"Unsupported expression. attr_path={referencing_attr_path}, src_var_name={src_var_name}"
346
- )
342
+ raise CtxpException(f"Unsupported expression. attr_path={referencing_attr_path}, src_var_name={src_var_name}")
347
343
  new_attr_path = mapped_value[len(tmp_var_name) + 1 :]
348
344
  ret = []
349
345
  for i in target_value:
@@ -2,9 +2,7 @@
2
2
 
3
3
  import re
4
4
 
5
- PATTERN = (
6
- "([-+]?)P(?:([-+]?[0-9]+)D)?(T(?:([-+]?[0-9]+)H)?(?:([-+]?[0-9]+)M)?(?:([-+]?[0-9]+)(?:[.,]([0-9]{0,9}))?S)?)?"
7
- )
5
+ PATTERN = "([-+]?)P(?:([-+]?[0-9]+)D)?(T(?:([-+]?[0-9]+)H)?(?:([-+]?[0-9]+)M)?(?:([-+]?[0-9]+)(?:[.,]([0-9]{0,9}))?S)?)?"
8
6
 
9
7
  # Examples
10
8
 
@@ -21,7 +21,9 @@ from logging.handlers import RotatingFileHandler
21
21
  import coloredlogs
22
22
 
23
23
  LOG_FORMAT_SIMPLE = "%(levelname).4s %(asctime)s %(name)-16s %(message)s"
24
- LOG_FORMAT_LONG = "%(color_on)s%(asctime)s.%(msecs)03d [%(process)-5d:%(threadName)-12s] %(levelname)-7s [%(name)-16s] %(message)s%(color_off)s"
24
+ LOG_FORMAT_LONG = (
25
+ "%(color_on)s%(asctime)s.%(msecs)03d [%(process)-5d:%(threadName)-12s] %(levelname)-7s [%(name)-16s] %(message)s%(color_off)s"
26
+ )
25
27
  DATE_FORMAT_SIMPLE = "%H:%M:%S"
26
28
 
27
29
 
@@ -133,9 +135,7 @@ def setup(
133
135
  )
134
136
 
135
137
 
136
- def setup_console_output(
137
- logger, console_log_output, console_log_level, console_log_color, console_log_mask, log_line_template, date_fmt
138
- ):
138
+ def setup_console_output(logger, console_log_output, console_log_level, console_log_color, console_log_mask, log_line_template, date_fmt):
139
139
  if not date_fmt:
140
140
  date_fmt = coloredlogs.DEFAULT_DATE_FORMAT
141
141
  coloredlogs.install(
@@ -82,9 +82,7 @@ def current(reload: bool = False, exit_on_failure: bool = True, exclude_secret:
82
82
  data = get(profile_name, reload)
83
83
 
84
84
  if data is None and exit_on_failure:
85
- panic(
86
- "Profile not set. Use 'hcs profile use [profile-name]' to choose one, or use 'hcs profile init' to create default profiles."
87
- )
85
+ panic("Profile not set. Use 'hcs profile use [profile-name]' to choose one, or use 'hcs profile init' to create default profiles.")
88
86
 
89
87
  if exclude_secret:
90
88
  data = dotdict(dict(data))
@@ -31,7 +31,7 @@ def all():
31
31
  return _recent
32
32
 
33
33
 
34
- def require(provided, k: str):
34
+ def require(k: str, provided):
35
35
  if not k:
36
36
  raise Exception("Missing 'key' in recent.require(current, key).")
37
37
 
@@ -0,0 +1,102 @@
1
+ import json
2
+ import logging
3
+ import sys
4
+ import time
5
+ from datetime import datetime, timezone
6
+
7
+ import click
8
+ import httpx
9
+ from yumako import env
10
+
11
+ log = logging.getLogger(__name__)
12
+
13
+ _record = None
14
+ _enabled = None
15
+ _version = None
16
+
17
+
18
+ def disable():
19
+ global _enabled
20
+ _enabled = False
21
+
22
+
23
+ def _is_disabled():
24
+ global _enabled
25
+ if _enabled is None:
26
+ _enabled = env.bool("HCS_CLI_TELEMETRY", True)
27
+ return not _enabled
28
+
29
+
30
+ def _get_version():
31
+ global _version
32
+ if _version is None:
33
+ try:
34
+ from importlib.metadata import version
35
+
36
+ _version = version("hcs-cli")
37
+ except Exception as e:
38
+ log.debug(f"Failed to get hcs-cli version: {e}")
39
+ _version = "unknown"
40
+ return _version
41
+
42
+
43
+ def start(cmd_path: str, params: dict):
44
+ if _is_disabled():
45
+ return
46
+
47
+ global _record
48
+ _record = {
49
+ "@timestamp": datetime.now(timezone.utc).isoformat(timespec="milliseconds"),
50
+ "command": cmd_path,
51
+ "options": [k.replace("_", "-") for k, v in params.items() if v],
52
+ "return": -1,
53
+ "error": None,
54
+ "time_ms": -1,
55
+ "version": _get_version(),
56
+ "env": {
57
+ "python_version": sys.version,
58
+ "platform": sys.platform,
59
+ "executable": sys.executable,
60
+ },
61
+ }
62
+
63
+
64
+ def end(return_code: int = 0, error: Exception = None):
65
+ if _is_disabled():
66
+ return
67
+
68
+ if _record is None:
69
+ return
70
+
71
+ if error:
72
+ if isinstance(error, click.exceptions.Exit):
73
+ return_code = error.exit_code
74
+ elif isinstance(error, SystemExit):
75
+ return_code = error.code
76
+ else:
77
+ _record["error"] = str(error)
78
+ if return_code == 0:
79
+ return_code = 1
80
+ _record["return"] = return_code
81
+ _record["time_ms"] = int((time.time() - datetime.fromisoformat(_record["@timestamp"]).timestamp()) * 1000)
82
+
83
+ # print('TELEMETRY end', json.dumps(_record, indent=4), flush=True)
84
+
85
+ _injest(_record)
86
+ return _record
87
+
88
+
89
+ def _injest(doc):
90
+ try:
91
+ response = httpx.post(
92
+ f"https://collie.omnissa.com/es/hcs-cli/_doc",
93
+ auth=("append_user", "public"),
94
+ headers={"Content-Type": "application/json"},
95
+ content=json.dumps(doc),
96
+ timeout=4,
97
+ verify=False,
98
+ )
99
+ response.raise_for_status()
100
+ except Exception as e:
101
+ log.debug(f"Telemetry ingestion failed: {e}", exc_info=True)
102
+ return
@@ -125,7 +125,7 @@ def print_output(data: Any, args: dict, file=sys.stdout):
125
125
 
126
126
 
127
127
  def print_error(error):
128
- critical_errors = [KeyError, TypeError]
128
+ critical_errors = [KeyError, TypeError, AttributeError, ValueError]
129
129
  for ex in critical_errors:
130
130
  if isinstance(error, ex):
131
131
  traceback.print_exception(type(error), error, error.__traceback__, file=sys.stderr)
@@ -189,6 +189,10 @@ def _exclude_fields(obj: Any, fields_exclude: str):
189
189
 
190
190
 
191
191
  def panic(reason: Any = None, code: int = 1):
192
+ if isinstance(reason, SystemExit):
193
+ os._exit(reason.code)
194
+ if isinstance(reason, click.exceptions.Exit):
195
+ os._exit(reason.exit_code)
192
196
  if isinstance(reason, Exception):
193
197
  text = error_details(reason)
194
198
  else:
@@ -41,19 +41,13 @@ def _prepare_data(data: dict, additional_context: dict, target_resource_name: st
41
41
  data.update(additional_context)
42
42
  blueprint, pending = process_template(data)
43
43
 
44
- if (
45
- target_resource_name
46
- and target_resource_name not in blueprint["resource"]
47
- and target_resource_name not in blueprint["runtime"]
48
- ):
44
+ if target_resource_name and target_resource_name not in blueprint["resource"] and target_resource_name not in blueprint["runtime"]:
49
45
  raise PlanException("Target resource or runtime not found: " + target_resource_name)
50
46
 
51
47
  for k, v in pending.items():
52
48
  if v.startswith("default.") or v.startswith("var."):
53
49
  if not k.find(".conditions."):
54
- raise PlanException(
55
- f"Invalid blueprint. Unresolved static references. Variable not found: {v}. Required by {k}"
56
- )
50
+ raise PlanException(f"Invalid blueprint. Unresolved static references. Variable not found: {v}. Required by {k}")
57
51
  deployment_id = blueprint["deploymentId"]
58
52
  state_file = deployment_id + ".state.yml"
59
53
  prev = data_util.load_data_file(state_file, default={})
@@ -291,9 +285,7 @@ def _deploy_res(name, res, state):
291
285
  else:
292
286
  new_state = handler.update(res_data, res_state)
293
287
  else:
294
- raise PlanException(
295
- f"Unknown action. This is a problem of the concrete plugin.decide function. Plugin={name}, action={action}"
296
- )
288
+ raise PlanException(f"Unknown action. This is a problem of the concrete plugin.decide function. Plugin={name}, action={action}")
297
289
 
298
290
  if new_state:
299
291
  fn_set_state(new_state)
@@ -38,9 +38,9 @@ def _is_auth_valid(auth_data):
38
38
  _login_lock = threading.Lock()
39
39
 
40
40
 
41
- def login(force_refresh: bool = False):
41
+ def login(force_refresh: bool = False, verbose: bool = False):
42
42
  """Ensure login state, using credentials from the current profile. Return oauth token."""
43
- return _populate_token_with_cache(profile.current().csp, force_refresh)
43
+ return _populate_token_with_cache(profile.current().csp, force_refresh, verbose)
44
44
 
45
45
 
46
46
  def refresh_oauth_token(old_oauth_token: dict, csp_url: str):
@@ -98,17 +98,22 @@ def save_auth_cache(auth_config: dict, token: dict):
98
98
  profile.auth.set(cache)
99
99
 
100
100
 
101
- def _populate_token_with_cache(auth_config: dict, force_refresh: bool = False):
102
-
101
+ def _populate_token_with_cache(auth_config: dict, force_refresh: bool = False, verbose: bool = False):
102
+ if verbose:
103
+ print("_populate_token_with_cache")
103
104
  with _login_lock:
104
105
  cache, hash, token = _get_auth_cache(auth_config)
105
106
  if token and not force_refresh:
107
+ if verbose:
108
+ print("Using cached auth token.")
106
109
  return token
107
110
 
108
111
  # invalid token. Refresh or recreate it.
109
112
  if token:
110
113
  # try using refresh token if possible
111
114
  if auth_config.get("provider", "vmwarecsp") == "vmwarecsp":
115
+ if verbose:
116
+ print("Provider: vmwarecsp.")
112
117
  try:
113
118
  token = refresh_oauth_token(token, auth_config.url)
114
119
  except Exception as e:
@@ -116,11 +121,17 @@ def _populate_token_with_cache(auth_config: dict, force_refresh: bool = False):
116
121
  token = None
117
122
  else:
118
123
  # hcs auth-service. Does not support refresh token.
124
+ if verbose:
125
+ print("Provider: hcs auth-service.")
119
126
  token = None
120
127
 
121
128
  if not token:
122
129
  if _has_credential(auth_config):
130
+ if verbose:
131
+ print("Config:", auth_config)
123
132
  token = CspClient.create(**auth_config).oauth_token()
133
+ if verbose:
134
+ print("Token:", json.dumps(token, indent=4))
124
135
  else:
125
136
  if auth_config.get("browser"):
126
137
  from .login_support import login_via_browser
@@ -129,9 +140,7 @@ def _populate_token_with_cache(auth_config: dict, force_refresh: bool = False):
129
140
  if not token:
130
141
  raise CtxpException("Browser auth failed.")
131
142
  else:
132
- raise CtxpException(
133
- "Browser auth was never attempted and no client credentials or API token provided."
134
- )
143
+ raise CtxpException("Browser auth was never attempted and no client credentials or API token provided.")
135
144
 
136
145
  if not token.get("expires_at"):
137
146
  token["expires_at"] = int(time.time() + token["expires_in"])
@@ -32,19 +32,31 @@ _caches = {}
32
32
  _client_instance_lock = threading.RLock()
33
33
 
34
34
 
35
- def _lazy_init(
36
- service_name: str, hdc: str = None, region: str = None
37
- ): # make it deferred so no need to initialize profile
38
- if region and hdc:
39
- raise Exception("region and hdc cannot be specified at the same time.")
40
-
41
- # check per-service override in profile
35
+ def _get_service_override(service_name: str):
42
36
  profile_data = profile.current()
43
37
  override = profile_data.get("override", {})
44
38
  service_override = {}
45
39
  for k, v in override.items():
46
40
  if service_name == k.lower():
47
41
  service_override = v
42
+
43
+ if not service_override:
44
+ if service_name == "org-service":
45
+ service_override = override.get("org", {})
46
+ elif service_name.find("-") >= 0:
47
+ camel_name = "".join(word.capitalize() for word in service_name.split("-"))
48
+ camel_name = camel_name[0].lower() + camel_name[1:]
49
+ service_override = override.get(camel_name, {})
50
+ return service_override
51
+
52
+
53
+ def _lazy_init(service_name: str, hdc: str = None, region: str = None): # make it deferred so no need to initialize profile
54
+ if region and hdc:
55
+ raise Exception("region and hdc cannot be specified at the same time.")
56
+
57
+ profile_data = profile.current()
58
+
59
+ service_override = _get_service_override(service_name)
48
60
  service_override_url = service_override.get("url")
49
61
  if service_override_url:
50
62
  log.debug(f"Using per-service override for {service_name}: {service_override_url}")
@@ -83,7 +95,7 @@ def _lazy_init(
83
95
  if provider == "vmwarecsp":
84
96
  token_url = profile_data.csp.url
85
97
  elif provider == "auth-service":
86
- token_url = profile_data.auth.tokenUrl
98
+ token_url = profile_data.auth["tokenUrl"]
87
99
  else:
88
100
  raise CtxpException(f"Unknown provider: {provider}. Supported providers: vmwarecsp, auth-service.")
89
101
 
@@ -164,9 +176,13 @@ def regional_service_client(service_name: str, region: str = None):
164
176
  return instance
165
177
 
166
178
 
167
- def service_client(service_name: str, region: str = None, hdc: str = None):
179
+ def is_regional_service(service_name: str):
168
180
  regional_services = ["vmhub", "connection-service"]
169
- if service_name in regional_services:
181
+ return service_name in regional_services
182
+
183
+
184
+ def service_client(service_name: str, region: str = None, hdc: str = None):
185
+ if is_regional_service(service_name):
170
186
  return regional_service_client(service_name, region)
171
187
  else:
172
188
  return hdc_service_client(service_name, hdc)
@@ -358,9 +374,7 @@ def wait_for_res_status(
358
374
  if is_ready(status):
359
375
  return t
360
376
  if not is_transition(status):
361
- raise CtxpException(
362
- prefix + f"Unexpected status: {status}. If this is a transition, add it to status_map['transition']."
363
- )
377
+ raise CtxpException(prefix + f"Unexpected status: {status}. If this is a transition, add it to status_map['transition'].")
364
378
 
365
379
  now = time.time()
366
380
  remaining_seconds = timeout_seconds - (now - start)
@@ -112,9 +112,7 @@ class CspClient:
112
112
  "Accept": "application/json",
113
113
  }
114
114
  # <no org id for this API>
115
- resp = self._client.post(
116
- "/csp/gateway/am/api/auth/api-tokens/authorize", headers=headers, data=f"api_token={api_token}"
117
- )
115
+ resp = self._client.post("/csp/gateway/am/api/auth/api-tokens/authorize", headers=headers, data=f"api_token={api_token}")
118
116
  self._oauth_token = resp.json()
119
117
  return self._oauth_token
120
118
 
@@ -64,7 +64,7 @@ _auth_success_html = """
64
64
  <h3>You have successfully logged into VMware Horizon Cloud Service.</h3>
65
65
  <p>You can close this window, and return to the terminal.</p>
66
66
  <br/>
67
- <p><a href="https://github.com/euc-eng/hcs-cli">HCS CLI</a> is in beta phase. <a href="https://github.com/euc-eng/hcs-cli/blob/main/doc/hcs-cli-cheatsheet.md">Cheatsheet</a>:</p>
67
+ <p><a href="https://github.com/euc-eng/hcs-cli/blob/dev/README.md">HCS CLI</a> is in beta. <a href="https://github.com/euc-eng/hcs-cli/blob/main/doc/hcs-cli-cheatsheet.md">Cheatsheet</a>:</p>
68
68
  <code>
69
69
  # To get the login details: <br/>
70
70
  hcs login -d <br/><br/>
@@ -153,9 +153,7 @@ class JobView:
153
153
  _MyPlainBarColumn(),
154
154
  # TextColumn("[white][progress.percentage][white]{task.percentage:>3.0f}%"),
155
155
  # TaskProgressColumn(show_speed=True),
156
- TimeRemainingColumn(
157
- compact=True, elapsed_when_finished=True, table_column=Column(style="white", min_width=5)
158
- ),
156
+ TimeRemainingColumn(compact=True, elapsed_when_finished=True, table_column=Column(style="white", min_width=5)),
159
157
  TextColumn("{task.fields[details]}", table_column=Column(max_width=msg_width, no_wrap=True)),
160
158
  get_time=monotonic,
161
159
  )
@@ -23,29 +23,29 @@ keywords = [
23
23
  dynamic = ["version"]
24
24
 
25
25
  dependencies = [
26
- "Authlib>=1.2.1",
27
- "click>=8.1.7",
26
+ "Authlib>=1.6.0",
27
+ "click>=8.2.1",
28
28
  "coloredlogs>=15.0.1",
29
- "cryptography>=42.0.7",
30
- "graphviz>=0.20.3",
31
- "httpx>=0.27.0",
32
- "packaging>=24.0",
33
- "portalocker>=2.8.2",
34
- "PyJWT>=2.8.0",
35
- "pyOpenSSL>=24.1.0",
29
+ "cryptography>=45.0.5",
30
+ "graphviz>=0.21",
31
+ "httpx>=0.28.0",
32
+ "packaging>=25.0",
33
+ "portalocker>=3.0.0",
34
+ "PyJWT>=2.10.1",
35
+ "pyOpenSSL>=25.1.0",
36
36
  "PyYAML>=6.0.1",
37
37
  "questionary>=2.0.1",
38
- "rich>=13.7.1",
38
+ "rich>=14.0.0",
39
39
  "setuptools>=70.0.0",
40
40
  "retry>=0.9.2",
41
41
  "tabulate>=0.9.0",
42
- "rel>=0.4.7",
42
+ "rel>=0.4.9.20",
43
43
  "websocket_client>=1.2.3",
44
- "psutil>=5.9.4",
44
+ "psutil>=7.0.0",
45
45
  "schedule>=1.1.0",
46
- "yumako>=0.1.24",
47
- "pydantic>=2.0.0",
48
- "python-dotenv>=1.1.0",
46
+ "yumako>=0.1.27",
47
+ "pydantic>=2.11.7",
48
+ "python-dotenv>=1.1.1",
49
49
  ]
50
50
 
51
51
  [project.optional-dependencies]
@@ -80,7 +80,7 @@ exclude = [
80
80
  ]
81
81
 
82
82
  [tool.black]
83
- line-length = 120
83
+ line-length = 140
84
84
  target-version = ['py39']
85
85
  include = '\.pyi?$'
86
86
  extend-exclude = '''
@@ -1 +0,0 @@
1
- __version__ = "0.1.290"
File without changes
File without changes