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
jcli/plugins/node.py
DELETED
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
"""Jenkins Node 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.node import create_node, delete_node, get_node, list_nodes, toggle_offline
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
@click.group("node", help="Manage Jenkins nodes (agents).")
|
|
12
|
-
def node_group():
|
|
13
|
-
"""Node management commands."""
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
@node_group.command("list")
|
|
17
|
-
@click.pass_context
|
|
18
|
-
def list_nodes_cmd(ctx: click.Context):
|
|
19
|
-
"""List all Jenkins nodes."""
|
|
20
|
-
fmt = get_formatter(ctx)
|
|
21
|
-
client = get_client(ctx)
|
|
22
|
-
|
|
23
|
-
try:
|
|
24
|
-
nodes = list_nodes(client)
|
|
25
|
-
if nodes:
|
|
26
|
-
headers = ["Name", "Online", "Temporarily Offline", "Architecture"]
|
|
27
|
-
rows = [
|
|
28
|
-
[
|
|
29
|
-
n.get("displayName", "?"),
|
|
30
|
-
"Yes" if not n.get("offline", True) else "No",
|
|
31
|
-
"Yes" if n.get("temporarilyOffline", False) else "No",
|
|
32
|
-
n.get("monitorData", {}).get("hudson.node_monitors.ArchitectureMonitor", "?"),
|
|
33
|
-
]
|
|
34
|
-
for n in nodes
|
|
35
|
-
]
|
|
36
|
-
fmt.print_table(headers, rows, title="Jenkins Nodes")
|
|
37
|
-
else:
|
|
38
|
-
fmt.print_info("No nodes found.")
|
|
39
|
-
except Exception as exc:
|
|
40
|
-
fmt.print_error(str(exc))
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
@node_group.command("get")
|
|
44
|
-
@click.argument("name")
|
|
45
|
-
@click.pass_context
|
|
46
|
-
def get_node_cmd(ctx: click.Context, name: str):
|
|
47
|
-
"""Get details of a specific node."""
|
|
48
|
-
fmt = get_formatter(ctx)
|
|
49
|
-
client = get_client(ctx)
|
|
50
|
-
|
|
51
|
-
try:
|
|
52
|
-
node = get_node(client, name)
|
|
53
|
-
if fmt.format_type == "json":
|
|
54
|
-
fmt.print_json(node)
|
|
55
|
-
else:
|
|
56
|
-
headers = ["Property", "Value"]
|
|
57
|
-
rows = [
|
|
58
|
-
["Display Name", node.get("displayName", "?")],
|
|
59
|
-
["Description", node.get("description", "") or "-"],
|
|
60
|
-
["Offline", "Yes" if node.get("offline", True) else "No"],
|
|
61
|
-
["Temporarily Offline", "Yes" if node.get("temporarilyOffline", False) else "No"],
|
|
62
|
-
["Num Executors", str(node.get("numExecutors", "?"))],
|
|
63
|
-
["Idle", "Yes" if node.get("idle", False) else "No"],
|
|
64
|
-
["JNLP Agent", "Yes" if node.get("jnlpAgent", False) else "No"],
|
|
65
|
-
]
|
|
66
|
-
# Add monitor data if available
|
|
67
|
-
monitor = node.get("monitorData", {})
|
|
68
|
-
if monitor:
|
|
69
|
-
for key, val in monitor.items():
|
|
70
|
-
if key.startswith("hudson.node_monitors."):
|
|
71
|
-
label = key.replace("hudson.node_monitors.", "")
|
|
72
|
-
rows.append([f"Monitor: {label}", str(val)])
|
|
73
|
-
fmt.print_table(headers, rows, title=f"Node: {name}")
|
|
74
|
-
except Exception as exc:
|
|
75
|
-
fmt.print_error(str(exc))
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
@node_group.command("delete")
|
|
79
|
-
@click.argument("name")
|
|
80
|
-
@click.option("--force", "-f", is_flag=True, help="Skip confirmation prompt.")
|
|
81
|
-
@click.pass_context
|
|
82
|
-
def delete_node_cmd(ctx: click.Context, name: str, force: bool):
|
|
83
|
-
"""Delete a Jenkins node (agent)."""
|
|
84
|
-
fmt = get_formatter(ctx)
|
|
85
|
-
client = get_client(ctx)
|
|
86
|
-
|
|
87
|
-
if not force:
|
|
88
|
-
if not click.confirm(f"Are you sure you want to delete node '{name}'?"):
|
|
89
|
-
fmt.print_info("Delete cancelled.")
|
|
90
|
-
return
|
|
91
|
-
|
|
92
|
-
try:
|
|
93
|
-
delete_node(client, name)
|
|
94
|
-
fmt.print_success(f"Node '{name}' deleted successfully.")
|
|
95
|
-
except Exception as exc:
|
|
96
|
-
fmt.print_error(str(exc))
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
@node_group.command("toggle")
|
|
100
|
-
@click.argument("name")
|
|
101
|
-
@click.option("--message", "-m", default="", help="Reason for toggling offline/online.")
|
|
102
|
-
@click.pass_context
|
|
103
|
-
def toggle_node_cmd(ctx: click.Context, name: str, message: str):
|
|
104
|
-
"""Toggle a node offline or online."""
|
|
105
|
-
fmt = get_formatter(ctx)
|
|
106
|
-
client = get_client(ctx)
|
|
107
|
-
|
|
108
|
-
try:
|
|
109
|
-
toggle_offline(client, name, msg=message)
|
|
110
|
-
fmt.print_success(f"Node '{name}' toggled successfully.")
|
|
111
|
-
except Exception as exc:
|
|
112
|
-
fmt.print_error(str(exc))
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
@node_group.command("create")
|
|
116
|
-
@click.argument("name")
|
|
117
|
-
@click.option("--executors", type=int, default=1, help="Number of executors (default: 1).")
|
|
118
|
-
@click.option("--remote-fs", default="/tmp", help="Remote filesystem root (default: /tmp).")
|
|
119
|
-
@click.option("--labels", default="", help="Node labels.")
|
|
120
|
-
@click.pass_context
|
|
121
|
-
def create_node_cmd(ctx: click.Context, name: str, executors: int, remote_fs: str, labels: str):
|
|
122
|
-
"""Create a new Jenkins agent node."""
|
|
123
|
-
client = get_client(ctx)
|
|
124
|
-
fmt = get_formatter(ctx)
|
|
125
|
-
try:
|
|
126
|
-
create_node(client, name, num_executors=executors, remote_fs=remote_fs, labels=labels)
|
|
127
|
-
fmt.print_success(f"Node '{name}' created.")
|
|
128
|
-
except Exception as exc:
|
|
129
|
-
fmt.print_error(str(exc))
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
def register(parent_group):
|
|
133
|
-
"""Register the node subgroup under the parent Click group."""
|
|
134
|
-
parent_group.add_command(node_group)
|
jcli/plugins/pipeline.py
DELETED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
"""Jenkins Pipeline management commands."""
|
|
2
|
-
|
|
3
|
-
import click
|
|
4
|
-
|
|
5
|
-
from jcli.cli_helpers import get_client, get_formatter
|
|
6
|
-
from jcli.sdk.exceptions import (
|
|
7
|
-
JenkinsAPIError,
|
|
8
|
-
JenkinsAuthError,
|
|
9
|
-
JenkinsConnectionError,
|
|
10
|
-
JenkinsNotFoundError,
|
|
11
|
-
)
|
|
12
|
-
from jcli.sdk.pipeline import (
|
|
13
|
-
get_pending_input,
|
|
14
|
-
get_pipeline_log,
|
|
15
|
-
get_pipeline_stages,
|
|
16
|
-
validate_jenkinsfile,
|
|
17
|
-
)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
@click.group("pipeline", help="Manage Jenkins pipelines.")
|
|
21
|
-
def pipeline_group():
|
|
22
|
-
"""Pipeline management commands."""
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
@pipeline_group.command("stages")
|
|
26
|
-
@click.argument("job_name")
|
|
27
|
-
@click.argument("build_number")
|
|
28
|
-
@click.pass_context
|
|
29
|
-
def stages(ctx, job_name, build_number):
|
|
30
|
-
"""List stages for a Pipeline build.
|
|
31
|
-
|
|
32
|
-
JOB_NAME is the Pipeline job name.
|
|
33
|
-
BUILD_NUMBER is the build number to query.
|
|
34
|
-
"""
|
|
35
|
-
client = get_client(ctx)
|
|
36
|
-
fmt = get_formatter(ctx)
|
|
37
|
-
|
|
38
|
-
try:
|
|
39
|
-
result = get_pipeline_stages(client, job_name, build_number)
|
|
40
|
-
fmt.print_json(result)
|
|
41
|
-
except (JenkinsNotFoundError, JenkinsAuthError, JenkinsConnectionError, JenkinsAPIError) as exc:
|
|
42
|
-
fmt.print_error(str(exc))
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
@pipeline_group.command("log")
|
|
46
|
-
@click.argument("job_name")
|
|
47
|
-
@click.argument("build_number")
|
|
48
|
-
@click.argument("node_id")
|
|
49
|
-
@click.pass_context
|
|
50
|
-
def log(ctx, job_name, build_number, node_id):
|
|
51
|
-
"""Get log output for a Pipeline step.
|
|
52
|
-
|
|
53
|
-
JOB_NAME is the Pipeline job name.
|
|
54
|
-
BUILD_NUMBER is the build number to query.
|
|
55
|
-
NODE_ID is the flow node ID from stage/step info.
|
|
56
|
-
"""
|
|
57
|
-
client = get_client(ctx)
|
|
58
|
-
fmt = get_formatter(ctx)
|
|
59
|
-
|
|
60
|
-
try:
|
|
61
|
-
result = get_pipeline_log(client, job_name, build_number, node_id)
|
|
62
|
-
fmt.print_json(result)
|
|
63
|
-
except (JenkinsNotFoundError, JenkinsAuthError, JenkinsConnectionError, JenkinsAPIError) as exc:
|
|
64
|
-
fmt.print_error(str(exc))
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
@pipeline_group.command("validate")
|
|
68
|
-
@click.argument("file_path", type=click.Path(exists=True, dir_okay=False))
|
|
69
|
-
@click.pass_context
|
|
70
|
-
def validate(ctx, file_path):
|
|
71
|
-
"""Validate a Jenkinsfile against the Jenkins server.
|
|
72
|
-
|
|
73
|
-
FILE_PATH is the path to the Jenkinsfile to validate.
|
|
74
|
-
"""
|
|
75
|
-
with open(file_path, "r", encoding="utf-8") as f:
|
|
76
|
-
content = f.read()
|
|
77
|
-
|
|
78
|
-
client = get_client(ctx)
|
|
79
|
-
fmt = get_formatter(ctx)
|
|
80
|
-
|
|
81
|
-
result = validate_jenkinsfile(client, content)
|
|
82
|
-
|
|
83
|
-
fmt.print_json(result)
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
@pipeline_group.command("pending")
|
|
87
|
-
@click.argument("job_name")
|
|
88
|
-
@click.argument("build_number")
|
|
89
|
-
@click.pass_context
|
|
90
|
-
def pending(ctx, job_name, build_number):
|
|
91
|
-
"""Show pending input actions for a Pipeline build.
|
|
92
|
-
|
|
93
|
-
JOB_NAME is the Pipeline job name.
|
|
94
|
-
BUILD_NUMBER is the build number to query.
|
|
95
|
-
"""
|
|
96
|
-
client = get_client(ctx)
|
|
97
|
-
fmt = get_formatter(ctx)
|
|
98
|
-
|
|
99
|
-
try:
|
|
100
|
-
result = get_pending_input(client, job_name, build_number)
|
|
101
|
-
fmt.print_json(result)
|
|
102
|
-
except (JenkinsNotFoundError, JenkinsAuthError, JenkinsConnectionError, JenkinsAPIError) as exc:
|
|
103
|
-
fmt.print_error(str(exc))
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
def register(parent_group):
|
|
107
|
-
"""Register the pipeline subgroup under the parent Click group."""
|
|
108
|
-
parent_group.add_command(pipeline_group)
|
jcli/plugins/plugin.py
DELETED
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
"""Jenkins Plugin 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.plugin import (
|
|
9
|
-
check_plugin_updates,
|
|
10
|
-
get_plugin,
|
|
11
|
-
install_plugins,
|
|
12
|
-
list_plugins,
|
|
13
|
-
uninstall_plugin,
|
|
14
|
-
)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def _parse_plugin_spec(spec: str) -> tuple[str, str | None]:
|
|
18
|
-
"""Parse a plugin spec like 'git' or 'git@5.0.0'.
|
|
19
|
-
|
|
20
|
-
Returns:
|
|
21
|
-
(short_name, version_or_None)
|
|
22
|
-
"""
|
|
23
|
-
if "@" in spec:
|
|
24
|
-
name, _, version = spec.partition("@")
|
|
25
|
-
return name, version
|
|
26
|
-
return spec, None
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
# ------------------------------------------------------------------
|
|
30
|
-
# Plugin command group
|
|
31
|
-
# ------------------------------------------------------------------
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
@click.group("plugin", help="Manage Jenkins plugins.")
|
|
35
|
-
@click.pass_context
|
|
36
|
-
def plugin_group(ctx):
|
|
37
|
-
"""Plugin management commands."""
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
@plugin_group.command("list")
|
|
41
|
-
@click.pass_context
|
|
42
|
-
def list_cmd(ctx):
|
|
43
|
-
"""List installed Jenkins plugins."""
|
|
44
|
-
client = get_client(ctx)
|
|
45
|
-
fmt = get_formatter(ctx)
|
|
46
|
-
|
|
47
|
-
try:
|
|
48
|
-
plugins = list_plugins(client)
|
|
49
|
-
except Exception as exc:
|
|
50
|
-
fmt.print_error(str(exc))
|
|
51
|
-
raise SystemExit(1)
|
|
52
|
-
|
|
53
|
-
if not plugins:
|
|
54
|
-
fmt.print_info("No plugins found.")
|
|
55
|
-
return
|
|
56
|
-
|
|
57
|
-
headers = ["Name", "Version", "Active", "Update"]
|
|
58
|
-
rows = [
|
|
59
|
-
[p.get("shortName", "?"), p.get("version", "?"),
|
|
60
|
-
"Yes" if p.get("active") else "No",
|
|
61
|
-
"Yes" if p.get("hasUpdate") else "No"]
|
|
62
|
-
for p in plugins
|
|
63
|
-
]
|
|
64
|
-
fmt.print_table(headers, rows, title="Installed Plugins")
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
@plugin_group.command("get")
|
|
68
|
-
@click.argument("name")
|
|
69
|
-
@click.pass_context
|
|
70
|
-
def get_cmd(ctx, name):
|
|
71
|
-
"""Show details for a single plugin."""
|
|
72
|
-
client = get_client(ctx)
|
|
73
|
-
fmt = get_formatter(ctx)
|
|
74
|
-
|
|
75
|
-
try:
|
|
76
|
-
plugin = get_plugin(client, name)
|
|
77
|
-
except Exception as exc:
|
|
78
|
-
fmt.print_error(str(exc))
|
|
79
|
-
raise SystemExit(1)
|
|
80
|
-
|
|
81
|
-
# Display all fields in a vertical key-value table
|
|
82
|
-
headers = ["Field", "Value"]
|
|
83
|
-
rows = [[str(k), str(v)] for k, v in plugin.items()] if plugin else []
|
|
84
|
-
fmt.print_table(
|
|
85
|
-
headers,
|
|
86
|
-
rows,
|
|
87
|
-
title=f"Plugin: {plugin.get('shortName', name)}" if plugin else f"Plugin {name}",
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
@plugin_group.command("install")
|
|
92
|
-
@click.argument("plugin_spec")
|
|
93
|
-
@click.pass_context
|
|
94
|
-
def install_cmd(ctx, plugin_spec):
|
|
95
|
-
"""Install a Jenkins plugin.
|
|
96
|
-
|
|
97
|
-
PLUGIN_SPEC can be just a plugin name (e.g. 'git') or
|
|
98
|
-
name@version (e.g. 'git@5.0.0') to install a specific version.
|
|
99
|
-
"""
|
|
100
|
-
client = get_client(ctx)
|
|
101
|
-
fmt = get_formatter(ctx)
|
|
102
|
-
|
|
103
|
-
name, version = _parse_plugin_spec(plugin_spec)
|
|
104
|
-
plugins_dict: dict[str, str | None] = {name: version}
|
|
105
|
-
|
|
106
|
-
try:
|
|
107
|
-
install_plugins(client, plugins_dict)
|
|
108
|
-
except Exception as exc:
|
|
109
|
-
fmt.print_error(str(exc))
|
|
110
|
-
raise SystemExit(1)
|
|
111
|
-
|
|
112
|
-
msg = f"Plugin '{name}' installed successfully"
|
|
113
|
-
if version:
|
|
114
|
-
msg += f" (version {version})"
|
|
115
|
-
fmt.print_success(msg)
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
@plugin_group.command("uninstall")
|
|
119
|
-
@click.argument("name")
|
|
120
|
-
@click.pass_context
|
|
121
|
-
def uninstall_cmd(ctx, name):
|
|
122
|
-
"""Uninstall a Jenkins plugin.
|
|
123
|
-
|
|
124
|
-
The plugin is scheduled for removal; a server restart is typically
|
|
125
|
-
required to complete the uninstall.
|
|
126
|
-
"""
|
|
127
|
-
client = get_client(ctx)
|
|
128
|
-
fmt = get_formatter(ctx)
|
|
129
|
-
|
|
130
|
-
try:
|
|
131
|
-
uninstall_plugin(client, name)
|
|
132
|
-
except Exception as exc:
|
|
133
|
-
fmt.print_error(str(exc))
|
|
134
|
-
raise SystemExit(1)
|
|
135
|
-
|
|
136
|
-
fmt.print_success(f"Plugin '{name}' scheduled for uninstall (restart may be required)")
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
@plugin_group.command("check-updates")
|
|
140
|
-
@click.pass_context
|
|
141
|
-
def check_updates_cmd(ctx):
|
|
142
|
-
"""Check for available plugin updates."""
|
|
143
|
-
client = get_client(ctx)
|
|
144
|
-
fmt = get_formatter(ctx)
|
|
145
|
-
try:
|
|
146
|
-
result = check_plugin_updates(client)
|
|
147
|
-
fmt.print_info("Plugin update check triggered. Run 'jcli plugin list' to see results.")
|
|
148
|
-
if isinstance(result, dict) and result:
|
|
149
|
-
fmt.print_json(result)
|
|
150
|
-
except Exception as exc:
|
|
151
|
-
fmt.print_error(str(exc))
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
@plugin_group.command("restart")
|
|
155
|
-
@click.option("--yes", "-y", is_flag=True, help="Skip confirmation prompt.")
|
|
156
|
-
@click.pass_context
|
|
157
|
-
def restart_cmd(ctx, yes):
|
|
158
|
-
"""Restart Jenkins after plugin changes."""
|
|
159
|
-
if not yes:
|
|
160
|
-
click.confirm("Restart Jenkins server?", abort=True)
|
|
161
|
-
client = get_client(ctx)
|
|
162
|
-
fmt = get_formatter(ctx)
|
|
163
|
-
try:
|
|
164
|
-
from jcli.sdk.system import safe_restart
|
|
165
|
-
safe_restart(client)
|
|
166
|
-
fmt.print_success("Jenkins restart initiated.")
|
|
167
|
-
except Exception as exc:
|
|
168
|
-
fmt.print_error(str(exc))
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
def register(parent_group):
|
|
172
|
-
"""Register the plugin subgroup under the parent Click group."""
|
|
173
|
-
parent_group.add_command(plugin_group)
|
jcli/plugins/system.py
DELETED
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
"""Jenkins System management commands."""
|
|
2
|
-
|
|
3
|
-
import logging
|
|
4
|
-
|
|
5
|
-
import click
|
|
6
|
-
|
|
7
|
-
from jcli.cli_helpers import get_client, get_formatter
|
|
8
|
-
from jcli.sdk.client import JenkinsClient
|
|
9
|
-
from jcli.sdk.config import Config
|
|
10
|
-
from jcli.sdk.exceptions import JenkinsConfigError
|
|
11
|
-
from jcli.sdk.system import (
|
|
12
|
-
cancel_quiet_down,
|
|
13
|
-
generate_token,
|
|
14
|
-
get_system_info,
|
|
15
|
-
get_system_load,
|
|
16
|
-
list_users,
|
|
17
|
-
quiet_down,
|
|
18
|
-
run_script,
|
|
19
|
-
safe_restart,
|
|
20
|
-
)
|
|
21
|
-
|
|
22
|
-
logger = logging.getLogger(__name__)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
@click.group("system", help="Jenkins system information and management.")
|
|
26
|
-
def system_group():
|
|
27
|
-
"""System management commands."""
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
@system_group.command("info")
|
|
31
|
-
@click.pass_context
|
|
32
|
-
def system_info(ctx: click.Context):
|
|
33
|
-
"""Show Jenkins system information."""
|
|
34
|
-
formatter = get_formatter(ctx)
|
|
35
|
-
client = get_client(ctx)
|
|
36
|
-
|
|
37
|
-
info = get_system_info(client)
|
|
38
|
-
|
|
39
|
-
formatter.print_info(f"Jenkins {info['version']}")
|
|
40
|
-
formatter.print_info(f"Mode: {info['mode']}")
|
|
41
|
-
formatter.print_info(f"Quieting down: {info['quietingDown']}")
|
|
42
|
-
|
|
43
|
-
rows = [
|
|
44
|
-
("Executors", str(info["num_executors"])),
|
|
45
|
-
("Nodes", str(info["num_nodes"])),
|
|
46
|
-
("Jobs", str(info["num_jobs"])),
|
|
47
|
-
("Slave Agent Port", str(info["slaveAgentPort"])),
|
|
48
|
-
]
|
|
49
|
-
formatter.print_table(
|
|
50
|
-
headers=("Property", "Value"),
|
|
51
|
-
rows=rows,
|
|
52
|
-
title="System Overview",
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
@system_group.command("restart")
|
|
57
|
-
@click.pass_context
|
|
58
|
-
def system_restart(ctx: click.Context):
|
|
59
|
-
"""Trigger a safe restart of Jenkins."""
|
|
60
|
-
formatter = get_formatter(ctx)
|
|
61
|
-
client = get_client(ctx)
|
|
62
|
-
|
|
63
|
-
if safe_restart(client):
|
|
64
|
-
formatter.print_success("Safe restart initiated.")
|
|
65
|
-
else:
|
|
66
|
-
formatter.print_error("Failed to initiate safe restart.")
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
@system_group.command("quiet-down")
|
|
70
|
-
@click.option("--reason", "-r", default="", help="Reason for quiet-down.")
|
|
71
|
-
@click.pass_context
|
|
72
|
-
def system_quiet_down(ctx: click.Context, reason: str):
|
|
73
|
-
"""Put Jenkins into quiet-down mode."""
|
|
74
|
-
formatter = get_formatter(ctx)
|
|
75
|
-
client = get_client(ctx)
|
|
76
|
-
|
|
77
|
-
if quiet_down(client, reason=reason):
|
|
78
|
-
formatter.print_success("Jenkins is now in quiet-down mode.")
|
|
79
|
-
if reason:
|
|
80
|
-
formatter.print_info(f"Reason: {reason}")
|
|
81
|
-
else:
|
|
82
|
-
formatter.print_error("Failed to enter quiet-down mode.")
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
@system_group.command("cancel-quiet-down")
|
|
86
|
-
@click.pass_context
|
|
87
|
-
def system_cancel_quiet_down(ctx: click.Context):
|
|
88
|
-
"""Cancel quiet-down mode."""
|
|
89
|
-
formatter = get_formatter(ctx)
|
|
90
|
-
client = get_client(ctx)
|
|
91
|
-
|
|
92
|
-
if cancel_quiet_down(client):
|
|
93
|
-
formatter.print_success("Quiet-down mode cancelled.")
|
|
94
|
-
else:
|
|
95
|
-
formatter.print_error("Failed to cancel quiet-down mode.")
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
@system_group.command("load")
|
|
99
|
-
@click.pass_context
|
|
100
|
-
def system_load(ctx: click.Context):
|
|
101
|
-
"""Show current system load (queue length, executor usage)."""
|
|
102
|
-
formatter = get_formatter(ctx)
|
|
103
|
-
client = get_client(ctx)
|
|
104
|
-
|
|
105
|
-
load = get_system_load(client)
|
|
106
|
-
|
|
107
|
-
rows = [
|
|
108
|
-
("Queue Length", str(load["queue_length"])),
|
|
109
|
-
("Total Executors", str(load["total_executors"])),
|
|
110
|
-
("Busy Executors", str(load["busy_executors"])),
|
|
111
|
-
("Idle Executors", str(load["idle_executors"])),
|
|
112
|
-
]
|
|
113
|
-
formatter.print_table(
|
|
114
|
-
headers=("Metric", "Value"),
|
|
115
|
-
rows=rows,
|
|
116
|
-
title="System Load",
|
|
117
|
-
)
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
@system_group.command("script")
|
|
121
|
-
@click.argument("script")
|
|
122
|
-
@click.pass_context
|
|
123
|
-
def script_cmd(ctx: click.Context, script: str):
|
|
124
|
-
"""Execute a Groovy script on the Jenkins server.
|
|
125
|
-
|
|
126
|
-
SCRIPT is the Groovy script text to execute.
|
|
127
|
-
"""
|
|
128
|
-
client = get_client(ctx)
|
|
129
|
-
fmt = get_formatter(ctx)
|
|
130
|
-
try:
|
|
131
|
-
result = run_script(client, script)
|
|
132
|
-
print(result)
|
|
133
|
-
except Exception as exc:
|
|
134
|
-
fmt.print_error(str(exc))
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
@system_group.command("users")
|
|
138
|
-
@click.pass_context
|
|
139
|
-
def users_cmd(ctx: click.Context):
|
|
140
|
-
"""List all Jenkins users."""
|
|
141
|
-
client = get_client(ctx)
|
|
142
|
-
fmt = get_formatter(ctx)
|
|
143
|
-
try:
|
|
144
|
-
data = list_users(client)
|
|
145
|
-
users = data.get("users", [])
|
|
146
|
-
if not users:
|
|
147
|
-
fmt.print_info("No users found.")
|
|
148
|
-
return
|
|
149
|
-
user_ids = [u["user"]["fullName"] for u in users]
|
|
150
|
-
fmt.print_json(user_ids)
|
|
151
|
-
except Exception as exc:
|
|
152
|
-
fmt.print_error(str(exc))
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
@system_group.command("token")
|
|
156
|
-
@click.argument("username")
|
|
157
|
-
@click.option("--name", "-n", default="jcli-generated", help="Token name/label.")
|
|
158
|
-
@click.pass_context
|
|
159
|
-
def token_cmd(ctx: click.Context, username: str, name: str):
|
|
160
|
-
"""Generate a new API token for a user."""
|
|
161
|
-
client = get_client(ctx)
|
|
162
|
-
fmt = get_formatter(ctx)
|
|
163
|
-
try:
|
|
164
|
-
result = generate_token(client, username, token_name=name)
|
|
165
|
-
fmt.print_json(result)
|
|
166
|
-
fmt.print_success(f"Token generated for user '{username}'.")
|
|
167
|
-
except Exception as exc:
|
|
168
|
-
fmt.print_error(str(exc))
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
def register(parent_group):
|
|
172
|
-
"""Register the system subgroup under the parent Click group."""
|
|
173
|
-
parent_group.add_command(system_group)
|