python-jcli 1.0.0__py3-none-any.whl → 1.0.1__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.
- jcli/__init__.py +1 -1
- jcli/cli.py +30 -10
- jcli/cli_helpers.py +5 -42
- jcli/skills/jcli/build/SKILL.md +123 -0
- jcli/specs/_auth.yaml +15 -0
- jcli/{plugins/skills.py → specs/plugins/jcli_commands.py} +64 -46
- jcli/specs/plugins/jenkins_auth.py +75 -0
- jcli/specs/plugins/jenkins_methods.py +332 -0
- jcli/specs/plugins/jenkins_pipeline_validate.py +51 -0
- jcli/specs/plugins/jenkins_plugin.py +77 -0
- jcli/specs/plugins/jenkins_system.py +137 -0
- jcli/specs/resources/build.yaml +160 -0
- jcli/specs/resources/credential.yaml +97 -0
- jcli/specs/resources/job.yaml +184 -0
- jcli/specs/resources/node.yaml +69 -0
- jcli/specs/resources/pipeline.yaml +62 -0
- jcli/specs/resources/plugin.yaml +65 -0
- jcli/specs/resources/system.yaml +72 -0
- jcli/specs/resources/view.yaml +75 -0
- {python_jcli-1.0.0.dist-info → python_jcli-1.0.1.dist-info}/METADATA +10 -9
- python_jcli-1.0.1.dist-info/RECORD +40 -0
- jcli/plugins/__init__.py +0 -28
- jcli/plugins/build.py +0 -197
- jcli/plugins/config.py +0 -268
- jcli/plugins/credential.py +0 -210
- jcli/plugins/job.py +0 -425
- jcli/plugins/node.py +0 -134
- jcli/plugins/pipeline.py +0 -108
- jcli/plugins/plugin.py +0 -173
- jcli/plugins/system.py +0 -173
- jcli/plugins/view.py +0 -148
- jcli/sdk/build.py +0 -182
- jcli/sdk/credential.py +0 -143
- jcli/sdk/job.py +0 -191
- jcli/sdk/job_templates.py +0 -128
- jcli/sdk/node.py +0 -139
- jcli/sdk/pipeline.py +0 -121
- jcli/sdk/plugin.py +0 -155
- jcli/sdk/system.py +0 -202
- jcli/sdk/view.py +0 -94
- python_jcli-1.0.0.dist-info/RECORD +0 -44
- {python_jcli-1.0.0.dist-info → python_jcli-1.0.1.dist-info}/WHEEL +0 -0
- {python_jcli-1.0.0.dist-info → python_jcli-1.0.1.dist-info}/entry_points.txt +0 -0
- {python_jcli-1.0.0.dist-info → python_jcli-1.0.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
name: view
|
|
2
|
+
description: Manage Jenkins views
|
|
3
|
+
methods:
|
|
4
|
+
list:
|
|
5
|
+
description: List all Jenkins views
|
|
6
|
+
http:
|
|
7
|
+
method: GET
|
|
8
|
+
path: api/json
|
|
9
|
+
query:
|
|
10
|
+
tree: "views[name,url,jobs[name]]"
|
|
11
|
+
output:
|
|
12
|
+
items_path: $.views
|
|
13
|
+
fields:
|
|
14
|
+
- name: name
|
|
15
|
+
alias: Name
|
|
16
|
+
- name: url
|
|
17
|
+
alias: URL
|
|
18
|
+
- name: jobs
|
|
19
|
+
alias: Jobs
|
|
20
|
+
get:
|
|
21
|
+
description: Get details for a view
|
|
22
|
+
http:
|
|
23
|
+
method: GET
|
|
24
|
+
path: "view/{{ name }}/api/json"
|
|
25
|
+
params:
|
|
26
|
+
path:
|
|
27
|
+
- name: name
|
|
28
|
+
type: string
|
|
29
|
+
argument: true
|
|
30
|
+
create:
|
|
31
|
+
description: Create a view from XML config
|
|
32
|
+
http:
|
|
33
|
+
method: POST
|
|
34
|
+
path: createView
|
|
35
|
+
query:
|
|
36
|
+
name: '{{ name }}'
|
|
37
|
+
headers:
|
|
38
|
+
Content-Type: application/xml
|
|
39
|
+
body: '{{ config_xml }}'
|
|
40
|
+
params:
|
|
41
|
+
query:
|
|
42
|
+
- name: name
|
|
43
|
+
type: string
|
|
44
|
+
argument: true
|
|
45
|
+
body:
|
|
46
|
+
- name: config_xml
|
|
47
|
+
type: file
|
|
48
|
+
argument: true
|
|
49
|
+
update:
|
|
50
|
+
description: Update a view's configuration
|
|
51
|
+
http:
|
|
52
|
+
method: POST
|
|
53
|
+
path: "view/{{ name }}/config.xml"
|
|
54
|
+
headers:
|
|
55
|
+
Content-Type: application/xml
|
|
56
|
+
body: '{{ config_xml }}'
|
|
57
|
+
params:
|
|
58
|
+
path:
|
|
59
|
+
- name: name
|
|
60
|
+
type: string
|
|
61
|
+
argument: true
|
|
62
|
+
body:
|
|
63
|
+
- name: config_xml
|
|
64
|
+
type: file
|
|
65
|
+
argument: true
|
|
66
|
+
delete:
|
|
67
|
+
description: Delete a view
|
|
68
|
+
http:
|
|
69
|
+
method: POST
|
|
70
|
+
path: "view/{{ name }}/doDelete"
|
|
71
|
+
params:
|
|
72
|
+
path:
|
|
73
|
+
- name: name
|
|
74
|
+
type: string
|
|
75
|
+
argument: true
|
|
@@ -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, ...)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
jcli/__init__.py,sha256=d4QHYmS_30j0hPN8NmNPnQ_Z0TphDRbu4MtQj9cT9e8,22
|
|
2
|
+
jcli/__main__.py,sha256=ARL8XYr45T4pIf0gnyCCbK8uljcn1LF7TKcFapxHt_k,34
|
|
3
|
+
jcli/cli.py,sha256=krV91DUIFEQnO_OzS08JahY5GFIQB5Omwvj2YcQVX_Y,21921
|
|
4
|
+
jcli/cli_helpers.py,sha256=xtMiNfoicBnc4g4qi56PJT3S8KwS56up5tOANNE-SmY,610
|
|
5
|
+
jcli/sdk/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
jcli/sdk/client.py,sha256=hnBZcS82ziPyHepPWq7femUqQppw8QCtwbdMrWYVrxc,8723
|
|
7
|
+
jcli/sdk/config.py,sha256=fSBTWqE0i8jpzmtGfo2M-Ln2Y5eQRQD0StS76glyCsI,6992
|
|
8
|
+
jcli/sdk/exceptions.py,sha256=zv5oYOr_t0rma4lVudLp3gLdL5idSzlKGc7g2MowcL0,1737
|
|
9
|
+
jcli/sdk/output/__init__.py,sha256=Rt0F77oq24tqHvTDV4HyWKAjDtOyPOh9m7ucFy7B77s,70
|
|
10
|
+
jcli/sdk/output/formatter.py,sha256=OUjQxeD-2iSNncuuRaLa01qLi899ux3F39KNkc8ik2Q,3876
|
|
11
|
+
jcli/skills/jcli/SKILL.md,sha256=X9xHxRvNa4F3hUN8bhckBXAJZx2MWY58hGqqd0y158M,3040
|
|
12
|
+
jcli/skills/jcli/auth/SKILL.md,sha256=4_DLD51SwKTXh2Xi0UiQbqG4uXw7qIKv2SOOopS2Qn0,1996
|
|
13
|
+
jcli/skills/jcli/build/SKILL.md,sha256=J2UbuAAuAHiIrMbnzLH2Q8mQcRvy3JkuTS9GyhMfR64,3557
|
|
14
|
+
jcli/skills/jcli/credential/SKILL.md,sha256=3VsVdGalsIySvGfFO79IdL0b5OjF7kJBS9_4P56Wt2A,1244
|
|
15
|
+
jcli/skills/jcli/job/SKILL.md,sha256=bmGyiyLXS2n395bA31gehcMRe094SUBq-FzUlUDvpQM,2066
|
|
16
|
+
jcli/skills/jcli/node/SKILL.md,sha256=k4qi6ApspdIlBcxb0v2ItPrPwT7d9AnFq4N-vGhGcqY,1247
|
|
17
|
+
jcli/skills/jcli/pipeline/SKILL.md,sha256=kUwnxBCUx53zi7efVgtJg9ainO-YuX3griaCqoUyzxU,1231
|
|
18
|
+
jcli/skills/jcli/plugin/SKILL.md,sha256=--1t5LekIHAtnCWUnsQ9BKkPIgAQ7n-p7_QGd8H2jbI,1178
|
|
19
|
+
jcli/skills/jcli/system/SKILL.md,sha256=SHdcLbaJyifjt4KJlmdcHdYLsFGwpF-6lqiWdYpw3co,1932
|
|
20
|
+
jcli/skills/jcli/view/SKILL.md,sha256=f2QZEzvFocIlR7Yh0RKnp5y4sijwUJU_1fxiW7gKJcA,1047
|
|
21
|
+
jcli/specs/_auth.yaml,sha256=h4wfmdjIetxCAQGpPHmp89FIRPXHP9N5vwDkw6PIPcc,276
|
|
22
|
+
jcli/specs/plugins/jcli_commands.py,sha256=6d9HEmPeyjwTaY6z9dFUbD7TYjkOn2C4WkUb_J2U1Wc,15878
|
|
23
|
+
jcli/specs/plugins/jenkins_auth.py,sha256=CbjWOHCEUoKpbV4WkRFTUCoPCLF5srHnWIkcIG1TUAA,2714
|
|
24
|
+
jcli/specs/plugins/jenkins_methods.py,sha256=9Rd8xxqXQl8ikKSlB15kgp61Y0UQDgSL7XsY5u5L2-Q,11793
|
|
25
|
+
jcli/specs/plugins/jenkins_pipeline_validate.py,sha256=N7DuQgtam_idKQaPWSsmxa4G4ohf-c8YUn6hLfF-znQ,1868
|
|
26
|
+
jcli/specs/plugins/jenkins_plugin.py,sha256=Bdg1CZI6kHsa1Po3KaR6057ON2t1SmuRt17MvRLc63U,2533
|
|
27
|
+
jcli/specs/plugins/jenkins_system.py,sha256=n_m_c8gHfIXmqUyDSiGZytblDM7H5QQCoDUEBhI30NQ,4289
|
|
28
|
+
jcli/specs/resources/build.yaml,sha256=niDH8L8HSHrIa_u5BMP9FJXgd3i2UWiTXUXOjKzSsM0,3994
|
|
29
|
+
jcli/specs/resources/credential.yaml,sha256=SUTOmYN_lafMAzuhH8noZj3HJgUvwG4FkXytoTcxA-M,2402
|
|
30
|
+
jcli/specs/resources/job.yaml,sha256=jvy-ARhPKCTaEpSyU1bJ7C1_HHo7l_Kb0b8yOku3FHs,4075
|
|
31
|
+
jcli/specs/resources/node.yaml,sha256=FhMEQ1kxucP6XyoTiVys7yoy8y3o-stbBdKv_SgIckA,1594
|
|
32
|
+
jcli/specs/resources/pipeline.yaml,sha256=kk-0B3adC8gA6YaFOOfQ5uAZQ11X3wCfvO-nLTx8hoE,1646
|
|
33
|
+
jcli/specs/resources/plugin.yaml,sha256=pqR5GUi1FHAL8iFsPbGCEr3sKSmR1UwK9CcZR5dLvJI,1561
|
|
34
|
+
jcli/specs/resources/system.yaml,sha256=ZhRcG5FTrKqIz-QNTxhPdO-y0WE29QVo3-U8oiMXYcc,1529
|
|
35
|
+
jcli/specs/resources/view.yaml,sha256=ELWZFZy667gh7SzwXCpxTacaduiqH7n2PAKdqzgnDLw,1596
|
|
36
|
+
python_jcli-1.0.1.dist-info/METADATA,sha256=4sTjfkW2xI6-6Ghz6RacmyZEvO41jBxAULceIVA_xZc,12519
|
|
37
|
+
python_jcli-1.0.1.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
38
|
+
python_jcli-1.0.1.dist-info/entry_points.txt,sha256=g-Gb8voGH3Ru4NEoCUMJXJiMZMapdAfYi6Xt1phyFi4,39
|
|
39
|
+
python_jcli-1.0.1.dist-info/top_level.txt,sha256=WFqrbWSEfhvQfbN2m-_Wi5KKCwv1_23LhzPUqo7AdQc,5
|
|
40
|
+
python_jcli-1.0.1.dist-info/RECORD,,
|
jcli/plugins/__init__.py
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
"""Jenkins CLI plugins - register all subcommands."""
|
|
2
|
-
|
|
3
|
-
import click
|
|
4
|
-
|
|
5
|
-
from jcli.plugins.build import register as register_build
|
|
6
|
-
from jcli.plugins.config import register as register_config
|
|
7
|
-
from jcli.plugins.credential import register as register_credential
|
|
8
|
-
from jcli.plugins.job import register as register_job
|
|
9
|
-
from jcli.plugins.node import register as register_node
|
|
10
|
-
from jcli.plugins.pipeline import register as register_pipeline
|
|
11
|
-
from jcli.plugins.plugin import register as register_plugin
|
|
12
|
-
from jcli.plugins.skills import register as register_skills
|
|
13
|
-
from jcli.plugins.system import register as register_system
|
|
14
|
-
from jcli.plugins.view import register as register_view
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def register_commands(group: click.Group) -> None:
|
|
18
|
-
"""Register all plugin subcommands under the main CLI group."""
|
|
19
|
-
register_build(group)
|
|
20
|
-
register_config(group)
|
|
21
|
-
register_credential(group)
|
|
22
|
-
register_job(group)
|
|
23
|
-
register_node(group)
|
|
24
|
-
register_pipeline(group)
|
|
25
|
-
register_plugin(group)
|
|
26
|
-
register_skills(group)
|
|
27
|
-
register_system(group)
|
|
28
|
-
register_view(group)
|
jcli/plugins/build.py
DELETED
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
"""Jenkins Build management commands."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import click
|
|
6
|
-
|
|
7
|
-
from jcli.cli_helpers import get_client, get_formatter
|
|
8
|
-
from jcli.sdk.build import (
|
|
9
|
-
cancel_queue_item,
|
|
10
|
-
get_build,
|
|
11
|
-
get_build_artifacts,
|
|
12
|
-
get_build_log,
|
|
13
|
-
get_builds,
|
|
14
|
-
get_queue,
|
|
15
|
-
stop_build,
|
|
16
|
-
trigger_build,
|
|
17
|
-
)
|
|
18
|
-
from jcli.sdk.exceptions import JenkinsError
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def _parse_params(raw_params: tuple[str, ...]) -> dict[str, str]:
|
|
22
|
-
"""Parse ``KEY=VAL`` strings into a dict."""
|
|
23
|
-
params: dict[str, str] = {}
|
|
24
|
-
for item in raw_params:
|
|
25
|
-
if "=" not in item:
|
|
26
|
-
raise click.BadParameter(f"Invalid parameter format: {item} (expected KEY=VAL)")
|
|
27
|
-
key, _, value = item.partition("=")
|
|
28
|
-
params[key.strip()] = value.strip()
|
|
29
|
-
return params
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
# ------------------------------------------------------------------
|
|
33
|
-
# Build group
|
|
34
|
-
# ------------------------------------------------------------------
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
@click.group("build", help="Manage Jenkins builds.")
|
|
38
|
-
def build_group():
|
|
39
|
-
"""Build management commands."""
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
@build_group.command("trigger")
|
|
43
|
-
@click.argument("job_name")
|
|
44
|
-
@click.option(
|
|
45
|
-
"--params", "-p",
|
|
46
|
-
multiple=True,
|
|
47
|
-
help="Build parameters as KEY=VAL pairs. Repeatable.",
|
|
48
|
-
)
|
|
49
|
-
@click.pass_context
|
|
50
|
-
def trigger(ctx: click.Context, job_name: str, params: tuple[str, ...]):
|
|
51
|
-
"""Trigger a new build for JOB_NAME.
|
|
52
|
-
|
|
53
|
-
Use --params/-p KEY=VAL to pass build parameters (repeat for multiple).
|
|
54
|
-
"""
|
|
55
|
-
client = get_client(ctx)
|
|
56
|
-
fmt = get_formatter(ctx)
|
|
57
|
-
try:
|
|
58
|
-
param_dict = _parse_params(params) if params else None
|
|
59
|
-
result = trigger_build(client, job_name, param_dict)
|
|
60
|
-
fmt.print_success(f"Build triggered for {job_name}")
|
|
61
|
-
fmt.print_json(result)
|
|
62
|
-
except JenkinsError as exc:
|
|
63
|
-
fmt.print_error(str(exc))
|
|
64
|
-
raise SystemExit(1)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
@build_group.command("list")
|
|
68
|
-
@click.argument("job_name")
|
|
69
|
-
@click.option("--limit", "-n", default=10, type=int, help="Maximum builds to show.")
|
|
70
|
-
@click.pass_context
|
|
71
|
-
def list_builds(ctx: click.Context, job_name: str, limit: int):
|
|
72
|
-
"""List recent builds for JOB_NAME."""
|
|
73
|
-
client = get_client(ctx)
|
|
74
|
-
fmt = get_formatter(ctx)
|
|
75
|
-
try:
|
|
76
|
-
builds = get_builds(client, job_name, limit=limit)
|
|
77
|
-
if not builds:
|
|
78
|
-
fmt.print_info(f"No builds found for {job_name}")
|
|
79
|
-
return
|
|
80
|
-
|
|
81
|
-
headers = ["Number", "Result", "Timestamp", "Duration", "Building"]
|
|
82
|
-
rows = [
|
|
83
|
-
[
|
|
84
|
-
b.get("number", ""),
|
|
85
|
-
str(b.get("result") or "IN PROGRESS"),
|
|
86
|
-
str(b.get("timestamp", "")),
|
|
87
|
-
str(b.get("duration", "")),
|
|
88
|
-
str(b.get("building", False)),
|
|
89
|
-
]
|
|
90
|
-
for b in builds
|
|
91
|
-
]
|
|
92
|
-
fmt.print_table(headers, rows, title=f"Builds for {job_name}")
|
|
93
|
-
except JenkinsError as exc:
|
|
94
|
-
fmt.print_error(str(exc))
|
|
95
|
-
raise SystemExit(1)
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
@build_group.command("get")
|
|
99
|
-
@click.argument("job_name")
|
|
100
|
-
@click.argument("number", type=int)
|
|
101
|
-
@click.pass_context
|
|
102
|
-
def get_build_cmd(ctx: click.Context, job_name: str, number: int):
|
|
103
|
-
"""Show details for a specific build."""
|
|
104
|
-
client = get_client(ctx)
|
|
105
|
-
fmt = get_formatter(ctx)
|
|
106
|
-
try:
|
|
107
|
-
build = get_build(client, job_name, number)
|
|
108
|
-
fmt.print_json(build)
|
|
109
|
-
except JenkinsError as exc:
|
|
110
|
-
fmt.print_error(str(exc))
|
|
111
|
-
raise SystemExit(1)
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
@build_group.command("log")
|
|
115
|
-
@click.argument("job_name")
|
|
116
|
-
@click.argument("number", type=int)
|
|
117
|
-
@click.pass_context
|
|
118
|
-
def log_cmd(ctx: click.Context, job_name: str, number: int):
|
|
119
|
-
"""Show console log for a build."""
|
|
120
|
-
client = get_client(ctx)
|
|
121
|
-
fmt = get_formatter(ctx)
|
|
122
|
-
try:
|
|
123
|
-
log_text = get_build_log(client, job_name, number)
|
|
124
|
-
fmt.console.print(log_text)
|
|
125
|
-
except JenkinsError as exc:
|
|
126
|
-
fmt.print_error(str(exc))
|
|
127
|
-
raise SystemExit(1)
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
@build_group.command("stop")
|
|
131
|
-
@click.argument("job_name")
|
|
132
|
-
@click.argument("number", type=int)
|
|
133
|
-
@click.pass_context
|
|
134
|
-
def stop_cmd(ctx: click.Context, job_name: str, number: int):
|
|
135
|
-
"""Stop a running build."""
|
|
136
|
-
client = get_client(ctx)
|
|
137
|
-
fmt = get_formatter(ctx)
|
|
138
|
-
try:
|
|
139
|
-
stop_build(client, job_name, number)
|
|
140
|
-
fmt.print_success(f"Build {job_name}#{number} stopped")
|
|
141
|
-
except JenkinsError as exc:
|
|
142
|
-
fmt.print_error(str(exc))
|
|
143
|
-
raise SystemExit(1)
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
@build_group.command("queue")
|
|
147
|
-
@click.pass_context
|
|
148
|
-
def queue_cmd(ctx: click.Context):
|
|
149
|
-
"""Show the current Jenkins build queue."""
|
|
150
|
-
client = get_client(ctx)
|
|
151
|
-
fmt = get_formatter(ctx)
|
|
152
|
-
try:
|
|
153
|
-
items = get_queue(client)
|
|
154
|
-
if not items:
|
|
155
|
-
fmt.print_info("Build queue is empty")
|
|
156
|
-
return
|
|
157
|
-
|
|
158
|
-
headers = ["ID", "Task", "Why", "In Queue Since"]
|
|
159
|
-
rows = [
|
|
160
|
-
[
|
|
161
|
-
str(item.get("id", "")),
|
|
162
|
-
str(item.get("task", {}).get("name", "")),
|
|
163
|
-
str(item.get("why", "")),
|
|
164
|
-
str(item.get("inQueueSince", "")),
|
|
165
|
-
]
|
|
166
|
-
for item in items
|
|
167
|
-
]
|
|
168
|
-
fmt.print_table(headers, rows, title="Build Queue")
|
|
169
|
-
except JenkinsError as exc:
|
|
170
|
-
fmt.print_error(str(exc))
|
|
171
|
-
raise SystemExit(1)
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
@build_group.command("artifacts")
|
|
175
|
-
@click.argument("job_name")
|
|
176
|
-
@click.argument("build_number")
|
|
177
|
-
@click.pass_context
|
|
178
|
-
def artifacts_cmd(ctx: click.Context, job_name: str, build_number: str):
|
|
179
|
-
"""List artifacts for a build."""
|
|
180
|
-
client = get_client(ctx)
|
|
181
|
-
fmt = get_formatter(ctx)
|
|
182
|
-
try:
|
|
183
|
-
data = get_build_artifacts(client, job_name, build_number)
|
|
184
|
-
artifacts = data.get("artifacts", [])
|
|
185
|
-
if not artifacts:
|
|
186
|
-
fmt.print_info("No artifacts found.")
|
|
187
|
-
return
|
|
188
|
-
headers = ["File Name", "Relative Path"]
|
|
189
|
-
rows = [[a["fileName"], a["relativePath"]] for a in artifacts]
|
|
190
|
-
fmt.print_table(headers, rows, title=f"Artifacts: {job_name} #{build_number}")
|
|
191
|
-
except Exception as exc:
|
|
192
|
-
fmt.print_error(str(exc))
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
def register(parent_group: click.Group):
|
|
196
|
-
"""Register the build subgroup under the parent Click group."""
|
|
197
|
-
parent_group.add_command(build_group)
|
jcli/plugins/config.py
DELETED
|
@@ -1,268 +0,0 @@
|
|
|
1
|
-
"""Configuration management CLI commands for jcli."""
|
|
2
|
-
|
|
3
|
-
import click
|
|
4
|
-
|
|
5
|
-
from jcli.sdk.config import Config, ProfileNotFoundError
|
|
6
|
-
from jcli.sdk.output.formatter import OutputFormatter
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def _get_config(ctx: click.Context) -> Config:
|
|
10
|
-
"""Get or create Config from Click context."""
|
|
11
|
-
if "config" not in ctx.obj:
|
|
12
|
-
ctx.obj["config"] = Config().load()
|
|
13
|
-
return ctx.obj["config"]
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def _get_formatter(ctx: click.Context) -> OutputFormatter:
|
|
17
|
-
"""Get or create OutputFormatter from Click context."""
|
|
18
|
-
if "formatter" not in ctx.obj:
|
|
19
|
-
fmt = ctx.obj.get("format", "table")
|
|
20
|
-
ctx.obj["formatter"] = OutputFormatter(fmt)
|
|
21
|
-
return ctx.obj["formatter"]
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
@click.group()
|
|
25
|
-
def config_group():
|
|
26
|
-
"""Manage jcli configuration."""
|
|
27
|
-
pass
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
@config_group.command("init")
|
|
31
|
-
@click.option("--force", is_flag=True, help="Overwrite existing config file")
|
|
32
|
-
@click.pass_context
|
|
33
|
-
def config_init(ctx: click.Context, force: bool):
|
|
34
|
-
"""Initialize configuration file with default template."""
|
|
35
|
-
cfg = _get_config(ctx)
|
|
36
|
-
fmt = _get_formatter(ctx)
|
|
37
|
-
|
|
38
|
-
if cfg.config_path.exists() and not force:
|
|
39
|
-
fmt.print_error(f"Config file already exists: {cfg.config_path}")
|
|
40
|
-
fmt.print_info("Use --force to overwrite")
|
|
41
|
-
return
|
|
42
|
-
|
|
43
|
-
# Create fresh config with template
|
|
44
|
-
cfg = Config()
|
|
45
|
-
cfg.load()
|
|
46
|
-
cfg.save()
|
|
47
|
-
|
|
48
|
-
fmt.print_success(f"Config file created: {cfg.config_path}")
|
|
49
|
-
fmt.print_info("Edit it with your Jenkins server details:")
|
|
50
|
-
fmt.print_info(f" url: https://jenkins.example.com")
|
|
51
|
-
fmt.print_info(f" username: admin")
|
|
52
|
-
fmt.print_info(f" api_token: your-api-token-here")
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
@config_group.command("show")
|
|
56
|
-
@click.option("--profile", "-p", help="Show specific profile (default: active)")
|
|
57
|
-
@click.pass_context
|
|
58
|
-
def config_show(ctx: click.Context, profile: str | None):
|
|
59
|
-
"""Show configuration details."""
|
|
60
|
-
cfg = _get_config(ctx)
|
|
61
|
-
fmt = _get_formatter(ctx)
|
|
62
|
-
|
|
63
|
-
if profile:
|
|
64
|
-
try:
|
|
65
|
-
data = cfg.get_profile(profile)
|
|
66
|
-
except ProfileNotFoundError:
|
|
67
|
-
fmt.print_error(f"Profile '{profile}' not found")
|
|
68
|
-
return
|
|
69
|
-
active_name = cfg.get_active_profile_name()
|
|
70
|
-
is_active = " (active)" if profile == active_name else ""
|
|
71
|
-
fmt.print_info(f"Profile: {profile}{is_active}")
|
|
72
|
-
_show_profile(fmt, data)
|
|
73
|
-
else:
|
|
74
|
-
active_name = cfg.get_active_profile_name()
|
|
75
|
-
fmt.print_info(f"Config file: {cfg.config_path}")
|
|
76
|
-
fmt.print_info(f"Active profile: {active_name}")
|
|
77
|
-
fmt.print_info("")
|
|
78
|
-
try:
|
|
79
|
-
data = cfg.get_active_profile()
|
|
80
|
-
_show_profile(fmt, data)
|
|
81
|
-
except ProfileNotFoundError:
|
|
82
|
-
fmt.print_error(f"Active profile '{active_name}' not found")
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
def _show_profile(fmt: OutputFormatter, data: dict):
|
|
86
|
-
"""Display profile fields."""
|
|
87
|
-
fields = [
|
|
88
|
-
("url", "URL"),
|
|
89
|
-
("username", "Username"),
|
|
90
|
-
("api_token", "API Token"),
|
|
91
|
-
("description", "Description"),
|
|
92
|
-
]
|
|
93
|
-
for key, label in fields:
|
|
94
|
-
value = data.get(key, "")
|
|
95
|
-
# Mask token for display
|
|
96
|
-
if key == "api_token" and value and len(value) > 8:
|
|
97
|
-
display = value[:4] + "****" + value[-4:]
|
|
98
|
-
else:
|
|
99
|
-
display = value
|
|
100
|
-
fmt.print_info(f" {label}: {display}")
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
@config_group.command("list")
|
|
104
|
-
@click.pass_context
|
|
105
|
-
def config_list(ctx: click.Context):
|
|
106
|
-
"""List all configured profiles."""
|
|
107
|
-
cfg = _get_config(ctx)
|
|
108
|
-
fmt = _get_formatter(ctx)
|
|
109
|
-
|
|
110
|
-
profiles = cfg.list_profiles()
|
|
111
|
-
active_name = cfg.get_active_profile_name()
|
|
112
|
-
|
|
113
|
-
if not profiles:
|
|
114
|
-
fmt.print_info("No profiles configured. Run 'jcli config init' to create one.")
|
|
115
|
-
return
|
|
116
|
-
|
|
117
|
-
headers = ["Name", "URL", "Username", "Active"]
|
|
118
|
-
rows = []
|
|
119
|
-
for name, data in profiles.items():
|
|
120
|
-
active_mark = "✓" if name == active_name else ""
|
|
121
|
-
rows.append([
|
|
122
|
-
name,
|
|
123
|
-
data.get("url", ""),
|
|
124
|
-
data.get("username", ""),
|
|
125
|
-
active_mark,
|
|
126
|
-
])
|
|
127
|
-
|
|
128
|
-
fmt.print_table(headers, rows, title="Profiles")
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
@config_group.command("set")
|
|
132
|
-
@click.argument("profile")
|
|
133
|
-
@click.argument("field", type=click.Choice(["url", "username", "api_token", "description"]))
|
|
134
|
-
@click.argument("value")
|
|
135
|
-
@click.pass_context
|
|
136
|
-
def config_set(ctx: click.Context, profile: str, field: str, value: str):
|
|
137
|
-
"""Set a configuration field for a profile.
|
|
138
|
-
|
|
139
|
-
Examples:
|
|
140
|
-
jcli config set dev url https://jenkins-dev.example.com
|
|
141
|
-
jcli config set dev username admin
|
|
142
|
-
jcli config set dev api_token abc123
|
|
143
|
-
jcli config set dev description "Development Jenkins"
|
|
144
|
-
"""
|
|
145
|
-
cfg = _get_config(ctx)
|
|
146
|
-
fmt = _get_formatter(ctx)
|
|
147
|
-
|
|
148
|
-
try:
|
|
149
|
-
data = cfg.get_profile(profile)
|
|
150
|
-
except ProfileNotFoundError:
|
|
151
|
-
fmt.print_error(f"Profile '{profile}' not found")
|
|
152
|
-
fmt.print_info(f"Create it first with: jcli config add {profile}")
|
|
153
|
-
return
|
|
154
|
-
|
|
155
|
-
# Update the field
|
|
156
|
-
data[field] = value
|
|
157
|
-
cfg.add_profile(
|
|
158
|
-
name=profile,
|
|
159
|
-
url=data.get("url", ""),
|
|
160
|
-
username=data.get("username", ""),
|
|
161
|
-
api_token=data.get("api_token", ""),
|
|
162
|
-
description=data.get("description", ""),
|
|
163
|
-
)
|
|
164
|
-
|
|
165
|
-
fmt.print_success(f"Updated profile '{profile}': {field} = {value}")
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
@config_group.command("add")
|
|
169
|
-
@click.argument("profile")
|
|
170
|
-
@click.option("--url", "-u", default="", help="Jenkins server URL")
|
|
171
|
-
@click.option("--username", default="", help="Jenkins username")
|
|
172
|
-
@click.option("--api-token", default="", help="Jenkins API token")
|
|
173
|
-
@click.option("--description", "-d", default="", help="Profile description")
|
|
174
|
-
@click.pass_context
|
|
175
|
-
def config_add(
|
|
176
|
-
ctx: click.Context,
|
|
177
|
-
profile: str,
|
|
178
|
-
url: str,
|
|
179
|
-
username: str,
|
|
180
|
-
api_token: str,
|
|
181
|
-
description: str,
|
|
182
|
-
):
|
|
183
|
-
"""Add a new configuration profile.
|
|
184
|
-
|
|
185
|
-
Examples:
|
|
186
|
-
jcli config add dev --url https://jenkins-dev.example.com --username admin
|
|
187
|
-
jcli config add prod -u https://jenkins-prod.example.com -d "Production"
|
|
188
|
-
"""
|
|
189
|
-
cfg = _get_config(ctx)
|
|
190
|
-
fmt = _get_formatter(ctx)
|
|
191
|
-
|
|
192
|
-
# Check if profile already exists
|
|
193
|
-
profiles = cfg.list_profiles()
|
|
194
|
-
if profile in profiles:
|
|
195
|
-
fmt.print_error(f"Profile '{profile}' already exists")
|
|
196
|
-
fmt.print_info(f"Use 'jcli config set {profile} <field> <value>' to update")
|
|
197
|
-
return
|
|
198
|
-
|
|
199
|
-
cfg.add_profile(
|
|
200
|
-
name=profile,
|
|
201
|
-
url=url or "https://jenkins.example.com",
|
|
202
|
-
username=username or "admin",
|
|
203
|
-
api_token=api_token or "",
|
|
204
|
-
description=description or f"{profile} Jenkins instance",
|
|
205
|
-
)
|
|
206
|
-
|
|
207
|
-
fmt.print_success(f"Profile '{profile}' added")
|
|
208
|
-
fmt.print_info("Configure it with:")
|
|
209
|
-
fmt.print_info(f" jcli config set {profile} url https://your-jenkins.com")
|
|
210
|
-
fmt.print_info(f" jcli config set {profile} api_token your-token")
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
@config_group.command("delete")
|
|
214
|
-
@click.argument("profile")
|
|
215
|
-
@click.option("--yes", "-y", is_flag=True, help="Skip confirmation")
|
|
216
|
-
@click.pass_context
|
|
217
|
-
def config_delete(ctx: click.Context, profile: str, yes: bool):
|
|
218
|
-
"""Delete a configuration profile.
|
|
219
|
-
|
|
220
|
-
Examples:
|
|
221
|
-
jcli config delete dev
|
|
222
|
-
jcli config delete old-server --yes
|
|
223
|
-
"""
|
|
224
|
-
cfg = _get_config(ctx)
|
|
225
|
-
fmt = _get_formatter(ctx)
|
|
226
|
-
|
|
227
|
-
profiles = cfg.list_profiles()
|
|
228
|
-
if profile not in profiles:
|
|
229
|
-
fmt.print_error(f"Profile '{profile}' not found")
|
|
230
|
-
return
|
|
231
|
-
|
|
232
|
-
if not yes:
|
|
233
|
-
if not click.confirm(f"Delete profile '{profile}'?"):
|
|
234
|
-
fmt.print_info("Cancelled")
|
|
235
|
-
return
|
|
236
|
-
|
|
237
|
-
cfg.remove_profile(profile)
|
|
238
|
-
fmt.print_success(f"Profile '{profile}' deleted")
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
@config_group.command("use")
|
|
242
|
-
@click.argument("profile")
|
|
243
|
-
@click.pass_context
|
|
244
|
-
def config_use(ctx: click.Context, profile: str):
|
|
245
|
-
"""Switch active profile.
|
|
246
|
-
|
|
247
|
-
Examples:
|
|
248
|
-
jcli config use prod
|
|
249
|
-
jcli config use dev
|
|
250
|
-
"""
|
|
251
|
-
cfg = _get_config(ctx)
|
|
252
|
-
fmt = _get_formatter(ctx)
|
|
253
|
-
|
|
254
|
-
try:
|
|
255
|
-
cfg.set_active_profile(profile)
|
|
256
|
-
except ProfileNotFoundError:
|
|
257
|
-
fmt.print_error(f"Profile '{profile}' not found")
|
|
258
|
-
profiles = cfg.list_profiles()
|
|
259
|
-
if profiles:
|
|
260
|
-
fmt.print_info(f"Available profiles: {', '.join(profiles.keys())}")
|
|
261
|
-
return
|
|
262
|
-
|
|
263
|
-
fmt.print_success(f"Active profile set to '{profile}'")
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
def register(group: click.Group) -> None:
|
|
267
|
-
"""Register config commands with the main CLI group."""
|
|
268
|
-
group.add_command(config_group, "config")
|