Git Product home page Git Product logo

api4jenkins's Introduction

Unit Test Integration Test CodeQL codecov PyPI PyPI - Python Version PyPI - Wheel Documentation Status GitHub

Jenkins Python Client

Python3 client library for Jenkins API which provides sync and async APIs.

Features

  • Provides sync and async APIs
  • Object oriented, each Jenkins item has corresponding class, easy to use and extend
  • Base on api/json, easy to query/filter attribute of item
  • Setup relationship between class just like Jenkins item
  • Support api for almost every Jenkins item
  • Pythonic
  • Test with latest Jenkins LTS

Installation

python3 -m pip install api4jenkins

Quick start

Sync example:

>>> from api4jenkins import Jenkins
>>> client = Jenkins('http://127.0.0.1:8080/', auth=('admin', 'admin'))
>>> client.version
'2.176.2'
>>> xml = """<?xml version='1.1' encoding='UTF-8'?>
... <project>
...   <builders>
...     <hudson.tasks.Shell>
...       <command>echo $JENKINS_VERSION</command>
...     </hudson.tasks.Shell>
...   </builders>
... </project>"""
>>> client.create_job('path/to/job', xml)
>>> import time
>>> item = client.build_job('path/to/job')
>>> while not item.get_build():
...      time.sleep(1)
>>> build = item.get_build()
>>> for line in build.progressive_output():
...     print(line)
...
Started by user admin
Running as SYSTEM
Building in workspace /var/jenkins_home/workspace/freestylejob
[freestylejob] $ /bin/sh -xe /tmp/jenkins2989549474028065940.sh
+ echo $JENKINS_VERSION
2.176.2
Finished: SUCCESS
>>> build.building
False
>>> build.result
'SUCCESS'

Async example

import asyncio
import time
from api4jenkins import AsyncJenkins

async main():
    client = AsyncJenkins('http://127.0.0.1:8080/', auth=('admin', 'admin'))
    print(await client.version)
    xml = """<?xml version='1.1' encoding='UTF-8'?>
    <project>
      <builders>
        <hudson.tasks.Shell>
          <command>echo $JENKINS_VERSION</command>
        </hudson.tasks.Shell>
      </builders>
    </project>"""
    await client.create_job('job', xml)
    item = await client.build_job('job')
    while not await item.get_build():
        time.sleep(1)
    build = await item.get_build()
    async for line in build.progressive_output():
        print(line)

    print(await build.building)
    print(await build.result)

asyncio.run(main())

Documentation

User Guide and API Reference is available on Read the Docs

api4jenkins's People

Contributors

cavcrosby avatar cybersa avatar devqore avatar fz0000 avatar gjabouley-invn avatar joelee2012 avatar pquentin avatar shmarlovsky avatar zhan9san avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

api4jenkins's Issues

How to get build parameters

Thanks for your awesome jenkins python wrapper.

I wonder know could we get the build parameters from Build class?

Get AuthorizationMatrixProperty permissions for each folder

Hello,
How can we get the 'AuthorizationMatrixProperty' (permissions) for each folder configured with matrix-authorization ?
We loop through each folder name, get the config.xml and extract the permissions from the xml tree?

# folder config.xml output example:
<com.cloudbees.hudson.plugins.folder.properties.AuthorizationMatrixProperty>
<inheritanceStrategy class="org.jenkinsci.plugins.matrixauth.inheritance.InheritParentStrategy"/>
<permission>com.cloudbees.plugins.credentials.CredentialsProvider.Create:user1example</permission>
.........................................

Authentication with pre-existing api token

Great library! It would be great to enable the use of existing api tokens so usernames / passwords aren't passed around in plaintext. Something like:

j = Jenkins(url, api_token="xyz")

Would preserve backwards compatibility

In Jenkins(jenkins_url, auth=(user,token)), jenkins_url changes port to 80

At first, the Jenkins API makes the connection well. However, when it gets to post, it changes.

Is it possible to make the Jenkins API just strictly follow the input parameter http://1.2.3.4:8080/jenkins instead?

2022-11-01 16:35:10,635 DEBUG    [ retry | from_int ] Converted retries value: 1 -> Retry(total=1, connect=None, read=None, redirect=None, status=None)
2022-11-01 16:35:10,635 DEBUG    [ requester | send ] GET: http://1.2.3.4:8080/jenkins/crumbIssuer/api/json with parameters: {}
2022-11-01 16:35:10,638 DEBUG    [ connectionpool | _new_conn ] Starting new HTTP connection (1): 1.2.3.4:8080
2022-11-01 16:35:10,643 DEBUG    [ connectionpool | _make_request ] http://1.2.3.4:8080 "GET /jenkins/crumbIssuer/api/json HTTP/1.1" 200 155
2022-11-01 16:35:10,644 DEBUG    [ requester | send ] Response: <Response [200]>
2022-11-01 16:35:10,645 DEBUG    [ requester | send ] GET: http://1.2.3.4:8080/jenkins/ with parameters: {'headers': {'Jenkins-Crumb': '699fc00f48ee20ec273953d41cf2387239a30cc39219b55fff5849ce69e7e65d'}}
2022-11-01 16:35:10,673 DEBUG    [ connectionpool | _make_request ] http://1.2.3.4:8080 "GET /jenkins/ HTTP/1.1" 200 11463
2022-11-01 16:35:10,675 DEBUG    [ requester | send ] Response: <Response [200]>
2022-11-01 16:35:10,676 DEBUG    [ requester | send ] GET: http://1.2.3.4:8080/jenkins/api/json with parameters: {'headers': {'Jenkins-Crumb': '699fc00f48ee20ec273953d41cf2387239a30cc39219b55fff5849ce69e7e65d'},
 'params': {'depth': 0, 'tree': '_class'}}
