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/view.py
DELETED
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
"""Jenkins View management commands."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import json
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
|
|
8
|
-
import click
|
|
9
|
-
|
|
10
|
-
from jcli.cli_helpers import get_client, get_formatter
|
|
11
|
-
from jcli.sdk.view import create_view as sdk_create_view
|
|
12
|
-
from jcli.sdk.view import delete_view as sdk_delete_view
|
|
13
|
-
from jcli.sdk.view import get_view as sdk_get_view
|
|
14
|
-
from jcli.sdk.view import list_views as sdk_list_views
|
|
15
|
-
from jcli.sdk.view import update_view as sdk_update_view
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def _format_output(ctx: click.Context, data: object) -> str:
|
|
19
|
-
"""Format data according to the user-specified output format."""
|
|
20
|
-
ctx_obj = ctx.obj or {}
|
|
21
|
-
fmt = ctx_obj.get("format", "table")
|
|
22
|
-
|
|
23
|
-
if fmt == "json":
|
|
24
|
-
return json.dumps(data, indent=2, ensure_ascii=False)
|
|
25
|
-
|
|
26
|
-
if fmt == "yaml":
|
|
27
|
-
import yaml
|
|
28
|
-
|
|
29
|
-
return yaml.dump(data, default_flow_style=False, allow_unicode=True)
|
|
30
|
-
|
|
31
|
-
# Default: table-like plain text
|
|
32
|
-
return str(data)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
# ------------------------------------------------------------------
|
|
36
|
-
# Click command group
|
|
37
|
-
# ------------------------------------------------------------------
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
@click.group("view", help="Manage Jenkins views.")
|
|
41
|
-
def view_group():
|
|
42
|
-
"""View management commands."""
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
@view_group.command("list")
|
|
46
|
-
@click.pass_context
|
|
47
|
-
def view_list(ctx: click.Context) -> None:
|
|
48
|
-
"""List all Jenkins views."""
|
|
49
|
-
client = get_client(ctx)
|
|
50
|
-
views = sdk_list_views(client)
|
|
51
|
-
|
|
52
|
-
# Build table output
|
|
53
|
-
ctx_obj = ctx.obj or {}
|
|
54
|
-
if ctx_obj.get("format", "table") == "table":
|
|
55
|
-
if not views:
|
|
56
|
-
click.echo("No views found.")
|
|
57
|
-
return
|
|
58
|
-
for v in views:
|
|
59
|
-
job_count = len(v.get("jobs", []))
|
|
60
|
-
click.echo(f" {v['name']} ({job_count} jobs)")
|
|
61
|
-
return
|
|
62
|
-
|
|
63
|
-
click.echo(_format_output(ctx, views))
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
@view_group.command("get")
|
|
67
|
-
@click.argument("view_name", metavar="NAME")
|
|
68
|
-
@click.pass_context
|
|
69
|
-
def view_get(ctx: click.Context, view_name: str) -> None:
|
|
70
|
-
"""Get details for a Jenkins view."""
|
|
71
|
-
client = get_client(ctx)
|
|
72
|
-
data = sdk_get_view(client, view_name)
|
|
73
|
-
|
|
74
|
-
# Table output: show key fields
|
|
75
|
-
ctx_obj = ctx.obj or {}
|
|
76
|
-
if ctx_obj.get("format", "table") == "table":
|
|
77
|
-
click.echo(f"Name: {data.get('name', view_name)}")
|
|
78
|
-
click.echo(f"URL: {data.get('url', '')}")
|
|
79
|
-
click.echo(f"Description: {data.get('description', '')}")
|
|
80
|
-
jobs = data.get("jobs", [])
|
|
81
|
-
if jobs:
|
|
82
|
-
click.echo("Jobs:")
|
|
83
|
-
for j in jobs:
|
|
84
|
-
click.echo(f" - {j['name']} [{j.get('color', 'unknown')}]")
|
|
85
|
-
return
|
|
86
|
-
|
|
87
|
-
click.echo(_format_output(ctx, data))
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
@view_group.command("create")
|
|
91
|
-
@click.argument("view_name", metavar="NAME")
|
|
92
|
-
@click.argument(
|
|
93
|
-
"config_file",
|
|
94
|
-
metavar="CONFIG_XML",
|
|
95
|
-
type=click.Path(exists=True, dir_okay=False, path_type=Path),
|
|
96
|
-
)
|
|
97
|
-
@click.pass_context
|
|
98
|
-
def view_create(ctx: click.Context, view_name: str, config_file: Path) -> None:
|
|
99
|
-
"""Create a new Jenkins view from an XML config file."""
|
|
100
|
-
xml_data = config_file.read_text(encoding="utf-8")
|
|
101
|
-
|
|
102
|
-
client = get_client(ctx)
|
|
103
|
-
sdk_create_view(client, view_name, xml_data)
|
|
104
|
-
click.echo(f"View '{view_name}' created successfully.")
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
@view_group.command("update")
|
|
108
|
-
@click.argument("view_name")
|
|
109
|
-
@click.argument("config_file", type=click.Path(exists=True, dir_okay=False))
|
|
110
|
-
@click.pass_context
|
|
111
|
-
def update_view_cmd(ctx, view_name, config_file):
|
|
112
|
-
"""Update a view's configuration from an XML file."""
|
|
113
|
-
client = get_client(ctx)
|
|
114
|
-
fmt = get_formatter(ctx)
|
|
115
|
-
try:
|
|
116
|
-
with open(config_file, "r", encoding="utf-8") as f:
|
|
117
|
-
config_xml = f.read()
|
|
118
|
-
sdk_update_view(client, view_name, config_xml)
|
|
119
|
-
fmt.print_success(f"View '{view_name}' updated.")
|
|
120
|
-
except Exception as exc:
|
|
121
|
-
fmt.print_error(str(exc))
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
@view_group.command("delete")
|
|
125
|
-
@click.argument("view_name", metavar="NAME")
|
|
126
|
-
@click.option(
|
|
127
|
-
"--yes",
|
|
128
|
-
"-y",
|
|
129
|
-
is_flag=True,
|
|
130
|
-
help="Skip confirmation prompt.",
|
|
131
|
-
)
|
|
132
|
-
@click.pass_context
|
|
133
|
-
def view_delete(ctx: click.Context, view_name: str, yes: bool) -> None:
|
|
134
|
-
"""Delete a Jenkins view."""
|
|
135
|
-
if not yes:
|
|
136
|
-
click.confirm(
|
|
137
|
-
f"Are you sure you want to delete view '{view_name}'?",
|
|
138
|
-
abort=True,
|
|
139
|
-
)
|
|
140
|
-
|
|
141
|
-
client = get_client(ctx)
|
|
142
|
-
sdk_delete_view(client, view_name)
|
|
143
|
-
click.echo(f"View '{view_name}' deleted successfully.")
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
def register(parent_group: click.Group) -> None:
|
|
147
|
-
"""Register the view subgroup under the parent Click group."""
|
|
148
|
-
parent_group.add_command(view_group)
|
jcli/sdk/build.py
DELETED
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
"""Jenkins Build SDK — trigger, query, stop builds and manage queue."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import time
|
|
6
|
-
import urllib.parse
|
|
7
|
-
from typing import Any
|
|
8
|
-
|
|
9
|
-
from jcli.sdk.client import JenkinsClient
|
|
10
|
-
from jcli.sdk.exceptions import JenkinsError
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def _job_path(job_name: str) -> str:
|
|
14
|
-
"""Convert a job name to a Jenkins URL path segment.
|
|
15
|
-
|
|
16
|
-
Handles nested jobs (folder/sub-job) by converting each slash-separated
|
|
17
|
-
segment into ``/job/<segment>``.
|
|
18
|
-
"""
|
|
19
|
-
segments = job_name.split("/")
|
|
20
|
-
return "/job/" + "/job/".join(segments)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def trigger_build(
|
|
24
|
-
client: JenkinsClient,
|
|
25
|
-
job_name: str,
|
|
26
|
-
params: dict[str, Any] | None = None,
|
|
27
|
-
) -> dict[str, Any]:
|
|
28
|
-
"""Trigger a new build for the given job.
|
|
29
|
-
|
|
30
|
-
If *params* is provided the build is triggered with parameters via
|
|
31
|
-
``/buildWithParameters``, otherwise a plain ``/build`` is used.
|
|
32
|
-
Jenkins returns a ``Location`` header pointing to the queued item.
|
|
33
|
-
This function follows that redirect and parses the queue-item JSON.
|
|
34
|
-
|
|
35
|
-
:returns: Queue item info as returned by Jenkins (dict).
|
|
36
|
-
"""
|
|
37
|
-
path = _job_path(job_name)
|
|
38
|
-
if params:
|
|
39
|
-
url = f"{path}/buildWithParameters"
|
|
40
|
-
resp = client.request("POST", url, params=params, allow_redirects=False)
|
|
41
|
-
else:
|
|
42
|
-
url = f"{path}/build"
|
|
43
|
-
resp = client.request("POST", url, allow_redirects=False)
|
|
44
|
-
|
|
45
|
-
# Jenkins responds with 201 Created or 302 Found and a Location header
|
|
46
|
-
# pointing to the queue item. Follow that URL to get the queue item JSON.
|
|
47
|
-
location = resp.headers.get("Location")
|
|
48
|
-
if location:
|
|
49
|
-
queue_path = urllib.parse.urlparse(location).path
|
|
50
|
-
# Strip the base URL prefix — client.get_json accepts an absolute path
|
|
51
|
-
if queue_path.startswith(client.base_url):
|
|
52
|
-
queue_path = queue_path[len(client.base_url) :]
|
|
53
|
-
try:
|
|
54
|
-
return client.get_json(queue_path + "api/json")
|
|
55
|
-
except JenkinsError:
|
|
56
|
-
pass
|
|
57
|
-
|
|
58
|
-
# Fallback — return a minimal dict
|
|
59
|
-
return {"status": "queued", "url": location or ""}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
def get_build(
|
|
63
|
-
client: JenkinsClient,
|
|
64
|
-
job_name: str,
|
|
65
|
-
number: int,
|
|
66
|
-
) -> dict[str, Any]:
|
|
67
|
-
"""Get details for a specific build.
|
|
68
|
-
|
|
69
|
-
:returns: Build details as returned by the Jenkins API.
|
|
70
|
-
"""
|
|
71
|
-
path = f"{_job_path(job_name)}/{number}"
|
|
72
|
-
return client.get_json(f"{path}/api/json")
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
def get_builds(
|
|
76
|
-
client: JenkinsClient,
|
|
77
|
-
job_name: str,
|
|
78
|
-
limit: int = 10,
|
|
79
|
-
) -> list[dict[str, Any]]:
|
|
80
|
-
"""List recent builds for a job.
|
|
81
|
-
|
|
82
|
-
:param limit: Maximum number of builds to return (applied client-side).
|
|
83
|
-
:returns: List of build dicts (trimmed to *limit*).
|
|
84
|
-
"""
|
|
85
|
-
path = _job_path(job_name)
|
|
86
|
-
tree = f"builds[number,url,result,timestamp,duration,building]{f'{{{0},{limit - 1}}}' if limit > 0 else ''}"
|
|
87
|
-
data = client.get_json(f"{path}/api/json", tree=tree)
|
|
88
|
-
builds: list[dict[str, Any]] = data.get("builds", [])
|
|
89
|
-
return builds[:limit]
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
def get_build_log(
|
|
93
|
-
client: JenkinsClient,
|
|
94
|
-
job_name: str,
|
|
95
|
-
number: int,
|
|
96
|
-
start: int = 0,
|
|
97
|
-
) -> str:
|
|
98
|
-
"""Retrieve the console log for a build.
|
|
99
|
-
|
|
100
|
-
:param start: Byte offset to start reading from (useful for polling).
|
|
101
|
-
:returns: Console log text.
|
|
102
|
-
"""
|
|
103
|
-
path = f"{_job_path(job_name)}/{number}/consoleText"
|
|
104
|
-
resp = client.request("GET", path)
|
|
105
|
-
text = resp.text
|
|
106
|
-
if start > 0:
|
|
107
|
-
return text[start:]
|
|
108
|
-
return text
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
def stop_build(
|
|
112
|
-
client: JenkinsClient,
|
|
113
|
-
job_name: str,
|
|
114
|
-
number: int,
|
|
115
|
-
) -> dict[str, Any]:
|
|
116
|
-
"""Stop (abort) a running build.
|
|
117
|
-
|
|
118
|
-
:returns: Empty dict on success.
|
|
119
|
-
"""
|
|
120
|
-
path = f"{_job_path(job_name)}/{number}"
|
|
121
|
-
client.request("POST", f"{path}/stop")
|
|
122
|
-
return {}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
def get_queue(client: JenkinsClient) -> list[dict[str, Any]]:
|
|
126
|
-
"""Return all items currently in the Jenkins build queue.
|
|
127
|
-
|
|
128
|
-
:returns: List of queue item dicts.
|
|
129
|
-
"""
|
|
130
|
-
data = client.get_json("/queue/api/json")
|
|
131
|
-
return data.get("items", [])
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
def cancel_queue_item(
|
|
135
|
-
client: JenkinsClient,
|
|
136
|
-
queue_id: int,
|
|
137
|
-
) -> dict[str, Any]:
|
|
138
|
-
"""Cancel a queued item.
|
|
139
|
-
|
|
140
|
-
:param queue_id: Numeric queue item ID.
|
|
141
|
-
:returns: Empty dict on success.
|
|
142
|
-
"""
|
|
143
|
-
client.request("POST", "/queue/cancelItem", params={"id": queue_id})
|
|
144
|
-
return {}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
def get_build_artifacts(
|
|
148
|
-
client: JenkinsClient,
|
|
149
|
-
job_name: str,
|
|
150
|
-
build_number: int | str,
|
|
151
|
-
) -> Any:
|
|
152
|
-
"""Get build artifacts.
|
|
153
|
-
|
|
154
|
-
Lists artifacts produced by a build.
|
|
155
|
-
"""
|
|
156
|
-
path = _job_path(job_name) + f"/{build_number}/api/json"
|
|
157
|
-
return client.get_json(path, tree="artifacts[fileName,relativePath]")
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
def wait_for_build(
|
|
161
|
-
client: JenkinsClient,
|
|
162
|
-
job_name: str,
|
|
163
|
-
number: int,
|
|
164
|
-
timeout: int = 600,
|
|
165
|
-
poll_interval: int = 2,
|
|
166
|
-
) -> dict[str, Any]:
|
|
167
|
-
"""Poll until a build completes or *timeout* is reached.
|
|
168
|
-
|
|
169
|
-
:param timeout: Maximum seconds to wait.
|
|
170
|
-
:param poll_interval: Seconds between status checks.
|
|
171
|
-
:returns: Final build dict.
|
|
172
|
-
:raises TimeoutError: if the build does not finish within *timeout*.
|
|
173
|
-
"""
|
|
174
|
-
deadline = time.monotonic() + timeout
|
|
175
|
-
while time.monotonic() < deadline:
|
|
176
|
-
build = get_build(client, job_name, number)
|
|
177
|
-
if not build.get("building", False):
|
|
178
|
-
return build
|
|
179
|
-
time.sleep(poll_interval)
|
|
180
|
-
raise TimeoutError(
|
|
181
|
-
f"Build {job_name}#{number} did not complete within {timeout}s"
|
|
182
|
-
)
|
jcli/sdk/credential.py
DELETED
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
"""Jenkins Credential SDK.
|
|
2
|
-
|
|
3
|
-
Provides functions for managing Jenkins credentials via the REST API.
|
|
4
|
-
Default store is ``system``, default domain is ``_`` (global domain).
|
|
5
|
-
|
|
6
|
-
API reference:
|
|
7
|
-
- List: GET /credentials/store/{store}/domain/{domain}/api/json
|
|
8
|
-
- Get: GET /credentials/store/{store}/domain/_/credential/{id}/api/json
|
|
9
|
-
- Create: POST /credentials/store/{store}/domain/{domain}/createCredentials
|
|
10
|
-
- Update: POST /credentials/store/{store}/domain/_/credential/{id}/updateCredentials
|
|
11
|
-
- Delete: POST /credentials/store/{store}/domain/{domain}/credential/{id}/doDelete
|
|
12
|
-
"""
|
|
13
|
-
|
|
14
|
-
from __future__ import annotations
|
|
15
|
-
|
|
16
|
-
from typing import Any
|
|
17
|
-
|
|
18
|
-
from jcli.sdk.client import JenkinsClient
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
DEFAULT_STORE = "system"
|
|
22
|
-
DEFAULT_DOMAIN = "_"
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def _build_base_path(store: str, domain: str) -> str:
|
|
26
|
-
"""Build the base path for credential store/domain operations."""
|
|
27
|
-
return f"/credentials/store/{store}/domain/{domain}"
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def list_credentials(
|
|
31
|
-
client: JenkinsClient,
|
|
32
|
-
store: str = DEFAULT_STORE,
|
|
33
|
-
domain: str = DEFAULT_DOMAIN,
|
|
34
|
-
depth: int | None = None,
|
|
35
|
-
) -> Any:
|
|
36
|
-
"""List all credentials in a store/domain.
|
|
37
|
-
|
|
38
|
-
Args:
|
|
39
|
-
client: Jenkins API client.
|
|
40
|
-
store: Credential store ID (default: ``system``).
|
|
41
|
-
domain: Credential domain ID (default: ``_``).
|
|
42
|
-
depth: Optional API depth parameter for nested expansion.
|
|
43
|
-
|
|
44
|
-
Returns:
|
|
45
|
-
Parsed JSON response from Jenkins.
|
|
46
|
-
"""
|
|
47
|
-
path = f"{_build_base_path(store, domain)}/api/json"
|
|
48
|
-
params: dict[str, Any] = {}
|
|
49
|
-
if depth is not None:
|
|
50
|
-
params["depth"] = str(depth)
|
|
51
|
-
return client.get_json(path, params=params)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def get_credential(
|
|
55
|
-
client: JenkinsClient,
|
|
56
|
-
cred_id: str,
|
|
57
|
-
store: str = DEFAULT_STORE,
|
|
58
|
-
) -> Any:
|
|
59
|
-
"""Get details of a single credential.
|
|
60
|
-
|
|
61
|
-
Args:
|
|
62
|
-
client: Jenkins API client.
|
|
63
|
-
cred_id: Credential ID.
|
|
64
|
-
store: Credential store ID (default: ``system``).
|
|
65
|
-
|
|
66
|
-
Returns:
|
|
67
|
-
Parsed JSON response containing credential details.
|
|
68
|
-
"""
|
|
69
|
-
path = (
|
|
70
|
-
f"{_build_base_path(store, DEFAULT_DOMAIN)}"
|
|
71
|
-
f"/credential/{cred_id}/api/json"
|
|
72
|
-
)
|
|
73
|
-
return client.get_json(path)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
def create_credential(
|
|
77
|
-
client: JenkinsClient,
|
|
78
|
-
xml_data: str | bytes,
|
|
79
|
-
store: str = DEFAULT_STORE,
|
|
80
|
-
domain: str = DEFAULT_DOMAIN,
|
|
81
|
-
) -> Any:
|
|
82
|
-
"""Create a new credential from XML config.
|
|
83
|
-
|
|
84
|
-
The XML must follow the Jenkins credentials XStream format for the
|
|
85
|
-
target credential type (e.g. ``UsernamePasswordCredentialsImpl``).
|
|
86
|
-
|
|
87
|
-
Args:
|
|
88
|
-
client: Jenkins API client.
|
|
89
|
-
xml_data: Credential XML config (str or bytes).
|
|
90
|
-
store: Credential store ID (default: ``system``).
|
|
91
|
-
domain: Credential domain ID (default: ``_``).
|
|
92
|
-
|
|
93
|
-
Returns:
|
|
94
|
-
``requests.Response`` from the POST request.
|
|
95
|
-
"""
|
|
96
|
-
path = f"{_build_base_path(store, domain)}/createCredentials"
|
|
97
|
-
return client.post_xml(path, xml_data)
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
def update_credential(
|
|
101
|
-
client: JenkinsClient,
|
|
102
|
-
cred_id: str,
|
|
103
|
-
xml_data: str | bytes,
|
|
104
|
-
store: str = DEFAULT_STORE,
|
|
105
|
-
) -> Any:
|
|
106
|
-
"""Update an existing credential.
|
|
107
|
-
|
|
108
|
-
Args:
|
|
109
|
-
client: Jenkins API client.
|
|
110
|
-
cred_id: Credential ID to update.
|
|
111
|
-
xml_data: New credential XML config.
|
|
112
|
-
store: Credential store ID (default: ``system``).
|
|
113
|
-
|
|
114
|
-
Returns:
|
|
115
|
-
``requests.Response`` from the POST request.
|
|
116
|
-
"""
|
|
117
|
-
path = (
|
|
118
|
-
f"{_build_base_path(store, DEFAULT_DOMAIN)}"
|
|
119
|
-
f"/credential/{cred_id}/updateCredentials"
|
|
120
|
-
)
|
|
121
|
-
return client.post_xml(path, xml_data)
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
def delete_credential(
|
|
125
|
-
client: JenkinsClient,
|
|
126
|
-
cred_id: str,
|
|
127
|
-
store: str = DEFAULT_STORE,
|
|
128
|
-
) -> Any:
|
|
129
|
-
"""Delete a credential.
|
|
130
|
-
|
|
131
|
-
Args:
|
|
132
|
-
client: Jenkins API client.
|
|
133
|
-
cred_id: Credential ID to delete.
|
|
134
|
-
store: Credential store ID (default: ``system``).
|
|
135
|
-
|
|
136
|
-
Returns:
|
|
137
|
-
``requests.Response`` from the POST request.
|
|
138
|
-
"""
|
|
139
|
-
path = (
|
|
140
|
-
f"{_build_base_path(store, DEFAULT_DOMAIN)}"
|
|
141
|
-
f"/credential/{cred_id}/doDelete"
|
|
142
|
-
)
|
|
143
|
-
return client.post_data(path, data={})
|
jcli/sdk/job.py
DELETED
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
"""Jenkins Job SDK — CRUD and lifecycle operations for Jenkins jobs."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from typing import Any
|
|
6
|
-
|
|
7
|
-
from jcli.sdk.client import JenkinsClient
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def list_jobs(client: JenkinsClient, depth: int = 0) -> list[dict[str, Any]]:
|
|
11
|
-
"""List all jobs with name, url, and color.
|
|
12
|
-
|
|
13
|
-
Args:
|
|
14
|
-
client: Authenticated JenkinsClient instance.
|
|
15
|
-
depth: Recursion depth for folder jobs (0 = top-level only).
|
|
16
|
-
|
|
17
|
-
Returns:
|
|
18
|
-
List of job dicts with keys: name, url, color.
|
|
19
|
-
"""
|
|
20
|
-
data = client.get_json("/api/json", tree="jobs[name,url,color]")
|
|
21
|
-
return data.get("jobs", [])
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def get_job(client: JenkinsClient, name: str) -> dict[str, Any]:
|
|
25
|
-
"""Get full details for a specific job.
|
|
26
|
-
|
|
27
|
-
Args:
|
|
28
|
-
client: Authenticated JenkinsClient instance.
|
|
29
|
-
name: Job name (URL-encoding is handled by the client).
|
|
30
|
-
|
|
31
|
-
Returns:
|
|
32
|
-
Job details dict as returned by the Jenkins REST API.
|
|
33
|
-
|
|
34
|
-
Raises:
|
|
35
|
-
JenkinsNotFoundError: If the job does not exist.
|
|
36
|
-
"""
|
|
37
|
-
return client.get_json(f"/job/{name}/api/json")
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def create_job(client: JenkinsClient, name: str, config_xml: str) -> None:
|
|
41
|
-
"""Create a new job from an XML configuration string.
|
|
42
|
-
|
|
43
|
-
Args:
|
|
44
|
-
client: Authenticated JenkinsClient instance.
|
|
45
|
-
name: Name for the new job.
|
|
46
|
-
config_xml: Jenkins job config XML as a string.
|
|
47
|
-
|
|
48
|
-
Raises:
|
|
49
|
-
JenkinsAPIError: On creation failure (e.g. duplicate name, bad XML).
|
|
50
|
-
"""
|
|
51
|
-
client.post_xml(f"/createItem?name={name}", config_xml)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def delete_job(client: JenkinsClient, name: str) -> None:
|
|
55
|
-
"""Delete a job.
|
|
56
|
-
|
|
57
|
-
Args:
|
|
58
|
-
client: Authenticated JenkinsClient instance.
|
|
59
|
-
name: Name of the job to delete.
|
|
60
|
-
|
|
61
|
-
Raises:
|
|
62
|
-
JenkinsNotFoundError: If the job does not exist.
|
|
63
|
-
"""
|
|
64
|
-
client.post_data(f"/job/{name}/doDelete", data={})
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
def copy_job(client: JenkinsClient, from_name: str, new_name: str) -> None:
|
|
68
|
-
"""Copy an existing job to a new name.
|
|
69
|
-
|
|
70
|
-
Args:
|
|
71
|
-
client: Authenticated JenkinsClient instance.
|
|
72
|
-
from_name: Source job name.
|
|
73
|
-
new_name: Name for the copied job.
|
|
74
|
-
|
|
75
|
-
Raises:
|
|
76
|
-
JenkinsNotFoundError: If the source job does not exist.
|
|
77
|
-
"""
|
|
78
|
-
client.post_data(
|
|
79
|
-
"/createItem",
|
|
80
|
-
data={"name": new_name, "mode": "copy", "from": from_name},
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
def rename_job(
|
|
85
|
-
client: "JenkinsClient",
|
|
86
|
-
old_name: str,
|
|
87
|
-
new_name: str,
|
|
88
|
-
) -> Any:
|
|
89
|
-
"""Rename a job.
|
|
90
|
-
|
|
91
|
-
Args:
|
|
92
|
-
client: Jenkins API client.
|
|
93
|
-
old_name: Current job name.
|
|
94
|
-
new_name: New job name.
|
|
95
|
-
|
|
96
|
-
Returns:
|
|
97
|
-
Response from Jenkins (redirect on success).
|
|
98
|
-
|
|
99
|
-
Raises:
|
|
100
|
-
JenkinsNotFoundError: If the job does not exist.
|
|
101
|
-
"""
|
|
102
|
-
path = f"/job/{old_name}/doRename"
|
|
103
|
-
return client.request("POST", path, params={"newName": new_name})
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
def enable_job(client: JenkinsClient, name: str) -> None:
|
|
107
|
-
"""Enable a disabled job.
|
|
108
|
-
|
|
109
|
-
Args:
|
|
110
|
-
client: Authenticated JenkinsClient instance.
|
|
111
|
-
name: Name of the job to enable.
|
|
112
|
-
|
|
113
|
-
Raises:
|
|
114
|
-
JenkinsNotFoundError: If the job does not exist.
|
|
115
|
-
"""
|
|
116
|
-
client.post_data(f"/job/{name}/enable", data={})
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
def disable_job(client: JenkinsClient, name: str) -> None:
|
|
120
|
-
"""Disable a job.
|
|
121
|
-
|
|
122
|
-
Args:
|
|
123
|
-
client: Authenticated JenkinsClient instance.
|
|
124
|
-
name: Name of the job to disable.
|
|
125
|
-
|
|
126
|
-
Raises:
|
|
127
|
-
JenkinsNotFoundError: If the job does not exist.
|
|
128
|
-
"""
|
|
129
|
-
client.post_data(f"/job/{name}/disable", data={})
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
def update_job_config(
|
|
133
|
-
client: JenkinsClient,
|
|
134
|
-
name: str,
|
|
135
|
-
config_xml: str,
|
|
136
|
-
) -> Any:
|
|
137
|
-
"""Update a job's configuration.
|
|
138
|
-
|
|
139
|
-
Args:
|
|
140
|
-
client: Jenkins API client.
|
|
141
|
-
name: Job name.
|
|
142
|
-
config_xml: New XML configuration.
|
|
143
|
-
|
|
144
|
-
Returns:
|
|
145
|
-
Response from Jenkins.
|
|
146
|
-
"""
|
|
147
|
-
return client.post_xml(f"/job/{name}/config.xml", config_xml)
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
def get_job_config(client: JenkinsClient, name: str) -> str:
|
|
151
|
-
"""Retrieve the XML configuration for a job.
|
|
152
|
-
|
|
153
|
-
Args:
|
|
154
|
-
client: Authenticated JenkinsClient instance.
|
|
155
|
-
name: Name of the job.
|
|
156
|
-
|
|
157
|
-
Returns:
|
|
158
|
-
Job configuration as an XML string.
|
|
159
|
-
|
|
160
|
-
Raises:
|
|
161
|
-
JenkinsNotFoundError: If the job does not exist.
|
|
162
|
-
"""
|
|
163
|
-
resp = client.request("GET", f"/job/{name}/config.xml")
|
|
164
|
-
return resp.text
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
def create_folder(client: JenkinsClient, name: str) -> Any:
|
|
168
|
-
"""Create a Jenkins folder.
|
|
169
|
-
|
|
170
|
-
Args:
|
|
171
|
-
client: Jenkins API client.
|
|
172
|
-
name: Folder name.
|
|
173
|
-
|
|
174
|
-
Returns:
|
|
175
|
-
Response from Jenkins.
|
|
176
|
-
"""
|
|
177
|
-
path = f"/createItem?name={name}&mode=com.cloudbees.hudson.plugins.folder.Folder"
|
|
178
|
-
return client.request("POST", path)
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
def delete_folder(client: JenkinsClient, name: str) -> Any:
|
|
182
|
-
"""Delete a Jenkins folder and all its contents.
|
|
183
|
-
|
|
184
|
-
Args:
|
|
185
|
-
client: Jenkins API client.
|
|
186
|
-
name: Folder name.
|
|
187
|
-
|
|
188
|
-
Returns:
|
|
189
|
-
Response from Jenkins.
|
|
190
|
-
"""
|
|
191
|
-
return client.request("POST", f"/job/{name}/doDelete")
|