jenkinsctl 0.1.0__tar.gz → 1.0.0__tar.gz
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.
Potentially problematic release.
This version of jenkinsctl might be problematic. Click here for more details.
- {jenkinsctl-0.1.0 → jenkinsctl-1.0.0}/PKG-INFO +27 -18
- {jenkinsctl-0.1.0 → jenkinsctl-1.0.0}/README.md +2 -4
- jenkinsctl-1.0.0/jenkinsctl/cli.py +158 -0
- jenkinsctl-1.0.0/jenkinsctl/commands/__init__.py +0 -0
- jenkinsctl-1.0.0/jenkinsctl/commands/build.py +46 -0
- jenkinsctl-1.0.0/jenkinsctl/commands/config.py +15 -0
- jenkinsctl-1.0.0/jenkinsctl/commands/enable_completion.py +22 -0
- jenkinsctl-1.0.0/jenkinsctl/commands/jobs.py +18 -0
- jenkinsctl-1.0.0/jenkinsctl/commands/json.py +14 -0
- jenkinsctl-1.0.0/jenkinsctl/commands/list.py +62 -0
- jenkinsctl-1.0.0/jenkinsctl/commands/logs.py +12 -0
- jenkinsctl-1.0.0/jenkinsctl/commands/rebuild.py +14 -0
- jenkinsctl-1.0.0/jenkinsctl/configs/__init__.py +0 -0
- jenkinsctl-1.0.0/jenkinsctl/configs/logging_config.py +19 -0
- jenkinsctl-1.0.0/jenkinsctl/configs/session.py +21 -0
- jenkinsctl-1.0.0/jenkinsctl/jenkins/cli_helper.py +25 -0
- jenkinsctl-1.0.0/jenkinsctl/jenkins/commons.py +9 -0
- jenkinsctl-1.0.0/jenkinsctl/jenkins/console_util.py +31 -0
- jenkinsctl-1.0.0/jenkinsctl/jenkins/job.py +69 -0
- jenkinsctl-1.0.0/jenkinsctl/jenkins/utils.py +37 -0
- jenkinsctl-1.0.0/pyproject.toml +36 -0
- jenkinsctl-0.1.0/jenkinsctl/commons.py +0 -8
- jenkinsctl-0.1.0/jenkinsctl/jbuild.py +0 -115
- jenkinsctl-0.1.0/jenkinsctl/jenkins.py +0 -53
- jenkinsctl-0.1.0/jenkinsctl/jget_config.py +0 -41
- jenkinsctl-0.1.0/jenkinsctl.egg-info/PKG-INFO +0 -118
- jenkinsctl-0.1.0/jenkinsctl.egg-info/SOURCES.txt +0 -15
- jenkinsctl-0.1.0/jenkinsctl.egg-info/dependency_links.txt +0 -1
- jenkinsctl-0.1.0/jenkinsctl.egg-info/entry_points.txt +0 -2
- jenkinsctl-0.1.0/jenkinsctl.egg-info/requires.txt +0 -4
- jenkinsctl-0.1.0/jenkinsctl.egg-info/top_level.txt +0 -2
- jenkinsctl-0.1.0/pyproject.toml +0 -32
- jenkinsctl-0.1.0/setup.cfg +0 -4
- {jenkinsctl-0.1.0 → jenkinsctl-1.0.0}/LICENSE +0 -0
- {jenkinsctl-0.1.0 → jenkinsctl-1.0.0}/jenkinsctl/__init__.py +0 -0
- {jenkinsctl-0.1.0/jenkinsctl → jenkinsctl-1.0.0/jenkinsctl/configs}/config.py +0 -0
|
@@ -1,24 +1,35 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: jenkinsctl
|
|
3
|
-
Version:
|
|
4
|
-
Summary:
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
Project-URL: Repository, https://github.com/amanshaw4511/jenkinsctl
|
|
8
|
-
Project-URL: Issues, https://github.com/amanshaw4511/jenkinsctl/issues
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A command-line tool to interact with Jenkins jobs 🚀
|
|
5
|
+
Home-page: https://github.com/amanshaw4511/jenkinsctl
|
|
6
|
+
License: GPL-3.0-or-later
|
|
9
7
|
Keywords: jenkins,jenkin
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
Author: Aman Shaw
|
|
9
|
+
Author-email: amanshaw4511@protonmail.com
|
|
10
|
+
Maintainer: Aman Shaw
|
|
11
|
+
Maintainer-email: amanshaw4511@protonmail.com
|
|
12
|
+
Requires-Python: >=3.8,<4.0
|
|
13
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
12
14
|
Classifier: Operating System :: OS Independent
|
|
13
|
-
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Requires-Dist: click (>=8.1.7,<9.0.0)
|
|
22
|
+
Requires-Dist: click-completion (>=0.5.2,<0.6.0)
|
|
23
|
+
Requires-Dist: dynaconf (>=3.2.6,<4.0.0)
|
|
24
|
+
Requires-Dist: python-dateutil (>=2.9.0.post0,<3.0.0)
|
|
25
|
+
Requires-Dist: pyyaml (>=6.0.2,<7.0.0)
|
|
26
|
+
Requires-Dist: requests (>=2.32.3,<3.0.0)
|
|
27
|
+
Requires-Dist: rich (>=13.8.1,<14.0.0)
|
|
28
|
+
Project-URL: Repository, https://github.com/amanshaw4511/jenkinsctl
|
|
14
29
|
Description-Content-Type: text/markdown
|
|
15
|
-
License-File: LICENSE
|
|
16
|
-
Requires-Dist: api4jenkins
|
|
17
|
-
Requires-Dist: dynaconf
|
|
18
|
-
Requires-Dist: pyyaml
|
|
19
|
-
Requires-Dist: argcomplete
|
|
20
30
|
|
|
21
|
-
|
|
31
|
+