2022-11-01 16:35:10,682 DEBUG    [ connectionpool | _make_request ] http://1.2.3.4:8080 "GET /jenkins/api/json?depth=0&tree=_class HTTP/1.1" 200 48
2022-11-01 16:35:10,683 DEBUG    [ requester | send ] Response: <Response [200]>
2022-11-01 16:35:10,683 DEBUG    [ requester | send ] GET: http://1.2.3.4:8080/jenkins/api/json with parameters: {'headers': {'Jenkins-Crumb': '699fc00f48ee20ec273953d41cf2387239a30cc39219b55fff5849ce69e7e65d'},
 'params': {'depth': 0, 'tree': 'jobs[name,url]'}}
2022-11-01 16:35:10,690 DEBUG    [ connectionpool | _make_request ] http://1.2.3.4:8080 "GET /jenkins/api/json?depth=0&tree=jobs%5Bname%2Curl%5D HTTP/1.1" 200 964
2022-11-01 16:35:10,691 DEBUG    [ requester | send ] Response: <Response [200]>
2022-11-01 16:35:10,691 DEBUG    [ requester | send ] POST: http://docker.mywebsite.com/jenkins/job/JENKINS_TASK/buildWithParameters with parameters: {'headers': {'Jenkins-Crumb': '699fc00f48ee20ec273953d41cf2387239a30cc39219b55fff5849ce69e7e65d'},
 'params': {'x': 'x',
            'y': y,
            'z': 'z',
            'a': 'a',
            'b': 'b'}}
2022-11-01 16:35:10,693 DEBUG    [ connectionpool | _new_conn ] Starting new HTTP connection (1): docker.mywebsite.com:80
...

In the last line (and the line before): 2022-11-01 16:35:10,693 DEBUG [ connectionpool | _new_conn ] Starting new HTTP connection (1): docker.mywebsite.com:80
You can see, instead of the IP address + 8080 port number, it changes.

I also tried with Jenkins(jenkins_url, auth=(user, token), allow_redirects=False, verify=False) but doubting I did it right and/or if it did anything. I got the kwargs from here which redirected me to here.

I saw this previous GitHub issue, but unsure how to understand the conversation at the end -- a bit vague.

AttributeError: has no class PlaceholderExecutable, Patch new class with api4jenkins._patch_to

I am getting this error using api4jenkins 1.5 that I am not getting in 1.4. Is there anything special I need to do when using 1.5?

File "/usr/local/lib/python3.8/dist-packages/api4jenkins/queue.py", line 59, in get_build
    for build in self.jenkins.nodes.iter_builds():
  File "/usr/local/lib/python3.8/dist-packages/api4jenkins/node.py", line 54, in iter_builds
    yield from _new_items(self.jenkins, builds)
  File "/usr/local/lib/python3.8/dist-packages/api4jenkins/node.py", line 66, in _new_items
    yield new_item(jenkins, 'api4jenkins.build', item)
  File "/usr/local/lib/python3.8/dist-packages/api4jenkins/item.py", line 41, in func
    raise AttributeError(f'{module} has no class {class_name}, '
AttributeError: <module 'api4jenkins.build' from '/usr/local/lib/python3.8/dist-packages/api4jenkins/build.py'> has no class PlaceholderExecutable, Patch new class with api4jenkins._patch_to

utf-8 support

def create(self, name, xml):
return self.handle_req('POST', 'createItem', params={'name': name},
headers=self.headers, data=xml)

If the job xml contains non-ascii characters, it may cause an error.
So I suggest using

    def create(self, name, xml):
        return self.handle_req('POST', 'createItem', params={'name': name},
                               headers=self.headers, data=xml.encode('utf-8'))

[FEATURE REQUEST] Credentials domains

Currently api4jenkins supports to manage system and folder based credentials, which must be in the default domain(_).

It would be great if api4jenkins could manage credentials in different domains and if it could even manage credentials domains.

Another Python package, python-jenkins, can handle credentials in all domains, but it can not manage system credentials and credentials in GitHub organization folders. Therefore I am using now api4jenkins.

I brought up the other package just to point out that credentials domains managements could be implemented here too.

get_build has a bug

Hi when I call item.get_build() after having started a job the get_build() goes in error.
The stack trace is:

Traceback (most recent call last):
  File "/usr/local/lib/python3.6/site-packages/api4jenkins/item.py", line 73, in handle_req
    return self.jenkins.send_req(method, self.url + entry, **kwargs)
  File "/usr/local/lib/python3.6/site-packages/api4jenkins/requester.py", line 28, in send
    resp.raise_for_status()
  File "/usr/local/lib/python3.6/site-packages/requests/models.py", line 960, in raise_for_status
    raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 404 Client Error: Not Found for url: https://myjenkins.mycompany.com/job/APP1/job/APP1_BLABLA/job/APP1_BLABLA_BLABLA/12/api/json?depth=0&tree=queueId

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/var/lib/jenkins/workspace/Test/Git/myJclient/scripts/job1.py", line 22, in main
    while not item.get_build():
  File "/usr/local/lib/python3.6/site-packages/api4jenkins/queue.py", line 62, in get_build
    if int(build.queue_id) == self.id:
  File "/usr/local/lib/python3.6/site-packages/api4jenkins/item.py", line 129, in __getattr__
    return self.api_json(tree=attr)[attr]
  File "/usr/local/lib/python3.6/site-packages/api4jenkins/item.py", line 68, in api_json
    return self.handle_req('GET', 'api/json', params=params).json()
  File "/usr/local/lib/python3.6/site-packages/api4jenkins/item.py", line 77, in handle_req
    f'Not found {entry} for item: {self}') from e
