python-jcli 0.1.0__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 -0
- jcli/__main__.py +3 -0
- jcli/cli.py +79 -0
- jcli/cli_helpers.py +58 -0
- jcli/plugins/__init__.py +28 -0
- jcli/plugins/build.py +197 -0
- jcli/plugins/config.py +268 -0
- jcli/plugins/credential.py +210 -0
- jcli/plugins/job.py +425 -0
- jcli/plugins/node.py +134 -0
- jcli/plugins/pipeline.py +108 -0
- jcli/plugins/plugin.py +173 -0
- jcli/plugins/skills.py +399 -0
- jcli/plugins/system.py +173 -0
- jcli/plugins/view.py +148 -0
- jcli/sdk/__init__.py +0 -0
- jcli/sdk/build.py +182 -0
- jcli/sdk/client.py +264 -0
- jcli/sdk/config.py +227 -0
- jcli/sdk/credential.py +143 -0
- jcli/sdk/exceptions.py +59 -0
- jcli/sdk/job.py +191 -0
- jcli/sdk/job_templates.py +128 -0
- jcli/sdk/node.py +139 -0
- jcli/sdk/output/__init__.py +3 -0
- jcli/sdk/output/formatter.py +131 -0
- jcli/sdk/pipeline.py +121 -0
- jcli/sdk/plugin.py +155 -0
- jcli/sdk/system.py +202 -0
- jcli/sdk/view.py +94 -0
- jcli/skills/jcli/SKILL.md +73 -0
- jcli/skills/jcli/config/SKILL.md +81 -0
- jcli/skills/jcli/credential/SKILL.md +47 -0
- jcli/skills/jcli/job/SKILL.md +55 -0
- jcli/skills/jcli/node/SKILL.md +51 -0
- jcli/skills/jcli/pipeline/SKILL.md +58 -0
- jcli/skills/jcli/plugin/SKILL.md +51 -0
- jcli/skills/jcli/system/SKILL.md +70 -0
- jcli/skills/jcli/view/SKILL.md +47 -0
- python_jcli-0.1.0.dist-info/METADATA +311 -0
- python_jcli-0.1.0.dist-info/RECORD +44 -0
- python_jcli-0.1.0.dist-info/WHEEL +5 -0
- python_jcli-0.1.0.dist-info/entry_points.txt +2 -0
- python_jcli-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"""Jenkins Credential management commands."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
|
|
9
|
+
from jcli.cli_helpers import get_client, get_formatter
|
|
10
|
+
from jcli.sdk.credential import (
|
|
11
|
+
create_credential,
|
|
12
|
+
delete_credential,
|
|
13
|
+
get_credential,
|
|
14
|
+
list_credentials,
|
|
15
|
+
update_credential,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _display_result(ctx: click.Context, result: Any) -> None:
|
|
20
|
+
"""Display result using the configured formatter.
|
|
21
|
+
|
|
22
|
+
If result is a dict with a ``credentials`` key, render as a table.
|
|
23
|
+
If result is a dict, pretty-print.
|
|
24
|
+
Otherwise, print as JSON.
|
|
25
|
+
"""
|
|
26
|
+
formatter = get_formatter(ctx)
|
|
27
|
+
|
|
28
|
+
if isinstance(result, dict) and "credentials" in result:
|
|
29
|
+
creds = result["credentials"]
|
|
30
|
+
if not creds:
|
|
31
|
+
formatter.print_info("No credentials found.")
|
|
32
|
+
return
|
|
33
|
+
rows = [
|
|
34
|
+
[c.get("id", ""), c.get("typeName", ""), c.get("description", "")]
|
|
35
|
+
for c in creds
|
|
36
|
+
]
|
|
37
|
+
formatter.print_table(
|
|
38
|
+
headers=["ID", "Type", "Description"],
|
|
39
|
+
rows=rows,
|
|
40
|
+
title="Credentials",
|
|
41
|
+
)
|
|
42
|
+
else:
|
|
43
|
+
formatter.print_json(result)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@click.group("credential", help="Manage Jenkins credentials.")
|
|
47
|
+
def credential_group():
|
|
48
|
+
"""Credential management commands."""
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@credential_group.command("list")
|
|
52
|
+
@click.option(
|
|
53
|
+
"--store", "-s", default="system", help="Credential store ID (default: system)."
|
|
54
|
+
)
|
|
55
|
+
@click.option(
|
|
56
|
+
"--domain", "-d", default="_", help="Credential domain ID (default: _)."
|
|
57
|
+
)
|
|
58
|
+
@click.pass_context
|
|
59
|
+
def cmd_list(ctx: click.Context, store: str, domain: str) -> None:
|
|
60
|
+
"""List Jenkins credentials."""
|
|
61
|
+
client = get_client(ctx)
|
|
62
|
+
try:
|
|
63
|
+
result = list_credentials(client, store=store, domain=domain, depth=1)
|
|
64
|
+
_display_result(ctx, result)
|
|
65
|
+
except Exception as exc:
|
|
66
|
+
get_formatter(ctx).print_error(str(exc))
|
|
67
|
+
sys.exit(1)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@credential_group.command("get")
|
|
71
|
+
@click.argument("cred_id")
|
|
72
|
+
@click.option(
|
|
73
|
+
"--store", "-s", default="system", help="Credential store ID (default: system)."
|
|
74
|
+
)
|
|
75
|
+
@click.pass_context
|
|
76
|
+
def cmd_get(ctx: click.Context, cred_id: str, store: str) -> None:
|
|
77
|
+
"""Get credential details."""
|
|
78
|
+
client = get_client(ctx)
|
|
79
|
+
try:
|
|
80
|
+
result = get_credential(client, cred_id=cred_id, store=store)
|
|
81
|
+
get_formatter(ctx).print_json(result)
|
|
82
|
+
except Exception as exc:
|
|
83
|
+
get_formatter(ctx).print_error(str(exc))
|
|
84
|
+
sys.exit(1)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@credential_group.command("create")
|
|
88
|
+
@click.argument("cred_id")
|
|
89
|
+
@click.argument(
|
|
90
|
+
"config_xml",
|
|
91
|
+
type=click.Path(exists=True, dir_okay=False, path_type=Path),
|
|
92
|
+
)
|
|
93
|
+
@click.option(
|
|
94
|
+
"--store", "-s", default="system", help="Credential store ID (default: system)."
|
|
95
|
+
)
|
|
96
|
+
@click.option(
|
|
97
|
+
"--domain", "-d", default="_", help="Credential domain ID (default: _)."
|
|
98
|
+
)
|
|
99
|
+
@click.pass_context
|
|
100
|
+
def cmd_create(
|
|
101
|
+
ctx: click.Context,
|
|
102
|
+
cred_id: str,
|
|
103
|
+
config_xml: Path,
|
|
104
|
+
store: str,
|
|
105
|
+
domain: str,
|
|
106
|
+
) -> None:
|
|
107
|
+
"""Create a credential from an XML config file.
|
|
108
|
+
|
|
109
|
+
CRED_ID is the credential ID (ignored by Jenkins API, used for reference).
|
|
110
|
+
CONFIG_XML is a path to the credential XML file.
|
|
111
|
+
|
|
112
|
+
The XML must follow Jenkins' XStream format for the target credential type.
|
|
113
|
+
Example for UsernamePasswordCredentialsImpl:
|
|
114
|
+
|
|
115
|
+
\\b
|
|
116
|
+
<com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl>
|
|
117
|
+
<scope>GLOBAL</scope>
|
|
118
|
+
<id>my-cred-id</id>
|
|
119
|
+
<description>My credential</description>
|
|
120
|
+
<username>admin</username>
|
|
121
|
+
<password>secret</password>
|
|
122
|
+
</com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl>
|
|
123
|
+
"""
|
|
124
|
+
client = get_client(ctx)
|
|
125
|
+
formatter = get_formatter(ctx)
|
|
126
|
+
try:
|
|
127
|
+
xml_data = config_xml.read_text(encoding="utf-8")
|
|
128
|
+
create_credential(client, xml_data=xml_data, store=store, domain=domain)
|
|
129
|
+
formatter.print_success(f"Credential created in store '{store}' domain '{domain}'.")
|
|
130
|
+
except Exception as exc:
|
|
131
|
+
formatter.print_error(str(exc))
|
|
132
|
+
sys.exit(1)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@credential_group.command("update")
|
|
136
|
+
@click.argument("cred_id")
|
|
137
|
+
@click.argument(
|
|
138
|
+
"config_file",
|
|
139
|
+
type=click.Path(exists=True, dir_okay=False, path_type=Path),
|
|
140
|
+
)
|
|
141
|
+
@click.option(
|
|
142
|
+
"--store", "-s", default="system", help="Credential store ID (default: system)."
|
|
143
|
+
)
|
|
144
|
+
@click.pass_context
|
|
145
|
+
def cmd_update(
|
|
146
|
+
ctx: click.Context,
|
|
147
|
+
cred_id: str,
|
|
148
|
+
config_file: Path,
|
|
149
|
+
store: str,
|
|
150
|
+
) -> None:
|
|
151
|
+
"""Update a credential from an XML config file.
|
|
152
|
+
|
|
153
|
+
CRED_ID is the credential ID to update.
|
|
154
|
+
CONFIG_FILE is a path to the credential XML file.
|
|
155
|
+
|
|
156
|
+
The XML must follow Jenkins' XStream format for the target credential type.
|
|
157
|
+
Example for UsernamePasswordCredentialsImpl:
|
|
158
|
+
|
|
159
|
+
\\b
|
|
160
|
+
<com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl>
|
|
161
|
+
<scope>GLOBAL</scope>
|
|
162
|
+
<id>my-cred-id</id>
|
|
163
|
+
<description>My credential</description>
|
|
164
|
+
<username>admin</username>
|
|
165
|
+
<password>new-secret</password>
|
|
166
|
+
</com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl>
|
|
167
|
+
"""
|
|
168
|
+
client = get_client(ctx)
|
|
169
|
+
formatter = get_formatter(ctx)
|
|
170
|
+
try:
|
|
171
|
+
xml_data = config_file.read_text(encoding="utf-8")
|
|
172
|
+
update_credential(client, cred_id=cred_id, xml_data=xml_data, store=store)
|
|
173
|
+
formatter.print_success(f"Credential '{cred_id}' updated in store '{store}'.")
|
|
174
|
+
except Exception as exc:
|
|
175
|
+
formatter.print_error(str(exc))
|
|
176
|
+
sys.exit(1)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
@credential_group.command("delete")
|
|
180
|
+
@click.argument("cred_id")
|
|
181
|
+
@click.option(
|
|
182
|
+
"--store", "-s", default="system", help="Credential store ID (default: system)."
|
|
183
|
+
)
|
|
184
|
+
@click.option(
|
|
185
|
+
"--force", "-f", is_flag=True, default=False, help="Skip confirmation prompt."
|
|
186
|
+
)
|
|
187
|
+
@click.pass_context
|
|
188
|
+
def cmd_delete(
|
|
189
|
+
ctx: click.Context, cred_id: str, store: str, force: bool
|
|
190
|
+
) -> None:
|
|
191
|
+
"""Delete a credential."""
|
|
192
|
+
if not force:
|
|
193
|
+
click.confirm(
|
|
194
|
+
f"Delete credential '{cred_id}' from store '{store}'?",
|
|
195
|
+
abort=True,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
client = get_client(ctx)
|
|
199
|
+
formatter = get_formatter(ctx)
|
|
200
|
+
try:
|
|
201
|
+
delete_credential(client, cred_id=cred_id, store=store)
|
|
202
|
+
formatter.print_success(f"Credential '{cred_id}' deleted.")
|
|
203
|
+
except Exception as exc:
|
|
204
|
+
formatter.print_error(str(exc))
|
|
205
|
+
sys.exit(1)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def register(parent_group: click.Group) -> None:
|
|
209
|
+
"""Register the credential subgroup under the parent Click group."""
|
|
210
|
+
parent_group.add_command(credential_group)
|
jcli/plugins/job.py
ADDED
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
"""Jenkins Job 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.exceptions import JenkinsError, JenkinsNotFoundError
|
|
9
|
+
from jcli.sdk.job import (
|
|
10
|
+
copy_job,
|
|
11
|
+
create_folder,
|
|
12
|
+
create_job,
|
|
13
|
+
delete_folder,
|
|
14
|
+
delete_job,
|
|
15
|
+
disable_job,
|
|
16
|
+
enable_job,
|
|
17
|
+
get_job,
|
|
18
|
+
get_job_config,
|
|
19
|
+
list_jobs,
|
|
20
|
+
rename_job,
|
|
21
|
+
update_job_config,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# ------------------------------------------------------------------
|
|
26
|
+
# Click group
|
|
27
|
+
# ------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@click.group("job", help="Manage Jenkins jobs.")
|
|
31
|
+
def job_group() -> None:
|
|
32
|
+
"""Job management commands."""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# ------------------------------------------------------------------
|
|
36
|
+
# list
|
|
37
|
+
# ------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@job_group.command("list")
|
|
41
|
+
@click.pass_context
|
|
42
|
+
def list_cmd(ctx: click.Context) -> None:
|
|
43
|
+
"""List all Jenkins jobs."""
|
|
44
|
+
client = get_client(ctx)
|
|
45
|
+
fmt = get_formatter(ctx)
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
jobs = list_jobs(client)
|
|
49
|
+
except JenkinsError as exc:
|
|
50
|
+
fmt.print_error(str(exc))
|
|
51
|
+
raise SystemExit(1) from exc
|
|
52
|
+
|
|
53
|
+
if not jobs:
|
|
54
|
+
fmt.print_info("No jobs found.")
|
|
55
|
+
return
|
|
56
|
+
|
|
57
|
+
headers = ["Name", "URL", "Color"]
|
|
58
|
+
rows = [[j.get("name", ""), j.get("url", ""), j.get("color", "")] for j in jobs]
|
|
59
|
+
fmt.print_table(headers, rows, title="Jenkins Jobs")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# ------------------------------------------------------------------
|
|
63
|
+
# get
|
|
64
|
+
# ------------------------------------------------------------------
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@job_group.command("get")
|
|
68
|
+
@click.argument("name")
|
|
69
|
+
@click.pass_context
|
|
70
|
+
def get_cmd(ctx: click.Context, name: str) -> None:
|
|
71
|
+
"""Show details for a specific job."""
|
|
72
|
+
client = get_client(ctx)
|
|
73
|
+
fmt = get_formatter(ctx)
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
job = get_job(client, name)
|
|
77
|
+
except JenkinsNotFoundError:
|
|
78
|
+
fmt.print_error(f"Job '{name}' not found.")
|
|
79
|
+
raise SystemExit(1)
|
|
80
|
+
except JenkinsError as exc:
|
|
81
|
+
fmt.print_error(str(exc))
|
|
82
|
+
raise SystemExit(1) from exc
|
|
83
|
+
|
|
84
|
+
fmt.print_json(job)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
# ------------------------------------------------------------------
|
|
88
|
+
# create
|
|
89
|
+
# ------------------------------------------------------------------
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@job_group.command("create")
|
|
93
|
+
@click.argument("name")
|
|
94
|
+
@click.argument("config_file", type=click.Path(exists=True, dir_okay=False, readable=True), required=False)
|
|
95
|
+
@click.option("--type", "job_type", type=click.Choice(["freestyle", "pipeline"]), help="Job type (skip config_file).")
|
|
96
|
+
@click.option("--git-url", help="Git repository URL.")
|
|
97
|
+
@click.option("--git-branch", default="main", help="Git branch (default: main).")
|
|
98
|
+
@click.option("--description", default="", help="Job description.")
|
|
99
|
+
@click.option("--shell", help="Shell script to run (freestyle only).")
|
|
100
|
+
@click.option("--cron", help="Cron schedule for periodic build (freestyle only).")
|
|
101
|
+
@click.option("--jenkinsfile", default="Jenkinsfile", help="Jenkinsfile path (pipeline only).")
|
|
102
|
+
@click.option("--script", help="Inline Pipeline script (pipeline only).")
|
|
103
|
+
@click.pass_context
|
|
104
|
+
def create_cmd(
|
|
105
|
+
ctx: click.Context,
|
|
106
|
+
name: str,
|
|
107
|
+
config_file: str | None,
|
|
108
|
+
job_type: str | None,
|
|
109
|
+
git_url: str | None,
|
|
110
|
+
git_branch: str,
|
|
111
|
+
description: str,
|
|
112
|
+
shell: str | None,
|
|
113
|
+
cron: str | None,
|
|
114
|
+
jenkinsfile: str,
|
|
115
|
+
script: str | None,
|
|
116
|
+
) -> None:
|
|
117
|
+
"""Create a new job from an XML configuration file or parameters.
|
|
118
|
+
|
|
119
|
+
NAME is the name for the new job.
|
|
120
|
+
CONFIG_FILE is the path to the XML file (optional if --type is specified).
|
|
121
|
+
|
|
122
|
+
Examples:
|
|
123
|
+
jcli job create myjob config.xml
|
|
124
|
+
jcli job create myjob --type freestyle --git-url https://... --shell "make build"
|
|
125
|
+
jcli job create mypipe --type pipeline --git-url https://... --jenkinsfile Jenkinsfile
|
|
126
|
+
jcli job create mypipe --type pipeline --script "pipeline { agent any; stages { stage('build') { steps { echo 'hi' } } } }"
|
|
127
|
+
"""
|
|
128
|
+
client = get_client(ctx)
|
|
129
|
+
fmt = get_formatter(ctx)
|
|
130
|
+
|
|
131
|
+
try:
|
|
132
|
+
if config_file:
|
|
133
|
+
# XML file mode (backward compatible)
|
|
134
|
+
try:
|
|
135
|
+
with open(config_file, "r", encoding="utf-8") as f:
|
|
136
|
+
config_xml = f.read()
|
|
137
|
+
except OSError as exc:
|
|
138
|
+
fmt.print_error(f"Cannot read config file '{config_file}': {exc}")
|
|
139
|
+
raise SystemExit(1) from exc
|
|
140
|
+
elif job_type:
|
|
141
|
+
# Parameter mode
|
|
142
|
+
from jcli.sdk.job_templates import generate_freestyle_xml, generate_pipeline_xml
|
|
143
|
+
|
|
144
|
+
if job_type == "freestyle":
|
|
145
|
+
config_xml = generate_freestyle_xml(
|
|
146
|
+
git_url=git_url or "",
|
|
147
|
+
git_branch=git_branch,
|
|
148
|
+
shell_script=shell or "",
|
|
149
|
+
description=description,
|
|
150
|
+
cron_schedule=cron or "",
|
|
151
|
+
)
|
|
152
|
+
else:
|
|
153
|
+
config_xml = generate_pipeline_xml(
|
|
154
|
+
git_url=git_url or "",
|
|
155
|
+
git_branch=git_branch,
|
|
156
|
+
jenkinsfile_path=jenkinsfile,
|
|
157
|
+
description=description,
|
|
158
|
+
script=script or "",
|
|
159
|
+
)
|
|
160
|
+
else:
|
|
161
|
+
raise click.UsageError("Must specify either CONFIG_FILE or --type")
|
|
162
|
+
|
|
163
|
+
create_job(client, name, config_xml)
|
|
164
|
+
except JenkinsError as exc:
|
|
165
|
+
fmt.print_error(str(exc))
|
|
166
|
+
raise SystemExit(1) from exc
|
|
167
|
+
|
|
168
|
+
fmt.print_success(f"Job '{name}' created.")
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
# ------------------------------------------------------------------
|
|
172
|
+
# update
|
|
173
|
+
# ------------------------------------------------------------------
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@job_group.command("update")
|
|
177
|
+
@click.argument("job_name")
|
|
178
|
+
@click.argument("config_file", type=click.Path(exists=True, dir_okay=False))
|
|
179
|
+
@click.pass_context
|
|
180
|
+
def update_cmd(ctx: click.Context, job_name: str, config_file: str) -> None:
|
|
181
|
+
"""Update a job's configuration from an XML file.
|
|
182
|
+
|
|
183
|
+
JOB_NAME is the name of the job to update.
|
|
184
|
+
CONFIG_FILE is the path to the new XML configuration file.
|
|
185
|
+
"""
|
|
186
|
+
client = get_client(ctx)
|
|
187
|
+
fmt = get_formatter(ctx)
|
|
188
|
+
|
|
189
|
+
try:
|
|
190
|
+
with open(config_file, "r", encoding="utf-8") as f:
|
|
191
|
+
config_xml = f.read()
|
|
192
|
+
except OSError as exc:
|
|
193
|
+
fmt.print_error(f"Cannot read config file '{config_file}': {exc}")
|
|
194
|
+
raise SystemExit(1) from exc
|
|
195
|
+
|
|
196
|
+
try:
|
|
197
|
+
update_job_config(client, job_name, config_xml)
|
|
198
|
+
except JenkinsError as exc:
|
|
199
|
+
fmt.print_error(str(exc))
|
|
200
|
+
raise SystemExit(1) from exc
|
|
201
|
+
|
|
202
|
+
fmt.print_success(f"Job '{job_name}' updated.")
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
# ------------------------------------------------------------------
|
|
206
|
+
# delete
|
|
207
|
+
# ------------------------------------------------------------------
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
@job_group.command("delete")
|
|
211
|
+
@click.argument("name")
|
|
212
|
+
@click.option("--yes", "-y", is_flag=True, help="Skip confirmation prompt.")
|
|
213
|
+
@click.pass_context
|
|
214
|
+
def delete_cmd(ctx: click.Context, name: str, yes: bool) -> None:
|
|
215
|
+
"""Delete a job.
|
|
216
|
+
|
|
217
|
+
Prompts for confirmation unless --yes/-y is given.
|
|
218
|
+
"""
|
|
219
|
+
client = get_client(ctx)
|
|
220
|
+
fmt = get_formatter(ctx)
|
|
221
|
+
|
|
222
|
+
if not yes:
|
|
223
|
+
confirm = input(f"Are you sure you want to delete job '{name}'? [y/N]: ")
|
|
224
|
+
if confirm.lower() not in ("y", "yes"):
|
|
225
|
+
fmt.print_info("Delete cancelled.")
|
|
226
|
+
return
|
|
227
|
+
|
|
228
|
+
try:
|
|
229
|
+
delete_job(client, name)
|
|
230
|
+
except JenkinsNotFoundError:
|
|
231
|
+
fmt.print_error(f"Job '{name}' not found.")
|
|
232
|
+
raise SystemExit(1)
|
|
233
|
+
except JenkinsError as exc:
|
|
234
|
+
fmt.print_error(str(exc))
|
|
235
|
+
raise SystemExit(1) from exc
|
|
236
|
+
|
|
237
|
+
fmt.print_success(f"Job '{name}' deleted.")
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
# ------------------------------------------------------------------
|
|
241
|
+
# copy
|
|
242
|
+
# ------------------------------------------------------------------
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
@job_group.command("copy")
|
|
246
|
+
@click.argument("from_name")
|
|
247
|
+
@click.argument("new_name")
|
|
248
|
+
@click.pass_context
|
|
249
|
+
def copy_cmd(ctx: click.Context, from_name: str, new_name: str) -> None:
|
|
250
|
+
"""Copy a job to a new name.
|
|
251
|
+
|
|
252
|
+
FROM_NAME is the source job.
|
|
253
|
+
NEW_NAME is the name for the copied job.
|
|
254
|
+
"""
|
|
255
|
+
client = get_client(ctx)
|
|
256
|
+
fmt = get_formatter(ctx)
|
|
257
|
+
|
|
258
|
+
try:
|
|
259
|
+
copy_job(client, from_name, new_name)
|
|
260
|
+
except JenkinsNotFoundError:
|
|
261
|
+
fmt.print_error(f"Source job '{from_name}' not found.")
|
|
262
|
+
raise SystemExit(1)
|
|
263
|
+
except JenkinsError as exc:
|
|
264
|
+
fmt.print_error(str(exc))
|
|
265
|
+
raise SystemExit(1) from exc
|
|
266
|
+
|
|
267
|
+
fmt.print_success(f"Job '{from_name}' copied to '{new_name}'.")
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
# ------------------------------------------------------------------
|
|
271
|
+
# rename
|
|
272
|
+
# ------------------------------------------------------------------
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
@job_group.command("rename")
|
|
276
|
+
@click.argument("old_name")
|
|
277
|
+
@click.argument("new_name")
|
|
278
|
+
@click.pass_context
|
|
279
|
+
def rename_job_cmd(ctx: click.Context, old_name: str, new_name: str) -> None:
|
|
280
|
+
"""Rename a job.
|
|
281
|
+
|
|
282
|
+
OLD_NAME is the current name of the job.
|
|
283
|
+
NEW_NAME is the new name to assign to the job.
|
|
284
|
+
"""
|
|
285
|
+
client = get_client(ctx)
|
|
286
|
+
fmt = get_formatter(ctx)
|
|
287
|
+
|
|
288
|
+
try:
|
|
289
|
+
rename_job(client, old_name, new_name)
|
|
290
|
+
except JenkinsNotFoundError:
|
|
291
|
+
fmt.print_error(f"Job '{old_name}' not found.")
|
|
292
|
+
raise SystemExit(1)
|
|
293
|
+
except JenkinsError as exc:
|
|
294
|
+
fmt.print_error(str(exc))
|
|
295
|
+
raise SystemExit(1) from exc
|
|
296
|
+
|
|
297
|
+
fmt.print_success(f"Job renamed from '{old_name}' to '{new_name}'.")
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
# ------------------------------------------------------------------
|
|
301
|
+
# enable / disable
|
|
302
|
+
# ------------------------------------------------------------------
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
@job_group.command("enable")
|
|
306
|
+
@click.argument("name")
|
|
307
|
+
@click.pass_context
|
|
308
|
+
def enable_cmd(ctx: click.Context, name: str) -> None:
|
|
309
|
+
"""Enable a disabled job."""
|
|
310
|
+
client = get_client(ctx)
|
|
311
|
+
fmt = get_formatter(ctx)
|
|
312
|
+
|
|
313
|
+
try:
|
|
314
|
+
enable_job(client, name)
|
|
315
|
+
except JenkinsNotFoundError:
|
|
316
|
+
fmt.print_error(f"Job '{name}' not found.")
|
|
317
|
+
raise SystemExit(1)
|
|
318
|
+
except JenkinsError as exc:
|
|
319
|
+
fmt.print_error(str(exc))
|
|
320
|
+
raise SystemExit(1) from exc
|
|
321
|
+
|
|
322
|
+
fmt.print_success(f"Job '{name}' enabled.")
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
@job_group.command("disable")
|
|
326
|
+
@click.argument("name")
|
|
327
|
+
@click.pass_context
|
|
328
|
+
def disable_cmd(ctx: click.Context, name: str) -> None:
|
|
329
|
+
"""Disable a job."""
|
|
330
|
+
client = get_client(ctx)
|
|
331
|
+
fmt = get_formatter(ctx)
|
|
332
|
+
|
|
333
|
+
try:
|
|
334
|
+
disable_job(client, name)
|
|
335
|
+
except JenkinsNotFoundError:
|
|
336
|
+
fmt.print_error(f"Job '{name}' not found.")
|
|
337
|
+
raise SystemExit(1)
|
|
338
|
+
except JenkinsError as exc:
|
|
339
|
+
fmt.print_error(str(exc))
|
|
340
|
+
raise SystemExit(1) from exc
|
|
341
|
+
|
|
342
|
+
fmt.print_success(f"Job '{name}' disabled.")
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
# ------------------------------------------------------------------
|
|
346
|
+
# config
|
|
347
|
+
# ------------------------------------------------------------------
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
@job_group.command("config")
|
|
351
|
+
@click.argument("name")
|
|
352
|
+
@click.pass_context
|
|
353
|
+
def config_cmd(ctx: click.Context, name: str) -> None:
|
|
354
|
+
"""Print the XML configuration of a job."""
|
|
355
|
+
client = get_client(ctx)
|
|
356
|
+
fmt = get_formatter(ctx)
|
|
357
|
+
|
|
358
|
+
try:
|
|
359
|
+
xml_text = get_job_config(client, name)
|
|
360
|
+
except JenkinsNotFoundError:
|
|
361
|
+
fmt.print_error(f"Job '{name}' not found.")
|
|
362
|
+
raise SystemExit(1)
|
|
363
|
+
except JenkinsError as exc:
|
|
364
|
+
fmt.print_error(str(exc))
|
|
365
|
+
raise SystemExit(1) from exc
|
|
366
|
+
|
|
367
|
+
fmt.console.print(xml_text)
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
# ------------------------------------------------------------------
|
|
371
|
+
# create-folder
|
|
372
|
+
# ------------------------------------------------------------------
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
@job_group.command("create-folder")
|
|
376
|
+
@click.argument("name")
|
|
377
|
+
@click.pass_context
|
|
378
|
+
def create_folder_cmd(ctx: click.Context, name: str) -> None:
|
|
379
|
+
"""Create a new folder."""
|
|
380
|
+
client = get_client(ctx)
|
|
381
|
+
fmt = get_formatter(ctx)
|
|
382
|
+
|
|
383
|
+
try:
|
|
384
|
+
create_folder(client, name)
|
|
385
|
+
except JenkinsError as exc:
|
|
386
|
+
fmt.print_error(str(exc))
|
|
387
|
+
raise SystemExit(1) from exc
|
|
388
|
+
|
|
389
|
+
fmt.print_success(f"Folder '{name}' created.")
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
# ------------------------------------------------------------------
|
|
393
|
+
# delete-folder
|
|
394
|
+
# ------------------------------------------------------------------
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
@job_group.command("delete-folder")
|
|
398
|
+
@click.argument("name")
|
|
399
|
+
@click.option("--yes", "-y", is_flag=True, help="Skip confirmation prompt.")
|
|
400
|
+
@click.pass_context
|
|
401
|
+
def delete_folder_cmd(ctx: click.Context, name: str, yes: bool) -> None:
|
|
402
|
+
"""Delete a folder and all its contents."""
|
|
403
|
+
if not yes:
|
|
404
|
+
click.confirm(f"Delete folder '{name}' and ALL its contents?", abort=True)
|
|
405
|
+
|
|
406
|
+
client = get_client(ctx)
|
|
407
|
+
fmt = get_formatter(ctx)
|
|
408
|
+
|
|
409
|
+
try:
|
|
410
|
+
delete_folder(client, name)
|
|
411
|
+
except JenkinsError as exc:
|
|
412
|
+
fmt.print_error(str(exc))
|
|
413
|
+
raise SystemExit(1) from exc
|
|
414
|
+
|
|
415
|
+
fmt.print_success(f"Folder '{name}' deleted.")
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
# ------------------------------------------------------------------
|
|
419
|
+
# Registration
|
|
420
|
+
# ------------------------------------------------------------------
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def register(parent_group: click.Group) -> None:
|
|
424
|
+
"""Register the job subgroup under the parent Click group."""
|
|
425
|
+
parent_group.add_command(job_group)
|