|
|
32
|
+
# jenkinsctl [](https://badge.fury.io/py/jenkinsctl) [](https://pepy.tech/project/jenkinsctl) [](https://www.gnu.org/licenses/gpl-3.0)
|
|
22
33
|
Build Jenkins jobs effortlessly using a single command. 🚀
|
|
23
34
|
|
|
24
35
|
## Installation 📦
|
|
@@ -106,9 +117,6 @@ params:
|
|
|
106
117
|
param2: 11
|
|
107
118
|
param3: false
|
|
108
119
|
```
|
|
109
|
-
.. |PyPI-Downloads| image:: https://img.shields.io/pypi/dm/shtab.svg?label=pypi%20downloads&logo=PyPI&logoColor=white
|
|
110
|
-
:target: https://pepy.tech/project/shtab
|
|
111
|
-
:alt: Downloads
|
|
112
120
|
|
|
113
121
|
### Generating Config from Existing Builds
|
|
114
122
|
Capture and reproduce configurations from previous Jenkins builds.
|
|
@@ -116,3 +124,4 @@ To generate a YAML configuration file from a specific build (e.g. 2nd build) of
|
|
|
116
124
|
```sh
|
|
117
125
|
jenkinsctl config my_job 2 > my_job.yaml
|
|
118
126
|
```
|
|
127
|
+
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+

|
|
2
|
+
# jenkinsctl [](https://badge.fury.io/py/jenkinsctl) [](https://pepy.tech/project/jenkinsctl) [](https://www.gnu.org/licenses/gpl-3.0)
|
|
2
3
|
Build Jenkins jobs effortlessly using a single command. 🚀
|
|
3
4
|
|
|
4
5
|
## Installation 📦
|
|
@@ -86,9 +87,6 @@ params:
|
|
|
86
87
|
param2: 11
|
|
87
88
|
param3: false
|
|
88
89
|
```
|
|
89
|
-
.. |PyPI-Downloads| image:: https://img.shields.io/pypi/dm/shtab.svg?label=pypi%20downloads&logo=PyPI&logoColor=white
|
|
90
|
-
:target: https://pepy.tech/project/shtab
|
|
91
|
-
:alt: Downloads
|
|
92
90
|
|
|
93
91
|
### Generating Config from Existing Builds
|
|
94
92
|
Capture and reproduce configurations from previous Jenkins builds.
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from io import TextIOWrapper
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
import click_completion
|
|
7
|
+
|
|
8
|
+
from jenkinsctl.commands.build import build_handler
|
|
9
|
+
from jenkinsctl.commands.config import config_handler
|
|
10
|
+
from jenkinsctl.commands.enable_completion import handle_enable_completion
|
|
11
|
+
from jenkinsctl.commands.jobs import jobs_handler
|
|
12
|
+
from jenkinsctl.commands.json import json_handler
|
|
13
|
+
from jenkinsctl.commands.list import list_handler
|
|
14
|
+
from jenkinsctl.commands.logs import logs_handler
|
|
15
|
+
from jenkinsctl.commands.rebuild import rebuild_handler
|
|
16
|
+
from jenkinsctl.configs.logging_config import setup_logging
|
|
17
|
+
from jenkinsctl.jenkins.cli_helper import error_handler_and_session
|
|
18
|
+
|
|
19
|
+
click_completion.init()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@click.group()
|
|
23
|
+
@click.option('-v', '--verbose', is_flag=True, help="Enable verbose output")
|
|
24
|
+
@click.pass_context
|
|
25
|
+
def cli(ctx: click.Context, verbose: bool) -> None:
|
|
26
|
+
"""A command-line tool to interact with Jenkins jobs"""
|
|
27
|
+
log_level: int = logging.DEBUG if verbose else logging.INFO
|
|
28
|
+
ctx.logger = setup_logging(log_level)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@cli.command("list")
|
|
32
|
+
@click.argument("job_name")
|
|
33
|
+
@click.option("-n", "--number", default=5, type=int, help="No of builds to list (default: 5)")
|
|
34
|
+
def list_command(job_name: str, number: int) -> None:
|
|
35
|
+
"""
|
|
36
|
+
List all builds of a Jenkins job
|
|
37
|
+
|
|
38
|
+
\b
|
|
39
|
+
JOB_NAME: Name of the Jenkins job
|
|
40
|
+
"""
|
|
41
|
+
with error_handler_and_session() as session:
|
|
42
|
+
list_handler(session, job_name, number)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@cli.command("logs")
|
|
46
|
+
@click.argument("job_name")
|
|
47
|
+
@click.argument("build_no", required=False, type=int)
|
|
48
|
+
def logs_command(job_name: str, build_no: Optional[int]) -> None:
|
|
49
|
+
"""
|
|
50
|
+
Print logs of a Jenkins build
|
|
51
|
+
|
|
52
|
+
\b
|
|
53
|
+
JOB_NAME: Name of the Jenkins job
|
|
54
|
+
BUILD_NO: Build number (default: last build)
|
|
55
|
+
"""
|
|
56
|
+
with error_handler_and_session() as session:
|
|
57
|
+
logs_handler(session, job_name, build_no)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@cli.command("json")
|
|
61
|
+
@click.argument("job_name")
|
|
62
|
+
@click.argument("build_no", required=False, type=int)
|
|
63
|
+
def json_command(job_name: str, build_no: Optional[int]) -> None:
|
|
64
|
+
"""
|
|
65
|
+
Fetch and print the JSON API response of a Jenkins build
|
|
66
|
+
|
|
67
|
+
\b
|
|
68
|
+
JOB_NAME: Name of the Jenkins job
|
|
69
|
+
BUILD_NO: Build number (default: last build)
|
|
70
|
+
"""
|
|
71
|
+
with error_handler_and_session() as session:
|
|
72
|
+
json_handler(session, job_name, build_no)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@cli.command("config")
|
|
76
|
+
@click.argument("job_name")
|
|
77
|
+
@click.argument("build_no", required=False, type=int)
|
|
78
|
+
def config_command(job_name: str, build_no: Optional[int]) -> None:
|
|
79
|
+
"""
|
|
80
|
+
Get the configuration of a specific build in YAML format
|
|
81
|
+
|
|
82
|
+
\b
|
|
83
|
+
JOB_NAME: Name of the Jenkins job
|
|
84
|
+
BUILD_NO: Build number (default: last build)
|
|
85
|
+
"""
|
|
86
|
+
with error_handler_and_session() as session:
|
|
87
|
+
config_handler(session, job_name, build_no)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@cli.command("rebuild")
|
|
91
|
+
@click.argument("job_name")
|
|
92
|
+
@click.argument("build_no", required=False, type=int)
|
|
93
|
+
def rebuild_command(job_name: str, build_no: Optional[int]) -> None:
|
|
94
|
+
"""
|
|
95
|
+
Rebuild a specific Jenkins job
|
|
96
|
+
|
|
97
|
+
\b
|
|
98
|
+
JOB_NAME: Name of the Jenkins job
|
|
99
|
+
BUILD_NO: Build number (default: last build)
|
|
100
|
+
"""
|
|
101
|
+
with error_handler_and_session() as session:
|
|
102
|
+
rebuild_handler(session, job_name, build_no)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@cli.command("build")
|
|
106
|
+
@click.option("-p", "--param", multiple=True,
|
|
107
|
+
help="Override parameters in the YAML configuration (e.g., --param key=value)")
|
|
108
|
+
@click.option("-f", "--file", type=click.File('r'), required=True, help="YAML configuration file for the Jenkins job")
|
|
109
|
+
def build_command(file: TextIOWrapper, param: tuple[str]) -> None:
|
|
110
|
+
"""
|
|
111
|
+
Trigger a new Jenkins build
|
|
112
|
+
"""
|
|
113
|
+
params = [p for p in param]
|
|
114
|
+
with error_handler_and_session() as session:
|
|
115
|
+
build_handler(session, file, params)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@cli.command("enable-completion")
|
|
119
|
+
@click.argument('shell', required=False)
|
|
120
|
+
def enable_completion(shell: str):
|
|
121
|
+
"""
|
|
122
|
+
Enable shell autocompletion for jenkinsctl.
|
|
123
|
+
|
|
124
|
+
This command allows you to set up autocompletion for the jenkinsctl command-line tool,
|
|
125
|
+
making it easier to use by providing suggestions as you type.
|
|
126
|
+
|
|
127
|
+
\b
|
|
128
|
+
SHELL: Specify a shell for which to enable autocompletion.
|
|
129
|
+
Supported options include:
|
|
130
|
+
- bash
|
|
131
|
+
- zsh
|
|
132
|
+
- fish
|
|
133
|
+
|
|
134
|
+
If no shell is specified, it will attempt to autodetect your shell.
|
|
135
|
+
"""
|
|
136
|
+
if shell is None:
|
|
137
|
+
shell = click_completion.core.get_auto_shell() # Detect the current shell
|
|
138
|
+
|
|
139
|
+
handle_enable_completion(shell)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@cli.command("jobs")
|
|
143
|
+
@click.argument("folder_name", default="")
|
|
144
|
+
def jobs_command(folder_name: str) -> None:
|
|
145
|
+
"""
|
|
146
|
+
List all jobs in a Jenkins folder.
|
|
147
|
+
|
|
148
|
+
\b
|
|
149
|
+
FOLDER_NAME: Path of the folder to list jobs (e.g., projectA/subProjectX).
|
|
150
|
+
If no folder is specified, it lists jobs in the root folder.
|
|
151
|
+
"""
|
|
152
|
+
with error_handler_and_session() as session:
|
|
153
|
+
jobs_handler(session, folder_name)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
# Entry point
|
|
157
|
+
if __name__ == '__main__':
|
|
158
|
+
cli()
|
|
File without changes
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import sys
|
|
3
|
+
from io import TextIOWrapper
|
|
4
|
+
from typing import List
|
|
5
|
+
|
|
6
|
+
import yaml
|
|
7
|
+
|
|
8
|
+
from jenkinsctl.configs.session import Session
|
|
9
|
+
from jenkinsctl.jenkins.console_util import json_preety
|
|
10
|
+
from jenkinsctl.jenkins.job import build_job
|
|
11
|
+
|
|
12
|
+
log = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_config_from_yaml(file):
|
|
16
|
+
config_data = None
|
|
17
|
+
try:
|
|
18
|
+
with open(file.name, "r") as config_file:
|
|
19
|
+
config_data = yaml.safe_load(config_file) # Use safe_load for YAML
|
|
20
|
+
except FileNotFoundError:
|
|
21
|
+
print(f"Error: Configuration file '{file}' not found.")
|
|
22
|
+
sys.exit()
|
|
23
|
+
except yaml.YAMLError as e:
|
|
24
|
+
print(f"Error: Invalid YAML format in '{file}': {e}")
|
|
25
|
+
sys.exit()
|
|
26
|
+
|
|
27
|
+
return config_data
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def get_conf(file, params):
|
|
31
|
+
config = get_config_from_yaml(file)
|
|
32
|
+
override_params(params, config)
|
|
33
|
+
return config
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def override_params(params, file_config):
|
|
37
|
+
for param in params:
|
|
38
|
+
name, value = param.split('=')
|
|
39
|
+
file_config['params'][name] = value
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def build_handler(session: Session, file: TextIOWrapper, params: List[str]):
|
|
43
|
+
conf = get_conf(file, params)
|
|
44
|
+
log.debug(f"config: {json_preety(conf)}")
|
|
45
|
+
location = build_job(session, conf["job"], conf["params"])
|
|
46
|
+
print(location)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from jenkinsctl.configs.session import Session
|
|
4
|
+
from jenkinsctl.jenkins.commons import get_last_build_no_if_none
|
|
5
|
+
from jenkinsctl.jenkins.job import get_build
|
|
6
|
+
from jenkinsctl.jenkins.utils import get_build_params, to_yaml, normalize_job_path
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def config_handler(session: Session, job_name: str, build_no: Optional[int]):
|
|
10
|
+
job_name = normalize_job_path(job_name)
|
|
11
|
+
build_no = get_last_build_no_if_none(session, job_name, build_no)
|
|
12
|
+
|
|
13
|
+
build = get_build(session, job_name, build_no)
|
|
14
|
+
params = get_build_params(build)
|
|
15
|
+
print(to_yaml(job_name, params))
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
def handle_enable_completion(shell: str):
|
|
2
|
+
if shell == "bash":
|
|
3
|
+
print("\n# To enable autocompletion for bash, add the following to your ~/.bashrc:")
|
|
4
|
+
print("echo 'eval \"$(_JENKINSCTL_COMPLETE=bash_source jenkinsctl)\"' >> ~/.bashrc")
|
|
5
|
+
print("\n# Then reload your bash configuration:")
|
|
6
|
+
print("source ~/.bashrc")
|
|
7
|
+
|
|
8
|
+
elif shell == "zsh":
|
|
9
|
+
print("\n# To enable autocompletion for zsh, add the following to your ~/.zshrc:")
|
|
10
|
+
print("echo 'eval \"$(_JENKINSCTL_COMPLETE=zsh_source jenkinsctl)\"' >> ~/.zshrc")
|
|
11
|
+
print("\n# Then reload your zsh configuration:")
|
|
12
|
+
print("source ~/.zshrc")
|
|
13
|
+
|
|
14
|
+
elif shell == "fish":
|
|
15
|
+
print(
|
|
16
|
+
"\n# To enable autocompletion for fish, add the following to your Fish config (~/.config/fish/config.fish):")
|
|
17
|
+
print("echo 'eval (jenkinsctl enable-completion fish | source)' >> ~/.config/fish/config.fish")
|
|
18
|
+
print("\n# Then reload your fish shell configuration:")
|
|
19
|
+
print("source ~/.config/fish/config.fish")
|
|
20
|
+
|
|
21
|
+
else:
|
|
22
|
+
print("\n# Unsupported shell. Please specify bash, zsh, or fish.")
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from jenkinsctl.configs.session import Session
|
|
2
|
+
from jenkinsctl.jenkins.job import get_jobs
|
|
3
|
+
from jenkinsctl.jenkins.utils import normalize_job_path
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def jobs_handler(session: Session, folder_name: str):
|
|
7
|
+
folder_name = normalize_job_path(folder_name)
|
|
8
|
+
response = get_jobs(session, folder_name)
|
|
9
|
+
all_jobs = response["jobs"]
|
|
10
|
+
|
|
11
|
+
folders = [job for job in all_jobs if job["_class"] == "com.cloudbees.hudson.plugins.folder.Folder"]
|
|
12
|
+
jobs = [job for job in all_jobs if job["_class"] != "com.cloudbees.hudson.plugins.folder.Folder"]
|
|
13
|
+
|
|
14
|
+
for folder in folders:
|
|
15
|
+
print(f"[{folder["name"]}]")
|
|
16
|
+
|
|
17
|
+
for job in jobs:
|
|
18
|
+
print(f"{job["name"]}")
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from jenkinsctl.configs.session import Session
|
|
4
|
+
from jenkinsctl.jenkins.commons import get_last_build_no_if_none
|
|
5
|
+
from jenkinsctl.jenkins.console_util import print_json
|
|
6
|
+
from jenkinsctl.jenkins.job import progressive_log, get_build
|
|
7
|
+
from jenkinsctl.jenkins.utils import normalize_job_path
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def json_handler(session: Session, job_name: str, build_no: Optional[int]):
|
|
11
|
+
job_name = normalize_job_path(job_name)
|
|
12
|
+
build_no = get_last_build_no_if_none(session, job_name, build_no)
|
|
13
|
+
build = get_build(session, job_name, build_no)
|
|
14
|
+
print_json(build)
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from itertools import islice
|
|
2
|
+
|
|
3
|
+
from rich.console import Console
|
|
4
|
+
from rich.table import Table
|
|
5
|
+
|
|
6
|
+
from jenkinsctl.configs.session import Session
|
|
7
|
+
from jenkinsctl.jenkins.console_util import format_timestamp
|
|
8
|
+
from jenkinsctl.jenkins.job import get_job, get_builds_iter
|
|
9
|
+
from jenkinsctl.jenkins.utils import normalize_job_path
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def list_handler(session: Session, job_name: str, number: int):
|
|
13
|
+
job_name = normalize_job_path(job_name)
|
|
14
|
+
job = get_job(session, job_name)
|
|
15
|
+
|
|
16
|
+
console = Console()
|
|
17
|
+
table = Table(show_header=True, header_style="bold cyan", box=None)
|
|
18
|
+
|
|
19
|
+
table.add_column("NUMBER", width=6)
|
|
20
|
+
table.add_column("IN PROGRESS", width=15)
|
|
21
|
+
table.add_column("RESULT", width=10)
|
|
22
|
+
table.add_column("BUILDING", width=8)
|
|
23
|
+
table.add_column("BRANCH", width=15)
|
|
24
|
+
table.add_column("REVISION", width=8)
|
|
25
|
+
table.add_column("STARTED BY", width=15)
|
|
26
|
+
table.add_column("BUILD TIME", width=20)
|
|
27
|
+
|
|
28
|
+
for build in islice(get_builds_iter(session, job), number):
|
|
29
|
+
number = build["number"]
|
|
30
|
+
building = build["building"]
|
|
31
|
+
in_progress = build["inProgress"]
|
|
32
|
+
result = build["result"]
|
|
33
|
+
timestamp = build["timestamp"]
|
|
34
|
+
|
|
35
|
+
actions = build["actions"]
|
|
36
|
+
causes = next((action["causes"] for action in actions if action.get("causes") is not None), None)
|
|
37
|
+
user_id = next((cause["userId"] for cause in causes if cause.get("userId") is not None), None)
|
|
38
|
+
|
|
39
|
+
branch = None
|
|
40
|
+
revision = None
|
|
41
|
+
# fixme: not working below code
|
|
42
|
+
# last_build = next((action["lastBuildRevision"] for action in actions if action.get("lastBuildRevision") is not None))
|
|
43
|
+
last_build = None
|
|
44
|
+
if last_build:
|
|
45
|
+
branch_prefix = "refs/remotes/origin/"
|
|
46
|
+
branch = last_build["branch"][0]["name"]
|
|
47
|
+
if branch.startswith(branch_prefix):
|
|
48
|
+
branch = branch[len(branch_prefix):]
|
|
49
|
+
revision = last_build["SHA1"][:5]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
table.add_row(
|
|
53
|
+
str(number),
|
|
54
|
+
str(in_progress),
|
|
55
|
+
str(result),
|
|
56
|
+
str(building),
|
|
57
|
+
branch,
|
|
58
|
+
revision,
|
|
59
|
+
user_id,
|
|
60
|
+
str(format_timestamp(timestamp)))
|
|
61
|
+
|
|
62
|
+
console.print(table)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from jenkinsctl.configs.session import Session
|
|
4
|
+
from jenkinsctl.jenkins.commons import get_last_build_no_if_none
|
|
5
|
+
from jenkinsctl.jenkins.job import progressive_log
|
|
6
|
+
from jenkinsctl.jenkins.utils import normalize_job_path
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def logs_handler(session: Session, job_name: str, build_no: Optional[int]):
|
|
10
|
+
job_name = normalize_job_path(job_name)
|
|
11
|
+
build_no = get_last_build_no_if_none(session, job_name, build_no)
|
|
12
|
+
progressive_log(session, job_name, build_no)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from jenkinsctl.configs.session import Session
|
|
4
|
+
from jenkinsctl.jenkins.commons import get_last_build_no_if_none
|
|
5
|
+
from jenkinsctl.jenkins.job import get_build, build_job
|
|
6
|
+
from jenkinsctl.jenkins.utils import get_build_params, normalize_job_path
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def rebuild_handler(session: Session, job_name: str, build_no: Optional[int]):
|
|
10
|
+
job_name = normalize_job_path(job_name)
|
|
11
|
+
build_no = get_last_build_no_if_none(session, job_name, build_no)
|
|
12
|
+
build = get_build(session, job_name, build_no)
|
|
13
|
+
location = build_job(session, job_name, get_build_params(build))
|
|
14
|
+
print(location)
|
|
File without changes
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
def setup_logging(log_level=logging.INFO):
|
|
5
|
+
logger = logging.getLogger()
|
|
6
|
+
logger.setLevel(log_level)
|
|
7
|
+
|
|
8
|
+
console_handler = logging.StreamHandler(sys.stdout)
|
|
9
|
+
console_handler.setLevel(log_level)
|
|
10
|
+
console_handler.setFormatter(logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s"))
|
|
11
|
+
|
|
12
|
+
file_handler = logging.FileHandler('jenkinsctl.log')
|
|
13
|
+
file_handler.setLevel(log_level)
|
|
14
|
+
file_handler.setFormatter(logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s"))
|
|
15
|
+
|
|
16
|
+
logger.addHandler(console_handler)
|
|
17
|
+
logger.addHandler(file_handler)
|
|
18
|
+
|
|
19
|
+
return logger
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from urllib.parse import urljoin
|
|
2
|
+
|
|
3
|
+
import requests
|
|
4
|
+
import logging
|
|
5
|
+
|
|
6
|
+
logger = logging.getLogger(__name__)
|
|
7
|
+
|
|
8
|
+
class Session(requests.Session):
|
|
9
|
+
def __init__(self, base_url: str):
|
|
10
|
+
super().__init__()
|
|
11
|
+
self.base_url = base_url
|
|
12
|
+
|
|
13
|
+
def request(
|
|
14
|
+
self,
|
|
15
|
+
method,
|
|
16
|
+
url,
|
|
17
|
+
**kwargs
|
|
18
|
+
):
|
|
19
|
+
joined_url = urljoin(self.base_url, url)
|
|
20
|
+
return super().request(method, joined_url, **kwargs)
|
|
21
|
+
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from contextlib import contextmanager
|
|
3
|
+
|
|
4
|
+
from jenkinsctl.configs.config import settings
|
|
5
|
+
from jenkinsctl.configs.session import Session
|
|
6
|
+
|
|
7
|
+
log = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
@contextmanager
|
|
10
|
+
def error_handler_and_session():
|
|
11
|
+
server_url: str = settings.server_url
|
|
12
|
+
username: str = settings.username
|
|
13
|
+
api_key: str = settings.api_key
|
|
14
|
+
|
|
15
|
+
session = None
|
|
16
|
+
try:
|
|
17
|
+
session = Session(server_url)
|
|
18
|
+
session.auth = (username, api_key)
|
|
19
|
+
yield session
|
|
20
|
+
except Exception as e:
|
|
21
|
+
log.debug(f"An error occurred", exc_info=e )
|
|
22
|
+
print(f"An error occurred, run same command with --verbose flag to print error details")
|
|
23
|
+
finally:
|
|
24
|
+
if session:
|
|
25
|
+
session.close()
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from jenkinsctl.jenkins.job import get_job
|
|
2
|
+
from jenkinsctl.jenkins.utils import get_last_build
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def get_last_build_no_if_none(session, job_name, build_no):
|
|
6
|
+
if build_no is None:
|
|
7
|
+
job = get_job(session, job_name)
|
|
8
|
+
return get_last_build(job)
|
|
9
|
+
return build_no
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from rich.console import Console
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from dateutil import tz
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_console():
|
|
9
|
+
return Console()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def format_timestamp(epoch_timestamp):
|
|
13
|
+
# Convert epoch timestamp to a naive datetime object
|
|
14
|
+
naive_dt = datetime.fromtimestamp(epoch_timestamp / 1000)
|
|
15
|
+
|
|
16
|
+
# Get the local timezone
|
|
17
|
+
local_tz = tz.tzlocal()
|
|
18
|
+
|
|
19
|
+
# Convert naive datetime to local timezone
|
|
20
|
+
local_dt = naive_dt.astimezone(local_tz)
|
|
21
|
+
|
|
22
|
+
# Format the datetime object to a string
|
|
23
|
+
return local_dt.strftime('%Y-%m-%d %H:%M')
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def print_json(dict_obj):
|
|
27
|
+
json_str = json.dumps(dict_obj)
|
|
28
|
+
get_console().print_json(json_str)
|
|
29
|
+
|
|
30
|
+
def json_preety(dict_obj):
|
|
31
|
+
return json.dumps(dict_obj, indent=2)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import time
|
|
2
|
+
|
|
3
|
+
from urllib.parse import urlparse
|
|
4
|
+
|
|
5
|
+
from jenkinsctl.configs.session import Session
|
|
6
|
+
|
|
7
|
+
def _remove_base_url(url: str):
|
|
8
|
+
return urlparse(url).path
|
|
9
|
+
|
|
10
|
+
def _get(session: Session, url: str):
|
|
11
|
+
url = _remove_base_url(url)
|
|
12
|
+
url = f"{url}api/json"
|
|
13
|
+
return session.get(url).json()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def build_job(session: Session, job_name: str, params: dict):
|
|
17
|
+
response = None
|
|
18
|
+
if len(params) == 0:
|
|
19
|
+
url = f"/job/{job_name}/build"
|
|
20
|
+
response = session.post(url)
|
|
21
|
+
else:
|
|
22
|
+
url = f"/job/{job_name}/buildWithParameters"
|
|
23
|
+
response = session.post(url, params=params)
|
|
24
|
+
|
|
25
|
+
return response.headers.get("Location")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_job(session: Session, job_name: str):
|
|
29
|
+
url = f"/job/{job_name}/"
|
|
30
|
+
return _get(session, url)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def get_jobs(session: Session, folder_name: str):
|
|
34
|
+
if folder_name.strip() == "":
|
|
35
|
+
return _get(session, "")
|
|
36
|
+
|
|
37
|
+
url = f"/job/{folder_name}/"
|
|
38
|
+
return _get(session, url)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def get_builds_iter(session: Session, job_json):
|
|
42
|
+
builds = job_json["builds"]
|
|
43
|
+
for build in builds:
|
|
44
|
+
yield _get_build(session, job_json, build["number"])
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _get_build(session: Session, job_json, build_no):
|
|
48
|
+
builds = job_json["builds"]
|
|
49
|
+
build = next((build for build in builds if build["number"] == build_no), None)
|
|
50
|
+
|
|
51
|
+
return _get(session, build["url"])
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def get_build(session: Session, job_name: str, build_no: int):
|
|
55
|
+
url = f"/job/{job_name}/{build_no}/"
|
|
56
|
+
return _get(session, url)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def progressive_log(session: Session, job_name: str, build_no: int):
|
|
60
|
+
url = f"/job/{job_name}/{build_no}/logText/progressiveText"
|
|
61
|
+
start_byte = 0
|
|
62
|
+
while True:
|
|
63
|
+
response = session.get(url, params={'start': start_byte})
|
|
64
|
+
text = response.text
|
|
65
|
+
print(text, end="")
|
|
66
|
+
start_byte = int(response.headers.get('X-Text-Size', 0))
|
|
67
|
+
if response.headers.get('X-More-Data') == 'false' or text.strip() == "":
|
|
68
|
+
break
|
|
69
|
+
time.sleep(2)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import yaml
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def get_build_params(build_json):
|
|
5
|
+
actions = build_json["actions"]
|
|
6
|
+
params = next((action["parameters"] for action in actions if action.get("parameters") is not None), [])
|
|
7
|
+
params = dict([(param["name"], param["value"]) for param in params])
|
|
8
|
+
return params
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def normalize_job_path(path: str):
|
|
12
|
+
return (path.strip()
|
|
13
|
+
.removeprefix("/")
|
|
14
|
+
.replace("/", "/job/"))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def print_build(build_json):
|
|
18
|
+
number = build_json["number"]
|
|
19
|
+
building = build_json["building"]
|
|
20
|
+
in_progress = build_json["inProgress"]
|
|
21
|
+
result = build_json["result"]
|
|
22
|
+
timestamp = build_json["timestamp"]
|
|
23
|
+
|
|
24
|
+
actions = build_json["actions"]
|
|
25
|
+
causes = next((action["causes"] for action in actions if action.get("causes") is not None), None)
|
|
26
|
+
user_id = next((cause["userId"] for cause in causes if cause.get("userId") is not None), None)
|
|
27
|
+
|
|
28
|
+
print(number, building, in_progress, result, timestamp, user_id)
|
|
29
|
+
print(get_build_params(build_json))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def get_last_build(job) -> int:
|
|
33
|
+
return job["lastBuild"]["number"]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def to_yaml(job_name, params):
|
|
37
|
+
return yaml.dump({"job": job_name, "params": params})
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "jenkinsctl"
|
|
3
|
+
version = "1.0.0"
|
|
4
|
+
description = "A command-line tool to interact with Jenkins jobs 🚀"
|
|
5
|
+
authors = ["Aman Shaw <amanshaw4511@protonmail.com>"]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
license = "GPL-3.0-or-later"
|
|
8
|
+
maintainers = [
|
|
9
|
+
"Aman Shaw <amanshaw4511@protonmail.com>"
|
|
10
|
+
]
|
|
11
|
+
homepage = "https://github.com/amanshaw4511/jenkinsctl"
|
|
12
|
+
repository = "https://github.com/amanshaw4511/jenkinsctl"
|
|
13
|
+
keywords = ["jenkins", "jenkin"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Operating System :: OS Independent",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[tool.poetry.dependencies]
|
|
20
|
+
python = "^3.8"
|
|
21
|
+
pyyaml = "^6.0.2"
|
|
22
|
+
dynaconf = "^3.2.6"
|
|
23
|
+
rich = "^13.8.1"
|
|
24
|
+
python-dateutil = "^2.9.0.post0"
|
|
25
|
+
requests = "^2.32.3"
|
|
26
|
+
click = "^8.1.7"
|
|
27
|
+
click-completion = "^0.5.2"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
[build-system]
|
|
31
|
+
requires = ["poetry-core"]
|
|
32
|
+
build-backend = "poetry.core.masonry.api"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
[tool.poetry.scripts]
|
|
36
|
+
jenkinsctl = "jenkinsctl.cli:cli"
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
# PYTHON_ARGCOMPLETE_OK
|
|
3
|
-
|
|
4
|
-
import time
|
|
5
|
-
import sys
|
|
6
|
-
import yaml
|
|
7
|
-
from threading import Thread
|
|
8
|
-
|
|
9
|
-
from .commons import use_vprint
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def handle_build_command(args):
|
|
13
|
-
vprint = use_vprint(args.verbose)
|
|
14
|
-
vprint(f"Passed args : {vars(args)}")
|
|
15
|
-
|
|
16
|
-
conf = get_conf(args, vprint)
|
|
17
|
-
vprint("Final config: ", conf)
|
|
18
|
-
|
|
19
|
-
create_build(args.client, conf, args.suppress_logs, vprint)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def get_config_from_yaml(file):
|
|
23
|
-
config_data = None
|
|
24
|
-
try:
|
|
25
|
-
with open(file.name, "r") as config_file:
|
|
26
|
-
config_data = yaml.safe_load(config_file) # Use safe_load for YAML
|
|
27
|
-
except FileNotFoundError:
|
|
28
|
-
print(f"Error: Configuration file '{file}' not found.")
|
|
29
|
-
sys.exit()
|
|
30
|
-
except yaml.YAMLError as e:
|
|
31
|
-
print(f"Error: Invalid YAML format in '{file}': {e}")
|
|
32
|
-
sys.exit()
|
|
33
|
-
|
|
34
|
-
return config_data
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def get_conf(args, vprint):
|
|
38
|
-
config = get_config_from_yaml(args.file)
|
|
39
|
-
vprint(f"Config from file : {config}")
|
|
40
|
-
override_params(args, config)
|
|
41
|
-
return config
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def override_params(args, file_config):
|
|
45
|
-
for param in args.param:
|
|
46
|
-
name, value = param.split('=')
|
|
47
|
-
file_config['params'][name] = value
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def get_build(queued_item):
|
|
51
|
-
build = queued_item.get_build()
|
|
52
|
-
while build == None:
|
|
53
|
-
time.sleep(2)
|
|
54
|
-
build = queued_item.get_build()
|
|
55
|
-
return build
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def get_build_no(queued_item):
|
|
59
|
-
return queued_item.api_json()["executable"]["number"]
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
def print_build_log(build):
|
|
63
|
-
for line in build.progressive_output():
|
|
64
|
-
print(line)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
def approve_pending_input(build):
|
|
68
|
-
if not hasattr(build, 'get_pending_input'):
|
|
69
|
-
return
|
|
70
|
-
|
|
71
|
-
while not build.get_pending_input() and build.building:
|
|
72
|
-
time.sleep(1)
|
|
73
|
-
|
|
74
|
-
if build.building:
|
|
75
|
-
build.get_pending_input().submit()
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
def create_build(client, conf, suppress_logs: bool, vprint=print):
|
|
79
|
-
|
|
80
|
-
client = client()
|
|
81
|
-
vprint(f"client version: {client.version}")
|
|
82
|
-
|
|
83
|
-
job = client.get_job(conf["job"])
|
|
84
|
-
vprint(f"job : {job}")
|
|
85
|
-
|
|
86
|
-
queued_item = job.build(**conf["params"])
|
|
87
|
-
vprint(f"queued : {queued_item}")
|
|
88
|
-
|
|
89
|
-
build = get_build(queued_item)
|
|
90
|
-
vprint(f"build : {build}")
|
|
91
|
-
|
|
92
|
-
build_number = get_build_no(queued_item)
|
|
93
|
-
|
|
94
|
-
if not suppress_logs:
|
|
95
|
-
print(f"STARTED... build number : {build_number}")
|
|
96
|
-
|
|
97
|
-
approve_pending_input_thread = Thread(
|
|
98
|
-
target=approve_pending_input, args=(build,))
|
|
99
|
-
approve_pending_input_thread.start()
|
|
100
|
-
|
|
101
|
-
if not suppress_logs:
|
|
102
|
-
log_thread = Thread(target=print_build_log, args=(build,))
|
|
103
|
-
log_thread.start()
|
|
104
|
-
log_thread.join()
|
|
105
|
-
|
|
106
|
-
approve_pending_input_thread.join()
|
|
107
|
-
|
|
108
|
-
if build.result == "FAILURE":
|
|
109
|
-
print(f"FAILED... build number : {build_number}")
|
|
110
|
-
sys.exit(1)
|
|
111
|
-
|
|
112
|
-
if not suppress_logs:
|
|
113
|
-
print(f"FINISHED... build number : {build_number}")
|
|
114
|
-
else:
|
|
115
|
-
print(build_number)
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
# PYTHON_ARGCOMPLETE_OK
|
|
3
|
-
|
|
4
|
-
import argparse
|
|
5
|
-
import argcomplete
|
|
6
|
-
from .jbuild import handle_build_command
|
|
7
|
-
from api4jenkins import Jenkins
|
|
8
|
-
from .config import settings
|
|
9
|
-
from .jget_config import handle_get_config
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
server_url = settings.server_url
|
|
13
|
-
username = settings.username
|
|
14
|
-
api_key = settings.api_key
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def get_client():
|
|
18
|
-
return Jenkins(server_url, auth=(username, api_key))
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def get_args():
|
|
22
|
-
parser = argparse.ArgumentParser()
|
|
23
|
-
subparsers = parser.add_subparsers(title="Subcommand", dest="subcommand")
|
|
24
|
-
|
|
25
|
-
add_build_subparser(subparsers)
|
|
26
|
-
add_get_config_subparser(subparsers)
|
|
27
|
-
|
|
28
|
-
argcomplete.autocomplete(parser)
|
|
29
|
-
args = parser.parse_args()
|
|
30
|
-
args.func(args)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def add_build_subparser(subparsers):
|
|
34
|
-
subparser = subparsers.add_parser("build", help="run new build")
|
|
35
|
-
subparser.set_defaults(func=handle_build_command, client=get_client)
|
|
36
|
-
subparser.add_argument(
|
|
37
|
-
"-f", "--file", type=argparse.FileType("r"), help="Yaml configuration file"
|
|
38
|
-
)
|
|
39
|
-
subparser.add_argument("-v", "--verbose", action="store_true")
|
|
40
|
-
subparser.add_argument("-s", "--suppress-logs", action="store_true")
|
|
41
|
-
subparser.add_argument("--param", action="append", default=[])
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def add_get_config_subparser(subparsers):
|
|
45
|
-
subparser = subparsers.add_parser("config", help="get config of a build")
|
|
46
|
-
subparser.set_defaults(func=handle_get_config, client=get_client)
|
|
47
|
-
subparser.add_argument("job_name")
|
|
48
|
-
subparser.add_argument("build_no", type=int, nargs="?")
|
|
49
|
-
subparser.add_argument("-v", "--verbose", action="store_true")
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if __name__ == '__main__':
|
|
53
|
-
get_args()
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import yaml
|
|
2
|
-
|
|
3
|
-
from .commons import use_vprint
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def handle_get_config(args):
|
|
7
|
-
vprint = use_vprint(args.verbose)
|
|
8
|
-
vprint(f"Passed args : {vars(args)}")
|
|
9
|
-
|
|
10
|
-
client = args.client()
|
|
11
|
-
|
|
12
|
-
job_name = args.job_name
|
|
13
|
-
|
|
14
|
-
build = get_build(client, vprint, job_name, args.build_no)
|
|
15
|
-
|
|
16
|
-
params = get_params(build)
|
|
17
|
-
|
|
18
|
-
print(to_yaml(job_name, params))
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def get_build(client, vprint, job_name, build_no):
|
|
22
|
-
job = client.get_job(job_name)
|
|
23
|
-
|
|
24
|
-
final_build_no = get_last_build_no(job) if build_no is None else build_no
|
|
25
|
-
vprint(f"final build no {final_build_no}")
|
|
26
|
-
|
|
27
|
-
build = job.get(final_build_no)
|
|
28
|
-
|
|
29
|
-
return build
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def get_last_build_no(job):
|
|
33
|
-
return job.get_last_build().number
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def get_params(build):
|
|
37
|
-
return dict([(param.name, param.value) for param in build.get_parameters()])
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def to_yaml(job_name, params):
|
|
41
|
-
return yaml.dump({"job": job_name, "params": params})
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: jenkinsctl
|
|
3
|
-
Version: 0.1.0
|
|
4
|
-
Summary: Build Jenkins jobs effortlessly using a single command. 🚀
|
|
5
|
-
Author-email: Aman Shaw <amanshaw4511@protonmail.com>
|
|
6
|
-
Project-URL: Homepage, https://github.com/amanshaw4511/jenkinsctl
|
|
7
|
-
Project-URL: Repository, https://github.com/amanshaw4511/jenkinsctl
|
|
8
|
-
Project-URL: Issues, https://github.com/amanshaw4511/jenkinsctl/issues
|
|
9
|
-
Keywords: jenkins,jenkin
|
|
10
|
-
Classifier: Programming Language :: Python :: 3
|
|
11
|
-
Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)
|
|
12
|
-
Classifier: Operating System :: OS Independent
|
|
13
|
-
Requires-Python: >=3.8
|
|
14
|
-
Description-Content-Type: text/markdown
|
|
15
|
-
License-File: LICENSE
|
|
16
|
-
Requires-Dist: api4jenkins
|
|
17
|
-
Requires-Dist: dynaconf
|
|
18
|
-
Requires-Dist: pyyaml
|
|
19
|
-
Requires-Dist: argcomplete
|
|
20
|
-
|
|
21
|
-
# jenkinsctl [](https://badge.fury.io/py/jenkinsctl) [](https://pepy.tech/project/jenkinsctl)
|
|
22
|
-
Build Jenkins jobs effortlessly using a single command. 🚀
|
|
23
|
-
|
|
24
|
-
## Installation 📦
|
|
25
|
-
|
|
26
|
-
```sh
|
|
27
|
-
pip install jenkinsctl
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
## Jenkins Configuration 🛠️
|
|
31
|
-
Before using jenkinsctl, configure your Jenkins server details in your shell profile.
|
|
32
|
-
Add these lines in your ~/.bashrc or ~/.zshrc file:
|
|
33
|
-
```sh
|
|
34
|
-
export JENKINS_SERVER_URL=http://localhost:8080
|
|
35
|
-
export JENKINS_USERNAME=amanshaw4511
|
|
36
|
-
export JENKINS_API_KEY=21df49caf41726094323b803a6de363eae
|
|
37
|
-
```
|
|
38
|
-
Adjust the values to match your Jenkins server's URL, your username, and the corresponding API key. This configuration is essential for jenkinsctl to interact with Jenkins and execute tasks efficiently.
|
|
39
|
-
|
|
40
|
-
How to Get the API Token: https://www.baeldung.com/ops/jenkins-api-token
|
|
41
|
-
## Usage 🤖
|
|
42
|
-
```sh
|
|
43
|
-
$ jenkinsctl --help
|
|
44
|
-
usage: jenkinsctl [-h] {build,config} ...
|
|
45
|
-
|
|
46
|
-
options:
|
|
47
|
-
-h, --help show this help message and exit
|
|
48
|
-
|
|
49
|
-
Subcommand:
|
|
50
|
-
{build,config}
|
|
51
|
-
build run new build
|
|
52
|
-
config get config of a build
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
### Run a Jenkins Job
|
|
56
|
-
```sh
|
|
57
|
-
$ jenkinsctl build --help
|
|
58
|
-
usage: jenkinsctl build [-h] [-f FILE] [-v] [-s SUPPRESS_LOGS] [--param PARAM]
|
|
59
|
-
|
|
60
|
-
options:
|
|
61
|
-
-h, --help show this help message and exit
|
|
62
|
-
-f FILE, --file FILE Yaml configuration file
|
|
63
|
-
-v, --verbose
|
|
64
|
-
-s SUPPRESS_LOGS, --suppress-logs SUPPRESS_LOGS
|
|
65
|
-
--param PARAM
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
### Get Config of a Jenkin Build in YAML Format
|
|
69
|
-
```sh
|
|
70
|
-
$ jenkinsctl config --help
|
|
71
|
-
usage: jenkinsctl config [-h] job_name build_no
|
|
72
|
-
|
|
73
|
-
positional arguments:
|
|
74
|
-
job_name
|
|
75
|
-
build_no
|
|
76
|
-
|
|
77
|
-
options:
|
|
78
|
-
-h, --help show this help message and exit
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
## Examples 🎭
|
|
82
|
-
### Runing a Jenkins Job
|
|
83
|
-
Create a YAML configuration file, let's say `my_job.yaml`, with job parameters like this:
|
|
84
|
-
```yaml
|
|
85
|
-
job: my_job
|
|
86
|
-
params:
|
|
87
|
-
param1: some value
|
|
88
|
-
param2: 10
|
|
89
|
-
param3: true
|
|
90
|
-
```
|
|
91
|
-
Initiate the job build using the following command:
|
|
92
|
-
```sh
|
|
93
|
-
jenkinsctl build -f my_job.yaml
|
|
94
|
-
```
|
|
95
|
-
This command executes the job based on the specified YAML configuration.
|
|
96
|
-
|
|
97
|
-
### Overriding Specific Parameter from Configuration
|
|
98
|
-
```sh
|
|
99
|
-
jenkinsctl build -f my_job.yaml --param param2=11 --param param3=false
|
|
100
|
-
```
|
|
101
|
-
This command will override the value of `param2` and `param3` from original configuration file `my_job.yaml`, passing an effective configuration as follows to run jenkin job :
|
|
102
|
-
```yaml
|
|
103
|
-
job: my_job
|
|
104
|
-
params:
|
|
105
|
-
param1: some value
|
|
106
|
-
param2: 11
|
|
107
|
-
param3: false
|
|
108
|
-
```
|
|
109
|
-
.. |PyPI-Downloads| image:: https://img.shields.io/pypi/dm/shtab.svg?label=pypi%20downloads&logo=PyPI&logoColor=white
|
|
110
|
-
:target: https://pepy.tech/project/shtab
|
|
111
|
-
:alt: Downloads
|
|
112
|
-
|
|
113
|
-
### Generating Config from Existing Builds
|
|
114
|
-
Capture and reproduce configurations from previous Jenkins builds.
|
|
115
|
-
To generate a YAML configuration file from a specific build (e.g. 2nd build) of a job (e.g., `my_job`), use the following command:
|
|
116
|
-
```sh
|
|
117
|
-
jenkinsctl config my_job 2 > my_job.yaml
|
|
118
|
-
```
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
LICENSE
|
|
2
|
-
README.md
|
|
3
|
-
pyproject.toml
|
|
4
|
-
jenkinsctl/__init__.py
|
|
5
|
-
jenkinsctl/commons.py
|
|
6
|
-
jenkinsctl/config.py
|
|
7
|
-
jenkinsctl/jbuild.py
|
|
8
|
-
jenkinsctl/jenkins.py
|
|
9
|
-
jenkinsctl/jget_config.py
|
|
10
|
-
jenkinsctl.egg-info/PKG-INFO
|
|
11
|
-
jenkinsctl.egg-info/SOURCES.txt
|
|
12
|
-
jenkinsctl.egg-info/dependency_links.txt
|
|
13
|
-
jenkinsctl.egg-info/entry_points.txt
|
|
14
|
-
jenkinsctl.egg-info/requires.txt
|
|
15
|
-
jenkinsctl.egg-info/top_level.txt
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
|
jenkinsctl-0.1.0/pyproject.toml
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
[build-system]
|
|
2
|
-
requires = ["setuptools"]
|
|
3
|
-
build-backend = "setuptools.build_meta"
|
|
4
|
-
|
|
5
|
-
[project]
|
|
6
|
-
name = "jenkinsctl"
|
|
7
|
-
version = "v0.1.0"
|
|
8
|
-
dependencies = ["api4jenkins", "dynaconf", "pyyaml", "argcomplete"]
|
|
9
|
-
authors = [{ name = "Aman Shaw", email = "amanshaw4511@protonmail.com" }]
|
|
10
|
-
description = "Build Jenkins jobs effortlessly using a single command. 🚀"
|
|
11
|
-
readme = "README.md"
|
|
12
|
-
requires-python = ">=3.8"
|
|
13
|
-
classifiers = [
|
|
14
|
-
"Programming Language :: Python :: 3",
|
|
15
|
-
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
|
|
16
|
-
"Operating System :: OS Independent",
|
|
17
|
-
]
|
|
18
|
-
keywords = ["jenkins", "jenkin"]
|
|
19
|
-
|
|
20
|
-
[project.urls]
|
|
21
|
-
Homepage = "https://github.com/amanshaw4511/jenkinsctl"
|
|
22
|
-
Repository = "https://github.com/amanshaw4511/jenkinsctl"
|
|
23
|
-
Issues = "https://github.com/amanshaw4511/jenkinsctl/issues"
|
|
24
|
-
|
|
25
|
-
[project.scripts]
|
|
26
|
-
jenkinsctl = "jenkinsctl.jenkins:get_args"
|
|
27
|
-
|
|
28
|
-
[tool.setuptools.packages.find]
|
|
29
|
-
where = ["."] # list of folders that contain the packages (["."] by default)
|
|
30
|
-
#include = [] # package names should match these glob patterns (["*"] by default)
|
|
31
|
-
exclude = [] # exclude packages matching these glob patterns (empty by default)
|
|
32
|
-
#namespaces = false # to disable scanning PEP 420 namespaces (true by default)
|
jenkinsctl-0.1.0/setup.cfg
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|