api4jenkins.exceptions.ItemNotFoundError: Not found api/json for item: <WorkflowRun: https://myjenkins.mycompany.com/job/APP1/job/APP1_BLABLA/job/APP1_BLABLA_BLABLA/12/>

I understood the error, on my jenkins (multinode) some user aborted a job but jenkins did not delete it from the queue (zombie job in the queue) and the get_build() goes in error.

This is my code:

    # Create Jenkins client
    j = Jenkins('https://'+baseurl, auth=(username, password), verify=False)

    # Run build with args
    item = j.build_job(jobname,
        param1=param1_value,
        param2=param2_value,
        param3=param3_value
    )
    # Wait for the queue element
    while not item.get_build():
        time.sleep(1)

QueueItem get_build() gets wrong build when build is triggered with delay parameter

api4jenksin version = 1.5.1
Jenkins version = 2.249.3 (open source install)

I'm triggering builds for 2 different jobs and I'm passing in a non 0 delay parameter to one or both of them. The order in which I trigger these 2 jobs affects the get_build() function of QueueItem.

Code to trigger a job. Takes job name, existing Jenkins server object and optional delay parameter.

trigger_build(job_name, jenkins_server, delay='0sec'):
    params = {
        'delay': delay
    }
    
    return jenkins_server.build_job(job_name, **params)

Case 1: Trigger delayed job first. No issue.

build_jobs = {}

build_jobs['first'] = trigger_build('Builds/App1/main', jenkins_server, '20sec')
build_jobs['second'] = trigger_build('Builds/App2/main', jenkins_server)

for x in build_jobs:
    print ("{} type is {} url is {}".format(x, type(build_jobs[x]), build_jobs[x].get_job().url))

while True:
    print('Waiting for queue to clear....')
    time.sleep(5)

    q_is_empty = True

    for job in build_jobs:
        if not build_jobs[job].get_build():
            print("{} is still queued".format(job))
            q_is_empty = False
            break

    if q_is_empty:
        break

print('Build queue cleared.\n')

for x in build_jobs:
    job = build_jobs[x].get_build().get_job()
    print("{} = {}".format(x, job.url))

Output for case 1,

first type is <class 'api4jenkins.queue.QueueItem'>, https://my.jenkins.server/job/Builds/job/App1/job/main/
second type is <class 'api4jenkins.queue.QueueItem'>, https://my.jenkins.server/job/Builds/job/App2/job/main/

Waiting for queue to clear....'
first is still queued
first is still queued
first is still queued
Build queue cleared

first = https://my.jenkins.server/job/Builds/job/App1/job/main/
second = https://my.jenkins.server/job/Builds/job/App2/job/main/

Case 2: Trigger delayed job last. I lose reference to the second job
Simply move the delay param to the second job,

build_jobs = {}

build_jobs['first'] = trigger_build('Builds/App1/main', jenkins_server)
build_jobs['second'] = trigger_build('Builds/App2/main', jenkins_server, '20sec')

The rest of the code stays the same but I get the following output,

first type is <class 'api4jenkins.queue.QueueItem'>, https://my.jenkins.server/job/Builds/job/App1/job/main/
second type is <class 'api4jenkins.queue.QueueItem'>, https://my.jenkins.server/job/Builds/job/App2/job/main/

Waiting for queue to clear....' <---- No waiting on the delayed job like in the first case.
Build queue cleared

first = https://my.jenkins.server/job/Builds/job/App1/job/main/  
second = https://my.jenkins.server/job/Builds/job/App1/job/main/ <---- This is the URL to the first job.

Case 3: Both jobs have same delay OR the first job has a longer delay than the second job. No issue.

build_jobs = {}

build_jobs['first'] = trigger_build('Builds/App1/main', jenkins_server, '20sec')
build_jobs['second'] = trigger_build('Builds/App2/main', jenkins_server, '20sec')

The output is identical to use case 1.

Case 4: The second job has a longer delay than the first job

build_jobs = {}

build_jobs['first'] = trigger_build('Builds/App1/main', jenkins_server, '10sec')
build_jobs['second'] = trigger_build('Builds/App2/main', jenkins_server, '20sec')

The output is similar to use case 2.

first type is <class 'api4jenkins.queue.QueueItem'>, https://my.jenkins.server/job/Builds/job/App1/job/main/
second type is <class 'api4jenkins.queue.QueueItem'>, https://my.jenkins.server/job/Builds/job/App2/job/main/

Waiting for queue to clear....'
first is still queued
first is still queued
Build queue cleared

first = https://my.jenkins.server/job/Builds/job/App1/job/main/  
second = https://my.jenkins.server/job/Builds/job/App1/job/main/ <---- This is the URL to the first job.

item.get_build has bug

stacktrace

