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.
Files changed (44) hide show
  1. jcli/__init__.py +1 -1
  2. jcli/cli.py +30 -10
  3. jcli/cli_helpers.py +5 -42
  4. jcli/skills/jcli/build/SKILL.md +123 -0
  5. jcli/specs/_auth.yaml +15 -0
  6. jcli/{plugins/skills.py → specs/plugins/jcli_commands.py} +64 -46
  7. jcli/specs/plugins/jenkins_auth.py +75 -0
  8. jcli/specs/plugins/jenkins_methods.py +332 -0
  9. jcli/specs/plugins/jenkins_pipeline_validate.py +51 -0
  10. jcli/specs/plugins/jenkins_plugin.py +77 -0
  11. jcli/specs/plugins/jenkins_system.py +137 -0
  12. jcli/specs/resources/build.yaml +160 -0
  13. jcli/specs/resources/credential.yaml +97 -0
  14. jcli/specs/resources/job.yaml +184 -0
  15. jcli/specs/resources/node.yaml +69 -0
  16. jcli/specs/resources/pipeline.yaml +62 -0
  17. jcli/specs/resources/plugin.yaml +65 -0
  18. jcli/specs/resources/system.yaml +72 -0
  19. jcli/specs/resources/view.yaml +75 -0
  20. {python_jcli-1.0.0.dist-info → python_jcli-1.0.1.dist-info}/METADATA +10 -9
  21. python_jcli-1.0.1.dist-info/RECORD +40 -0
  22. jcli/plugins/__init__.py +0 -28
  23. jcli/plugins/build.py +0 -197
  24. jcli/plugins/config.py +0 -268
  25. jcli/plugins/credential.py +0 -210
  26. jcli/plugins/job.py +0 -425
  27. jcli/plugins/node.py +0 -134
  28. jcli/plugins/pipeline.py +0 -108
  29. jcli/plugins/plugin.py +0 -173
  30. jcli/plugins/system.py +0 -173
  31. jcli/plugins/view.py +0 -148
  32. jcli/sdk/build.py +0 -182
  33. jcli/sdk/credential.py +0 -143
  34. jcli/sdk/job.py +0 -191
  35. jcli/sdk/job_templates.py +0 -128
  36. jcli/sdk/node.py +0 -139
  37. jcli/sdk/pipeline.py +0 -121
  38. jcli/sdk/plugin.py +0 -155
  39. jcli/sdk/system.py +0 -202
  40. jcli/sdk/view.py +0 -94
  41. python_jcli-1.0.0.dist-info/RECORD +0 -44
  42. {python_jcli-1.0.0.dist-info → python_jcli-1.0.1.dist-info}/WHEEL +0 -0
  43. {python_jcli-1.0.0.dist-info → python_jcli-1.0.1.dist-info}/entry_points.txt +0 -0
  44. {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")