hcs-core 0.1.250__py3-none-any.whl → 0.1.316__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_core/__init__.py +1 -0
- hcs_core/ctxp/__init__.py +12 -4
- hcs_core/ctxp/_init.py +94 -22
- hcs_core/ctxp/built_in_cmds/_ut.py +4 -3
- hcs_core/ctxp/built_in_cmds/context.py +16 -1
- hcs_core/ctxp/built_in_cmds/profile.py +30 -11
- hcs_core/ctxp/cli_options.py +34 -13
- hcs_core/ctxp/cli_processor.py +33 -20
- hcs_core/ctxp/cmd_util.py +87 -0
- hcs_core/ctxp/config.py +1 -1
- hcs_core/ctxp/context.py +82 -3
- hcs_core/ctxp/data_util.py +56 -20
- hcs_core/ctxp/dispatcher.py +82 -0
- hcs_core/ctxp/duration.py +65 -0
- hcs_core/ctxp/extension.py +7 -6
- hcs_core/ctxp/fn_util.py +57 -0
- hcs_core/ctxp/fstore.py +39 -22
- hcs_core/ctxp/jsondot.py +259 -78
- hcs_core/ctxp/logger.py +7 -6
- hcs_core/ctxp/profile.py +53 -21
- hcs_core/ctxp/profile_store.py +1 -0
- hcs_core/ctxp/recent.py +3 -3
- hcs_core/ctxp/state.py +4 -3
- hcs_core/ctxp/task_schd.py +168 -0
- hcs_core/ctxp/telemetry.py +145 -0
- hcs_core/ctxp/template_util.py +21 -0
- hcs_core/ctxp/timeutil.py +11 -0
- hcs_core/ctxp/util.py +194 -33
- hcs_core/ctxp/var_template.py +3 -4
- hcs_core/plan/__init__.py +11 -5
- hcs_core/plan/base_provider.py +1 -0
- hcs_core/plan/core.py +29 -26
- hcs_core/plan/dag.py +15 -12
- hcs_core/plan/helper.py +4 -2
- hcs_core/plan/kop.py +21 -8
- hcs_core/plan/provider/dev/dummy.py +3 -3
- hcs_core/sglib/auth.py +137 -95
- hcs_core/sglib/cli_options.py +20 -5
- hcs_core/sglib/client_util.py +230 -62
- hcs_core/sglib/csp.py +73 -6
- hcs_core/sglib/ez_client.py +139 -41
- hcs_core/sglib/hcs_client.py +3 -9
- hcs_core/sglib/init.py +17 -0
- hcs_core/sglib/login_support.py +22 -83
- hcs_core/sglib/payload_util.py +3 -1
- hcs_core/sglib/requtil.py +38 -0
- hcs_core/sglib/utils.py +107 -0
- hcs_core/util/check_license.py +0 -2
- hcs_core/util/duration.py +6 -3
- hcs_core/util/job_view.py +35 -15
- hcs_core/util/pki_util.py +48 -1
- hcs_core/util/query_util.py +54 -8
- hcs_core/util/scheduler.py +3 -3
- hcs_core/util/ssl_util.py +1 -1
- hcs_core/util/versions.py +15 -12
- hcs_core-0.1.316.dist-info/METADATA +54 -0
- hcs_core-0.1.316.dist-info/RECORD +69 -0
- {hcs_core-0.1.250.dist-info → hcs_core-0.1.316.dist-info}/WHEEL +1 -2
- hcs_core-0.1.250.dist-info/METADATA +0 -36
- hcs_core-0.1.250.dist-info/RECORD +0 -59
- hcs_core-0.1.250.dist-info/top_level.txt +0 -1
hcs_core/__init__.py
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.316"
|
hcs_core/ctxp/__init__.py
CHANGED
|
@@ -13,7 +13,15 @@ See the License for the specific language governing permissions and
|
|
|
13
13
|
limitations under the License.
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
|
-
from .
|
|
17
|
-
from .
|
|
18
|
-
from . import
|
|
19
|
-
from .
|
|
16
|
+
from . import config as config
|
|
17
|
+
from . import profile as profile
|
|
18
|
+
from . import state as state
|
|
19
|
+
from . import var_template as var_template
|
|
20
|
+
from ._init import init as init
|
|
21
|
+
from ._init import init_cli as init_cli
|
|
22
|
+
from .profile_store import profile_store as profile_store
|
|
23
|
+
from .util import CtxpException as CtxpException
|
|
24
|
+
from .util import choose as choose
|
|
25
|
+
from .util import error as error
|
|
26
|
+
from .util import launch_text_editor as launch_text_editor
|
|
27
|
+
from .util import panic as panic
|
hcs_core/ctxp/_init.py
CHANGED
|
@@ -13,27 +13,99 @@ See the License for the specific language governing permissions and
|
|
|
13
13
|
limitations under the License.
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
|
-
import
|
|
16
|
+
import os
|
|
17
17
|
from os import path
|
|
18
18
|
from pathlib import Path
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
from . import cli_processor
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
19
|
+
|
|
20
|
+
import click
|
|
21
|
+
|
|
22
|
+
from . import cli_processor, config, profile, state, telemetry
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _get_store_path():
|
|
26
|
+
if os.name == "nt": # Windows OS
|
|
27
|
+
return str(Path.home())
|
|
28
|
+
uid = os.getuid()
|
|
29
|
+
if uid == 0 or uid == 1000:
|
|
30
|
+
return "/tmp"
|
|
31
|
+
if os.path.exists("/.dockerenv"):
|
|
32
|
+
return "/tmp"
|
|
33
|
+
return str(Path.home())
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
user_home = _get_store_path()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
_initialized_app_name = None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def init(app_name: str, store_path: str = user_home, config_path: str = "./config"):
|
|
43
|
+
global _initialized_app_name
|
|
44
|
+
if _initialized_app_name == app_name:
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
if _initialized_app_name is not None:
|
|
48
|
+
raise ValueError(f"App {app_name} already initialized with {_initialized_app_name}")
|
|
49
|
+
_initialized_app_name = app_name
|
|
50
|
+
try:
|
|
51
|
+
real_store_path = path.join(store_path, "." + app_name)
|
|
52
|
+
state.init(real_store_path, ".state")
|
|
53
|
+
profile.init(real_store_path)
|
|
54
|
+
config.init(config_path)
|
|
55
|
+
|
|
56
|
+
except Exception as e:
|
|
57
|
+
# critical errors, must print stack trace.
|
|
58
|
+
if _need_stack_trace(e):
|
|
59
|
+
raise e
|
|
60
|
+
else:
|
|
61
|
+
# Other errors, no stack.
|
|
62
|
+
from .util import panic
|
|
63
|
+
|
|
64
|
+
panic(e)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# init default with env if configured
|
|
68
|
+
_app_name = os.environ.get("CTXP_APP_NAME")
|
|
69
|
+
if _app_name:
|
|
70
|
+
init(_app_name)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def app_name():
|
|
74
|
+
dir_name = path.dirname(state._file._path)
|
|
75
|
+
name = dir_name[dir_name.rindex(os.sep) + 1 :]
|
|
76
|
+
if name.startswith("."):
|
|
77
|
+
return name[1:]
|
|
78
|
+
raise ValueError("Unable to determine app name: " + dir_name)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def init_cli(main_cli: click.Group, commands_dir: str = "./cmds"):
|
|
82
|
+
try:
|
|
83
|
+
telemetry.start(_initialized_app_name)
|
|
84
|
+
ret = cli_processor.init(main_cli, commands_dir)
|
|
85
|
+
telemetry.end()
|
|
86
|
+
return ret
|
|
87
|
+
except BaseException as e:
|
|
88
|
+
telemetry.end(error=e)
|
|
89
|
+
if _need_stack_trace(e):
|
|
90
|
+
raise e
|
|
91
|
+
else:
|
|
92
|
+
from .util import panic
|
|
93
|
+
|
|
94
|
+
panic(e)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _need_stack_trace(e) -> bool:
|
|
98
|
+
program_errors = [
|
|
99
|
+
LookupError,
|
|
100
|
+
TypeError,
|
|
101
|
+
ValueError,
|
|
102
|
+
ArithmeticError,
|
|
103
|
+
NameError,
|
|
104
|
+
SyntaxError,
|
|
105
|
+
KeyError,
|
|
106
|
+
AttributeError,
|
|
107
|
+
]
|
|
108
|
+
for t in program_errors:
|
|
109
|
+
if isinstance(e, t):
|
|
110
|
+
return True
|
|
111
|
+
return False
|
|
@@ -14,6 +14,7 @@ limitations under the License.
|
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
16
|
import click
|
|
17
|
+
|
|
17
18
|
import hcs_core.ctxp as ctxp
|
|
18
19
|
import hcs_core.ctxp.cli_options as cli
|
|
19
20
|
import hcs_core.ctxp.util as util
|
|
@@ -43,11 +44,11 @@ def echo_obj(opt: str, id: str):
|
|
|
43
44
|
|
|
44
45
|
@cli_test.command()
|
|
45
46
|
@click.option("--opt")
|
|
46
|
-
@click.argument("
|
|
47
|
+
@click.argument("obj_ids", nargs=-1)
|
|
47
48
|
@cli.formatter(_format_custom_table)
|
|
48
|
-
def echo_obj_list(opt: str,
|
|
49
|
+
def echo_obj_list(opt: str, obj_ids: tuple[str]):
|
|
49
50
|
ret = []
|
|
50
|
-
for i in
|
|
51
|
+
for i in obj_ids:
|
|
51
52
|
ret.append({"id": i, "opt": opt})
|
|
52
53
|
return ret
|
|
53
54
|
|
|
@@ -14,6 +14,7 @@ limitations under the License.
|
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
16
|
import click
|
|
17
|
+
|
|
17
18
|
import hcs_core.ctxp as ctxp
|
|
18
19
|
import hcs_core.ctxp.util as util
|
|
19
20
|
|
|
@@ -47,7 +48,7 @@ def get(name: str, key: str):
|
|
|
47
48
|
@click.argument("name")
|
|
48
49
|
@click.argument("key_value") # 'key value pair, example: k1=v1'
|
|
49
50
|
def set(name: str, key_value: str):
|
|
50
|
-
"""Set a context
|
|
51
|
+
"""Set a context property by name."""
|
|
51
52
|
parts = key_value.split("=")
|
|
52
53
|
if len(parts) != 2:
|
|
53
54
|
ctxp.panic("Invalid KEY_VALUE format. Valid example: key1=value1")
|
|
@@ -57,6 +58,20 @@ def set(name: str, key_value: str):
|
|
|
57
58
|
ctxp.context.set(name, data)
|
|
58
59
|
|
|
59
60
|
|
|
61
|
+
@context.command()
|
|
62
|
+
@click.argument("name")
|
|
63
|
+
@click.argument("key")
|
|
64
|
+
def unset(name: str, key: str):
|
|
65
|
+
"""Unset a context property by name."""
|
|
66
|
+
data = ctxp.context.get(name, default=None)
|
|
67
|
+
if data is None:
|
|
68
|
+
return
|
|
69
|
+
data.pop(key, None)
|
|
70
|
+
if len(data) == 0:
|
|
71
|
+
return ctxp.context.delete(name)
|
|
72
|
+
return ctxp.context.set(name, data)
|
|
73
|
+
|
|
74
|
+
|
|
60
75
|
@context.command()
|
|
61
76
|
@click.argument("name")
|
|
62
77
|
def delete(name: str):
|
|
@@ -13,11 +13,13 @@ See the License for the specific language governing permissions and
|
|
|
13
13
|
limitations under the License.
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
|
-
import click
|
|
17
|
-
import sys
|
|
18
16
|
import json
|
|
17
|
+
import sys
|
|
18
|
+
|
|
19
|
+
import click
|
|
19
20
|
import questionary
|
|
20
|
-
|
|
21
|
+
|
|
22
|
+
from hcs_core.ctxp import cli_options, cli_processor, panic, profile, util
|
|
21
23
|
|
|
22
24
|
|
|
23
25
|
@click.group(name="profile", cls=cli_processor.LazyGroup)
|
|
@@ -37,8 +39,10 @@ def use(name: str):
|
|
|
37
39
|
"""Switch to a specific profile. If no name specified, launch interactive list to choose profile."""
|
|
38
40
|
|
|
39
41
|
if name:
|
|
40
|
-
|
|
42
|
+
p = profile.use(name)
|
|
43
|
+
if p is None:
|
|
41
44
|
panic("No such profile: " + name)
|
|
45
|
+
return profile.get(name)
|
|
42
46
|
else:
|
|
43
47
|
current = profile.name()
|
|
44
48
|
choices = profile.names()
|
|
@@ -49,6 +53,7 @@ def use(name: str):
|
|
|
49
53
|
if ret:
|
|
50
54
|
if profile.use(ret) is None:
|
|
51
55
|
panic("No such profile: " + name)
|
|
56
|
+
return profile.current()
|
|
52
57
|
else:
|
|
53
58
|
# aborted
|
|
54
59
|
return "", 1
|
|
@@ -57,7 +62,8 @@ def use(name: str):
|
|
|
57
62
|
@profile_cmd_group.command()
|
|
58
63
|
@click.option("--from-name", "-f", required=False)
|
|
59
64
|
@click.option("--to-name", "-t", required=True)
|
|
60
|
-
|
|
65
|
+
@click.option("--no-edit", "-e", required=False, is_flag=True, help="Edit the profile after copying.")
|
|
66
|
+
def copy(from_name: str, to_name: str, no_edit: bool):
|
|
61
67
|
"""Copy profile."""
|
|
62
68
|
|
|
63
69
|
if not from_name:
|
|
@@ -68,7 +74,11 @@ def copy(from_name: str, to_name: str):
|
|
|
68
74
|
panic("No such profile: " + from_name)
|
|
69
75
|
if profile.exists(to_name):
|
|
70
76
|
panic("Profile already exists: " + to_name)
|
|
71
|
-
profile.create(to_name, data, True)
|
|
77
|
+
data = profile.create(to_name, data, True)
|
|
78
|
+
|
|
79
|
+
if no_edit:
|
|
80
|
+
return data
|
|
81
|
+
util.launch_text_editor(profile.file(to_name))
|
|
72
82
|
|
|
73
83
|
|
|
74
84
|
@profile_cmd_group.command()
|
|
@@ -78,9 +88,7 @@ def get(name: str):
|
|
|
78
88
|
if name:
|
|
79
89
|
data = profile.get(name)
|
|
80
90
|
if data is None:
|
|
81
|
-
panic(
|
|
82
|
-
"Profile not found. Use 'hcs profile list' to show available profiles, or 'hcs profile init' to create one."
|
|
83
|
-
)
|
|
91
|
+
panic("Profile not found. Use 'hcs profile list' to show available profiles, or 'hcs profile init' to create one.")
|
|
84
92
|
else:
|
|
85
93
|
data = profile.current()
|
|
86
94
|
if data is None:
|
|
@@ -92,9 +100,20 @@ def get(name: str):
|
|
|
92
100
|
|
|
93
101
|
|
|
94
102
|
@profile_cmd_group.command()
|
|
95
|
-
@
|
|
96
|
-
|
|
103
|
+
@cli_options.confirm
|
|
104
|
+
@click.argument("name", required=False)
|
|
105
|
+
def delete(confirm: bool, name: str):
|
|
97
106
|
"""Delete a profile by name."""
|
|
107
|
+
if not name:
|
|
108
|
+
name = profile.name()
|
|
109
|
+
|
|
110
|
+
if not profile.exists(name):
|
|
111
|
+
return
|
|
112
|
+
if not confirm:
|
|
113
|
+
question = f"Are you sure to delete profile '{name}'?"
|
|
114
|
+
if not questionary.confirm(question, default=False).ask():
|
|
115
|
+
click.echo("Aborted")
|
|
116
|
+
return
|
|
98
117
|
profile.delete(name)
|
|
99
118
|
|
|
100
119
|
|
hcs_core/ctxp/cli_options.py
CHANGED
|
@@ -13,8 +13,9 @@ See the License for the specific language governing permissions and
|
|
|
13
13
|
limitations under the License.
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
|
-
import
|
|
16
|
+
import os
|
|
17
17
|
|
|
18
|
+
import click
|
|
18
19
|
|
|
19
20
|
verbose = click.option(
|
|
20
21
|
"--verbose",
|
|
@@ -28,7 +29,7 @@ verbose = click.option(
|
|
|
28
29
|
output = click.option(
|
|
29
30
|
"--output",
|
|
30
31
|
"-o",
|
|
31
|
-
type=click.Choice(["json", "json-compact", "yaml", "
|
|
32
|
+
type=click.Choice(["json", "json-compact", "yaml", "yml", "table", "t", "text"], case_sensitive=False),
|
|
32
33
|
default=None,
|
|
33
34
|
hidden=True,
|
|
34
35
|
help="Specify output format",
|
|
@@ -42,13 +43,22 @@ field = click.option(
|
|
|
42
43
|
help="Specify fields to output, in comma separated field names.",
|
|
43
44
|
)
|
|
44
45
|
|
|
46
|
+
exclude_field = click.option(
|
|
47
|
+
"--exclude-field",
|
|
48
|
+
type=str,
|
|
49
|
+
required=False,
|
|
50
|
+
hidden=True,
|
|
51
|
+
help="Specify fields to exclude from output, in comma separated field names.",
|
|
52
|
+
)
|
|
53
|
+
|
|
45
54
|
wait = click.option(
|
|
46
55
|
"--wait",
|
|
47
56
|
"-w",
|
|
48
57
|
type=str,
|
|
49
|
-
|
|
58
|
+
is_flag=False,
|
|
59
|
+
flag_value="1m",
|
|
50
60
|
default="0",
|
|
51
|
-
help="Wait time. E.g. '30s', or '5m'. Default:
|
|
61
|
+
help="Wait time. E.g. '30s', or '5m'. Default: 1m.",
|
|
52
62
|
)
|
|
53
63
|
|
|
54
64
|
search = click.option(
|
|
@@ -66,12 +76,11 @@ sort = click.option(
|
|
|
66
76
|
help="Ascending/Descending. Format is property,{asc|desc} and default is ascending",
|
|
67
77
|
)
|
|
68
78
|
|
|
69
|
-
limit = click.option(
|
|
70
|
-
"--limit", "-l", type=int, required=False, default=20, help="Optionally, specify the number of records to fetch."
|
|
71
|
-
)
|
|
79
|
+
limit = click.option("--limit", "-l", type=int, required=False, default=100, help="Optionally, specify the number of records to fetch.")
|
|
72
80
|
|
|
73
|
-
|
|
74
|
-
"--
|
|
81
|
+
ids = click.option(
|
|
82
|
+
"--ids",
|
|
83
|
+
"--id-only", # to be removed.
|
|
75
84
|
is_flag=True,
|
|
76
85
|
type=bool,
|
|
77
86
|
required=False,
|
|
@@ -92,14 +101,26 @@ first = click.option(
|
|
|
92
101
|
|
|
93
102
|
force = click.option("--force/--grace", type=bool, default=True, help="Specify deletion mode: forceful, or graceful.")
|
|
94
103
|
|
|
95
|
-
confirm = click.option(
|
|
96
|
-
|
|
104
|
+
confirm = click.option("--confirm/--prompt", "-y", type=bool, default=False, help="Confirm the operation without prompt.")
|
|
105
|
+
|
|
106
|
+
env = click.option(
|
|
107
|
+
"--env",
|
|
108
|
+
multiple=True,
|
|
109
|
+
type=str,
|
|
110
|
+
required=False,
|
|
111
|
+
help="Alternative explicit in-line environment variable override in KEY=VALUE format.",
|
|
97
112
|
)
|
|
98
113
|
|
|
99
114
|
|
|
100
|
-
def
|
|
115
|
+
def apply_env(envs):
|
|
116
|
+
for kv in envs:
|
|
117
|
+
k, v = kv.split("=", 1)
|
|
118
|
+
os.environ[k.strip()] = v.strip()
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def formatter(formatter=None, columns=None):
|
|
101
122
|
def decorator(f):
|
|
102
|
-
f.formatter =
|
|
123
|
+
f.formatter = formatter if formatter else columns
|
|
103
124
|
return f
|
|
104
125
|
|
|
105
126
|
return decorator
|
hcs_core/ctxp/cli_processor.py
CHANGED
|
@@ -13,17 +13,18 @@ See the License for the specific language governing permissions and
|
|
|
13
13
|
limitations under the License.
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
|
-
import click
|
|
17
|
-
import sys
|
|
18
|
-
from click.core import Group
|
|
19
|
-
import os.path as path
|
|
20
|
-
import os
|
|
21
|
-
import re
|
|
22
16
|
import importlib
|
|
23
17
|
import importlib.util
|
|
18
|
+
import os
|
|
19
|
+
import os.path as path
|
|
20
|
+
import re
|
|
21
|
+
import sys
|
|
24
22
|
from pathlib import Path
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
|
|
24
|
+
import click
|
|
25
|
+
from click.core import Group
|
|
26
|
+
|
|
27
|
+
from .util import avoid_trace_for_ctrl_c, default_table_formatter, print_error, print_output, validate_error_return
|
|
27
28
|
|
|
28
29
|
_eager_loading = os.environ.get("_CTXP_EAGER_LOAD")
|
|
29
30
|
if _eager_loading:
|
|
@@ -59,6 +60,8 @@ class LazyGroup(click.Group):
|
|
|
59
60
|
|
|
60
61
|
def _ensure_extension(self):
|
|
61
62
|
if self._extension:
|
|
63
|
+
from .extension import ensure_extension
|
|
64
|
+
|
|
62
65
|
ensure_extension(self._extension)
|
|
63
66
|
|
|
64
67
|
|
|
@@ -73,6 +76,7 @@ def _ensure_sub_group(current: Group, mod_path: Path):
|
|
|
73
76
|
meta = _read_group_meta(mod_path)
|
|
74
77
|
help = meta.get("help")
|
|
75
78
|
extension = meta.get("extension")
|
|
79
|
+
hidden = meta.get("hidden", False)
|
|
76
80
|
|
|
77
81
|
subgroup = current.commands.get(name)
|
|
78
82
|
if subgroup and isinstance(subgroup, Group):
|
|
@@ -80,7 +84,7 @@ def _ensure_sub_group(current: Group, mod_path: Path):
|
|
|
80
84
|
subgroup.mod_path = mod_path
|
|
81
85
|
return subgroup
|
|
82
86
|
|
|
83
|
-
subgroup = LazyGroup(extension=extension, name=name, help=help, mod_path=mod_path)
|
|
87
|
+
subgroup = LazyGroup(extension=extension, name=name, help=help, mod_path=mod_path, hidden=hidden)
|
|
84
88
|
current.add_command(subgroup)
|
|
85
89
|
return subgroup
|
|
86
90
|
|
|
@@ -164,32 +168,41 @@ def _import_cmd_file(mod_path: Path, parent: click.core.Group):
|
|
|
164
168
|
# Create a new decorator that combines the individual decorators
|
|
165
169
|
def _default_io(cmd: click.Command):
|
|
166
170
|
|
|
167
|
-
from .cli_options import
|
|
171
|
+
from .cli_options import exclude_field, field, first, ids, output
|
|
168
172
|
|
|
169
173
|
cmd = output(cmd)
|
|
170
174
|
# cmd = cli_options.verbose(cmd)
|
|
171
175
|
cmd = field(cmd)
|
|
172
|
-
cmd =
|
|
176
|
+
cmd = ids(cmd)
|
|
173
177
|
cmd = first(cmd)
|
|
178
|
+
cmd = exclude_field(cmd)
|
|
174
179
|
callback = cmd.callback
|
|
175
180
|
|
|
176
181
|
def inner(*args, **kwargs):
|
|
177
182
|
io_args = {
|
|
178
183
|
"output": kwargs.pop("output"),
|
|
179
|
-
#'verbose': kwargs.pop('verbose'),
|
|
184
|
+
# 'verbose': kwargs.pop('verbose'),
|
|
180
185
|
"field": kwargs.pop("field"),
|
|
181
|
-
"
|
|
186
|
+
"ids": kwargs.pop("ids"),
|
|
182
187
|
"first": kwargs.pop("first"),
|
|
188
|
+
"exclude_field": kwargs.pop("exclude_field"),
|
|
183
189
|
}
|
|
184
|
-
|
|
190
|
+
ctx = click.get_current_context()
|
|
191
|
+
from .telemetry import update as telemetry_update
|
|
185
192
|
|
|
186
|
-
|
|
187
|
-
if not hasattr(callback, "formatter"):
|
|
188
|
-
from .util import CtxpException
|
|
193
|
+
telemetry_update(ctx.command_path, ctx.params)
|
|
189
194
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
195
|
+
if io_args["output"] == "table" or io_args["output"] == "t":
|
|
196
|
+
|
|
197
|
+
def _format(data):
|
|
198
|
+
if hasattr(callback, "formatter"):
|
|
199
|
+
formatter = callback.__getattribute__("formatter")
|
|
200
|
+
else:
|
|
201
|
+
formatter = default_table_formatter
|
|
202
|
+
if isinstance(formatter, dict):
|
|
203
|
+
return default_table_formatter(data, formatter)
|
|
204
|
+
else:
|
|
205
|
+
return formatter(data)
|
|
193
206
|
|
|
194
207
|
io_args["format"] = _format
|
|
195
208
|
ret = callback(*args, **kwargs)
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
import os.path
|
|
4
|
+
import subprocess
|
|
5
|
+
|
|
6
|
+
log = logging.getLogger(__name__)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def run_cmd(cmd, shell: bool = True):
|
|
10
|
+
cmd_text = " ".join(cmd)
|
|
11
|
+
log.info(f"Running command: {cmd_text}")
|
|
12
|
+
result = {}
|
|
13
|
+
result["command"] = cmd_text
|
|
14
|
+
# proc = subprocess.run(cmd, shell=shell, check=False, capture_output=True, timeout=60, encoding="utf-8")
|
|
15
|
+
proc = subprocess.run(cmd, shell=shell, check=False, timeout=60, **subprocess_args())
|
|
16
|
+
result["stdout"] = proc.stdout.decode("utf-8")
|
|
17
|
+
result["stderr"] = proc.stderr.decode("utf-8")
|
|
18
|
+
result["returnCode"] = proc.returncode
|
|
19
|
+
return result
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# Create a set of arguments which make a ``subprocess.Popen`` (and
|
|
23
|
+
# variants) call work with or without Pyinstaller, ``--noconsole`` or
|
|
24
|
+
# not, on Windows and Linux. Typical use::
|
|
25
|
+
#
|
|
26
|
+
# subprocess.call(['program_to_run', 'arg_1'], **subprocess_args())
|
|
27
|
+
#
|
|
28
|
+
# When calling ``check_output``::
|
|
29
|
+
#
|
|
30
|
+
# subprocess.check_output(['program_to_run', 'arg_1'],
|
|
31
|
+
# **subprocess_args(False))
|
|
32
|
+
def subprocess_args(include_stdout=True):
|
|
33
|
+
# The following is true only on Windows.
|
|
34
|
+
if hasattr(subprocess, "STARTUPINFO"):
|
|
35
|
+
# On Windows, subprocess calls will pop up a command window by default
|
|
36
|
+
# when run from Pyinstaller with the ``--noconsole`` option. Avoid this
|
|
37
|
+
# distraction.
|
|
38
|
+
si = subprocess.STARTUPINFO()
|
|
39
|
+
si.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
|
40
|
+
# Windows doesn't search the path by default. Pass it an environment so
|
|
41
|
+
# it will.
|
|
42
|
+
env = os.environ
|
|
43
|
+
else:
|
|
44
|
+
si = None
|
|
45
|
+
env = None
|
|
46
|
+
|
|
47
|
+
# ``subprocess.check_output`` doesn't allow specifying ``stdout``::
|
|
48
|
+
#
|
|
49
|
+
# Traceback (most recent call last):
|
|
50
|
+
# File "test_subprocess.py", line 58, in <module>
|
|
51
|
+
# **subprocess_args(stdout=None))
|
|
52
|
+
# File "C:\Python27\lib\subprocess.py", line 567, in check_output
|
|
53
|
+
# raise ValueError('stdout argument not allowed, it will be overridden.')
|
|
54
|
+
# ValueError: stdout argument not allowed, it will be overridden.
|
|
55
|
+
#
|
|
56
|
+
# So, add it only if it's needed.
|
|
57
|
+
if include_stdout:
|
|
58
|
+
ret = {"stdout": subprocess.PIPE}
|
|
59
|
+
else:
|
|
60
|
+
ret = {}
|
|
61
|
+
|
|
62
|
+
# On Windows, running this from the binary produced by Pyinstaller
|
|
63
|
+
# with the ``--noconsole`` option requires redirecting everything
|
|
64
|
+
# (stdin, stdout, stderr) to avoid an OSError exception
|
|
65
|
+
# "[Error 6] the handle is invalid."
|
|
66
|
+
ret.update(
|
|
67
|
+
{
|
|
68
|
+
"stdin": subprocess.PIPE,
|
|
69
|
+
"stderr": subprocess.PIPE,
|
|
70
|
+
"startupinfo": si,
|
|
71
|
+
"env": env,
|
|
72
|
+
}
|
|
73
|
+
)
|
|
74
|
+
return ret
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
# A simple test routine. Compare this output when run by Python, Pyinstaller,
|
|
78
|
+
# and Pyinstaller ``--noconsole``.
|
|
79
|
+
if __name__ == "__main__":
|
|
80
|
+
# Save the output from invoking Python to a text file. This is the only
|
|
81
|
+
# option when running with ``--noconsole``.
|
|
82
|
+
with open("out.txt", "w") as f:
|
|
83
|
+
try:
|
|
84
|
+
txt = subprocess.check_output(["python", "--help"], **subprocess_args(False))
|
|
85
|
+
f.write(txt)
|
|
86
|
+
except OSError as e:
|
|
87
|
+
f.write("Failed: " + str(e))
|
hcs_core/ctxp/config.py
CHANGED