File "./web/api/application/service.py", line 243, in _process_production_flow
    while not item.get_build():
  File "/usr/local/lib/python3.8/site-packages/api4jenkins/queue.py", line 58, in get_build
    for build in self.jenkins.nodes.iter_builds():
  File "/usr/local/lib/python3.8/site-packages/api4jenkins/node.py", line 48, in iter_builds
    yield self._new_instance_by_item('api4jenkins.build',
  File "/usr/local/lib/python3.8/site-packages/api4jenkins/item.py", line 96, in _new_instance_by_item
    class_name = self.class_delimiter.split(item['_class'])[-1]
TypeError: 'NoneType' object is not **subscriptable**

hi, thanks for your good quality python jenkins client :)

i got error when waiting build items

get all the configuration properties from a job ?

[ok, not really an issue, could be more of a question ; not sure there is a forum for questions - I tried StackOverflow, where I saw a tag for api4jenkins ]

I am trying to build stats on Jenkins jobs. I need to access a few of the job properties, like the URL of the git repo used, as seen in the UI ( here, in Source Code Management \ Git \ Repositories \ Repository URL )

However, I do not understand how to get that property , I am actually getting a small subset of the properties I see in the UI. ( my jobs are all FreeStyleProject )

from api4jenkins import Jenkins
client = Jenkins(JENKINS_BASE_URL, auth=(USERNAME, PASSWORD))
api_jobs = client.get_job('a_folder')
for j in api_jobs :   
   print(j.name) # FreeStyleProject, by the way
   pprint.pprint(j.api_json())
   pprint.pprint(j.configure())

-> no SCM, Git or Bitbucket properties in the returned data

Inconsistent behaviors of get_job method.

The following code returns None:

j.get_job('non-existed-job')

The following code raises an ItemNotFoundError exception:

j.get_job('non-existed-folder/non-existed-job')

ver:1.9

Differrent server URL

Hi there,
thanks for useful framework!

Intro
I've a nginx proxy with internal httpotp auth for jenkins auth.
For api4jenkins scripts I've used an internal server url, like http://jenkins:8080/ to skip httpauth for local scripts.
For user auth, I'm using an external server url, like https://jenkins-in-house.com/

The issue.
For some cases, api4jenkins returns server url described in Manage Jenkins - Configure System - Jenkins Location - Jenkins URL instead of initialized object url

j = Jenkins('http://the_jenkins:8080/', auth=('automation', 'token'))

print(j)
print(j.url)

for job in j.iter_jobs():
    print(f"job = {job}")
    print(f"name = {name}")

......

<Jenkins: http://the_jenkins:8080/>
http://the_jenkins:8080/

job                 = <WorkflowJob: https://jenkins-in-house.com/job/Just_some_job/>
name             = Just_some_job

Is is possible to use server url that initialized for auth, instead of jenkins url?
Thanks!

misspelled in method plugins.set_site

error message

Traceback (most recent call last):
File "jenkins_handler.py", line 52, in set_plugin_update_server
self._jenkins.plugins.set_site(update_server)
File "/usr/local/python375/lib/python3.7/site-packages/api4jenkins/plugin.py", line 38, in set_site
self.check_update_server()
File "/usr/local/python375/lib/python3.7/site-packages/api4jenkins/item.py", line 128, in getattr
return super().getattribute(name)
AttributeError: 'PluginsManager' object has no attribute 'check_update_server'

possible modification

plugin.py", line 38, in set_site
self.check_update_server() -> self.check_updates_server()

Unable to control the logger from v.2.0.0 or newer

In my application I am using the Python logging library and it was not having problems with api4jenkins till the version v1.15.0. As soon as I have started using a newer version, I get such messages in my logs for all operations:

2023-12-12 16:09:36 INFO     HTTP Request: GET https://jenkins.mycompany.com/crumbIssuer/api/json "HTTP/1.1 200 OK"
2023-12-12 16:09:36 INFO     HTTP Request: HEAD https://jenkins.mycompany.com/ "HTTP/1.1 200 OK"
2023-12-12 16:09:36 INFO     HTTP Request: GET https://jenkins.mycompany.com/user/myuser/api/json?depth=0 "HTTP/1.1 200 OK"
2023-12-12 16:09:36 INFO     HTTP Request: GET https://jenkins.mycompany.com/user/myuser/api/json?depth=0&tree=id "HTTP/1.1 200 OK"
2023-12-12 16:09:36 INFO     HTTP Request: GET https://jenkins.mycompany.com/user/myuser/api/json?depth=0&tree=absoluteUrl "HTTP/1.1 200 OK"
2023-12-12 16:09:36 INFO     HTTP Request: GET https://jenkins.mycompany.com/user/myuser/api/json?depth=0&tree=fullName "HTTP/1.1 200 OK"

It looks like sometimes these logs even lead to a The read operation timed out error which interrupts my application. I have tried to control the logger as follows:

import logging
...
jenkins_logger = logging.getLogger('api4jenkins')
jenkins_logger.setLevel(logging.CRITICAL)

but whatever log level I set, I still get the same high amount of log messages. The last 2 rows above were not necessary in the v1.15.0 version.

I am using Python 3.10.13 and I have encountered this issue with api4jenkins v2.0.0, v2.0.1 and v2.0.2. These versions have 3 loggers: api4jenkins.item, api4jenkins and api4jenkins.http. Instead the v1.15.0 version has 2 loggers: api4jenkins.requester and api4jenkins.

Approving the Pending Input is not working when Jenkins runs on Subpath

