python-jcli 1.0.0__tar.gz → 1.0.1__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.
- {python_jcli-1.0.0 → python_jcli-1.0.1}/PKG-INFO +10 -9
- {python_jcli-1.0.0 → python_jcli-1.0.1}/README.md +9 -8
- python_jcli-1.0.1/jcli/__init__.py +1 -0
- {python_jcli-1.0.0 → python_jcli-1.0.1}/jcli/cli.py +30 -10
- python_jcli-1.0.1/jcli/cli_helpers.py +21 -0
- python_jcli-1.0.1/jcli/skills/jcli/build/SKILL.md +123 -0
- python_jcli-1.0.1/jcli/specs/_auth.yaml +15 -0
- python_jcli-1.0.0/jcli/plugins/skills.py → python_jcli-1.0.1/jcli/specs/plugins/jcli_commands.py +64 -46
- python_jcli-1.0.1/jcli/specs/plugins/jenkins_auth.py +75 -0
- python_jcli-1.0.1/jcli/specs/plugins/jenkins_methods.py +332 -0
- python_jcli-1.0.1/jcli/specs/plugins/jenkins_pipeline_validate.py +51 -0
- python_jcli-1.0.1/jcli/specs/plugins/jenkins_plugin.py +77 -0
- python_jcli-1.0.1/jcli/specs/plugins/jenkins_system.py +137 -0
- python_jcli-1.0.1/jcli/specs/resources/build.yaml +160 -0
- python_jcli-1.0.1/jcli/specs/resources/credential.yaml +97 -0
- python_jcli-1.0.1/jcli/specs/resources/job.yaml +184 -0
- python_jcli-1.0.1/jcli/specs/resources/node.yaml +69 -0
- python_jcli-1.0.1/jcli/specs/resources/pipeline.yaml +62 -0
- python_jcli-1.0.1/jcli/specs/resources/plugin.yaml +65 -0
- python_jcli-1.0.1/jcli/specs/resources/system.yaml +72 -0
- python_jcli-1.0.1/jcli/specs/resources/view.yaml +75 -0
- {python_jcli-1.0.0 → python_jcli-1.0.1}/pyproject.toml +1 -1
- {python_jcli-1.0.0 → python_jcli-1.0.1}/python_jcli.egg-info/PKG-INFO +10 -9
- {python_jcli-1.0.0 → python_jcli-1.0.1}/python_jcli.egg-info/SOURCES.txt +18 -29
- python_jcli-1.0.1/tests/test_build_log.py +197 -0
- {python_jcli-1.0.0 → python_jcli-1.0.1}/tests/test_cli.py +1 -1
- {python_jcli-1.0.0 → python_jcli-1.0.1}/tests/test_client.py +2 -2
- {python_jcli-1.0.0 → python_jcli-1.0.1}/tests/test_skills.py +24 -15
- python_jcli-1.0.0/jcli/__init__.py +0 -1
- python_jcli-1.0.0/jcli/cli_helpers.py +0 -58
- python_jcli-1.0.0/jcli/plugins/__init__.py +0 -28
- python_jcli-1.0.0/jcli/plugins/build.py +0 -197
- python_jcli-1.0.0/jcli/plugins/config.py +0 -268
- python_jcli-1.0.0/jcli/plugins/credential.py +0 -210
- python_jcli-1.0.0/jcli/plugins/job.py +0 -425
- python_jcli-1.0.0/jcli/plugins/node.py +0 -134
- python_jcli-1.0.0/jcli/plugins/pipeline.py +0 -108
- python_jcli-1.0.0/jcli/plugins/plugin.py +0 -173
- python_jcli-1.0.0/jcli/plugins/system.py +0 -173
- python_jcli-1.0.0/jcli/plugins/view.py +0 -148
- python_jcli-1.0.0/jcli/sdk/build.py +0 -182
- python_jcli-1.0.0/jcli/sdk/credential.py +0 -143
- python_jcli-1.0.0/jcli/sdk/job.py +0 -191
- python_jcli-1.0.0/jcli/sdk/job_templates.py +0 -128
- python_jcli-1.0.0/jcli/sdk/node.py +0 -139
- python_jcli-1.0.0/jcli/sdk/pipeline.py +0 -121
- python_jcli-1.0.0/jcli/sdk/plugin.py +0 -155
- python_jcli-1.0.0/jcli/sdk/system.py +0 -202
- python_jcli-1.0.0/jcli/sdk/view.py +0 -94
- python_jcli-1.0.0/tests/test_build.py +0 -915
- python_jcli-1.0.0/tests/test_credential.py +0 -774
- python_jcli-1.0.0/tests/test_job.py +0 -1375
- python_jcli-1.0.0/tests/test_node.py +0 -420
- python_jcli-1.0.0/tests/test_pipeline.py +0 -544
- python_jcli-1.0.0/tests/test_plugin.py +0 -824
- python_jcli-1.0.0/tests/test_system.py +0 -877
- python_jcli-1.0.0/tests/test_view.py +0 -679
- {python_jcli-1.0.0 → python_jcli-1.0.1}/jcli/__main__.py +0 -0
- {python_jcli-1.0.0 → python_jcli-1.0.1}/jcli/sdk/__init__.py +0 -0
- {python_jcli-1.0.0 → python_jcli-1.0.1}/jcli/sdk/client.py +0 -0
- {python_jcli-1.0.0 → python_jcli-1.0.1}/jcli/sdk/config.py +0 -0
- {python_jcli-1.0.0 → python_jcli-1.0.1}/jcli/sdk/exceptions.py +0 -0
- {python_jcli-1.0.0 → python_jcli-1.0.1}/jcli/sdk/output/__init__.py +0 -0
- {python_jcli-1.0.0 → python_jcli-1.0.1}/jcli/sdk/output/formatter.py +0 -0
- {python_jcli-1.0.0 → python_jcli-1.0.1}/jcli/skills/jcli/SKILL.md +0 -0
- {python_jcli-1.0.0 → python_jcli-1.0.1}/jcli/skills/jcli/auth/SKILL.md +0 -0
- {python_jcli-1.0.0 → python_jcli-1.0.1}/jcli/skills/jcli/credential/SKILL.md +0 -0
- {python_jcli-1.0.0 → python_jcli-1.0.1}/jcli/skills/jcli/job/SKILL.md +0 -0
- {python_jcli-1.0.0 → python_jcli-1.0.1}/jcli/skills/jcli/node/SKILL.md +0 -0
- {python_jcli-1.0.0 → python_jcli-1.0.1}/jcli/skills/jcli/pipeline/SKILL.md +0 -0
- {python_jcli-1.0.0 → python_jcli-1.0.1}/jcli/skills/jcli/plugin/SKILL.md +0 -0
- {python_jcli-1.0.0 → python_jcli-1.0.1}/jcli/skills/jcli/system/SKILL.md +0 -0
- {python_jcli-1.0.0 → python_jcli-1.0.1}/jcli/skills/jcli/view/SKILL.md +0 -0
- {python_jcli-1.0.0 → python_jcli-1.0.1}/python_jcli.egg-info/dependency_links.txt +0 -0
- {python_jcli-1.0.0 → python_jcli-1.0.1}/python_jcli.egg-info/entry_points.txt +0 -0
- {python_jcli-1.0.0 → python_jcli-1.0.1}/python_jcli.egg-info/requires.txt +0 -0
- {python_jcli-1.0.0 → python_jcli-1.0.1}/python_jcli.egg-info/top_level.txt +0 -0
- {python_jcli-1.0.0 → python_jcli-1.0.1}/setup.cfg +0 -0
- {python_jcli-1.0.0 → python_jcli-1.0.1}/tests/test_cliyard_spike.py +0 -0
- {python_jcli-1.0.0 → python_jcli-1.0.1}/tests/test_config.py +0 -0
- {python_jcli-1.0.0 → python_jcli-1.0.1}/tests/test_exceptions.py +0 -0
- {python_jcli-1.0.0 → python_jcli-1.0.1}/tests/test_replay.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-jcli
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.1
|
|
4
4
|
Summary: Python Jenkins CLI tool
|
|
5
5
|
License: MIT
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -32,10 +32,10 @@ A command-line tool for managing Jenkins servers. jcli wraps the Jenkins REST AP
|
|
|
32
32
|
|
|
33
33
|
## Architecture
|
|
34
34
|
|
|
35
|
-
jcli 2.0 is driven by [cliyard](https://pypi.org/project/cliyard/) YAML specs instead of hand-written Click command groups. At startup, `jcli/cli.py` locates the spec directory (`JCLI_SPEC_DIR` env var > package-local `specs/`
|
|
35
|
+
jcli 2.0 is driven by [cliyard](https://pypi.org/project/cliyard/) YAML specs instead of hand-written Click command groups. At startup, `jcli/cli.py` locates the spec directory (`JCLI_SPEC_DIR` env var > package-local `jcli/specs/`) and calls `cliyard.runtime.create_cli()` to build the whole command tree. Commands stay declarative: adding or changing a command is an edit to a YAML file, not Python plumbing.
|
|
36
36
|
|
|
37
37
|
```
|
|
38
|
-
specs/
|
|
38
|
+
jcli/specs/
|
|
39
39
|
_auth.yaml Server auth chain (basic auth + crumb, as cliyard plugins)
|
|
40
40
|
resources/ One YAML spec per Jenkins domain
|
|
41
41
|
job.yaml job list/get/create/config/copy/enable/disable/delete
|
|
@@ -47,11 +47,12 @@ specs/
|
|
|
47
47
|
view.yaml view list/get/create/delete
|
|
48
48
|
system.yaml system info/load/quiet-down/restart/script
|
|
49
49
|
plugins/ Python method plugins for non-trivial logic
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
jcli_commands.py Skills + completion commands
|
|
51
|
+
jenkins_auth.py Basic + crumb authentication
|
|
52
|
+
jenkins_methods.py Build trigger/replay, node create
|
|
52
53
|
jenkins_pipeline_validate.py Jenkinsfile validation
|
|
53
|
-
jenkins_plugin.py
|
|
54
|
-
jenkins_system.py
|
|
54
|
+
jenkins_plugin.py Plugin install/uninstall/update-center
|
|
55
|
+
jenkins_system.py Groovy script, quiet-down, restart
|
|
55
56
|
```
|
|
56
57
|
|
|
57
58
|
A resource spec maps a command name to either a plain HTTP call (method, path, params, output mapping) or a `plugin:` reference. The auth chain in `_auth.yaml` injects `Authorization` and crumb headers into every request.
|
|
@@ -315,15 +316,15 @@ pytest --cov=jcli --cov-report=term-missing
|
|
|
315
316
|
```
|
|
316
317
|
jcli/
|
|
317
318
|
cli.py Entry point: builds the Click CLI from cliyard YAML specs
|
|
318
|
-
cli_helpers.py
|
|
319
|
+
cli_helpers.py Output-formatter helper used by the cliyard command plugins
|
|
319
320
|
__init__.py Version
|
|
320
|
-
plugins/ Legacy hand-written command modules (kept for SDK compatibility)
|
|
321
321
|
sdk/ Shared libraries
|
|
322
322
|
client.py Jenkins REST API client (HTTP, auth, crumb)
|
|
323
323
|
config.py Configuration management (YAML, env vars)
|
|
324
324
|
output/
|
|
325
325
|
formatter.py Table/JSON/YAML output formatting
|
|
326
326
|
exceptions.py Typed exceptions
|
|
327
|
+
skills/ Bundled skill documentation
|
|
327
328
|
specs/ cliyard YAML specs (command definitions)
|
|
328
329
|
_auth.yaml Auth chain: basic + crumb plugins
|
|
329
330
|
resources/ One YAML per Jenkins domain (job, build, node, ...)
|
|
@@ -15,10 +15,10 @@ A command-line tool for managing Jenkins servers. jcli wraps the Jenkins REST AP
|
|
|
15
15
|
|
|
16
16
|
## Architecture
|
|
17
17
|
|
|
18
|
-
jcli 2.0 is driven by [cliyard](https://pypi.org/project/cliyard/) YAML specs instead of hand-written Click command groups. At startup, `jcli/cli.py` locates the spec directory (`JCLI_SPEC_DIR` env var > package-local `specs/`
|
|
18
|
+
jcli 2.0 is driven by [cliyard](https://pypi.org/project/cliyard/) YAML specs instead of hand-written Click command groups. At startup, `jcli/cli.py` locates the spec directory (`JCLI_SPEC_DIR` env var > package-local `jcli/specs/`) and calls `cliyard.runtime.create_cli()` to build the whole command tree. Commands stay declarative: adding or changing a command is an edit to a YAML file, not Python plumbing.
|
|
19
19
|
|
|
20
20
|
```
|
|
21
|
-
specs/
|
|
21
|
+
jcli/specs/
|
|
22
22
|
_auth.yaml Server auth chain (basic auth + crumb, as cliyard plugins)
|
|
23
23
|
resources/ One YAML spec per Jenkins domain
|
|
24
24
|
job.yaml job list/get/create/config/copy/enable/disable/delete
|
|
@@ -30,11 +30,12 @@ specs/
|
|
|
30
30
|
view.yaml view list/get/create/delete
|
|
31
31
|
system.yaml system info/load/quiet-down/restart/script
|
|
32
32
|
plugins/ Python method plugins for non-trivial logic
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
jcli_commands.py Skills + completion commands
|
|
34
|
+
jenkins_auth.py Basic + crumb authentication
|
|
35
|
+
jenkins_methods.py Build trigger/replay, node create
|
|
35
36
|
jenkins_pipeline_validate.py Jenkinsfile validation
|
|
36
|
-
jenkins_plugin.py
|
|
37
|
-
jenkins_system.py
|
|
37
|
+
jenkins_plugin.py Plugin install/uninstall/update-center
|
|
38
|
+
jenkins_system.py Groovy script, quiet-down, restart
|
|
38
39
|
```
|
|
39
40
|
|
|
40
41
|
A resource spec maps a command name to either a plain HTTP call (method, path, params, output mapping) or a `plugin:` reference. The auth chain in `_auth.yaml` injects `Authorization` and crumb headers into every request.
|
|
@@ -298,15 +299,15 @@ pytest --cov=jcli --cov-report=term-missing
|
|
|
298
299
|
```
|
|
299
300
|
jcli/
|
|
300
301
|
cli.py Entry point: builds the Click CLI from cliyard YAML specs
|
|
301
|
-
cli_helpers.py
|
|
302
|
+
cli_helpers.py Output-formatter helper used by the cliyard command plugins
|
|
302
303
|
__init__.py Version
|
|
303
|
-
plugins/ Legacy hand-written command modules (kept for SDK compatibility)
|
|
304
304
|
sdk/ Shared libraries
|
|
305
305
|
client.py Jenkins REST API client (HTTP, auth, crumb)
|
|
306
306
|
config.py Configuration management (YAML, env vars)
|
|
307
307
|
output/
|
|
308
308
|
formatter.py Table/JSON/YAML output formatting
|
|
309
309
|
exceptions.py Typed exceptions
|
|
310
|
+
skills/ Bundled skill documentation
|
|
310
311
|
specs/ cliyard YAML specs (command definitions)
|
|
311
312
|
_auth.yaml Auth chain: basic + crumb plugins
|
|
312
313
|
resources/ One YAML per Jenkins domain (job, build, node, ...)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.0.1"
|
|
@@ -48,21 +48,14 @@ SPEC_DIR_ENV = "JCLI_SPEC_DIR"
|
|
|
48
48
|
|
|
49
49
|
|
|
50
50
|
def _default_spec_dir() -> Path:
|
|
51
|
-
"""Locate the cliyard YAML spec directory.
|
|
51
|
+
"""Locate the cliyard YAML spec directory (ships inside the package).
|
|
52
52
|
|
|
53
|
-
Priority: ``$JCLI_SPEC_DIR`` > package-local ``specs
|
|
54
|
-
``specs/`` (editable/dev layout).
|
|
53
|
+
Priority: ``$JCLI_SPEC_DIR`` env var > package-local ``jcli/specs/``.
|
|
55
54
|
"""
|
|
56
55
|
env = os.environ.get(SPEC_DIR_ENV)
|
|
57
56
|
if env:
|
|
58
57
|
return Path(env)
|
|
59
|
-
|
|
60
|
-
if pkg_specs.is_dir():
|
|
61
|
-
return pkg_specs
|
|
62
|
-
repo_specs = Path(__file__).resolve().parent.parent / "specs"
|
|
63
|
-
if repo_specs.is_dir():
|
|
64
|
-
return repo_specs
|
|
65
|
-
return pkg_specs
|
|
58
|
+
return Path(__file__).resolve().parent / "specs"
|
|
66
59
|
|
|
67
60
|
|
|
68
61
|
def extract_profile_override(argv: list[str]) -> str | None:
|
|
@@ -213,6 +206,9 @@ def _make_format_injector(callback: Callable[..., Any]) -> Callable[..., Any]:
|
|
|
213
206
|
``Error:``/``错误:`` markers and re-emits them on stderr with a non-zero
|
|
214
207
|
exit code.
|
|
215
208
|
|
|
209
|
+
Commands carrying a ``--follow`` flag (live log tailing) bypass the
|
|
210
|
+
stdout capture so the plugin can stream output in real time.
|
|
211
|
+
|
|
216
212
|
Note: ``ctx.exit(1)`` (not ``return 1``) is required — Click >= 8.4
|
|
217
213
|
ignores a plain integer return value from the top-level callback and
|
|
218
214
|
always exits with ``ctx.exit_code`` (default 0).
|
|
@@ -231,6 +227,9 @@ def _make_format_injector(callback: Callable[..., Any]) -> Callable[..., Any]:
|
|
|
231
227
|
if source in (None, ParameterSource.DEFAULT, ParameterSource.DEFAULT_MAP):
|
|
232
228
|
kwargs["format"] = global_format
|
|
233
229
|
|
|
230
|
+
if kwargs.get("follow"):
|
|
231
|
+
return callback(**kwargs)
|
|
232
|
+
|
|
234
233
|
buffer = io.StringIO()
|
|
235
234
|
real_stdout = sys.stdout
|
|
236
235
|
sys.stdout = buffer
|
|
@@ -498,6 +497,26 @@ def _add_jcli_auth_commands(cli: click.Group) -> None:
|
|
|
498
497
|
cli.add_command(auth)
|
|
499
498
|
|
|
500
499
|
|
|
500
|
+
def _add_tail_short_options(cli: click.Group) -> None:
|
|
501
|
+
"""Give ``build log`` tail-style short flags (``-f``/``-n``).
|
|
502
|
+
|
|
503
|
+
cliyard generates long-only options (``--follow``/``--lines``); add the
|
|
504
|
+
conventional ``tail`` short aliases on the ``build log`` command.
|
|
505
|
+
"""
|
|
506
|
+
build = cli.commands.get("build")
|
|
507
|
+
if not isinstance(build, click.Group):
|
|
508
|
+
return
|
|
509
|
+
log = build.commands.get("log")
|
|
510
|
+
if not log:
|
|
511
|
+
return
|
|
512
|
+
for p in log.params:
|
|
513
|
+
if isinstance(p, click.Option):
|
|
514
|
+
if p.name == "follow" and "-f" not in p.opts:
|
|
515
|
+
p.opts = ("-f", "--follow")
|
|
516
|
+
elif p.name == "lines" and "-n" not in p.opts:
|
|
517
|
+
p.opts = ("-n", "--lines")
|
|
518
|
+
|
|
519
|
+
|
|
501
520
|
def _build_cli(spec_dir: Path, server: str | None, profile: str | None) -> click.Group:
|
|
502
521
|
"""Create the cliyard CLI and apply the jcli wrapper layer."""
|
|
503
522
|
base_url = resolve_base_url(server, profile)
|
|
@@ -509,6 +528,7 @@ def _build_cli(spec_dir: Path, server: str | None, profile: str | None) -> click
|
|
|
509
528
|
wrap_subcommand_callbacks(cli)
|
|
510
529
|
set_group_help(cli)
|
|
511
530
|
_prune_non_jcli_commands(cli)
|
|
531
|
+
_add_tail_short_options(cli)
|
|
512
532
|
return cli
|
|
513
533
|
|
|
514
534
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Centralized CLI helpers.
|
|
2
|
+
|
|
3
|
+
v2 only needs the output-formatter helper (used by the cliyard command
|
|
4
|
+
plugins, e.g. ``jcli/specs/plugins/jcli_commands.py``). The v1 ``get_client``
|
|
5
|
+
helper was removed together with the legacy hand-written command modules
|
|
6
|
+
(``jcli/plugins/``).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import click
|
|
12
|
+
|
|
13
|
+
from jcli.sdk.output import OutputFormatter
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_formatter(ctx: click.Context) -> OutputFormatter:
|
|
17
|
+
"""Return an ``OutputFormatter`` matching the ``--format`` flag."""
|
|
18
|
+
obj = ctx.obj or {}
|
|
19
|
+
fmt = obj.get("format", "table")
|
|
20
|
+
|
|
21
|
+
return OutputFormatter(format_type=fmt)
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: jcli-build
|
|
3
|
+
version: 1.0.0
|
|
4
|
+
description: |
|
|
5
|
+
jcli 构建管理 - 管理 Jenkins 构建的触发、查看、停止等操作。
|
|
6
|
+
allowed-tools:
|
|
7
|
+
- Bash
|
|
8
|
+
- Read
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# jcli-build
|
|
12
|
+
|
|
13
|
+
管理 Jenkins 构建的触发、查看、停止等操作。
|
|
14
|
+
|
|
15
|
+
## 前提条件
|
|
16
|
+
|
|
17
|
+
- Python >= 3.10
|
|
18
|
+
- Jenkins 2.x 或更高版本
|
|
19
|
+
- 已安装 jcli:`pip install -e /data/git-project/jcli`
|
|
20
|
+
|
|
21
|
+
## 命令参考
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
jcli build list <job> # 列出构建历史
|
|
25
|
+
jcli build list <job> --limit 20 # 列出最近 20 次构建
|
|
26
|
+
jcli build get <job> <number> # 查看构建详情
|
|
27
|
+
jcli build log <job> <number> # 查看完整控制台日志
|
|
28
|
+
jcli build log <job> <number> -n 50 # 只显示末尾 50 行(tail -n)
|
|
29
|
+
jcli build log <job> <number> -f # 实时跟随(tail -f):先打印全部历史,再滚动新增,构建结束自动退出
|
|
30
|
+
jcli build log <job> <number> -f -n 50 # 显示末尾 50 行后实时跟随
|
|
31
|
+
jcli build artifacts <job> <number> # 查看构建产物
|
|
32
|
+
jcli build trigger <job> # 触发构建
|
|
33
|
+
jcli build trigger <job> --params BRANCH=main # 带参数触发构建
|
|
34
|
+
jcli build trigger <job> --params KEY1=VAL1 --params KEY2=VAL2 # 多个参数
|
|
35
|
+
jcli build rebuild <job> <number> # 重建(复用上次构建参数)
|
|
36
|
+
jcli build rebuild <job> <number> --set KEY=VAL # 重建并覆盖参数(可重复)
|
|
37
|
+
jcli build replay <job> <number> <jenkinsfile> # 回放(使用修改后的 Jenkinsfile)
|
|
38
|
+
jcli build stop <job> <number> # 停止构建
|
|
39
|
+
jcli build queue # 查看构建队列
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## 常见用例
|
|
43
|
+
|
|
44
|
+
### 查看构建失败原因
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# 1. 查看最近构建
|
|
48
|
+
jcli build list my-job
|
|
49
|
+
|
|
50
|
+
# 2. 查看失败构建的日志
|
|
51
|
+
jcli build log my-job 42
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 批量触发构建
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
for job in job1 job2 job3; do
|
|
58
|
+
jcli build trigger $job
|
|
59
|
+
done
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 监控构建状态
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
# 查看队列
|
|
66
|
+
jcli build queue
|
|
67
|
+
|
|
68
|
+
# 实时查看最新构建状态
|
|
69
|
+
watch -n 5 "jcli build list my-job --limit 1"
|
|
70
|
+
|
|
71
|
+
# 实时跟随构建日志(-f,构建结束自动退出)
|
|
72
|
+
jcli build log my-job 42 -f
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 带参数触发构建
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# 单个参数
|
|
79
|
+
jcli build trigger my-job --params BRANCH=develop
|
|
80
|
+
|
|
81
|
+
# 多个参数
|
|
82
|
+
jcli build trigger my-job --params BRANCH=develop --params DEPLOY=true --params ENV=staging
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### 实时查看构建日志(tail 风格)
|
|
86
|
+
|
|
87
|
+
`jcli build log` 沿用 `tail` 命令的参数语义:
|
|
88
|
+
|
|
89
|
+
| 命令 | 效果 |
|
|
90
|
+
|------|------|
|
|
91
|
+
| `jcli build log <job> <n>` | 输出完整日志 |
|
|
92
|
+
| `jcli build log <job> <n> -n 50` | 只显示末尾 50 行 |
|
|
93
|
+
| `jcli build log <job> <n> -f` | 实时跟随:先输出全部历史,再滚动显示新日志 |
|
|
94
|
+
| `jcli build log <job> <n> -f -n 50` | 先看末尾 50 行,再实时跟随 |
|
|
95
|
+
|
|
96
|
+
- `-f` / `--follow`:实时输出,新日志逐行滚动(tail -f)
|
|
97
|
+
- `-n N` / `--lines N`:只显示末尾 N 行,默认全部
|
|
98
|
+
- 跟随模式在**构建结束后自动退出**,也可随时 `Ctrl+C` 中断
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
# 构建失败排查:看末尾日志定位错误
|
|
102
|
+
jcli build log my-job 42 -n 100
|
|
103
|
+
|
|
104
|
+
# 实时观察正在构建的日志
|
|
105
|
+
jcli build log my-job 42 -f
|
|
106
|
+
|
|
107
|
+
# 触发构建后直接跟随
|
|
108
|
+
jcli build trigger my-job
|
|
109
|
+
jcli build log my-job 43 -f
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### 重建与回放
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
# 重建:复用上次构建的参数
|
|
116
|
+
jcli build rebuild my-job 42
|
|
117
|
+
|
|
118
|
+
# 重建并覆盖部分参数(--set 可重复)
|
|
119
|
+
jcli build rebuild my-job 42 --set DEPLOY=false
|
|
120
|
+
|
|
121
|
+
# 回放:使用修改后的 Jenkinsfile 文件重新运行 Pipeline
|
|
122
|
+
jcli build replay my-job 42 Jenkinsfile
|
|
123
|
+
```
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
name: jcli
|
|
2
|
+
version: "1.0.1"
|
|
3
|
+
description: Jenkins CLI tool
|
|
4
|
+
output:
|
|
5
|
+
default: table
|
|
6
|
+
server:
|
|
7
|
+
base_url: http://localhost:8090
|
|
8
|
+
auth:
|
|
9
|
+
steps:
|
|
10
|
+
- name: basic
|
|
11
|
+
type: plugin:jenkins_basic
|
|
12
|
+
config: {}
|
|
13
|
+
- name: crumb
|
|
14
|
+
type: plugin:jenkins_crumb
|
|
15
|
+
config: {}
|
python_jcli-1.0.0/jcli/plugins/skills.py → python_jcli-1.0.1/jcli/specs/plugins/jcli_commands.py
RENAMED
|
@@ -1,4 +1,14 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Top-level command plugins for jcli 2.0: skills, completion.
|
|
2
|
+
|
|
3
|
+
Migrated verbatim from jcli v1:
|
|
4
|
+
|
|
5
|
+
* ``skills`` — jcli/plugins/skills.py
|
|
6
|
+
* ``completion`` — jcli/cli.py ``add_completion_command``
|
|
7
|
+
|
|
8
|
+
Each group is registered as a cliyard command plugin
|
|
9
|
+
(``@register_command``), so ``create_cli`` attaches it to the top-level
|
|
10
|
+
Click group. Command semantics and output formats are unchanged from v1.
|
|
11
|
+
"""
|
|
2
12
|
|
|
3
13
|
from __future__ import annotations
|
|
4
14
|
|
|
@@ -8,14 +18,20 @@ from typing import Any
|
|
|
8
18
|
|
|
9
19
|
import click
|
|
10
20
|
|
|
21
|
+
from cliyard.plugin import register_command
|
|
22
|
+
|
|
11
23
|
from jcli.cli_helpers import get_formatter
|
|
12
24
|
|
|
13
|
-
# ------------------------------------------------------------------
|
|
14
|
-
# Constants
|
|
15
|
-
# ------------------------------------------------------------------
|
|
16
25
|
|
|
17
|
-
#
|
|
18
|
-
|
|
26
|
+
# =====================================================================
|
|
27
|
+
# Skills commands (verbatim from jcli/plugins/skills.py)
|
|
28
|
+
# =====================================================================
|
|
29
|
+
|
|
30
|
+
# Bundled skills directory (ships with jcli package):
|
|
31
|
+
# <repo-root>/jcli/skills/jcli/* — this plugin lives in <repo-root>/jcli/specs/plugins/
|
|
32
|
+
BUNDLED_SKILLS_DIR = (
|
|
33
|
+
Path(__file__).resolve().parent.parent.parent.parent / "jcli" / "skills" / "jcli"
|
|
34
|
+
)
|
|
19
35
|
|
|
20
36
|
# Default install directory for opencode
|
|
21
37
|
DEFAULT_INSTALL_DIR = Path.home() / ".config" / "opencode" / "skills"
|
|
@@ -24,11 +40,6 @@ DEFAULT_INSTALL_DIR = Path.home() / ".config" / "opencode" / "skills"
|
|
|
24
40
|
FRONTMATTER_PATTERN = re.compile(r"^---\s*\n(.*?)\n---\s*\n", re.DOTALL)
|
|
25
41
|
|
|
26
42
|
|
|
27
|
-
# ------------------------------------------------------------------
|
|
28
|
-
# Helpers
|
|
29
|
-
# ------------------------------------------------------------------
|
|
30
|
-
|
|
31
|
-
|
|
32
43
|
def parse_skill_metadata(skill_dir: Path) -> dict[str, Any]:
|
|
33
44
|
"""Parse SKILL.md frontmatter to extract metadata.
|
|
34
45
|
|
|
@@ -231,27 +242,17 @@ def uninstall_skill(name: str, install_dir: Path | None = None) -> bool:
|
|
|
231
242
|
return True
|
|
232
243
|
|
|
233
244
|
|
|
234
|
-
# ------------------------------------------------------------------
|
|
235
|
-
# Click group
|
|
236
|
-
# ------------------------------------------------------------------
|
|
237
|
-
|
|
238
|
-
|
|
239
245
|
@click.group("skills", help="Manage jcli skills (list, install, uninstall).")
|
|
240
246
|
def skills_group() -> None:
|
|
241
247
|
"""Skills management commands."""
|
|
242
248
|
|
|
243
249
|
|
|
244
|
-
# ------------------------------------------------------------------
|
|
245
|
-
# list
|
|
246
|
-
# ------------------------------------------------------------------
|
|
247
|
-
|
|
248
|
-
|
|
249
250
|
@skills_group.command("list")
|
|
250
251
|
@click.option("--installed", "-i", is_flag=True, help="Show only installed skills.")
|
|
251
252
|
@click.option("--bundled", "-b", is_flag=True, help="Show only bundled skills.")
|
|
252
253
|
@click.option("--dir", "install_dir", type=click.Path(exists=False), help="Custom install directory.")
|
|
253
254
|
@click.pass_context
|
|
254
|
-
def
|
|
255
|
+
def skills_list_cmd(ctx: click.Context, installed: bool, bundled: bool, install_dir: str | None) -> None:
|
|
255
256
|
"""List all available skills."""
|
|
256
257
|
fmt = get_formatter(ctx)
|
|
257
258
|
target_dir = Path(install_dir) if install_dir else DEFAULT_INSTALL_DIR
|
|
@@ -297,18 +298,13 @@ def list_cmd(ctx: click.Context, installed: bool, bundled: bool, install_dir: st
|
|
|
297
298
|
fmt.print_table(headers, rows, title="Jcli Skills")
|
|
298
299
|
|
|
299
300
|
|
|
300
|
-
# ------------------------------------------------------------------
|
|
301
|
-
# install
|
|
302
|
-
# ------------------------------------------------------------------
|
|
303
|
-
|
|
304
|
-
|
|
305
301
|
@skills_group.command("install")
|
|
306
302
|
@click.argument("name", required=False)
|
|
307
303
|
@click.option("--all", "-a", "install_all", is_flag=True, help="Install all bundled skills.")
|
|
308
304
|
@click.option("--force", "-f", is_flag=True, help="Force install (overwrite existing).")
|
|
309
305
|
@click.option("--dir", "install_dir", type=click.Path(exists=False), help="Custom install directory.")
|
|
310
306
|
@click.pass_context
|
|
311
|
-
def
|
|
307
|
+
def skills_install_cmd(ctx: click.Context, name: str | None, install_all: bool, force: bool, install_dir: str | None) -> None:
|
|
312
308
|
"""Install a skill.
|
|
313
309
|
|
|
314
310
|
NAME is the skill name to install (from bundled skills).
|
|
@@ -354,16 +350,11 @@ def install_cmd(ctx: click.Context, name: str | None, install_all: bool, force:
|
|
|
354
350
|
fmt.print_success(f"Skill '{name}' installed to {target_dir / name}")
|
|
355
351
|
|
|
356
352
|
|
|
357
|
-
# ------------------------------------------------------------------
|
|
358
|
-
# uninstall
|
|
359
|
-
# ------------------------------------------------------------------
|
|
360
|
-
|
|
361
|
-
|
|
362
353
|
@skills_group.command("uninstall")
|
|
363
354
|
@click.argument("name")
|
|
364
355
|
@click.option("--dir", "install_dir", type=click.Path(exists=False), help="Custom install directory.")
|
|
365
356
|
@click.pass_context
|
|
366
|
-
def
|
|
357
|
+
def skills_uninstall_cmd(ctx: click.Context, name: str, install_dir: str | None) -> None:
|
|
367
358
|
"""Uninstall a skill.
|
|
368
359
|
|
|
369
360
|
NAME is the skill name to uninstall.
|
|
@@ -382,17 +373,12 @@ def uninstall_cmd(ctx: click.Context, name: str, install_dir: str | None) -> Non
|
|
|
382
373
|
fmt.print_success(f"Skill '{name}' uninstalled from {target_dir}")
|
|
383
374
|
|
|
384
375
|
|
|
385
|
-
# ------------------------------------------------------------------
|
|
386
|
-
# get
|
|
387
|
-
# ------------------------------------------------------------------
|
|
388
|
-
|
|
389
|
-
|
|
390
376
|
@skills_group.command("get")
|
|
391
377
|
@click.argument("name")
|
|
392
378
|
@click.option("--installed", "-i", is_flag=True, help="Get from installed skills.")
|
|
393
379
|
@click.option("--dir", "install_dir", type=click.Path(exists=False), help="Custom install directory.")
|
|
394
380
|
@click.pass_context
|
|
395
|
-
def
|
|
381
|
+
def skills_get_cmd(ctx: click.Context, name: str, installed: bool, install_dir: str | None) -> None:
|
|
396
382
|
"""Show skill details and SKILL.md content."""
|
|
397
383
|
fmt = get_formatter(ctx)
|
|
398
384
|
target_dir = Path(install_dir) if install_dir else DEFAULT_INSTALL_DIR
|
|
@@ -432,11 +418,43 @@ def get_cmd(ctx: click.Context, name: str, installed: bool, install_dir: str | N
|
|
|
432
418
|
fmt.console.print(content)
|
|
433
419
|
|
|
434
420
|
|
|
435
|
-
#
|
|
436
|
-
#
|
|
437
|
-
#
|
|
421
|
+
# =====================================================================
|
|
422
|
+
# Completion command (verbatim from jcli/cli.py add_completion_command)
|
|
423
|
+
# =====================================================================
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
def build_completion_group(cli: click.Group) -> click.Group:
|
|
427
|
+
"""Build the ``completion show`` group attached to *cli*."""
|
|
428
|
+
from click.shell_completion import BashComplete, FishComplete, ZshComplete
|
|
429
|
+
|
|
430
|
+
@click.group()
|
|
431
|
+
def completion() -> None:
|
|
432
|
+
"""Shell completion support for jcli."""
|
|
433
|
+
|
|
434
|
+
@completion.command()
|
|
435
|
+
@click.argument("shell", type=click.Choice(["bash", "zsh", "fish"]))
|
|
436
|
+
def show(shell: str) -> None:
|
|
437
|
+
"""Output shell completion script for the specified shell."""
|
|
438
|
+
shell_cls = {"bash": BashComplete, "zsh": ZshComplete, "fish": FishComplete}
|
|
439
|
+
complete = shell_cls[shell](cli, {}, "jcli", "_JCLI_COMPLETE")
|
|
440
|
+
click.echo(complete.source(), nl=False)
|
|
441
|
+
|
|
442
|
+
cli.add_command(completion)
|
|
443
|
+
return completion
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
# =====================================================================
|
|
447
|
+
# cliyard command plugin registration
|
|
448
|
+
# =====================================================================
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
@register_command("skills")
|
|
452
|
+
def _register_skills(cli: click.Group, ctx: Any) -> None:
|
|
453
|
+
"""Attach the skills command group to the top-level CLI."""
|
|
454
|
+
cli.add_command(skills_group)
|
|
438
455
|
|
|
439
456
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
457
|
+
@register_command("completion")
|
|
458
|
+
def _register_completion(cli: click.Group, ctx: Any) -> None:
|
|
459
|
+
"""Attach the completion command group to the top-level CLI."""
|
|
460
|
+
build_completion_group(cli)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Jenkins auth steps: Basic Auth + Crumb (CSRF)."""
|
|
2
|
+
import base64
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
from cliyard.plugin import register_auth_step
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _config_path() -> str:
|
|
11
|
+
"""Resolve the jcli config file path at call time.
|
|
12
|
+
|
|
13
|
+
Prefers ``jcli.sdk.config.DEFAULT_CONFIG_FILE`` (so tests can point it
|
|
14
|
+
elsewhere via monkeypatch) and falls back to ``~/.jcli/config.yaml``
|
|
15
|
+
when the SDK is not importable.
|
|
16
|
+
"""
|
|
17
|
+
try:
|
|
18
|
+
from jcli.sdk.config import DEFAULT_CONFIG_FILE
|
|
19
|
+
|
|
20
|
+
return str(DEFAULT_CONFIG_FILE)
|
|
21
|
+
except Exception:
|
|
22
|
+
return os.path.expanduser("~/.jcli/config.yaml")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _load_profile():
|
|
26
|
+
import yaml
|
|
27
|
+
config_path = _config_path()
|
|
28
|
+
if not os.path.exists(config_path):
|
|
29
|
+
raise RuntimeError(f"config not found: {config_path}")
|
|
30
|
+
with open(config_path, "r", encoding="utf-8") as f:
|
|
31
|
+
cfg = yaml.safe_load(f) or {}
|
|
32
|
+
name = os.environ.get("JCLI_PROFILE") or cfg.get("active_profile", "default")
|
|
33
|
+
profile = dict(cfg.get("profiles", {}).get(name, {}))
|
|
34
|
+
for env_key, field in (("JCLI_URL", "url"), ("JCLI_USERNAME", "username"), ("JCLI_API_TOKEN", "api_token")):
|
|
35
|
+
if os.environ.get(env_key):
|
|
36
|
+
profile[field] = os.environ[env_key]
|
|
37
|
+
return profile
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@register_auth_step("jenkins_basic")
|
|
41
|
+
class JenkinsBasicAuth:
|
|
42
|
+
"""Set Basic Auth header from ~/.jcli/config.yaml profile."""
|
|
43
|
+
|
|
44
|
+
def execute(self, auth_state, config, http_client):
|
|
45
|
+
profile = _load_profile()
|
|
46
|
+
username = profile.get("username", "")
|
|
47
|
+
token = profile.get("api_token", "")
|
|
48
|
+
if not username or not token:
|
|
49
|
+
raise RuntimeError("missing username/api_token in profile")
|
|
50
|
+
raw = f"{username}:{token}".encode()
|
|
51
|
+
http_client.default_headers["Authorization"] = "Basic " + base64.b64encode(raw).decode()
|
|
52
|
+
auth_state["username"] = username
|
|
53
|
+
return {"username": username}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@register_auth_step("jenkins_crumb")
|
|
57
|
+
class JenkinsCrumb:
|
|
58
|
+
"""Fetch Jenkins crumb and inject as header. Tolerate 404 (CSRF disabled)."""
|
|
59
|
+
|
|
60
|
+
def execute(self, auth_state, config, http_client):
|
|
61
|
+
url = f"{http_client.base_url}/crumbIssuer/api/json"
|
|
62
|
+
try:
|
|
63
|
+
resp = http_client._session.get(url, headers=http_client.default_headers, timeout=10)
|
|
64
|
+
except requests.RequestException:
|
|
65
|
+
return {}
|
|
66
|
+
if resp.status_code == 404:
|
|
67
|
+
return {} # CSRF disabled
|
|
68
|
+
if resp.status_code != 200:
|
|
69
|
+
raise RuntimeError(f"crumb fetch failed: {resp.status_code}")
|
|
70
|
+
data = resp.json()
|
|
71
|
+
field = data.get("crumbRequestField")
|
|
72
|
+
value = data.get("crumb")
|
|
73
|
+
if field and value:
|
|
74
|
+
http_client.default_headers[field] = value
|
|
75
|
+
return {"crumb": value}
|