ocmo-cli 0.8.19__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.
- LICENSE +201 -0
- NOTICE +16 -0
- ocmo_cli/__init__.py +3 -0
- ocmo_cli/__main__.py +4 -0
- ocmo_cli/_address.py +182 -0
- ocmo_cli/_audit_item.py +33 -0
- ocmo_cli/_auth_resolve.py +66 -0
- ocmo_cli/_body_payload.py +203 -0
- ocmo_cli/_click_groups.py +227 -0
- ocmo_cli/_client.py +351 -0
- ocmo_cli/_client_commands.py +35 -0
- ocmo_cli/_command_registry.py +29 -0
- ocmo_cli/_commands_map.py +65 -0
- ocmo_cli/_config.py +183 -0
- ocmo_cli/_config_content.py +43 -0
- ocmo_cli/_delete_item_output.py +129 -0
- ocmo_cli/_diff_input.py +144 -0
- ocmo_cli/_dry_run.py +292 -0
- ocmo_cli/_errors.py +151 -0
- ocmo_cli/_exit.py +13 -0
- ocmo_cli/_export_metadata.py +47 -0
- ocmo_cli/_generated_registry.py +45 -0
- ocmo_cli/_get_cast_output.py +52 -0
- ocmo_cli/_get_item_list.py +23 -0
- ocmo_cli/_globalpermission_output.py +98 -0
- ocmo_cli/_import_metadata.py +140 -0
- ocmo_cli/_import_plan.py +221 -0
- ocmo_cli/_item_output.py +223 -0
- ocmo_cli/_lazy_mixin.py +29 -0
- ocmo_cli/_ls_wide.py +286 -0
- ocmo_cli/_mutating.py +39 -0
- ocmo_cli/_notice.py +17 -0
- ocmo_cli/_options.py +77 -0
- ocmo_cli/_output.py +424 -0
- ocmo_cli/_output_manifest.py +332 -0
- ocmo_cli/_resolve_draft.py +50 -0
- ocmo_cli/_resolve_options.py +143 -0
- ocmo_cli/_resolve_output.py +228 -0
- ocmo_cli/_resolve_parameters_output.py +72 -0
- ocmo_cli/_resolve_series.py +89 -0
- ocmo_cli/_resolve_series_chart.py +409 -0
- ocmo_cli/_resolve_write.py +185 -0
- ocmo_cli/_resolver_output.py +34 -0
- ocmo_cli/_resource_aliases.py +22 -0
- ocmo_cli/_sdk_dispatch.py +310 -0
- ocmo_cli/_typing.py +27 -0
- ocmo_cli/_version_output.py +39 -0
- ocmo_cli/_whoami_output.py +43 -0
- ocmo_cli/commands/__init__.py +1 -0
- ocmo_cli/commands/api.py +125 -0
- ocmo_cli/commands/api_health.py +12 -0
- ocmo_cli/commands/apply.py +153 -0
- ocmo_cli/commands/auth.py +786 -0
- ocmo_cli/commands/can_i.py +51 -0
- ocmo_cli/commands/completion.py +59 -0
- ocmo_cli/commands/config_cmd.py +235 -0
- ocmo_cli/commands/describe.py +109 -0
- ocmo_cli/commands/diff.py +86 -0
- ocmo_cli/commands/edit.py +200 -0
- ocmo_cli/commands/export.py +242 -0
- ocmo_cli/commands/generated.py +907 -0
- ocmo_cli/commands/gp_move.py +108 -0
- ocmo_cli/commands/import_.py +595 -0
- ocmo_cli/commands/item_relocate.py +187 -0
- ocmo_cli/commands/ls.py +481 -0
- ocmo_cli/commands/resolve.py +658 -0
- ocmo_cli/commands/resolve_draft.py +102 -0
- ocmo_cli/commands/resolve_parameters.py +65 -0
- ocmo_cli/commands/resolve_series.py +109 -0
- ocmo_cli/commands/schema.py +112 -0
- ocmo_cli/commands/timeline.py +135 -0
- ocmo_cli/commands/version_cmd.py +102 -0
- ocmo_cli/commands/whoami.py +12 -0
- ocmo_cli/main.py +241 -0
- ocmo_cli/output_formats.yaml +295 -0
- ocmo_cli-0.8.19.dist-info/METADATA +169 -0
- ocmo_cli-0.8.19.dist-info/RECORD +81 -0
- ocmo_cli-0.8.19.dist-info/WHEEL +4 -0
- ocmo_cli-0.8.19.dist-info/entry_points.txt +2 -0
- ocmo_cli-0.8.19.dist-info/licenses/LICENSE +201 -0
- ocmo_cli-0.8.19.dist-info/licenses/NOTICE +16 -0
ocmo_cli/main.py
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"""Root CLI entry point.
|
|
2
|
+
|
|
3
|
+
All heavy imports (SDK, yaml, httpx) are deferred into command functions so
|
|
4
|
+
that `ocmo --help` returns within the 200 ms budget on a warm cache.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
import click
|
|
13
|
+
|
|
14
|
+
from ._click_groups import LazyGroup
|
|
15
|
+
from ._client import OcmoCtx
|
|
16
|
+
from ._command_registry import HAND_WRITTEN_SHORT_HELP
|
|
17
|
+
from ._lazy_mixin import ClickLazyMixin
|
|
18
|
+
from ._output import _OUTPUT_FORMATS
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _validate_output_global(ctx: click.Context, param: click.Parameter, value: str | None) -> str | None:
|
|
22
|
+
if value is None:
|
|
23
|
+
return value
|
|
24
|
+
if value in _OUTPUT_FORMATS or value.startswith("jsonpath="):
|
|
25
|
+
return value
|
|
26
|
+
valid = ", ".join(_OUTPUT_FORMATS)
|
|
27
|
+
raise click.BadParameter(
|
|
28
|
+
f"{value!r} is not a valid format. Valid values: {valid}, or jsonpath=<expr>.",
|
|
29
|
+
ctx=ctx,
|
|
30
|
+
param=param,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# ---------------------------------------------------------------------------
|
|
35
|
+
# Global flags — attached to every invocation via callback
|
|
36
|
+
# ---------------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
CONTEXT_SETTINGS = dict(help_option_names=["--help", "-h"], max_content_width=120)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@click.group(context_settings=CONTEXT_SETTINGS, invoke_without_command=True)
|
|
42
|
+
@click.version_option(package_name="ocmo-cli", prog_name="ocmo")
|
|
43
|
+
@click.option(
|
|
44
|
+
"-n",
|
|
45
|
+
"--namespace",
|
|
46
|
+
envvar="OCMO_NAMESPACE",
|
|
47
|
+
default=None,
|
|
48
|
+
metavar="NS",
|
|
49
|
+
help="Namespace (overrides config and OCMO_NAMESPACE).",
|
|
50
|
+
)
|
|
51
|
+
@click.option(
|
|
52
|
+
"-o",
|
|
53
|
+
"--output",
|
|
54
|
+
"output_fmt",
|
|
55
|
+
default=None,
|
|
56
|
+
metavar="FORMAT",
|
|
57
|
+
callback=lambda ctx, p, v: _validate_output_global(ctx, p, v),
|
|
58
|
+
is_eager=False,
|
|
59
|
+
help=(
|
|
60
|
+
"Output format: table, json, yaml, name, path, raw, or jsonpath=<expr>. "
|
|
61
|
+
"Default: table on TTY, yaml otherwise."
|
|
62
|
+
),
|
|
63
|
+
)
|
|
64
|
+
@click.option(
|
|
65
|
+
"--dry-run", is_flag=True, default=False, help="Print what would be done without contacting mutating endpoints."
|
|
66
|
+
)
|
|
67
|
+
@click.option("-v", "--verbose", is_flag=True, default=False, help="Raise log level.")
|
|
68
|
+
@click.option("-q", "--quiet", is_flag=True, default=False, help="Suppress non-error stderr output.")
|
|
69
|
+
@click.option("--yes", "-y", is_flag=True, default=False, help="Skip confirmation prompts.")
|
|
70
|
+
@click.option("--no-color", is_flag=True, default=False, envvar="OCMO_NO_COLOR", help="Disable ANSI colour.")
|
|
71
|
+
@click.option(
|
|
72
|
+
"--skip-version-check",
|
|
73
|
+
is_flag=True,
|
|
74
|
+
default=False,
|
|
75
|
+
envvar="OCMO_SKIP_VERSION_CHECK",
|
|
76
|
+
help="Skip CLI/SDK/server version compatibility check.",
|
|
77
|
+
)
|
|
78
|
+
@click.pass_context
|
|
79
|
+
def cli(
|
|
80
|
+
ctx: click.Context,
|
|
81
|
+
namespace: str | None,
|
|
82
|
+
output_fmt: str | None,
|
|
83
|
+
dry_run: bool,
|
|
84
|
+
verbose: bool,
|
|
85
|
+
quiet: bool,
|
|
86
|
+
yes: bool,
|
|
87
|
+
no_color: bool,
|
|
88
|
+
skip_version_check: bool,
|
|
89
|
+
) -> None:
|
|
90
|
+
"""ocmo — configuration management CLI.
|
|
91
|
+
|
|
92
|
+
\b
|
|
93
|
+
Examples:
|
|
94
|
+
ocmo config set server https://ocmo.example.com
|
|
95
|
+
ocmo auth login
|
|
96
|
+
ocmo -n prod ls
|
|
97
|
+
ocmo -n prod get item app/web
|
|
98
|
+
ocmo -n prod resolve app/web --cast json
|
|
99
|
+
ocmo -n prod apply -f config.yaml app/web/main
|
|
100
|
+
"""
|
|
101
|
+
if no_color:
|
|
102
|
+
os.environ["OCMO_NO_COLOR"] = "1"
|
|
103
|
+
|
|
104
|
+
ctx.ensure_object(dict)
|
|
105
|
+
ctx.obj = OcmoCtx(
|
|
106
|
+
namespace=namespace,
|
|
107
|
+
output=output_fmt,
|
|
108
|
+
dry_run=dry_run,
|
|
109
|
+
verbose=verbose,
|
|
110
|
+
quiet=quiet,
|
|
111
|
+
yes=yes,
|
|
112
|
+
skip_version_check=skip_version_check,
|
|
113
|
+
no_color=no_color,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
if ctx.invoked_subcommand is None:
|
|
117
|
+
click.echo(ctx.get_help())
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
# ---------------------------------------------------------------------------
|
|
121
|
+
# Hand-written command modules
|
|
122
|
+
# ---------------------------------------------------------------------------
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _lazy_cmd(
|
|
126
|
+
name: str,
|
|
127
|
+
module: str,
|
|
128
|
+
attr: str,
|
|
129
|
+
short_help: str,
|
|
130
|
+
*,
|
|
131
|
+
hidden: bool = False,
|
|
132
|
+
) -> click.Command:
|
|
133
|
+
"""Return a placeholder that imports and delegates on first invocation."""
|
|
134
|
+
|
|
135
|
+
class LazyCommand(ClickLazyMixin, click.Command):
|
|
136
|
+
"""A stub that replaces itself with the real command on first use."""
|
|
137
|
+
|
|
138
|
+
def __init__(self) -> None:
|
|
139
|
+
super().__init__(name)
|
|
140
|
+
self.short_help = short_help
|
|
141
|
+
self.hidden = hidden
|
|
142
|
+
|
|
143
|
+
def _lazy_target(self) -> click.Command:
|
|
144
|
+
return self._real()
|
|
145
|
+
|
|
146
|
+
def _real(self) -> click.Command:
|
|
147
|
+
import importlib
|
|
148
|
+
|
|
149
|
+
mod = importlib.import_module(f".commands.{module}", package="ocmo_cli")
|
|
150
|
+
cmd = getattr(mod, attr)
|
|
151
|
+
assert isinstance(cmd, click.Command)
|
|
152
|
+
return cmd
|
|
153
|
+
|
|
154
|
+
def get_params(self, ctx: click.Context) -> list[click.Parameter]:
|
|
155
|
+
return self._real().get_params(ctx)
|
|
156
|
+
|
|
157
|
+
def get_help(self, ctx: click.Context) -> str:
|
|
158
|
+
return self._real().get_help(ctx)
|
|
159
|
+
|
|
160
|
+
def get_short_help_str(self, limit: int = 150) -> str:
|
|
161
|
+
text = self.short_help or ""
|
|
162
|
+
if limit and len(text) > limit:
|
|
163
|
+
return text[: limit - 3] + "..."
|
|
164
|
+
return text
|
|
165
|
+
|
|
166
|
+
def main( # type: ignore[override]
|
|
167
|
+
self,
|
|
168
|
+
args: list[str] | None = None,
|
|
169
|
+
prog_name: str | None = None,
|
|
170
|
+
complete_var: str | None = None,
|
|
171
|
+
standalone_mode: bool = True,
|
|
172
|
+
**extra: Any,
|
|
173
|
+
) -> Any:
|
|
174
|
+
return self._real().main(
|
|
175
|
+
args,
|
|
176
|
+
prog_name=prog_name,
|
|
177
|
+
complete_var=complete_var,
|
|
178
|
+
standalone_mode=standalone_mode,
|
|
179
|
+
**extra,
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
return LazyCommand()
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _register_hand_written() -> None:
|
|
186
|
+
# Register lazy stubs — actual imports happen only when the command runs.
|
|
187
|
+
for cli_name, (module, attr, short_help) in HAND_WRITTEN_SHORT_HELP.items():
|
|
188
|
+
cli.add_command(_lazy_cmd(cli_name, module, attr, short_help), name=cli_name)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
# ---------------------------------------------------------------------------
|
|
192
|
+
# Generated command groups (built from commands.yaml on first use)
|
|
193
|
+
# ---------------------------------------------------------------------------
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _build_generated_action(action: str) -> click.Group:
|
|
197
|
+
from .commands.generated import build_action_group
|
|
198
|
+
|
|
199
|
+
return build_action_group(action)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _register_generated() -> None:
|
|
203
|
+
from ._generated_registry import action_help, generated_action_names
|
|
204
|
+
|
|
205
|
+
# Hand-written commands take precedence — skip generated groups that share a name
|
|
206
|
+
existing_names = set(cli.commands or {})
|
|
207
|
+
for action in generated_action_names():
|
|
208
|
+
if action not in existing_names:
|
|
209
|
+
|
|
210
|
+
def _build(action_name: str = action) -> click.Group:
|
|
211
|
+
return _build_generated_action(action_name)
|
|
212
|
+
|
|
213
|
+
cli.add_command(
|
|
214
|
+
LazyGroup(
|
|
215
|
+
action,
|
|
216
|
+
help=action_help(action),
|
|
217
|
+
build_fn=_build,
|
|
218
|
+
),
|
|
219
|
+
name=action,
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
cli.add_command(
|
|
223
|
+
_lazy_cmd(
|
|
224
|
+
"api",
|
|
225
|
+
"api",
|
|
226
|
+
"api_cmd",
|
|
227
|
+
"Invoke any API operation directly by operation_id.",
|
|
228
|
+
hidden=True,
|
|
229
|
+
),
|
|
230
|
+
name="api",
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
from .commands.gp_move import register_gp_move_command
|
|
234
|
+
from .commands.item_relocate import register_item_relocate_commands
|
|
235
|
+
|
|
236
|
+
register_item_relocate_commands(cli)
|
|
237
|
+
register_gp_move_command(cli)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
_register_hand_written()
|
|
241
|
+
_register_generated()
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
# Output format manifest — source of truth for OCMO CLI -o/--output behavior.
|
|
2
|
+
#
|
|
3
|
+
# Command keys use space-separated paths: "get namespace", "ls", "resolve".
|
|
4
|
+
# Generated commands use "{action} {resource}" (canonical resource name).
|
|
5
|
+
#
|
|
6
|
+
# Profile ``supported_formats`` and defaults merge into each command entry.
|
|
7
|
+
# Per-command ``table`` / ``wide`` / ``raw`` blocks configure column layout.
|
|
8
|
+
|
|
9
|
+
profiles:
|
|
10
|
+
list_wide:
|
|
11
|
+
supported_formats: [table, wide, json, yaml, name]
|
|
12
|
+
default_tty: table
|
|
13
|
+
default_non_tty: yaml
|
|
14
|
+
name_field: name
|
|
15
|
+
|
|
16
|
+
list:
|
|
17
|
+
supported_formats: [table, json, yaml, name]
|
|
18
|
+
default_tty: table
|
|
19
|
+
default_non_tty: yaml
|
|
20
|
+
name_field: name
|
|
21
|
+
|
|
22
|
+
list_versions:
|
|
23
|
+
supported_formats: [table, wide, json, yaml, name]
|
|
24
|
+
default_tty: table
|
|
25
|
+
default_non_tty: yaml
|
|
26
|
+
name_field: version
|
|
27
|
+
|
|
28
|
+
document:
|
|
29
|
+
supported_formats: [raw, json, yaml, name]
|
|
30
|
+
default: raw
|
|
31
|
+
name_field: path
|
|
32
|
+
|
|
33
|
+
ls:
|
|
34
|
+
supported_formats: [table, wide, json, yaml, name, path]
|
|
35
|
+
default_tty: table
|
|
36
|
+
default_non_tty: yaml
|
|
37
|
+
name_field: path
|
|
38
|
+
|
|
39
|
+
resolve:
|
|
40
|
+
supported_formats: [raw, json, yaml, name]
|
|
41
|
+
default: raw
|
|
42
|
+
name_field: name
|
|
43
|
+
|
|
44
|
+
can_i:
|
|
45
|
+
supported_formats: [table, json, yaml]
|
|
46
|
+
default_tty: table
|
|
47
|
+
default_non_tty: yaml
|
|
48
|
+
|
|
49
|
+
schema:
|
|
50
|
+
supported_formats: [table, json, yaml]
|
|
51
|
+
default_tty: yaml
|
|
52
|
+
default_non_tty: yaml
|
|
53
|
+
|
|
54
|
+
commands:
|
|
55
|
+
# ── Hand-written ──────────────────────────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
ls:
|
|
58
|
+
profile: ls
|
|
59
|
+
table:
|
|
60
|
+
fields: [type, path]
|
|
61
|
+
wide:
|
|
62
|
+
fields: [permissions, versions, author, created, updated, type, path]
|
|
63
|
+
|
|
64
|
+
resolve:
|
|
65
|
+
profile: resolve
|
|
66
|
+
raw:
|
|
67
|
+
mode: metadata_and_data
|
|
68
|
+
hide_null_values: true
|
|
69
|
+
|
|
70
|
+
can-i:
|
|
71
|
+
profile: can_i
|
|
72
|
+
|
|
73
|
+
whoami:
|
|
74
|
+
profile: can_i
|
|
75
|
+
table:
|
|
76
|
+
fields: "*"
|
|
77
|
+
|
|
78
|
+
api-health:
|
|
79
|
+
profile: list
|
|
80
|
+
name_field: status
|
|
81
|
+
|
|
82
|
+
schema:
|
|
83
|
+
supported_formats: [json, yaml]
|
|
84
|
+
default_tty: yaml
|
|
85
|
+
default_non_tty: yaml
|
|
86
|
+
|
|
87
|
+
auth status:
|
|
88
|
+
profile: schema
|
|
89
|
+
|
|
90
|
+
edit config:
|
|
91
|
+
profile: document
|
|
92
|
+
|
|
93
|
+
edit template:
|
|
94
|
+
profile: document
|
|
95
|
+
|
|
96
|
+
edit secret:
|
|
97
|
+
profile: document
|
|
98
|
+
|
|
99
|
+
edit resolver:
|
|
100
|
+
profile: document
|
|
101
|
+
|
|
102
|
+
# ── Generated: get ────────────────────────────────────────────────────────
|
|
103
|
+
|
|
104
|
+
get namespace:
|
|
105
|
+
profile: list_wide
|
|
106
|
+
table:
|
|
107
|
+
fields: [name, description]
|
|
108
|
+
wide:
|
|
109
|
+
fields: [name, description, permissions_tag, webhooks_tag, git_sync_tag, created_at, updated_at]
|
|
110
|
+
|
|
111
|
+
get globalpermission:
|
|
112
|
+
profile: list_wide
|
|
113
|
+
name_field: id
|
|
114
|
+
table:
|
|
115
|
+
fields: [id, namespace, position]
|
|
116
|
+
wide:
|
|
117
|
+
fields: [id, namespace, position, read, write, delete, audit]
|
|
118
|
+
|
|
119
|
+
get lock:
|
|
120
|
+
profile: list_wide
|
|
121
|
+
name_field: path
|
|
122
|
+
table:
|
|
123
|
+
fields: [path, reason, locked_by]
|
|
124
|
+
wide:
|
|
125
|
+
fields: "*"
|
|
126
|
+
|
|
127
|
+
get audit:
|
|
128
|
+
profile: list_wide
|
|
129
|
+
name_field: id
|
|
130
|
+
table:
|
|
131
|
+
fields: [occurred_at, id, operation, object_id]
|
|
132
|
+
wide:
|
|
133
|
+
fields: [occurred_at, id, operation, object_id, auth_email, object_type, object_version, error]
|
|
134
|
+
|
|
135
|
+
get cast:
|
|
136
|
+
profile: schema
|
|
137
|
+
|
|
138
|
+
get cast list:
|
|
139
|
+
profile: list
|
|
140
|
+
name_field: format
|
|
141
|
+
table:
|
|
142
|
+
fields: [format]
|
|
143
|
+
|
|
144
|
+
get version:
|
|
145
|
+
profile: list_versions
|
|
146
|
+
table:
|
|
147
|
+
fields: [version, tags, updated_at]
|
|
148
|
+
wide:
|
|
149
|
+
fields: "*"
|
|
150
|
+
|
|
151
|
+
get item:
|
|
152
|
+
profile: document
|
|
153
|
+
|
|
154
|
+
get item list:
|
|
155
|
+
profile: list
|
|
156
|
+
name_field: path
|
|
157
|
+
table:
|
|
158
|
+
fields: [node_type, path, name]
|
|
159
|
+
|
|
160
|
+
# ── Generated: create ─────────────────────────────────────────────────────
|
|
161
|
+
|
|
162
|
+
create namespace:
|
|
163
|
+
profile: list
|
|
164
|
+
|
|
165
|
+
create globalpermission:
|
|
166
|
+
profile: document
|
|
167
|
+
name_field: id
|
|
168
|
+
|
|
169
|
+
create config:
|
|
170
|
+
profile: document
|
|
171
|
+
|
|
172
|
+
create template:
|
|
173
|
+
profile: document
|
|
174
|
+
|
|
175
|
+
create secret:
|
|
176
|
+
profile: document
|
|
177
|
+
|
|
178
|
+
create resolver:
|
|
179
|
+
profile: document
|
|
180
|
+
|
|
181
|
+
create lock:
|
|
182
|
+
profile: list
|
|
183
|
+
name_field: path
|
|
184
|
+
|
|
185
|
+
# ── Generated: update / delete / move / copy / tag ─────────────────────────
|
|
186
|
+
|
|
187
|
+
update namespace:
|
|
188
|
+
profile: list
|
|
189
|
+
|
|
190
|
+
update globalpermission:
|
|
191
|
+
profile: document
|
|
192
|
+
name_field: id
|
|
193
|
+
|
|
194
|
+
update config:
|
|
195
|
+
profile: document
|
|
196
|
+
|
|
197
|
+
update template:
|
|
198
|
+
profile: document
|
|
199
|
+
|
|
200
|
+
update secret:
|
|
201
|
+
profile: document
|
|
202
|
+
|
|
203
|
+
update resolver:
|
|
204
|
+
profile: document
|
|
205
|
+
|
|
206
|
+
update lock:
|
|
207
|
+
profile: list
|
|
208
|
+
name_field: path
|
|
209
|
+
|
|
210
|
+
delete namespace:
|
|
211
|
+
profile: list
|
|
212
|
+
name_field: namespace
|
|
213
|
+
|
|
214
|
+
delete item:
|
|
215
|
+
profile: list
|
|
216
|
+
name_field: path
|
|
217
|
+
supported_formats: [table, json, yaml, name, path]
|
|
218
|
+
table:
|
|
219
|
+
fields: [type, version, path]
|
|
220
|
+
|
|
221
|
+
delete globalpermission:
|
|
222
|
+
profile: list
|
|
223
|
+
name_field: id
|
|
224
|
+
|
|
225
|
+
delete lock:
|
|
226
|
+
profile: list
|
|
227
|
+
name_field: path
|
|
228
|
+
|
|
229
|
+
tag item:
|
|
230
|
+
profile: list_versions
|
|
231
|
+
table:
|
|
232
|
+
fields: [version, tags, updated_at]
|
|
233
|
+
wide:
|
|
234
|
+
fields: "*"
|
|
235
|
+
|
|
236
|
+
untag item:
|
|
237
|
+
profile: list_versions
|
|
238
|
+
table:
|
|
239
|
+
fields: [version, tags, updated_at]
|
|
240
|
+
wide:
|
|
241
|
+
fields: "*"
|
|
242
|
+
|
|
243
|
+
propagate config:
|
|
244
|
+
profile: list
|
|
245
|
+
name_field: path
|
|
246
|
+
|
|
247
|
+
search tree:
|
|
248
|
+
profile: list
|
|
249
|
+
name_field: path
|
|
250
|
+
|
|
251
|
+
resolve-series audit:
|
|
252
|
+
supported_formats: [chart, json, yaml]
|
|
253
|
+
default_tty: chart
|
|
254
|
+
default_non_tty: yaml
|
|
255
|
+
|
|
256
|
+
resolve parameters:
|
|
257
|
+
supported_formats: [table, json, yaml]
|
|
258
|
+
default_tty: table
|
|
259
|
+
default_non_tty: yaml
|
|
260
|
+
table:
|
|
261
|
+
fields: [name, type, value, resolved_value, description, transformers]
|
|
262
|
+
|
|
263
|
+
resolve draft:
|
|
264
|
+
profile: resolve
|
|
265
|
+
|
|
266
|
+
# Fallback for generated commands not listed explicitly.
|
|
267
|
+
_generated_defaults:
|
|
268
|
+
get:
|
|
269
|
+
profile: list_wide
|
|
270
|
+
create:
|
|
271
|
+
profile: list
|
|
272
|
+
update:
|
|
273
|
+
profile: list
|
|
274
|
+
delete:
|
|
275
|
+
profile: list
|
|
276
|
+
move:
|
|
277
|
+
profile: list
|
|
278
|
+
copy:
|
|
279
|
+
profile: list
|
|
280
|
+
tag:
|
|
281
|
+
profile: list
|
|
282
|
+
untag:
|
|
283
|
+
profile: list
|
|
284
|
+
rotate:
|
|
285
|
+
profile: list
|
|
286
|
+
propagate:
|
|
287
|
+
profile: list
|
|
288
|
+
search:
|
|
289
|
+
profile: list_wide
|
|
290
|
+
timeline:
|
|
291
|
+
profile: list_wide
|
|
292
|
+
resolve-series:
|
|
293
|
+
profile: list_wide
|
|
294
|
+
resolve:
|
|
295
|
+
profile: resolve
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: ocmo-cli
|
|
3
|
+
Version: 0.8.19
|
|
4
|
+
Summary: Command-line interface for the OCMO configuration service
|
|
5
|
+
Project-URL: Homepage, https://github.com/ocmo-oss/ocmo
|
|
6
|
+
Project-URL: Repository, https://github.com/ocmo-oss/ocmo
|
|
7
|
+
Project-URL: Documentation, https://github.com/ocmo-oss/ocmo/tree/main/cli
|
|
8
|
+
Author: OCMO OSS Contributors
|
|
9
|
+
License: Apache-2.0
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
License-File: NOTICE
|
|
12
|
+
Keywords: cli,configuration,devops,yaml
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Intended Audience :: System Administrators
|
|
17
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: System :: Systems Administration
|
|
23
|
+
Requires-Python: >=3.11
|
|
24
|
+
Requires-Dist: click<9,>=8.1
|
|
25
|
+
Requires-Dist: ocmo-sdk<0.9,>=0.8.19
|
|
26
|
+
Requires-Dist: pyyaml>=6.0
|
|
27
|
+
Requires-Dist: rich>=13.0
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
# ocmo-cli
|
|
31
|
+
|
|
32
|
+
Command-line interface for the OCMO configuration service. A thin shell over
|
|
33
|
+
[`ocmo-sdk`](https://pypi.org/project/ocmo-sdk/) with a `kubectl`-shaped verb/noun grammar. The CLI
|
|
34
|
+
contains no API protocol logic of its own.
|
|
35
|
+
|
|
36
|
+
**Distribution:** PyPI name `ocmo-cli` · **Python:** 3.11+ · **License:** Apache-2.0
|
|
37
|
+
|
|
38
|
+
**Pre-1.0.0:** Pin `ocmo-cli` and `ocmo-sdk` to the same release as your OCMO API server.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Installation
|
|
43
|
+
|
|
44
|
+
Published package:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
uv tool install ocmo-cli
|
|
48
|
+
# or
|
|
49
|
+
pipx install ocmo-cli
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Monorepo development (from repository root):
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
uv sync --all-packages
|
|
56
|
+
uv run ocmo --help
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Quick start
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
# Point at your server (saved to ~/.config/ocmo/config.yaml)
|
|
65
|
+
ocmo config set server https://ocmo.example.com
|
|
66
|
+
ocmo config set namespace prod
|
|
67
|
+
|
|
68
|
+
# Authenticate
|
|
69
|
+
ocmo auth login
|
|
70
|
+
|
|
71
|
+
# Use it
|
|
72
|
+
ocmo -n prod get config app/web
|
|
73
|
+
ocmo -n prod resolve app/web --cast json
|
|
74
|
+
ocmo -n prod ls tree app/
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Configuration
|
|
80
|
+
|
|
81
|
+
Precedence (same model as `kubectl`):
|
|
82
|
+
|
|
83
|
+
```text
|
|
84
|
+
command flag > OCMO_* env var > current context in ~/.config/ocmo/config.yaml > built-in default
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Manage contexts:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
ocmo config get-contexts
|
|
91
|
+
ocmo config use-context prod
|
|
92
|
+
ocmo config set server https://ocmo.example.com
|
|
93
|
+
ocmo config view
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Output formats
|
|
99
|
+
|
|
100
|
+
`-o, --output` accepts: `table` (default on TTY), `wide`, `json`, `yaml`, `name`, `raw`,
|
|
101
|
+
`jsonpath=<expr>`.
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Exit codes
|
|
106
|
+
|
|
107
|
+
| Code | Meaning |
|
|
108
|
+
|------|---------|
|
|
109
|
+
| 0 | Success |
|
|
110
|
+
| 1 | Generic runtime failure |
|
|
111
|
+
| 2 | Usage error |
|
|
112
|
+
| 3 | Not found (404) |
|
|
113
|
+
| 4 | Auth / permission failure (401, 403) |
|
|
114
|
+
| 5 | Conflict (409) |
|
|
115
|
+
| 6 | Path locked (423) |
|
|
116
|
+
| 7 | Validation failure (422, 413) |
|
|
117
|
+
| 8 | Hook execution failure |
|
|
118
|
+
| 9 | Import/export verification failure |
|
|
119
|
+
| 130 | Interrupted |
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## Development
|
|
124
|
+
|
|
125
|
+
From repository root (workspace includes CLI and SDK):
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
uv sync --all-packages
|
|
129
|
+
uv run pytest cli/tests -q
|
|
130
|
+
uv run ruff check cli/
|
|
131
|
+
uv run mypy cli/ocmo_cli
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Regenerate the command tree from the SDK operation map:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
cd cli
|
|
138
|
+
uv run python scripts/generate_commands.py --write
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Integration smoke tests (live API):
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
OCMO_RUN_INTEGRATION=1 uv run pytest cli/tests/test_smoke_integration.py -v
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Architecture notes
|
|
150
|
+
|
|
151
|
+
### Command surface
|
|
152
|
+
|
|
153
|
+
Generated operations are registered at import time from committed `commands.yaml` (produced from
|
|
154
|
+
`sdk/operations.yaml`). CI fails if the file drifts from the spec. Hand-written commands live in
|
|
155
|
+
`ocmo_cli/commands/`.
|
|
156
|
+
|
|
157
|
+
### Framework
|
|
158
|
+
|
|
159
|
+
Built on **click** (not typer) for fast cold start, dynamic command registration, and programmatic
|
|
160
|
+
`ocmo api` escape hatch.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Related components
|
|
165
|
+
|
|
166
|
+
- SDK: [../sdk/README.md](../sdk/README.md)
|
|
167
|
+
- API: [../api/README.md](../api/README.md)
|
|
168
|
+
- Integration guides: [../docs/how-to/README.md](../docs/how-to/README.md) · [../docs/reference/cli.md](../docs/reference/cli.md)
|
|
169
|
+
- Monorepo overview: [../README.md](../README.md)
|