I have configured the Jenkins instance to run on subpath using this configuration --prefix=$PREFIX (https://www.jenkins.io/doc/book/installing/initial-settings/#networking-parameters).

When i try to approve the Jobs, its failing with following error:

Sep 27 10:45:12 jenkins python[216706]: 2022-09-27 10:45:12,892 DEBUG POST: https://localhost/jenkins//jenkins/job/Test_QA_PL/3/input/D429835560e9194da64fefbc4517f23f/proceedEmpty with parameters: {'headers': {'Jenkins-Crumb': 'xxxx'}}
Sep 27 10:45:12 jenkins python[216706]: 2022-09-27 10:45:12,965 DEBUG https://localhost:443 "POST /jenkins//jenkins/job/Test_QA_PL/3/input/D429835560e9194da64fefbc4517f23f/proceedEmpty HTTP/1.1" 404 526
Sep 27 10:45:12 jenkins python[216706]: 2022-09-27 10:45:12,966 DEBUG Response: <Response [404]>
Sep 27 10:45:12 jenkins python[216706]: 2022-09-27 10:45:12,966 INFO sqlalchemy.engine.Engine ROLLBACK
Sep 27 10:45:12 jenkins python[216706]: 2022-09-27 10:45:12,966 INFO ROLLBACK
Sep 27 10:45:12 jenkins python[216706]: 2022-09-27 10:45:12,966 ERROR Exception on /jira [POST]
Sep 27 10:45:12 jenkins python[216706]: Traceback (most recent call last):
Sep 27 10:45:12 jenkins python[216706]: File "/opt/build_automator/venv/lib/python3.10/site-packages/api4jenkins/item.py", line 73, in handle_req
Sep 27 10:45:12 jenkins python[216706]: return self.jenkins.send_req(method, self.url + entry, **kwargs)
Sep 27 10:45:12 jenkins python[216706]: File "/opt/build_automator/venv/lib/python3.10/site-packages/api4jenkins/requester.py", line 28, in send
Sep 27 10:45:12 jenkins python[216706]: resp.raise_for_status()
Sep 27 10:45:12 jenkins python[216706]: File "/opt/build_automator/venv/lib/python3.10/site-packages/requests/models.py", line 960, in raise_for_status
Sep 27 10:45:12 jenkins python[216706]: raise HTTPError(http_error_msg, response=self)
Sep 27 10:45:12 jenkins python[216706]: requests.exceptions.HTTPError: 404 Client Error: Not Found for url: https://localhost/jenkins//jenkins/job/Test_QA_PL/3/input/D429835560e9194da64fefbc4517f23f/proceedEmpty
Sep 27 10:45:12 jenkins python[216706]: The above exception was the direct cause of the following exception:
Sep 27 10:45:12 jenkins python[216706]: Traceback (most recent call last):
Sep 27 10:45:12 jenkins python[216706]: File "/opt/build_automator/venv/lib/python3.10/site-packages/flask/app.py", line 2073, in wsgi_app
Sep 27 10:45:12 jenkins python[216706]: response = self.full_dispatch_request()
Sep 27 10:45:12 jenkins python[216706]: File "/opt/build_automator/venv/lib/python3.10/site-packages/flask/app.py", line 1518, in full_dispatch_request
Sep 27 10:45:12 jenkins python[216706]: rv = self.handle_user_exception(e)
Sep 27 10:45:12 jenkins python[216706]: File "/opt/build_automator/venv/lib/python3.10/site-packages/flask/app.py", line 1516, in full_dispatch_request
Sep 27 10:45:12 jenkins python[216706]: rv = self.dispatch_request()
Sep 27 10:45:12 jenkins python[216706]: File "/opt/build_automator/venv/lib/python3.10/site-packages/flask/app.py", line 1502, in dispatch_request
Sep 27 10:45:12 jenkins python[216706]: return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args)
Sep 27 10:45:12 jenkins python[216706]: File "/opt/build_automator/server.py", line 61, in jira_parse
Sep 27 10:45:12 jenkins python[216706]: jira_parser.parse(data)
Sep 27 10:45:12 jenkins python[216706]: File "/opt/build_automator/jira_parser.py", line 84, in parse
Sep 27 10:45:12 jenkins python[216706]: self.request_approved(jira_id,service,environment)
Sep 27 10:45:12 jenkins python[216706]: File "/opt/build_automator/jira_parser.py", line 142, in request_approved
Sep 27 10:45:12 jenkins python[216706]: if self.jenkins.approve_job(job_name):
Sep 27 10:45:12 jenkins python[216706]: File "/opt/build_automator/jenkins_utils.py", line 89, in approve_job
Sep 27 10:45:12 jenkins python[216706]: build.get_pending_input().submit()
Sep 27 10:45:12 jenkins python[216706]: File "/opt/build_automator/venv/lib/python3.10/site-packages/api4jenkins/input.py", line 43, in submit
Sep 27 10:45:12 jenkins python[216706]: return self.handle_req('POST', 'proceedEmpty')
Sep 27 10:45:12 jenkins python[216706]: File "/opt/build_automator/venv/lib/python3.10/site-packages/api4jenkins/item.py", line 76, in handle_req
Sep 27 10:45:12 jenkins python[216706]: raise ItemNotFoundError(
Sep 27 10:45:12 jenkins python[216706]: api4jenkins.exceptions.ItemNotFoundError: Not found proceedEmpty for item: <PendingInputAction: https://localhost/jenkins//jenkins/job/Test_QA_PL/3/input/D429835560e9194da64fefbc4517f23f/>

Build number included string occured error.

Hi Support,

i got error from smoke-test_1 | File "/usr/local/lib/python3.10/site-packages/api4jenkins/job.py", line 159, in get_build smoke-test_1 | if int(number) == int(item['number']): smoke-test_1 | ValueError: invalid literal for int() with base 10: '1521-sit02-master' Because our build number included string. Have anything possible to fix?

File "/usr/local/lib/python3.10/site-packages/api4jenkins/job.py", line 159, in get_build smoke-test_1 | if int(number) == int(item['number']):

remove int transition? thanks

Screen Shot 2022-09-15 at 16 35 17

Unable to store secret file credentials correctly

I want to store a Jenkins credential of type secret file as follows:

my_xml = '''<org.jenkinsci.plugins.plaincredentials.impl.FileCredentialsImpl plugin="plain-credentials">
    <id>###CREDENTIAL_ID###</id>
    <description>###CREDENTIAL_DESC###</description>
    <fileName>###SECRET_FILE_NAME###</fileName>
    <secretBytes>###SECRET_FILE_BYTES###</secretBytes>
</org.jenkinsci.plugins.plaincredentials.impl.FileCredentialsImpl>'''

secret_file_path = '/path/to/my/secret/file'
secret_file_name = os.path.basename(secret_file_path)
with open(secret_file_path, 'r') as secret_file:
    secret_file_string = secret_file.read()

my_xml = my_xml.replace('###CREDENTIAL_ID###', 'my-secret-file-cred-id')
my_xml = my_xml.replace('###CREDENTIAL_DESC###', 'My secret file description')
my_xml = my_xml.replace('###SECRET_FILE_NAME###', secret_file_name)
my_xml = my_xml.replace('###SECRET_FILE_BYTES###', secret_file_string)

folder = j.get_job('SOME_JENKINS_FOLDER')
folder.credentials.create(my_xml)

The Jenkins credential is successfully stored, but when I try to use it in my Jenkins pipeline this error is returned:

error: error loading config file "/root/.kube/config": yaml: invalid leading UTF-8 octet

The file in this case is a Kubeconfig in JSON format. In order to exclude that the file is the problem, I have then tried to upload manually the file via the GUI and the pipeline reads the secret file successfully and it runs without encountering the same problem.

I have seen this article about somebody solving the issue when using the API via curl, but I could not find anything helpful when creating the XML file like this case.

Did anybody face this issue? Is there a known solution for this matter? Thanks in advance!

ResourceWarning: unclosed socket in pytest

When using api4jenkins with pytest, it may happen that the following exception occurs:

    def unraisable_exception_runtest_hook() -> Generator[None, None, None]:
        with catch_unraisable_exception() as cm:
            yield
            if cm.unraisable:
                if cm.unraisable.err_msg is not None:
                    err_msg = cm.unraisable.err_msg
                else:
                    err_msg = "Exception ignored in"
                msg = f"{err_msg}: {cm.unraisable.object!r}\n\n"
                msg += "".join(
                    traceback.format_exception(
                        cm.unraisable.exc_type,
                        cm.unraisable.exc_value,
                        cm.unraisable.exc_traceback,
                    )
                )
>               warnings.warn(pytest.PytestUnraisableExceptionWarning(msg))
E               pytest.PytestUnraisableExceptionWarning: Exception ignored in: <socket.socket fd=-1, family=10, type=1, proto=6>
E               
E               Traceback (most recent call last):
E                 File "/usr/lib64/python3.11/encodings/idna.py", line 155, in encode
E                   result = input.encode('ascii')
E                            ^^^^^^^^^^^^^^^^^^^^^
E               ResourceWarning: unclosed <socket.socket fd=34, family=10, type=1, proto=6, laddr=('::1', 48440, 0, 0), raddr=('::1', 50001, 0, 0)>

This is because the http_client in the Jenkins class constructor (and probably the async version, too) isn't closed after being used. I think the garbage collector of python isn't working properly in this case because the jenkins object (instance) is being passed into all BaseItem items as reference.

As a workaround, the http client can be closed manually:

jenkins_client = Jenkins(...)

# Do something with the client
client.get_job(...)

# Close the http client
jenkins_client.http_client.close()

but I'd suggest to wrap the http client in an (auto closing) context manager or something like that.

New versions of Jenkins (V2.307) have renamed "master" to "Built-In Node"

for item in self.api_json(tree='computer[displayName]')['computer']:

Documented here: https://www.jenkins.io/doc/book/managing/built-in-node-migration/
Checking the displayName and replacing, fixes the issue

        for item in self.api_json(tree='computer[displayName]')['computer']:
            if (item['displayName'] == 'Built-In Node'):
                item['displayName'] = '(built-in)' 

There is probably a nicer way to do this with 'replace()'

WorkflowJob object has no attribute 'get_build'

Hi there,
I upgraded from version 1.15 to 2.0 and get an issue, the method get_build() is missing.

def get_build_data_by_number(self, job_name: str, build_number: int):
    """ Route Jenkins: Get build data by build number """
    job_item = self.get_job(job_name)
    if job_item:
      build_item = job_item.get(build_number)
    if build_item:
      return build_item.api_json()
    return False

How can I retreive information on a specific build from job?
Thanks

jenkins.get_job().build(with parameters)

Hello,

please, could you help me with my little issue? I'm trying to trigger build with one parameter.

'defaultParameterValue': {'_class': 'hudson.model.BooleanmParameterValue', 'name': 'UT', 'value': False}

I have tried:
.build(params=True)
.build(params=[True])
.build(params={'UT': True})
.build(params=[{'name': 'UT', 'value': True])

I wonder, should I completely copy job.get_parameters() and somehow change the value? Thank you.

Port number is ignored

When using a non-default port, e.g.

J = Jenkins('http://localhost:9090/', auth=('admin', 'admin'))
job = J.get_job('MetaWorker')
item = job.build()

the library still uses port 8080.

requests.exceptions.ConnectionError: HTTPConnectionPool(host='localhost', port=8080): Max retries exceeded with url: /job/MetaWorker/build (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x7fae50594e20>: Failed to establish a new connection: [Errno 61] Connection refused'))

After the multi-branch pipeline is disabled, the buildable property has no effect

I want to disable a multi-branch pipeline, but when I use disable(), I find the following problem

  1. Multibranch pipeline does not have buildable attribute
  2. When I disable the multi-branch pipeline, the buildable property of the master branch is still True
multi_branch_pipeline = jenkins.get_job("test_multi_branch")
multi_branch_master = jenkins.get_job("test_multi_branch/master")

print(multi_branch_pipeline.disable())
# print(multi_branch_pipeline.buildable)  # This will raise an exception `AttributeError: 'WorkflowMultiBranchProject' object has no attribute 'buildable'`
print(multi_branch_master.buildable)  # True

So how can I get the status of a multi-branch pipeline

Cannot get the specify build use get_build(number)

I know that only the first 100 builds are obtained now, but get_build(number) should be free to specify the build number to be reasonable

def get_build(self, number):
    for item in self.api_json(tree='allBuilds[number,displayName,url]')['allBuilds']:
        if number == item['number'] or number == item['displayName']:
            return self._new_instance_by_item('api4jenkins.build', item)
    return None

So it should be fine to change it to the above, except that it may be slower

Patching class to view not working

Hey,
first thank you for this helpful lib.
There is the first error message:

AttributeError: <module 'api4jenkins.view' from '<user>/test/lib/python3.8/site-packages/api4jenkins/view.py'> has no class SectionedView to describe
<url>/view/CI%20Builds%20(TFS)/, patch new class with api4jenkins._patch_to,
see: https://api4jenkins.readthedocs.io/en/latest/user/example.html#patch

Then I tried to fix it with this code snippet:

class SectionedView(Jenkins):
    pass
def test():
   jenkins = Jenkins(jenkins_url, auth=(username, password))
    _patch_to('api4jenkins.view', SectionedView)
    for view in jenkins.views:
        print(view)

Then the following error is:

item = {'_class': 'hudson.plugins.sectioned_view.SectionedView', 'name': 'CI Builds (TFS)', 'url': '<url>/view/CI%20Builds%20(TFS)/'}
    def func(jenkins, module, item):
        class_name = delimiter.split(item['_class'])[-1]
        module = import_module(module)
        if not hasattr(module, class_name):
            msg = f'''{module} has no class {class_name} to describe
                  {item["url"]}, patch new class with api4jenkins._patch_to,
                  see: https://api4jenkins.readthedocs.io/en/latest/user/example.html#patch'''
            raise AttributeError(msg)
        _class = getattr(module, class_name)
       return _class(jenkins, item['url'])
TypeError: __init__() takes 2 positional arguments but 3 were given

Do I something wrong?

Kind regards,
Benedikt

api4jenkins does not support requests 2.10

api4jenkins does not include version dependency for requests library -
it's not supported older version (at least requests 2.10)

api4jenkins should include version dependency

The optional param "recursive" is missing in some functions

The recursive is added in create_job in #48. So other related functions need to add it, too:


update

self.create_job(folder.full_name, EMPTY_FOLDER_XML)

to

self.create_job(folder.full_name, EMPTY_FOLDER_XML, recursive=recursive)

and update

def duplicate(self, path):
self.jenkins.create_job(path, self.configure())

to

def duplicate(self, path, recursive=False):
    self.jenkins.create_job(path, self.configure(), recursive=recursive)

Build job with files.

Hi

Currently the job.py/Project.build is only accepting parameters, this doesn't include any file parameter. Even if we pass a file parameter, this won't be properly mapped to requests file parameter. So, either we need to separate the file from params and pass it to self.handle_req method in build method. But this may create a conflict if the job already has a parameter named file. The following method does the job, but duplicates part of job.py/Project.build

def build_job_with_files(self, files: dict, **params) -> QueueItem:
    """
    same function from api4jenkins.job.Project.build. Added files additionally.
    :param files: dict of all file inputs with field name and file object like {'jsonfile': <fp>}
    :param params: build params
    """
    reserved = ['token', 'delay']
    if not params or all(k in reserved for k in params):
        entry = 'build'
    else:
        entry = 'buildWithParameters'
    resp = self.handle_req('POST', entry, params=params, files=files)
    return QueueItem(self, resp.headers['Location'])

Please let me know your comment, I can make PR with your suggestions if you think this make sense.

Retrieving all builds builds of a job (more than 100)?

Thanks for the wrapper.
Apologies if I have missed it, but is there a way to retrieve all builds of a job?
E.g. I have a job that has about 1000 builds, but when I run for build in job: it only gets the most recent 100.
In the Jenkins api reference it says you have to use the tree=allBuilds[โ€ฆ] parameter to force it to load all of the builds.
Thanks.

v2.0.0 syntax errors when using Python 3.7 due to walrus operator

Version 2.0.0 includes changes that use the walrus operator (:=), but setup.py still has python_requires='>=3.7'. Since the walrus operator was introduced in Python 3.8, this causes syntax errors upon import.

>>> import api4jenkins
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/jovyan/conda-envs/py37/lib/python3.7/site-packages/api4jenkins/__init__.py", line 160
    if job := self.get_job(full_name):
            ^
SyntaxError: invalid syntax

v1.13 job.get_build(build_number) return None , v1.12 OK

as the title said
my test code

        client = JenkinsApi(self.url, auth=(self.user, self.token))
        job = client.get_job(full_name)
        print(f"====> debug job: {job} ")
        build = job.get_build(number)
        # build = j.get_last_build()   # this is ok
        print(f"====> debug build: {build} ")

proxy agent setting

Hi ,

Have anything else possible added proxy items when creating Jenkins(url=, proxy='') like these. Because of some landscaped requirement setup proxy. Thanks.

proxies = {
   'http': 'http://proxy.example.com:8080',
   'https': 'http://secureproxy.example.com:8090',
}

url = 'http://mywebsite.com/example'

response = requests.post(url, proxies=proxies)

getting jsondecode error

My code is:
from api4jenkins import Jenkins as aj
def api4jenkins_test(jenkins_url, username, password, job_name):
client = aj(jenkins_url, auth=(username, password))
item = client.build_job(job_name)
while not item.get_build():
time.sleep(1)
build = item.get_build()
for line in build.progressive_output():
print(line)

Jenkins Version: Jenkins 2.346.1

Traceback (most recent call last):
File "/home/njagadeesh/workspace/testjob/run_jenkins_api.py", line 52, in
api4jenkins_test(jenkins_url, username, password, job_name)
File "/home/njagadeesh/workspace/testjob/run_jenkins_api.py", line 35, in api4jenkins_test
item = client.build_job(job_name)
File "/home/njagadeesh/ve3/lib/python3.10/site-packages/api4jenkins/init.py", line 185, in build_job
job = self._get_job_and_check(full_name)
File "/home/njagadeesh/ve3/lib/python3.10/site-packages/api4jenkins/init.py", line 189, in _get_job_and_check
job = self.get_job(full_name)
File "/home/njagadeesh/ve3/lib/python3.10/site-packages/api4jenkins/init.py", line 74, in get_job
if folder.exists():
File "/home/njagadeesh/ve3/lib/python3.10/site-packages/api4jenkins/item.py", line 111, in exists
self.api_json(tree='_class')
File "/home/njagadeesh/ve3/lib/python3.10/site-packages/api4jenkins/item.py", line 97, in api_json
return self.handle_req('GET', 'api/json', params=params).json()
File "/home/njagadeesh/ve3/lib/python3.10/site-packages/api4jenkins/item.py", line 100, in handle_req
self._add_crumb(self.jenkins.crumb, kwargs)
File "/home/njagadeesh/ve3/lib/python3.10/site-packages/api4jenkins/init.py", line 248, in crumb
'GET', f'{self.url}crumbIssuer/api/json').json()
File "/home/njagadeesh/ve3/lib/python3.10/site-packages/httpx/_models.py", line 764, in json
return jsonlib.loads(self.content, **kwargs)
File "/usr/lib/python3.10/json/init.py", line 346, in loads
return _default_decoder.decode(s)
File "/usr/lib/python3.10/json/decoder.py", line 337, in decode
obj, end = self.raw_decode(s, idx=_w(s, 0).end())
File "/usr/lib/python3.10/json/decoder.py", line 355, in raw_decode
raise JSONDecodeError("Expecting value", s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)

Not sure if this is older jenkins version

Access lists of nodes from /label/

Feature request:
We use pools of nodes, and operate on subsets.
eg "FOO_1", "FOO_2", "BAR"
jenkins:8080/labels/FOO_1&&FOO_2||BAR

In groovy, we use

// get a list of nodes, excluding those which are offline (removes 'master')
def nlist = nodesByLabel(label: "${poollist}", offline: `false).sort()

Could you add something similar ?
Thanks

Please document 100 job limit on get_build(number)

After #33
Can the limit of 100 jobs be documented ?

I spent quite a while debugging why I couldn't get a job which I knew was valid. Turns out it was older than 100 jobs.
Even better would be an optional parameter (default to 100) which forces the user to think and can define more if required.

"buildWithParameters"

Hi!
Not a real issue, but I stumbled over the build_job method, which simply do a
return handle_req('POST', entry, params=params)

Well, "params=params" is quite static, after some digging you always using "**kwargs" down to the Requester-method.
I personally didnt get a build with that method to work, so I had to modify it to
return handle_req('POST', entry, **kwargs)
the strict "params=params" makes no sence to me.

cheers
Matt

add return type to methods like get_job

This could help with autocompletion in editors.

Current:
Here I (/editor) do not know what the return type of get_job would be

client = Jenkins('jen_url', auth=('user', 'pass'))
project: WorkflowMultiBranchProject = client.get_job(full_name="project")
job: WorkflowJob = project.get(name="branch")
job.build()

After adding types:

client = Jenkins('jen_url', auth=('user', 'pass'))
project= client.get_job(full_name="project")
job = project.get(name="branch")
job.build()

SSL check false

Hi, I got following error, have anything else parameters to false check certificates?

Max retries exceeded with url: /crumbIssuer/api/json (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self signed certificate in certificate chain (_ssl.c:1129)')))

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.