├── .github
├── ISSUE_TEMPLATE.md
├── dependabot.yml
├── stale.yml
└── workflows
│ ├── build-docs.yml
│ ├── python-package.yml
│ └── release.yml
├── .gitignore
├── .pre-commit-config.yaml
├── CHANGELOG.md
├── LICENSE
├── Makefile
├── README.rst
├── TODO
├── bin
└── pipelint
├── doc
├── .gitignore
├── CONTRIBUTING.md
├── Makefile
├── _static
│ ├── jenkins.png
│ └── tmp.txt
├── conf.py
├── getting_started.rst
├── index.rst
├── jenkins.png
├── module_reference.rst
├── project_info.rst
├── readme_link.rst
├── ssl_certificate_verification
└── submodules
│ ├── api.rst
│ ├── artifact.rst
│ ├── build.rst
│ ├── command_line.rst
│ ├── credentials.rst
│ ├── custom_exceptions.rst
│ ├── executors.rst
│ ├── fingerprint.rst
│ ├── jenkins.rst
│ ├── jenkinsbase.rst
│ ├── jobs.rst
│ ├── label.rst
│ ├── mutable_jenkins.rst
│ ├── nodes.rst
│ ├── plugins.rst
│ ├── queue.rst
│ ├── results.rst
│ ├── utils.rst
│ └── views.rst
├── examples
├── __init__.py
├── addjob.xml
├── how_to
│ ├── add_command.py
│ ├── create_a_job.py
│ ├── create_credentials.py
│ ├── create_nested_views.py
│ ├── create_slave.py
│ ├── create_views.py
│ ├── delete_all_the_nodes_except_master.py
│ ├── get_config.py
│ ├── get_plugin_information.py
│ ├── get_version_info_from_last_good_build.py
│ ├── query_a_build.py
│ ├── readme.rst
│ ├── search_artifact_by_regexp.py
│ ├── search_artifacts.py
│ ├── start_parameterized_build.py
│ └── use_crumbs.py
└── low_level
│ ├── copy_a_job.py
│ ├── create_a_view_low_level.py
│ ├── example_param_build.py
│ ├── login_with_auth.py
│ ├── post_watcher.py
│ └── readme.rst
├── jenkinsapi
├── .gitignore
├── __init__.py
├── api.py
├── artifact.py
├── build.py
├── command_line
│ ├── __init__.py
│ ├── jenkins_invoke.py
│ └── jenkinsapi_version.py
├── config.py
├── constants.py
├── credential.py
├── credentials.py
├── custom_exceptions.py
├── executor.py
├── executors.py
├── fingerprint.py
├── jenkins.py
├── jenkinsbase.py
├── job.py
├── jobs.py
├── label.py
├── mutable_jenkins_thing.py
├── node.py
├── nodes.py
├── plugin.py
├── plugins.py
├── queue.py
├── result.py
├── result_set.py
├── utils
│ ├── __init__.py
│ ├── crumb_requester.py
│ ├── jenkins_launcher.py
│ ├── jsonp_to_json.py
│ ├── krb_requester.py
│ ├── manifest.py
│ ├── requester.py
│ └── simple_post_logger.py
├── view.py
└── views.py
├── jenkinsapi_tests
├── __init__.py
├── conftest.py
├── systests
│ ├── __init__.py
│ ├── config.xml
│ ├── conftest.py
│ ├── get-jenkins-war.sh
│ ├── jenkins_home.tar.gz
│ ├── job_configs.py
│ ├── test_authentication.py
│ ├── test_credentials.py
│ ├── test_crumbs_requester.py
│ ├── test_downstream_upstream.py
│ ├── test_env_vars.py
│ ├── test_executors.py
│ ├── test_generate_new_api_token.py
│ ├── test_invocation.py
│ ├── test_jenkins.py
│ ├── test_jenkins_artifacts.py
│ ├── test_jenkins_matrix.py
│ ├── test_nodes.py
│ ├── test_parameterized_builds.py
│ ├── test_plugins.py
│ ├── test_queue.py
│ ├── test_quiet_down.py
│ ├── test_restart.py
│ ├── test_safe_exit.py
│ ├── test_scm.py
│ ├── test_views.py
│ └── view_configs.py
├── test_utils
│ ├── __init__.py
│ └── random_strings.py
└── unittests
│ ├── __init__.py
│ ├── configs.py
│ ├── test_artifact.py
│ ├── test_build.py
│ ├── test_build_scm_git.py
│ ├── test_executors.py
│ ├── test_fingerprint.py
│ ├── test_jenkins.py
│ ├── test_job.py
│ ├── test_job_folders.py
│ ├── test_job_get_all_builds.py
│ ├── test_job_scm_hg.py
│ ├── test_label.py
│ ├── test_misc.py
│ ├── test_node.py
│ ├── test_nodes.py
│ ├── test_plugins.py
│ ├── test_requester.py
│ ├── test_result_set.py
│ └── test_view.py
├── pylintrc
└── pyproject.toml
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ##### ISSUE TYPE
2 |
3 | - Bug Report
4 | - Feature Idea
5 | - How to...
6 |
7 | ##### Jenkinsapi VERSION
8 |
9 | ##### Jenkins VERSION
10 |
11 | ##### SUMMARY
12 |
13 |
14 | ##### EXPECTED RESULTS
15 |
16 |
17 | ##### ACTUAL RESULTS
18 |
19 |
20 | ##### USEFUL INFORMATION
21 |
25 |
26 |
27 | ```
28 |
29 | ```
30 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
5 |
6 | version: 2
7 | updates:
8 | - package-ecosystem: "github-actions" # See documentation for possible values
9 | directory: "/" # Location of package manifests
10 | schedule:
11 | interval: "weekly"
12 |
--------------------------------------------------------------------------------
/.github/stale.yml:
--------------------------------------------------------------------------------
1 | # Configuration for probot-stale - https://github.com/probot/stale
2 |
3 | # Number of days of inactivity before an Issue or Pull Request becomes stale
4 | daysUntilStale: 60
5 |
6 | # Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
7 | # Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
8 | daysUntilClose: 7
9 |
10 | # Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled)
11 | onlyLabels: []
12 |
13 | # Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
14 | exemptLabels:
15 | - pinned
16 | - security
17 | - "[Status] Maybe Later"
18 | - "feature request"
19 | - "help wanted"
20 | - "improvement request"
21 |
22 | # Set to true to ignore issues in a project (defaults to false)
23 | exemptProjects: false
24 |
25 | # Set to true to ignore issues in a milestone (defaults to false)
26 | exemptMilestones: false
27 |
28 | # Set to true to ignore issues with an assignee (defaults to false)
29 | exemptAssignees: false
30 |
31 | # Label to use when marking as stale
32 | staleLabel: stale
33 |
34 | # Comment to post when marking as stale. Set to `false` to disable
35 | markComment: >
36 | This issue has been automatically marked as stale because it has not had
37 | recent activity. It will be closed if no further activity occurs. Thank you
38 | for your contributions.
39 |
40 | # Comment to post when removing the stale label.
41 | # unmarkComment: >
42 | # Your comment here.
43 |
44 | # Comment to post when closing a stale Issue or Pull Request.
45 | closeComment: >
46 | Closed due to inactivity
47 |
48 | # Limit the number of actions per hour, from 1-30. Default is 30
49 | limitPerRun: 30
50 |
51 | # Limit to only `issues` or `pulls`
52 | # only: issues
53 |
54 | # Optionally, specify configuration settings that are specific to just 'issues' or 'pulls':
55 | # pulls:
56 | # daysUntilStale: 30
57 | # markComment: >
58 | # This pull request has been automatically marked as stale because it has not had
59 | # recent activity. It will be closed if no further activity occurs. Thank you
60 | # for your contributions.
61 |
62 | # issues:
63 | # exemptLabels:
64 | # - confirmed
65 |
--------------------------------------------------------------------------------
/.github/workflows/build-docs.yml:
--------------------------------------------------------------------------------
1 | name: Sphinx build
2 |
3 | on: # yamllint disable-line rule:truthy
4 | push:
5 | branches:
6 | - master
7 | workflow_dispatch:
8 |
9 | jobs:
10 | sphinx-build:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v4
14 |
15 | - name: Install uv
16 | uses: astral-sh/setup-uv@v6
17 | with:
18 | python-version: "3.10"
19 |
20 | - name: Install dependencies
21 | run: |
22 | sudo apt-get update; sudo apt-get install libkrb5-dev gcc
23 |
24 | - name: Build HTML
25 | run: make
26 | working-directory: ./doc
27 |
28 | - name: Upload artifacts
29 | uses: actions/upload-artifact@v4
30 | with:
31 | name: html-docs
32 | path: ./doc/html/
33 |
34 | - name: Deploy Master Docs to GitHub Pages
35 | if: ${{ github.ref == 'refs/heads/master' }}
36 | uses: peaceiris/actions-gh-pages@v4
37 | with:
38 | github_token: ${{ secrets.GITHUB_TOKEN }}
39 | publish_dir: ./doc/html
40 | publish_branch: gh-pages
41 | keep_files: false
42 |
--------------------------------------------------------------------------------
/.github/workflows/python-package.yml:
--------------------------------------------------------------------------------
1 | name: CI_TEST
2 |
3 | on:
4 | push:
5 | branches: [ "master" ]
6 | paths:
7 | - '**.py'
8 | - '**.yml'
9 | pull_request:
10 | branches: [ "master" ]
11 | workflow_dispatch: # allow manual run
12 |
13 | concurrency:
14 | group: ${{ github.workflow }}-${{ github.ref }}
15 | cancel-in-progress: true
16 |
17 | jobs:
18 | build:
19 | runs-on: ubuntu-latest
20 | strategy:
21 | fail-fast: false
22 | matrix:
23 | python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
24 | token: ["stable"]
25 |
26 | steps:
27 | - name: Harden Runner
28 | uses: step-security/harden-runner@v2.12.0
29 | with:
30 | egress-policy: audit
31 | allowed-endpoints: >
32 | azure.archive.ubuntu.com:80
33 | esm.ubuntu.com:443
34 | files.pythonhosted.org:443
35 | ftp-chi.osuosl.org:443
36 | ftp-nyc.osuosl.org:443
37 | get.jenkins.io:443
38 | github.com:443
39 | api.github.com:443
40 | int.api.stepsecurity.io:443
41 | mirror.xmission.com:443
42 | motd.ubuntu.com:443
43 | packages.microsoft.com:443
44 | ppa.launchpadcontent.net:443
45 | pypi.org:443
46 | updates.jenkins-ci.org:80
47 | updates.jenkins.io:443
48 | mirrors.updates.jenkins.io:443
49 | updates.jenkins.io:80
50 |
51 | - name: Checkout
52 | uses: actions/checkout@v4
53 |
54 | - name: Install uv
55 | uses: astral-sh/setup-uv@v6
56 | with:
57 | python-version: ${{ matrix.python-version }}
58 |
59 | - name: Install python
60 | run: uv python install
61 |
62 | - name: setup java 21
63 | uses: actions/setup-java@v4
64 | with:
65 | java-version: '21'
66 | distribution: 'temurin'
67 |
68 | - name: Install dependencies
69 | run: |
70 | sudo apt-get update; sudo apt-get install libkrb5-dev gcc
71 |
72 | - name: Lint with ruff
73 | run: |
74 | uv run ruff check jenkinsapi/ --output-format full
75 |
76 | - name: Test with pytest
77 | env:
78 | JENKINS_VERSION: ${{ matrix.token }}
79 | run: |
80 | uv run pytest -sv --cov=jenkinsapi --cov-report=term-missing --cov-report=xml jenkinsapi_tests
81 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | release:
5 | types: [published]
6 |
7 | jobs:
8 | build:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - name: Checkout
12 | uses: actions/checkout@v4
13 |
14 | - name: Install uv
15 | uses: astral-sh/setup-uv@v6
16 | with:
17 | python-version: "3.13"
18 |
19 | - name: Install python
20 | run: uv python install
21 |
22 | - name: build
23 | run: uv build
24 |
25 | - name: Upload artifact
26 | uses: actions/upload-artifact@v4
27 | with:
28 | name: package
29 | path: dist/
30 | retention-days: 7
31 | if-no-files-found: error
32 |
33 | pypi:
34 | needs: [build]
35 | runs-on: ubuntu-latest
36 | permissions:
37 | id-token: write
38 | environment:
39 | name: pypi-publishing
40 | url: https://pypi.org/project/jenkinsapi/
41 | steps:
42 | - name: Download artifact
43 | uses: actions/download-artifact@v4
44 | with:
45 | name: package
46 | path: dist
47 |
48 | - name: Show tree
49 | run: tree
50 |
51 | - name: Publish
52 | uses: pypa/gh-action-pypi-publish@release/v1
53 | with:
54 | password: ${{ secrets.PYPI_API_TOKEN }}
55 |
56 | asset:
57 | needs: [build]
58 | runs-on: ubuntu-latest
59 | permissions:
60 | contents: write
61 | steps:
62 | - name: Download artifact
63 | uses: actions/download-artifact@v4
64 | with:
65 | name: package
66 | path: dist
67 |
68 | - name: Show tree
69 | run: tree
70 |
71 | - name: Add release asset
72 | uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631
73 | with:
74 | tag_name: ${{ github.event.release.tag_name }}
75 | fail_on_unmatched_files: true
76 | files: |
77 | dist/*
78 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .svn
2 | .project
3 | .pydevproject
4 | *.pyc
5 | *.egg-info
6 | /build
7 | /dist
8 | .settings
9 | *.DS_Store
10 | localinstance_files/
11 | .coverage/
12 | .coverage
13 | nosetests.xml
14 | coverage*/
15 | .idea/
16 | include/
17 | lib/
18 | *.egg
19 | .tox/*
20 | *.sw?
21 | /jenkinsapi_tests/systests/coverage.xml
22 | /.cache
23 | /AUTHORS
24 | /ChangeLog
25 | .pytest_cache
26 | .eggs
27 | coverage.xml
28 | .venv/
29 | .vscode/
30 | *.war
31 | venv/
32 | tags
33 | .pytype/
34 | uv.lock
35 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | repos:
3 | - repo: https://github.com/psf/black
4 | rev: 24.10.0
5 | hooks:
6 | - id: black
7 | args:
8 | - --line-length=79
9 | # these folders wont be formatted by black
10 | - --exclude="""\.git |
11 | \.__pycache__|
12 | \.hg|
13 | \.mypy_cache|
14 | \.tox|
15 | \.venv|
16 | _build|
17 | buck-out|
18 | build|
19 | dist"""
20 | language_version: python3
21 | - repo: https://github.com/pre-commit/pre-commit-hooks
22 | rev: v5.0.0
23 | hooks:
24 | - id: end-of-file-fixer
25 | - id: trailing-whitespace
26 | - id: mixed-line-ending
27 | - id: check-byte-order-marker
28 | - id: check-executables-have-shebangs
29 | - id: check-merge-conflict
30 | - id: check-symlinks
31 | - id: check-vcs-permalinks
32 | - id: debug-statements
33 | - id: check-yaml
34 | files: .*\.(yaml|yml)$
35 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## 3.3.13
4 |
5 | - docs: fix doc and add autobuild
6 | - feature: add launch method to Node class (#849)
7 | - convert to using ruff for linting (#895)
8 | - update harden runner (#894)
9 | - change url to pull jenkins.war file (#893)
10 | - Make sure to filter warnings to errors (#868)
11 | - Revert "dependabot" (#888)
12 | - dependabot (#887)
13 | - makefile: update for uv compatibility (#886)
14 | - update pre-commit versions (#885)
15 | - remove deprecated setup (#884)
16 | - housekeeping: readme, license, pyproject.toml (#881)
17 | - update install commands to use uv (#882)
18 | - ci: update java to 21 (#883)
19 | - convert to uv and pyproject.toml (#879)
20 | - ci: update checkout to v4 (#880)
21 | - ci: remove apt-get update, install, and pip upgrade as unnecessary (#878)
22 | - docs: fix build (#876)
23 | - add support for 3.12 and 3.13 (#877)
24 | - Correctly specify python version (#858)
25 | - Merge pull request #874 from clintonsteiner/fixSeveralCiIssues
26 | - ci: add in missing plugins
27 | - ci: harden runner - set to audit for now
28 | - ci: add mirrors to harden whitelist
29 | - test_build: fix failing tests
30 | - ci: tests fail because jenkins requires java 17 - setup in github action
31 | - makefile: fix py.test according to pytest documentation
32 | - ci: fix issue causing runner to crash in setup
33 | - Merge pull request #853 from clintonsteiner/updateReadme
34 | - Readme Updates: Remove redundant words, move install and example to top
35 | - [pre-commit.ci] auto fixes from pre-commit.com hooks
36 | - fix: jenkinsapi crashing jenkins_is_unavaiable
37 | - [pre-commit.ci] auto fixes from pre-commit.com hooks
38 | - Support for using unicode characters in update_config
39 | - Decode content using the encoding specified in the response
40 | - Fixing plugin dependencies
41 | - Fix console scripts in pyproject.toml
42 | - Added Python typing hints to most of the code
43 | - Bump version
44 | - Move utils into jenkinsapi.utils module
45 | - Remove blank line
46 | - Update Build object docs
47 | - Add pyproject.toml
48 | - Rename Travis and Tox files
49 | - Flake8 fixes
50 | - [pre-commit.ci] auto fixes from pre-commit.com hooks
51 | - Do not call Jenkins site when testing plugins
52 | - Download Jenkins silently and improve plugin downloads
53 | - Fix failing tests and commented ones not working
54 | - Add flake8 to test requirements
55 | - Install test dependencies
56 | - Install krb and pytest-cov in workflow
57 | - Add Github action
58 | - Pyflake8 fixes
59 | - Add pre-commit.ci configuration
60 | - Add debug logs in Queue.block_until_building (#745)
61 | - Fixing retrying logic by introducing a max_retries param (#739)
62 | - Add quiet period parameter to job invoke (#758)
63 | - useCrumbs -> use_crumbs #755 (#756)
64 | - job.py: manage scm data for multibranch pipelines (#742)
65 |
66 | ## 0.3.11 - Oct 31, 2019
67 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 PyContribs
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: test lint tox coverage dist
2 |
3 | test:
4 | uv run pytest -sv jenkinsapi_tests
5 |
6 | lint:
7 | uv run pycodestyle
8 | uv run pylint jenkinsapi/*.py
9 | uv run flake8 jenkinsapi/ --count --select=E9,F63,F7,F82 --ignore F821,W503,W504 --show-source --statistics
10 | uv run flake8 jenkinsapi/ --count --exit-zero --max-complexity=10 --max-line-length=79 --statistics
11 |
12 | tox:
13 | tox
14 |
15 | dist:
16 | uv build
17 |
18 | coverage:
19 | uv run pytest -sv --cov=jenkinsapi --cov-report=term-missing --cov-report=xml jenkinsapi_tests
20 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | Jenkinsapi
2 | ==========
3 |
4 | .. image:: https://badge.fury.io/py/jenkinsapi.png
5 | :target: http://badge.fury.io/py/jenkinsapi
6 |
7 | .. image:: https://codecov.io/gh/pycontribs/jenkinsapi/branch/master/graph/badge.svg
8 | :target: https://codecov.io/gh/pycontribs/jenkinsapi
9 |
10 | Installation
11 | ------------
12 |
13 | .. code-block:: bash
14 |
15 | pip install jenkinsapi
16 |
17 | Important Links
18 | ---------------
19 | * `Documentation `__
20 | * `Source Code `_
21 | * `Support and bug-reports `_
22 | * `Releases `_
23 |
24 |
25 | About this library
26 | -------------------
27 |
28 | Jenkins is the market leading continuous integration system.
29 |
30 | Jenkins (and its predecessor Hudson) are useful projects for automating common development tasks (e.g. unit-testing, production batches) - but they are somewhat Java-centric.
31 |
32 | Jenkinsapi makes scripting Jenkins tasks a breeze by wrapping the REST api into familiar python objects.
33 |
34 | Here is a list of some of the most commonly used functionality
35 |
36 | * Add, remove, and query Jenkins jobs
37 | * Control pipeline execution
38 | * Query the results of a completed build
39 | * Block until jobs are complete or run jobs asyncronously
40 | * Get objects representing the latest builds of a job
41 | * Artifact management
42 | * Search for artifacts by simple criteria
43 | * Install artifacts to custom-specified directory structures
44 | * Search for builds by source code revision
45 | * Create, destroy, and monitor
46 | * Build nodes (Webstart and SSH slaves)
47 | * Views (including nested views using NestedViews Jenkins plugin)
48 | * Credentials (username/password and ssh key)
49 | * Authentication support for username and password
50 | * Manage jenkins and plugin installation
51 |
52 | Full library capabilities are outlined in the `Documentation `__
53 |
54 | Get details of jobs running on Jenkins server
55 | ---------------------------------------------
56 |
57 | .. code-block:: python
58 |
59 | """Get job details of each job that is running on the Jenkins instance"""
60 | def get_job_details():
61 | # Refer Example #1 for definition of function 'get_server_instance'
62 | server = get_server_instance()
63 | for job_name, job_instance in server.get_jobs():
64 | print 'Job Name:%s' % (job_instance.name)
65 | print 'Job Description:%s' % (job_instance.get_description())
66 | print 'Is Job running:%s' % (job_instance.is_running())
67 | print 'Is Job enabled:%s' % (job_instance.is_enabled())
68 |
69 | Disable/Enable a Jenkins Job
70 | ----------------------------
71 |
72 | .. code-block:: python
73 |
74 | def disable_job():
75 | """Disable a Jenkins job"""
76 | # Refer Example #1 for definition of function 'get_server_instance'
77 | server = get_server_instance()
78 | job_name = 'nightly-build-job'
79 | if (server.has_job(job_name)):
80 | job_instance = server.get_job(job_name)
81 | job_instance.disable()
82 | print 'Name:%s,Is Job Enabled ?:%s' % (job_name,job_instance.is_enabled())
83 |
84 | Use the call ``job_instance.enable()`` to enable a Jenkins Job.
85 |
86 |
87 | Known issues
88 | ------------
89 | * Job deletion operations fail unless Cross-Site scripting protection is disabled.
90 |
91 | For other issues, please refer to the `support URL `_
92 |
93 | Development
94 | -----------
95 |
96 | * Make sure that you have Java_ installed. Jenkins will be automatically
97 | downloaded and started during tests.
98 | * Create virtual environment for development
99 | * Install package in development mode
100 |
101 | .. code-block:: bash
102 |
103 | uv sync
104 |
105 | * Make your changes, write tests and check your code
106 |
107 | .. code-block:: bash
108 |
109 | uv run pytest -sv
110 |
111 | Python versions
112 | ---------------
113 |
114 | The project has been tested against Python versions:
115 |
116 | * 3.8 - 3.13
117 |
118 | Jenkins versions
119 | ----------------
120 |
121 | Project tested on both stable (LTS) and latest Jenkins versions.
122 |
123 | Project Contributors
124 | --------------------
125 |
126 | * Aleksey Maksimov (ctpeko3a@gmail.com)
127 | * Salim Fadhley (sal@stodge.org)
128 | * Ramon van Alteren (ramon@vanalteren.nl)
129 | * Ruslan Lutsenko (ruslan.lutcenko@gmail.com)
130 | * Cleber J Santos (cleber@simplesconsultoria.com.br)
131 | * William Zhang (jollychang@douban.com)
132 | * Victor Garcia (bravejolie@gmail.com)
133 | * Bradley Harris (bradley@ninelb.com)
134 | * Kyle Rockman (kyle.rockman@mac.com)
135 | * Sascha Peilicke (saschpe@gmx.de)
136 | * David Johansen (david@makewhat.is)
137 | * Misha Behersky (bmwant@gmail.com)
138 | * Clinton Steiner (clintonsteiner@gmail.com)
139 |
140 | Please do not contact these contributors directly for support questions! Use the GitHub tracker instead.
141 |
142 | .. _Java: https://www.oracle.com/java/technologies/downloads/#java21
143 |
--------------------------------------------------------------------------------
/TODO:
--------------------------------------------------------------------------------
1 | TODO:
2 |
3 | * Clean up the fingerprint code
4 | * Clean up the resultset and results code
5 | * Add support for Jenkins 2.x features
6 | * Improve speed for large Jenkins installations
7 |
--------------------------------------------------------------------------------
/bin/pipelint:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -eo pipefail
3 |
4 | INPUT=/dev/stdin
5 | if [ -t 0 ]; then
6 | if [ "$#" -ne 1 ]; then
7 | echo "ERROR: Illegal number of parameters."
8 | echo "INFO: Use 'pipefail Jenkinsfile' or 'cat Jenkinsfile | pipefail'"
9 | exit 1
10 | fi
11 | INPUT=$1
12 | fi
13 | # put credentials inside ~/.netrc
14 | # define JENKINS_URL in your user profile
15 | JENKINS_URL=${JENKINS_URL:-http://localhost:8080}
16 |
17 | # failure to get crumb is ignored as this may be diabled on the server side
18 | CRUMB="-H `curl -nfs "$JENKINS_URL/crumbIssuer/api/xml?xpath=concat(//crumbRequestField,%22:%22,//crumb)"`" || CRUMB=''
19 |
20 | # The tee+grep trick assures that the exit code is 0 only if the server replied with "successfully validated"
21 | curl -nfs -X POST $CRUMB -F "jenkinsfile=<-" $JENKINS_URL/pipeline-model-converter/validate <$INPUT \
22 | | tee >(cat 1>&2) | grep 'successfully validated' >/dev/null
23 |
--------------------------------------------------------------------------------
/doc/.gitignore:
--------------------------------------------------------------------------------
1 | /**/html
2 | /docs_html.zip
3 | / html
4 | /build
5 |
--------------------------------------------------------------------------------
/doc/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | Contributing
2 | ============
3 |
4 | The JenkinsAPI project welcomes contributions via GitHub. Please bear in mind the following guidelines when preparing your pull-request.
5 |
6 | Pre-commit
7 | ----------
8 | Ensure pre-commit has been setup prior to comitting
9 |
10 | Build the Docs
11 | --------------
12 | From within doc: make && python -m http.server --directory html
13 |
14 | Python compatibility
15 | --------------------
16 |
17 | The project currently targets Python 3.8+.
18 |
19 | Code formatting
20 | ---------------
21 |
22 | The project follows strict PEP8 guidelines. Please use a tool like black to format your code before submitting a pull request. Tell black to use 79 characters per line (black -l 79).
23 |
24 | Test Driven Development
25 | -----------------------
26 |
27 | Please do not submit pull requests without tests. That's really important. Our project is all about test-driven development. It would be embarrasing if our project failed because of a lack of tests!
28 |
29 | You might want to follow a typical test driven development cycle: http://en.wikipedia.org/wiki/Test-driven_development
30 |
31 | Put simply: Write your tests first and only implement features required to make your tests pass. Do not let your implementation get ahead of your tests.
32 |
33 | Features implemented without tests will be removed. Unmaintained features (which break because of changes in Jenkins) will also be removed.
34 |
35 | Check the CI status before comitting
36 | ------------------------------------
37 |
38 | Project uses Github Actions, please verify that your branch passes all tests before making a pull request.
39 |
40 | Any problems?
41 | -------------
42 |
43 | If you are stuck on something, please post to the issue tracker. Do not contact the developers directly.
44 |
--------------------------------------------------------------------------------
/doc/Makefile:
--------------------------------------------------------------------------------
1 | SPHINXBUILD ?= uv run sphinx-build
2 | SOURCEDIR = .
3 | BUILDDIR = html
4 |
5 | .PHONY: all clean html
6 |
7 | all: clean html
8 |
9 | html:
10 | uv sync --group docs
11 | @$(SPHINXBUILD) -b html "$(SOURCEDIR)" "$(BUILDDIR)"
12 |
13 | clean:
14 | rm -rf ${BUILDDIR}/*
15 |
16 | run:
17 | uv run python -m http.server --directory "${BUILDDIR}"
18 |
--------------------------------------------------------------------------------
/doc/_static/jenkins.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycontribs/jenkinsapi/ffb35b6dca607b9aa5a65d4e49aa1ca76946adbf/doc/_static/jenkins.png
--------------------------------------------------------------------------------
/doc/_static/tmp.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycontribs/jenkinsapi/ffb35b6dca607b9aa5a65d4e49aa1ca76946adbf/doc/_static/tmp.txt
--------------------------------------------------------------------------------
/doc/getting_started.rst:
--------------------------------------------------------------------------------
1 | Getting Started
2 | ===============
3 |
4 | JenkinsAPI lets you query the state of a running Jenkins server. It also allows you to change configuration and automate minor tasks on nodes and jobs.
5 |
6 | Installation
7 | -------------
8 |
9 | .. code-block:: bash
10 |
11 | pip install jenkinsapi
12 |
13 | Example
14 | -------
15 |
16 | JenkinsAPI is intended to map the objects in Jenkins (e.g. Builds, Views, Jobs) into easily managed Python objects:
17 |
18 | .. code-block:: python
19 |
20 | from jenkinsapi.jenkins import Jenkins
21 | J = Jenkins('http://localhost:8080')
22 | print(J.version) # 1.542
23 | print(J.keys()) # foo, test_jenkinsapi
24 | print(J.get('test_jenkinsapi')) #
25 | print(J.get('test_jenkinsapi').get_last_good_build()) #
26 |
27 | Testing
28 | -------
29 |
30 | If you have installed the test dependencies on your system already, you can run
31 | the testsuite with the following command:
32 |
33 | .. code-block:: bash
34 |
35 | uv sync
36 | uv run pytest -sv --cov=jenkinsapi --cov-report=term-missing --cov-report=xml jenkinsapi_tests
37 |
38 | Otherwise using a virtualenv is recommended. Setuptools will automatically fetch
39 | missing test dependencies:
40 |
41 | .. code-block:: bash
42 |
43 | uv venv
44 | uv python install
45 | uv run pytest -sv --cov=jenkinsapi --cov-report=term-missing --cov-report=xml jenkinsapi_tests
46 |
47 | Get version of Jenkins
48 | ----------------------
49 |
50 | .. code-block:: python
51 |
52 | from jenkinsapi.jenkins import Jenkins
53 |
54 | def get_server_instance():
55 | jenkins_url = 'http://jenkins_host:8080'
56 | server = Jenkins(jenkins_url, username='foouser', password='foopassword')
57 | return server
58 |
59 | if __name__ == '__main__':
60 | print get_server_instance().version
61 |
62 | The above code prints version of Jenkins running on the host *jenkins_host*.
63 |
64 | From Jenkins vesion 1.426 onward one can specify an API token instead of your real password while authenticating the user against Jenkins instance.
65 |
66 | Refer to the the Jenkis wiki page `Authenticating scripted clients `_ for details about how a user can generate an API token.
67 |
68 | Once you have API token you can pass the API token instead of real password while creating an Jenkins server instance using Jenkins API.
69 |
70 | Get details of jobs running on Jenkins server
71 | ---------------------------------------------
72 |
73 | .. code-block:: python
74 |
75 | """Get job details of each job that is running on the Jenkins instance"""
76 | def get_job_details():
77 | # Refer Example #1 for definition of function 'get_server_instance'
78 | server = get_server_instance()
79 | for job_name, job_instance in server.get_jobs():
80 | print 'Job Name:%s' % (job_instance.name)
81 | print 'Job Description:%s' % (job_instance.get_description())
82 | print 'Is Job running:%s' % (job_instance.is_running())
83 | print 'Is Job enabled:%s' % (job_instance.is_enabled())
84 |
85 | Disable/Enable a Jenkins Job
86 | ----------------------------
87 |
88 | .. code-block:: python
89 |
90 | def disable_job():
91 | """Disable a Jenkins job"""
92 | # Refer Example #1 for definition of function 'get_server_instance'
93 | server = get_server_instance()
94 | job_name = 'nightly-build-job'
95 | if (server.has_job(job_name)):
96 | job_instance = server.get_job(job_name)
97 | job_instance.disable()
98 | print 'Name:%s,Is Job Enabled ?:%s' % (job_name,job_instance.is_enabled())
99 |
100 | Use the call ``job_instance.enable()`` to enable a Jenkins Job.
101 |
102 | Get Plugin details
103 | ------------------
104 |
105 | Below chunk of code gets the details of the plugins currently installed in the
106 | Jenkins instance.
107 |
108 | .. code-block:: python
109 |
110 | def get_plugin_details():
111 | # Refer Example #1 for definition of function 'get_server_instance'
112 | server = get_server_instance()
113 | for plugin in server.get_plugins().values():
114 | print "Short Name:%s" % (plugin.shortName)
115 | print "Long Name:%s" % (plugin.longName)
116 | print "Version:%s" % (plugin.version)
117 | print "URL:%s" % (plugin.url)
118 | print "Active:%s" % (plugin.active)
119 | print "Enabled:%s" % (plugin.enabled)
120 |
121 | Getting version information from a completed build
122 | --------------------------------------------------
123 |
124 | This is a typical use of JenkinsAPI - it was the very first use I had in mind when the project was first built.
125 |
126 | In a continuous-integration environment you want to be able to programatically detect the version-control information of the last succsessful build in order to trigger some kind of release process.
127 |
128 | .. code-block:: python
129 |
130 | from jenkinsapi.jenkins import Jenkins
131 |
132 | def getSCMInfroFromLatestGoodBuild(url, jobName, username=None, password=None):
133 | J = Jenkins(url, username, password)
134 | job = J[jobName]
135 | lgb = job.get_last_good_build()
136 | return lgb.get_revision()
137 |
138 | if __name__ == '__main__':
139 | print getSCMInfroFromLatestGoodBuild('http://localhost:8080', 'fooJob')
140 |
141 | When used with the Git source-control system line 20 will print out something like '8b4f4e6f6d0af609bb77f95d8fb82ff1ee2bba0d' - which looks suspiciously like a Git revision number.
142 |
--------------------------------------------------------------------------------
/doc/jenkins.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycontribs/jenkinsapi/ffb35b6dca607b9aa5a65d4e49aa1ca76946adbf/doc/jenkins.png
--------------------------------------------------------------------------------
/doc/module_reference.rst:
--------------------------------------------------------------------------------
1 | Module Reference
2 | ================
3 |
4 | .. toctree::
5 | :maxdepth: 4
6 |
7 | submodules/api
8 | submodules/artifact
9 | submodules/build
10 | submodules/credentials
11 | submodules/custom_exceptions
12 | submodules/command_line
13 | submodules/executors
14 | submodules/fingerprint
15 | submodules/jenkins
16 | submodules/jenkinsbase
17 | submodules/jobs
18 | submodules/label
19 | submodules/mutable_jenkins
20 | submodules/nodes
21 | submodules/plugins
22 | submodules/utils
23 | submodules/queue
24 | submodules/results
25 | submodules/views
26 |
--------------------------------------------------------------------------------
/doc/project_info.rst:
--------------------------------------------------------------------------------
1 | Project Info
2 | ============
3 |
4 | .. automodule:: jenkinsapi
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 |
--------------------------------------------------------------------------------
/doc/readme_link.rst:
--------------------------------------------------------------------------------
1 | .. include:: ../README.rst
2 |
--------------------------------------------------------------------------------
/doc/ssl_certificate_verification:
--------------------------------------------------------------------------------
1 | SSL Certificate Verification
2 | ============================
3 |
4 | There are times, when one would like to skip the SSL certificate verification.
5 |
6 | For instance, the system administrator has set a different and new SSL
7 | certificate to the Jenkins master causing `certificate verify failed` errors:
8 |
9 | SSLError: [Errno 1] _ssl.c:504: error:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed
10 |
11 | Unfortunately, he's not available right now, and you have to finish your tasks
12 | using the JenkinsAPI library by the end of today.
13 | In these times, skipping the SSL certificate verification can be helpful.
14 |
15 | ***Note***: It's not recommended to disable SSL verifying on a regular basis.
16 | To really fix this, please read
17 | [this post](http://stackoverflow.com/questions/30830901/python-requests-throwing-ssl-errors/30831120#30831120)
18 | at StackOverflow.
19 |
20 | Disabling the SSL verify
21 | ------------------------
22 |
23 | Before JenkinsAPI v??, if one wants to disable the SSL certificate
24 | verification, she/he will have to pass a `Requester` instance specifying that.
25 |
26 | It may look like this:
27 |
28 | from jenkinsapi.jenkins import Jenkins
29 | username = 'jenkins'
30 | password = 'changeme'
31 | baseurl = 'https://localhost:8443'
32 | jenkins = Jenkins(baseurl, username, password, requester=Requester(username, password, ssl_verify=False))
33 |
34 | As you can see, the last line is quite long.
35 | Not to mention that we pass the `username` and the `password` variables twice.
36 |
37 | There must be a better way!
38 |
39 | Disabling the SSL verify in JenkinsAPI v??
40 | ------------------------------------------
41 |
42 | All these problems are gone thanks to `ssl_verify` argument.
43 | You're not asked to pass a `Requester` instance anymore for just disabling
44 | the SSL certificate verification.
45 |
46 | This is how it looks like:
47 |
48 | jenkins = Jenkins(baseurl, username, password, ssl_verify=False)
49 |
50 | Now, the code is more readable than before.
51 |
52 | Notes
53 | -----
54 |
55 | If you specify both `ssl_verify` and `requester` arguments, then the
56 | `ssl_verify` argument will be ignored.
57 |
58 | For example:
59 |
60 | jenkins = jenkinsapi.Jenkins(baseurl, username, password, requester=Requester(username, password, ssl_verify=False), ssl_verify=True)
61 |
62 | will do SSL certificate verifying.
63 |
--------------------------------------------------------------------------------
/doc/submodules/api.rst:
--------------------------------------------------------------------------------
1 | User API
2 | ========
3 |
4 | .. automodule:: jenkinsapi.api
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 |
--------------------------------------------------------------------------------
/doc/submodules/artifact.rst:
--------------------------------------------------------------------------------
1 | Artifact
2 | ========
3 |
4 | .. automodule:: jenkinsapi.artifact
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 |
--------------------------------------------------------------------------------
/doc/submodules/build.rst:
--------------------------------------------------------------------------------
1 | Build
2 | =====
3 |
4 | .. automodule:: jenkinsapi.build
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 |
--------------------------------------------------------------------------------
/doc/submodules/command_line.rst:
--------------------------------------------------------------------------------
1 | Command\_line
2 | =============
3 |
4 | Command\_line.jenkins\_invoke module
5 | -----------------------------------------------
6 |
7 | .. automodule:: jenkinsapi.command_line.jenkins_invoke
8 | :members:
9 | :undoc-members:
10 | :show-inheritance:
11 |
--------------------------------------------------------------------------------
/doc/submodules/credentials.rst:
--------------------------------------------------------------------------------
1 | Credentials and Credential
2 | ==========================
3 |
4 | .. contents::
5 | :depth: 2
6 | :local:
7 | :backlinks: none
8 | :class: this-will-duplicate-information-and-it-is-still-useful-here
9 |
10 | Credentials
11 | -----------
12 |
13 | .. automodule:: jenkinsapi.credentials
14 | :members:
15 | :undoc-members:
16 | :show-inheritance:
17 |
18 | Credential
19 | ----------
20 |
21 | .. automodule:: jenkinsapi.credential
22 | :members:
23 | :undoc-members:
24 | :show-inheritance:
25 |
--------------------------------------------------------------------------------
/doc/submodules/custom_exceptions.rst:
--------------------------------------------------------------------------------
1 | Custom\_exceptions
2 | ------------------
3 |
4 | .. automodule:: jenkinsapi.custom_exceptions
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 |
--------------------------------------------------------------------------------
/doc/submodules/executors.rst:
--------------------------------------------------------------------------------
1 | Exectors and Executor
2 | =====================
3 |
4 | .. contents::
5 | :depth: 2
6 | :local:
7 | :backlinks: none
8 | :class: this-will-duplicate-information-and-it-is-still-useful-here
9 |
10 | Executors
11 | ---------
12 |
13 | .. automodule:: jenkinsapi.executors
14 | :members:
15 | :undoc-members:
16 | :show-inheritance:
17 |
18 | Executor
19 | --------
20 |
21 | .. automodule:: jenkinsapi.executor
22 | :members:
23 | :undoc-members:
24 | :show-inheritance:
25 |
--------------------------------------------------------------------------------
/doc/submodules/fingerprint.rst:
--------------------------------------------------------------------------------
1 | Fingerprint
2 | -----------
3 |
4 | .. automodule:: jenkinsapi.fingerprint
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 |
--------------------------------------------------------------------------------
/doc/submodules/jenkins.rst:
--------------------------------------------------------------------------------
1 | Jenkins
2 | -------
3 |
4 | .. automodule:: jenkinsapi.jenkins
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 |
--------------------------------------------------------------------------------
/doc/submodules/jenkinsbase.rst:
--------------------------------------------------------------------------------
1 | Jenkinsbase
2 | -----------
3 |
4 | .. automodule:: jenkinsapi.jenkinsbase
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 |
--------------------------------------------------------------------------------
/doc/submodules/jobs.rst:
--------------------------------------------------------------------------------
1 | Jobs and job
2 | ============
3 |
4 | .. contents::
5 | :depth: 2
6 | :local:
7 | :backlinks: none
8 | :class: this-will-duplicate-information-and-it-is-still-useful-here
9 |
10 | Jobs
11 | ----
12 |
13 | .. automodule:: jenkinsapi.jobs
14 | :members:
15 | :undoc-members:
16 | :show-inheritance:
17 |
18 | Job
19 | ---
20 |
21 | .. automodule:: jenkinsapi.job
22 | :members:
23 | :undoc-members:
24 | :show-inheritance:
25 |
--------------------------------------------------------------------------------
/doc/submodules/label.rst:
--------------------------------------------------------------------------------
1 | Label
2 | -----
3 |
4 | .. automodule:: jenkinsapi.label
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 |
--------------------------------------------------------------------------------
/doc/submodules/mutable_jenkins.rst:
--------------------------------------------------------------------------------
1 | Mutable_jenkins_thing
2 | ---------------------
3 |
4 | .. automodule:: jenkinsapi.mutable_jenkins_thing
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 |
--------------------------------------------------------------------------------
/doc/submodules/nodes.rst:
--------------------------------------------------------------------------------
1 | Nodes and node
2 | ==============
3 |
4 | .. contents::
5 | :depth: 2
6 | :local:
7 | :backlinks: none
8 | :class: this-will-duplicate-information-and-it-is-still-useful-here
9 |
10 | nodes
11 | -----
12 |
13 | .. automodule:: jenkinsapi.nodes
14 | :members:
15 | :undoc-members:
16 | :show-inheritance:
17 |
18 | node
19 | ----
20 |
21 | .. automodule:: jenkinsapi.node
22 | :members:
23 | :undoc-members:
24 | :show-inheritance:
25 |
--------------------------------------------------------------------------------
/doc/submodules/plugins.rst:
--------------------------------------------------------------------------------
1 | Plugins and Plugin
2 | ==================
3 |
4 | .. contents::
5 | :depth: 2
6 | :local:
7 | :backlinks: none
8 | :class: this-will-duplicate-information-and-it-is-still-useful-here
9 |
10 | Plugins
11 | -------
12 |
13 | .. automodule:: jenkinsapi.plugins
14 | :members:
15 | :undoc-members:
16 | :show-inheritance:
17 |
18 | Plugin
19 | ------
20 |
21 | .. automodule:: jenkinsapi.plugin
22 | :members:
23 | :undoc-members:
24 | :show-inheritance:
25 |
--------------------------------------------------------------------------------
/doc/submodules/queue.rst:
--------------------------------------------------------------------------------
1 | Queue
2 | -----
3 |
4 | .. automodule:: jenkinsapi.queue
5 | :members:
6 | :undoc-members:
7 | :show-inheritance:
8 |
--------------------------------------------------------------------------------
/doc/submodules/results.rst:
--------------------------------------------------------------------------------
1 | Result_set and result
2 | =====================
3 |
4 | .. contents::
5 | :depth: 2
6 | :local:
7 | :backlinks: none
8 | :class: this-will-duplicate-information-and-it-is-still-useful-here
9 |
10 | Result
11 | ------
12 |
13 | .. automodule:: jenkinsapi.result
14 | :members:
15 | :undoc-members:
16 | :show-inheritance:
17 |
18 | Result_set
19 | ----------
20 |
21 | .. automodule:: jenkinsapi.result_set
22 | :members:
23 | :undoc-members:
24 | :show-inheritance:
25 |
--------------------------------------------------------------------------------
/doc/submodules/utils.rst:
--------------------------------------------------------------------------------
1 | Utils
2 | =====
3 |
4 | crumb\_requester module
5 | ----------------------------------------
6 |
7 | .. automodule:: jenkinsapi.utils.crumb_requester
8 | :members:
9 | :undoc-members:
10 | :show-inheritance:
11 |
12 | jenkins\_launcher module
13 | -----------------------------------------
14 |
15 | .. automodule:: jenkinsapi.utils.jenkins_launcher
16 | :members:
17 | :undoc-members:
18 | :show-inheritance:
19 |
20 | jsonp\_to\_json module
21 | ---------------------------------------
22 |
23 | .. automodule:: jenkinsapi.utils.jsonp_to_json
24 | :members:
25 | :undoc-members:
26 | :show-inheritance:
27 |
28 | krb\_requester module
29 | --------------------------------------
30 |
31 | .. automodule:: jenkinsapi.utils.krb_requester
32 | :members:
33 | :undoc-members:
34 | :show-inheritance:
35 |
36 | manifest module
37 | --------------------------------
38 |
39 | .. automodule:: jenkinsapi.utils.manifest
40 | :members:
41 | :undoc-members:
42 | :show-inheritance:
43 |
44 | requester module
45 | ---------------------------------
46 |
47 | .. automodule:: jenkinsapi.utils.requester
48 | :members:
49 | :undoc-members:
50 | :show-inheritance:
51 |
52 | simple\_post\_logger module
53 | --------------------------------------------
54 |
55 | .. automodule:: jenkinsapi.utils.simple_post_logger
56 | :members:
57 | :undoc-members:
58 | :show-inheritance:
59 |
60 | Module contents
61 | ---------------
62 |
63 | .. automodule:: jenkinsapi.utils
64 | :members:
65 | :undoc-members:
66 | :show-inheritance:
67 |
--------------------------------------------------------------------------------
/doc/submodules/views.rst:
--------------------------------------------------------------------------------
1 | View and Views
2 | ==============
3 |
4 | .. contents::
5 | :depth: 2
6 | :local:
7 | :backlinks: none
8 | :class: this-will-duplicate-information-and-it-is-still-useful-here
9 |
10 | Views
11 | -----
12 |
13 | .. automodule:: jenkinsapi.views
14 | :members:
15 | :undoc-members:
16 | :show-inheritance:
17 |
18 | View
19 | ----
20 |
21 | .. automodule:: jenkinsapi.view
22 | :members:
23 | :undoc-members:
24 | :show-inheritance:
25 |
--------------------------------------------------------------------------------
/examples/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycontribs/jenkinsapi/ffb35b6dca607b9aa5a65d4e49aa1ca76946adbf/examples/__init__.py
--------------------------------------------------------------------------------
/examples/addjob.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | false
6 |
7 |
8 | true
9 | false
10 | false
11 | false
12 |
13 | false
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/examples/how_to/add_command.py:
--------------------------------------------------------------------------------
1 | """
2 | This example shows how to add new command to "Shell" build step
3 | """
4 |
5 | import xml.etree.ElementTree as et
6 | from jenkinsapi.jenkins import Jenkins
7 |
8 | J = Jenkins("http://localhost:8080")
9 | EMPTY_JOB_CONFIG = """
10 |
11 |
12 | jkkjjk
13 |
14 | false
15 |
16 |
17 | true
18 | false
19 | false
20 | false
21 |
22 | false
23 |
24 |
25 |
26 |
27 | """
28 |
29 | jobname = "foo_job"
30 | new_job = J.create_job(jobname, EMPTY_JOB_CONFIG)
31 | new_conf = new_job.get_config()
32 |
33 | root = et.fromstring(new_conf.strip())
34 |
35 | builders = root.find("builders")
36 | shell = et.SubElement(builders, "hudson.tasks.Shell")
37 | command = et.SubElement(shell, "command")
38 | command.text = "ls"
39 |
40 | print(et.tostring(root))
41 | J[jobname].update_config(et.tostring(root))
42 |
--------------------------------------------------------------------------------
/examples/how_to/create_a_job.py:
--------------------------------------------------------------------------------
1 | """
2 | This example shows how to create job from XML file and how to delete job
3 | """
4 |
5 | from pkg_resources import resource_string
6 | from jenkinsapi.jenkins import Jenkins
7 |
8 | jenkins = Jenkins("http://localhost:8080")
9 | job_name = "foo_job2"
10 | xml = resource_string("examples", "addjob.xml")
11 |
12 | print(xml)
13 |
14 | job = jenkins.create_job(jobname=job_name, xml=xml)
15 |
16 | # Get job from Jenkins by job name
17 | my_job = jenkins[job_name]
18 | print(my_job)
19 |
20 | # Delete job using method in Jenkins class
21 | #
22 | # Another way is to use:
23 | #
24 | # del jenkins[job_name]
25 | jenkins.delete_job(job_name)
26 |
--------------------------------------------------------------------------------
/examples/how_to/create_credentials.py:
--------------------------------------------------------------------------------
1 | """
2 | This example shows how to create credentials
3 | """
4 |
5 | import logging
6 | from jenkinsapi.jenkins import Jenkins
7 | from jenkinsapi.credential import UsernamePasswordCredential, SSHKeyCredential
8 |
9 | log_level = getattr(logging, "DEBUG")
10 | logging.basicConfig(level=log_level)
11 | logger = logging.getLogger()
12 |
13 | jenkins_url = "http://localhost:8080/"
14 |
15 | jenkins = Jenkins(jenkins_url)
16 |
17 | # Get a list of all global credentials
18 | creds = jenkins.credentials
19 | logging.info(jenkins.credentials.keys())
20 |
21 | # Create username and password credential
22 | creds_description1 = "My_username_credential"
23 | cred_dict = {
24 | "description": creds_description1,
25 | "userName": "userName",
26 | "password": "password",
27 | }
28 | creds[creds_description1] = UsernamePasswordCredential(cred_dict)
29 |
30 |
31 | # Create ssh key credential that uses private key as a value
32 | # In jenkins credential dialog you need to paste credential
33 | # In your code it is adviced to read it from file
34 | # For simplicity of this example reading key from file is not shown here
35 | def get_private_key_from_file():
36 | return "-----BEGIN RSA PRIVATE KEY-----"
37 |
38 |
39 | my_private_key = get_private_key_from_file()
40 |
41 | creds_description2 = "My_ssh_cred1"
42 | cred_dict = {
43 | "description": creds_description2,
44 | "userName": "userName",
45 | "passphrase": "",
46 | "private_key": my_private_key,
47 | }
48 | creds[creds_description2] = SSHKeyCredential(cred_dict)
49 |
50 | # Create ssh key credential that uses private key from path on Jenkins server
51 | my_private_key = "/home/jenkins/.ssh/special_key"
52 |
53 | creds_description3 = "My_ssh_cred2"
54 | cred_dict = {
55 | "description": creds_description3,
56 | "userName": "userName",
57 | "passphrase": "",
58 | "private_key": my_private_key,
59 | }
60 | creds[creds_description3] = SSHKeyCredential(cred_dict)
61 |
62 | # Remove credentials
63 | # We use credential description to find specific credential. This is the only
64 | # way to get specific credential from Jenkins via REST API
65 | del creds[creds_description1]
66 | del creds[creds_description2]
67 | del creds[creds_description3]
68 |
69 | # Remove all credentials
70 | for cred_descr in creds.keys():
71 | del creds[cred_descr]
72 |
--------------------------------------------------------------------------------
/examples/how_to/create_nested_views.py:
--------------------------------------------------------------------------------
1 | """
2 | How to create nested views using NestedViews Jenkins plugin
3 |
4 | This example requires NestedViews plugin to be installed in Jenkins
5 | You need to have at least one job in your Jenkins to see views
6 | """
7 |
8 | import logging
9 | from pkg_resources import resource_string
10 | from jenkinsapi.views import Views
11 | from jenkinsapi.jenkins import Jenkins
12 |
13 | log_level = getattr(logging, "DEBUG")
14 | logging.basicConfig(level=log_level)
15 | logger = logging.getLogger()
16 |
17 | jenkins_url = "http://127.0.0.1:8080/"
18 | jenkins = Jenkins(jenkins_url)
19 |
20 | job_name = "foo_job2"
21 | xml = resource_string("examples", "addjob.xml")
22 | j = jenkins.create_job(jobname=job_name, xml=xml)
23 |
24 | # Create ListView in main view
25 | logger.info("Attempting to create new nested view")
26 | top_view = jenkins.views.create("TopView", Views.NESTED_VIEW)
27 | logger.info("top_view is %s", top_view)
28 | if top_view is None:
29 | logger.error("View was not created")
30 | else:
31 | logger.info("View has been created")
32 |
33 | print("top_view.views=", top_view.views.keys())
34 | logger.info("Attempting to create view inside nested view")
35 | sub_view = top_view.views.create("SubView")
36 | if sub_view is None:
37 | logger.info("View was not created")
38 | else:
39 | logger.error("View has been created")
40 |
41 | logger.info("Attempting to delete sub_view")
42 | del top_view.views["SubView"]
43 | if "SubView" in top_view.views:
44 | logger.error("SubView was not deleted")
45 | else:
46 | logger.info("SubView has been deleted")
47 |
48 | # Another way of creating sub view
49 | # This way sub view will have jobs in it
50 | logger.info("Attempting to create view with jobs inside nested view")
51 | top_view.views["SubView"] = job_name
52 | if "SubView" not in top_view.views:
53 | logger.error("View was not created")
54 | else:
55 | logger.info("View has been created")
56 |
57 | logger.info("Attempting to delete sub_view")
58 | del top_view.views["SubView"]
59 | if "SubView" in top_view.views:
60 | logger.error("SubView was not deleted")
61 | else:
62 | logger.info("SubView has been deleted")
63 |
64 | logger.info("Attempting to delete top view")
65 | del jenkins.views["TopView"]
66 | if "TopView" not in jenkins.views:
67 | logger.info("View has been deleted")
68 | else:
69 | logger.error("View was not deleted")
70 |
71 | # Delete job that we created
72 | jenkins.delete_job(job_name)
73 |
--------------------------------------------------------------------------------
/examples/how_to/create_slave.py:
--------------------------------------------------------------------------------
1 | """
2 | How to create slaves/nodes
3 | """
4 |
5 | import logging
6 | import requests
7 | from jenkinsapi.jenkins import Jenkins
8 | from jenkinsapi.utils.requester import Requester
9 |
10 | requests.packages.urllib3.disable_warnings()
11 |
12 | log_level = getattr(logging, "DEBUG")
13 | logging.basicConfig(level=log_level)
14 | logger = logging.getLogger()
15 |
16 | jenkins_url = "http://localhost:8080/"
17 | username = "default_user" # In case Jenkins requires authentication
18 | password = "default_password"
19 |
20 | jenkins = Jenkins(
21 | jenkins_url,
22 | requester=Requester(
23 | username, password, baseurl=jenkins_url, ssl_verify=False
24 | ),
25 | )
26 |
27 | # Create JNLP(Java Webstart) slave
28 | node_dict = {
29 | "num_executors": 1, # Number of executors
30 | "node_description": "Test JNLP Node", # Just a user friendly text
31 | "remote_fs": "/tmp", # Remote workspace location
32 | "labels": "my_new_node", # Space separated labels string
33 | "exclusive": True, # Only run jobs assigned to it
34 | }
35 | new_jnlp_node = jenkins.nodes.create_node("My new webstart node", node_dict)
36 |
37 | node_dict = {
38 | "num_executors": 1,
39 | "node_description": "Test SSH Node",
40 | "remote_fs": "/tmp",
41 | "labels": "new_node",
42 | "exclusive": True,
43 | "host": "localhost", # Remote hostname
44 | "port": 22, # Remote post, usually 22
45 | "credential_description": "localhost cred", # Credential to use
46 | # [Mandatory for SSH node!]
47 | # (see Credentials example)
48 | "jvm_options": "-Xmx2000M", # JVM parameters
49 | "java_path": "/bin/java", # Path to java
50 | "prefix_start_slave_cmd": "",
51 | "suffix_start_slave_cmd": "",
52 | "max_num_retries": 0,
53 | "retry_wait_time": 0,
54 | "retention": "OnDemand", # Change to 'Always' for
55 | # immediate slave launch
56 | "ondemand_delay": 1,
57 | "ondemand_idle_delay": 5,
58 | "env": [ # Environment variables
59 | {"key": "TEST", "value": "VALUE"},
60 | {"key": "TEST2", "value": "value2"},
61 | ],
62 | }
63 | new_ssh_node = jenkins.nodes.create_node("My new SSH node", node_dict)
64 |
65 | # Take this slave offline
66 | if new_ssh_node.is_online():
67 | new_ssh_node.toggle_temporarily_offline()
68 |
69 | # Take this slave back online
70 | new_ssh_node.toggle_temporarily_offline()
71 |
72 | # Get a list of all slave names
73 | slave_names = jenkins.nodes.keys()
74 |
75 | # Get Node object
76 | my_node = jenkins.nodes["My new SSH node"]
77 | # Take this slave offline
78 | my_node.set_offline()
79 |
80 | # Delete slaves
81 | del jenkins.nodes["My new webstart node"]
82 | del jenkins.nodes["My new SSH node"]
83 |
--------------------------------------------------------------------------------
/examples/how_to/create_views.py:
--------------------------------------------------------------------------------
1 | """
2 | How to create views
3 | """
4 |
5 | import logging
6 | from pkg_resources import resource_string
7 | from jenkinsapi.jenkins import Jenkins
8 |
9 | logging.basicConfig(level=logging.INFO)
10 | logger = logging.getLogger()
11 |
12 | jenkins_url = "http://localhost:8080/"
13 |
14 | jenkins = Jenkins(jenkins_url, lazy=True)
15 |
16 | # Create ListView in main view
17 | logger.info("Attempting to create new view")
18 | test_view_name = "SimpleListView"
19 |
20 | # Views object appears as a dictionary of views
21 | if test_view_name not in jenkins.views:
22 | new_view = jenkins.views.create(test_view_name)
23 | if new_view is None:
24 | logger.error("View %s was not created", test_view_name)
25 | else:
26 | logger.info(
27 | "View %s has been created: %s", new_view.name, new_view.baseurl
28 | )
29 | else:
30 | logger.info("View %s already exists", test_view_name)
31 |
32 | # No error is raised if view already exists
33 | logger.info("Attempting to create view that already exists")
34 | my_view = jenkins.views.create(test_view_name)
35 |
36 | logger.info("Create job and assign it to a view")
37 | job_name = "foo_job2"
38 | xml = resource_string("examples", "addjob.xml")
39 |
40 | my_job = jenkins.create_job(jobname=job_name, xml=xml)
41 |
42 | # add_job supports two parameters: job_name and job object
43 | # passing job object will remove verification calls to Jenkins
44 | my_view.add_job(job_name, my_job)
45 | assert len(my_view) == 1
46 |
47 | logger.info("Attempting to delete view that already exists")
48 | del jenkins.views[test_view_name]
49 |
50 | if test_view_name in jenkins.views:
51 | logger.error("View was not deleted")
52 | else:
53 | logger.info("View has been deleted")
54 |
55 | # No error will be raised when attempting to remove non-existing view
56 | logger.info("Attempting to delete view that does not exist")
57 | del jenkins.views[test_view_name]
58 |
59 | # Create CategorizedJobsView
60 | config = """
61 |
62 |
63 |
64 | .dev.
65 | Development
66 |
67 |
68 | .hml.
69 | Homologation
70 |
71 |
72 |
73 | """
74 | view = jenkins.views.create(
75 | "My categorized jobs view", jenkins.views.CATEGORIZED_VIEW, config=config
76 | )
77 |
--------------------------------------------------------------------------------
/examples/how_to/delete_all_the_nodes_except_master.py:
--------------------------------------------------------------------------------
1 | """
2 | How to delete slaves/nodes
3 | """
4 |
5 | import logging
6 | from jenkinsapi.jenkins import Jenkins
7 |
8 | logging.basicConfig()
9 |
10 |
11 | j = Jenkins("http://localhost:8080")
12 |
13 | for node_id, _ in j.get_nodes().iteritems():
14 | if node_id != "master":
15 | print(node_id)
16 | j.delete_node(node_id)
17 |
18 | # Alternative way - this method will not delete 'master'
19 | for node in j.nodes.keys():
20 | del j.nodes[node]
21 |
--------------------------------------------------------------------------------
/examples/how_to/get_config.py:
--------------------------------------------------------------------------------
1 | """
2 | An example of how to use JenkinsAPI to fetch the config XML of a job.
3 | """
4 |
5 | from jenkinsapi.jenkins import Jenkins
6 |
7 | jenkins = Jenkins("http://localhost:8080")
8 | jobName = jenkins.keys()[0] # get the first job
9 |
10 | config = jenkins[jobName].get_config()
11 |
12 | print(config)
13 |
--------------------------------------------------------------------------------
/examples/how_to/get_plugin_information.py:
--------------------------------------------------------------------------------
1 | """
2 | Get information about currently installed plugins
3 | """
4 |
5 | from jenkinsapi.jenkins import Jenkins
6 |
7 |
8 | plugin_name = "subversion"
9 | jenkins = Jenkins("http://localhost:8080")
10 | plugin = jenkins.get_plugins()[plugin_name]
11 |
12 | print(repr(plugin))
13 |
--------------------------------------------------------------------------------
/examples/how_to/get_version_info_from_last_good_build.py:
--------------------------------------------------------------------------------
1 | """
2 | Extract version information from the latest build.
3 | """
4 |
5 | from jenkinsapi.jenkins import Jenkins
6 |
7 |
8 | job_name = "foo"
9 | jenkins = Jenkins("http://localhost:8080")
10 | job = jenkins[job_name]
11 | lgb = job.get_last_good_build()
12 | print(lgb.get_revision())
13 |
--------------------------------------------------------------------------------
/examples/how_to/query_a_build.py:
--------------------------------------------------------------------------------
1 | """
2 | How to get build from job and query that build
3 | """
4 |
5 | from jenkinsapi.jenkins import Jenkins
6 |
7 |
8 | jenkins = Jenkins("http://localhost:8080")
9 | # Print all jobs in Jenkins
10 | print(jenkins.items())
11 |
12 | job = jenkins.get_job("foo")
13 | build = job.get_last_build()
14 | print(build)
15 |
16 | mjn = build.get_master_job_name()
17 | print(mjn)
18 |
--------------------------------------------------------------------------------
/examples/how_to/readme.rst:
--------------------------------------------------------------------------------
1 | "How To..." examples
2 | ====================
3 |
4 | This directory contains a set of examples or recipes for common tasks in JenkinsAPI.
5 |
6 | ========================================= ==================================================
7 | Example Description
8 | ----------------------------------------- --------------------------------------------------
9 | add_command.py create new job and then add shell build step to it
10 | create_a_job.py create new job
11 | create_credentials.py create new credential
12 | create_nested_views.py create nested views using Nested Views Jenkins plugin
13 | create_slave.py create jnlp ans ssh slave
14 | create_views.py create views, assign and delete jobs in views
15 | delete_all_the_nodes.py delete all slaves except master
16 | get_config.py get job configuration XML
17 | get_plugin_infomation.py show information about plugin
18 | get_version_info_from_last_good_build.py get Git revision from last successful build
19 | query_a_build.py get build information
20 | search_artifact_by_regexp.py search for job artifacts using regular expression
21 | search_artifacts.py search for artifacts
22 | start_parameterized_build.py start a build with parameters
23 | use_crumbs.py how to work with CSRF protection enabled in Jenkins
24 | ========================================= ==================================================
25 |
--------------------------------------------------------------------------------
/examples/how_to/search_artifact_by_regexp.py:
--------------------------------------------------------------------------------
1 | """
2 | Search for job artifacts using regexp
3 | """
4 |
5 | import re
6 | from jenkinsapi.api import search_artifact_by_regexp
7 |
8 | jenkinsurl = "http://localhost:8080"
9 | jobid = "foo"
10 | artifact_regexp = re.compile(r"test1\.txt") # A file name I want.
11 | result = search_artifact_by_regexp(jenkinsurl, jobid, artifact_regexp)
12 | print((repr(result)))
13 |
--------------------------------------------------------------------------------
/examples/how_to/search_artifacts.py:
--------------------------------------------------------------------------------
1 | """
2 | Search for job artifacts
3 | """
4 |
5 | from jenkinsapi.api import search_artifacts
6 |
7 | jenkinsurl = "http://localhost:8080"
8 | jobid = "foo"
9 | # I need a build that contains all of these
10 | artifact_ids = ["test1.txt", "test2.txt"]
11 | result = search_artifacts(jenkinsurl, jobid, artifact_ids)
12 | print((repr(result)))
13 |
--------------------------------------------------------------------------------
/examples/how_to/start_parameterized_build.py:
--------------------------------------------------------------------------------
1 | """
2 | Start a Parameterized Build
3 | """
4 |
5 | from jenkinsapi.jenkins import Jenkins
6 |
7 | jenkins = Jenkins("http://localhost:8080")
8 |
9 | params = {"VERSION": "1.2.3", "PYTHON_VER": "2.7"}
10 |
11 | # This will start the job in non-blocking manner
12 | jenkins.build_job("foo", params)
13 |
14 |
15 | # This will start the job and will return a QueueItem object which
16 | # can be used to get build results
17 | job = jenkins["foo"]
18 | qi = job.invoke(build_params=params)
19 |
20 | # Block this script until build is finished
21 | if qi.is_queued() or qi.is_running():
22 | qi.block_until_complete()
23 |
24 | build = qi.get_build()
25 | print(build)
26 |
--------------------------------------------------------------------------------
/examples/how_to/use_crumbs.py:
--------------------------------------------------------------------------------
1 | """
2 | Example of using CrumbRequester - when CSRF protection is enabled in Jenkins
3 | """
4 |
5 | from jenkinsapi.jenkins import Jenkins
6 |
7 | jenkins = Jenkins(
8 | "http://localhost:8080",
9 | username="admin",
10 | password="password",
11 | use_crumb=True,
12 | )
13 |
14 | for job_name in jenkins.jobs:
15 | print(job_name)
16 |
--------------------------------------------------------------------------------
/examples/low_level/copy_a_job.py:
--------------------------------------------------------------------------------
1 | """
2 | A lower-level implementation of copying a job in Jenkins
3 | """
4 |
5 | import requests
6 | from pkg_resources import resource_string
7 | from jenkinsapi.jenkins import Jenkins
8 | from jenkinsapi_tests.test_utils.random_strings import random_string
9 |
10 | J = Jenkins("http://localhost:8080")
11 | jobName = random_string()
12 | jobName2 = "%s_2" % jobName
13 |
14 | url = "http://localhost:8080/createItem?from=%s&name=%s&mode=copy" % (
15 | jobName,
16 | jobName2,
17 | )
18 |
19 | xml = resource_string("examples", "addjob.xml")
20 | j = J.create_job(jobname=jobName, xml=xml)
21 |
22 |
23 | h = {"Content-Type": "application/x-www-form-urlencoded"}
24 | response = requests.post(url, data="dysjsjsjs", headers=h)
25 | print(response.text.encode("UTF-8"))
26 |
--------------------------------------------------------------------------------
/examples/low_level/create_a_view_low_level.py:
--------------------------------------------------------------------------------
1 | """
2 | A low level example:
3 | This is how JenkinsAPI creates views
4 | """
5 |
6 | import json
7 | import requests
8 |
9 | url = "http://localhost:8080/createView"
10 |
11 | str_view_name = "blahblah123"
12 | params = {} # {'name': str_view_name}
13 | headers = {"Content-Type": "application/x-www-form-urlencoded"}
14 | data = {
15 | "name": str_view_name,
16 | "mode": "hudson.model.ListView",
17 | "Submit": "OK",
18 | "json": json.dumps(
19 | {"name": str_view_name, "mode": "hudson.model.ListView"}
20 | ),
21 | }
22 | # Try 1
23 | result = requests.post(url, params=params, data=data, headers=headers)
24 | print(result.text.encode("UTF-8"))
25 |
--------------------------------------------------------------------------------
/examples/low_level/example_param_build.py:
--------------------------------------------------------------------------------
1 | import json
2 | import requests
3 |
4 |
5 | def foo():
6 | """
7 | A low level example of how JenkinsAPI runs a parameterized build
8 | """
9 | toJson = {"parameter": [{"name": "B", "value": "xyz"}]}
10 | url = "http://localhost:8080/job/ddd/build"
11 | # url = 'http://localhost:8000'
12 | headers = {"Content-Type": "application/x-www-form-urlencoded"}
13 | form = {"json": json.dumps(toJson)}
14 | response = requests.post(url, data=form, headers=headers)
15 | print(response.text.encode("UTF-8"))
16 |
17 |
18 | if __name__ == "__main__":
19 | foo()
20 |
--------------------------------------------------------------------------------
/examples/low_level/login_with_auth.py:
--------------------------------------------------------------------------------
1 | """
2 | A lower level example of how we login with authentication
3 | """
4 |
5 | from jenkinsapi import jenkins
6 |
7 |
8 | J = jenkins.Jenkins("http://localhost:8080", username="sal", password="foobar")
9 | J.poll()
10 |
11 | print(J.items())
12 |
--------------------------------------------------------------------------------
/examples/low_level/post_watcher.py:
--------------------------------------------------------------------------------
1 | """
2 | Save this file as server.py
3 | >>> python server.py 0.0.0.0 8001
4 | serving on 0.0.0.0:8001
5 |
6 | or simply
7 |
8 | >>> python server.py
9 | Serving on localhost:8000
10 |
11 | You can use this to test GET and POST methods.
12 | """
13 |
14 | import http.server as SimpleHTTPServer
15 | import socketserver
16 | import logging
17 | import cgi
18 |
19 | PORT = 8081 # <-- change this to be the actual port you want to run on
20 | INTERFACE = "localhost"
21 |
22 |
23 | class ServerHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
24 | def do_GET(self):
25 | logging.warning("======= GET STARTED =======")
26 | logging.warning(self.headers)
27 | SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
28 |
29 | def do_POST(self):
30 | logging.warning("======= POST STARTED =======")
31 | logging.warning(self.headers)
32 | form = cgi.FieldStorage(
33 | fp=self.rfile,
34 | headers=self.headers,
35 | environ={
36 | "REQUEST_METHOD": "POST",
37 | "CONTENT_TYPE": self.headers["Content-Type"],
38 | },
39 | )
40 | logging.warning("======= POST VALUES =======")
41 | for item in form.list:
42 | logging.warning(item)
43 | logging.warning("\n")
44 | SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
45 |
46 |
47 | Handler = ServerHandler
48 |
49 | httpd = socketserver.TCPServer(("", PORT), Handler)
50 |
51 | print(
52 | "Serving at: http://%(interface)s:%(port)s"
53 | % dict(interface=INTERFACE or "localhost", port=PORT)
54 | )
55 | httpd.serve_forever()
56 |
--------------------------------------------------------------------------------
/examples/low_level/readme.rst:
--------------------------------------------------------------------------------
1 | Low-Level Examples
2 | ==================
3 |
4 | These examoples are intended to explain how JenkinsAPI performs certain functions. While developing JenkinsAPI I created a number of small scripts like these in order to figure out the correct way to communicate. Ive retained a number of these as they provide some insights into how the various interfaces that Jenkins provides can be used.
5 |
--------------------------------------------------------------------------------
/jenkinsapi/.gitignore:
--------------------------------------------------------------------------------
1 | /__pycache__
2 |
--------------------------------------------------------------------------------
/jenkinsapi/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | About this library
3 | ==================
4 |
5 | Jenkins is the market leading continuous integration system, originally created
6 | by Kohsuke Kawaguchi. This API makes Jenkins even easier to use by providing an
7 | easy to use conventional python interface.
8 |
9 | Jenkins (and It's predecessor Hudson) are fantastic projects - but they are
10 | somewhat Java-centric. Thankfully the designers have provided an excellent and
11 | complete REST interface. This library wraps up that interface as more
12 | conventional python objects in order to make most Jenkins oriented
13 | tasks simpler.
14 |
15 | This library can help you:
16 |
17 | * Query the test-results of a completed build
18 | * Get a objects representing the latest builds of a job
19 | * Search for artifacts by simple criteria
20 | * Block until jobs are complete
21 | * Install artifacts to custom-specified directory structures
22 | * username/password auth support for jenkins instances with auth turned on
23 | * Ability to search for builds by subversion revision
24 | * Ability to add/remove/query jenkins slaves
25 |
26 | Installing JenkinsAPI
27 | =====================
28 |
29 | pip install jenkinsapi
30 |
31 | Project Authors
32 | ===============
33 |
34 | * Salim Fadhley (sal@stodge.org)
35 | * Ramon van Alteren (ramon@vanalteren.nl)
36 | * Ruslan Lutsenko (ruslan.lutcenko@gmail.com)
37 | * Aleksey Maksimov
38 | * Clinton Steiner
39 |
40 | Current code lives on github: https://github.com/pycontribs/jenkinsapi
41 |
42 | """
43 |
44 | from importlib.metadata import version
45 |
46 | from jenkinsapi import (
47 | # Modules
48 | command_line,
49 | utils,
50 | # Files
51 | api,
52 | artifact,
53 | build,
54 | config,
55 | constants,
56 | custom_exceptions,
57 | fingerprint,
58 | executors,
59 | executor,
60 | jenkins,
61 | jenkinsbase,
62 | job,
63 | node,
64 | result_set,
65 | result,
66 | view,
67 | )
68 |
69 | __all__ = [
70 | "command_line",
71 | "utils",
72 | "api",
73 | "artifact",
74 | "build",
75 | "config",
76 | "constants",
77 | "custom_exceptions",
78 | "executors",
79 | "executor",
80 | "fingerprint",
81 | "jenkins",
82 | "jenkinsbase",
83 | "job",
84 | "node",
85 | "result_set",
86 | "result",
87 | "view",
88 | ]
89 | __docformat__ = "epytext"
90 | __version__ = version("jenkinsapi")
91 |
--------------------------------------------------------------------------------
/jenkinsapi/artifact.py:
--------------------------------------------------------------------------------
1 | """
2 | Artifacts can be used to represent data created as a side-effect of running
3 | a Jenkins build.
4 |
5 | Artifacts are files which are associated with a single build. A build can
6 | have any number of artifacts associated with it.
7 |
8 | This module provides a class called Artifact which allows you to download
9 | objects from the server and also access them as a stream.
10 | """
11 |
12 | from __future__ import annotations
13 |
14 | import os
15 | import logging
16 | import hashlib
17 | from typing import Any, Literal
18 |
19 | from jenkinsapi.fingerprint import Fingerprint
20 | from jenkinsapi.custom_exceptions import ArtifactBroken
21 |
22 | log = logging.getLogger(__name__)
23 |
24 |
25 | class Artifact(object):
26 | """
27 | Represents a single Jenkins artifact, usually some kind of file
28 | generated as a by-product of executing a Jenkins build.
29 | """
30 |
31 | def __init__(
32 | self,
33 | filename: str,
34 | url: str,
35 | build: "Build",
36 | relative_path: str | None = None,
37 | ) -> None:
38 | self.filename: str = filename
39 | self.url: str = url
40 | self.build: "Build" = build
41 | self.relative_path: str | None = relative_path
42 |
43 | def save(self, fspath: str, strict_validation: bool = False) -> str:
44 | """
45 | Save the artifact to an explicit path. The containing directory must
46 | exist. Returns a reference to the file which has just been writen to.
47 |
48 | :param fspath: full pathname including the filename, str
49 | :return: filepath
50 | """
51 | log.info(msg="Saving artifact @ %s to %s" % (self.url, fspath))
52 | if not fspath.endswith(self.filename):
53 | log.warning(
54 | "Attempt to change the filename of artifact %s on save.",
55 | self.filename,
56 | )
57 | if os.path.exists(fspath):
58 | if self.build:
59 | try:
60 | if self._verify_download(fspath, strict_validation):
61 | log.info(
62 | "Local copy of %s is already up to date.",
63 | self.filename,
64 | )
65 | return fspath
66 | except ArtifactBroken:
67 | log.warning("Jenkins artifact could not be identified.")
68 | else:
69 | log.info(
70 | "This file did not originate from Jenkins, "
71 | "so cannot check."
72 | )
73 | else:
74 | log.info("Local file is missing, downloading new.")
75 | filepath = self._do_download(fspath)
76 | self._verify_download(filepath, strict_validation)
77 | return fspath
78 |
79 | def get_jenkins_obj(self) -> Jenkins:
80 | return self.build.get_jenkins_obj()
81 |
82 | def get_data(self) -> Any:
83 | """
84 | Grab the text of the artifact
85 | """
86 | response = self.get_jenkins_obj().requester.get_and_confirm_status(
87 | self.url
88 | )
89 | return response.content
90 |
91 | def _do_download(self, fspath: str) -> str:
92 | """
93 | Download the the artifact to a path.
94 | """
95 | data = self.get_jenkins_obj().requester.get_and_confirm_status(
96 | self.url, stream=True
97 | )
98 | with open(fspath, "wb") as out:
99 | for chunk in data.iter_content(chunk_size=1024):
100 | out.write(chunk)
101 | return fspath
102 |
103 | def _verify_download(self, fspath, strict_validation) -> Literal[True]:
104 | """
105 | Verify that a downloaded object has a valid fingerprint.
106 |
107 | Returns True if the fingerprint is valid, raises an exception if
108 | the fingerprint is invalid.
109 | """
110 | local_md5 = self._md5sum(fspath)
111 | baseurl = self.build.job.jenkins.baseurl
112 | fp = Fingerprint(baseurl, local_md5, self.build.job.jenkins)
113 | valid = fp.validate_for_build(
114 | self.filename, self.build.job.get_full_name(), self.build.buildno
115 | )
116 | if not valid or (fp.unknown and strict_validation):
117 | # strict = 404 as invalid
118 | raise ArtifactBroken(
119 | "Artifact %s seems to be broken, check %s"
120 | % (local_md5, baseurl)
121 | )
122 | return True
123 |
124 | def _md5sum(self, fspath: str, chunksize: int = 2**20) -> str:
125 | """
126 | A MD5 hashing function intended to produce the same results as that
127 | used by Jenkins.
128 | """
129 | md5 = hashlib.md5()
130 | with open(fspath, "rb") as f:
131 | for chunk in iter(lambda: f.read(chunksize), ""):
132 | if chunk:
133 | md5.update(chunk)
134 | else:
135 | break
136 | return md5.hexdigest()
137 |
138 | def save_to_dir(
139 | self, dirpath: str, strict_validation: bool = False
140 | ) -> str:
141 | """
142 | Save the artifact to a folder. The containing directory must exist,
143 | but use the artifact's default filename.
144 | """
145 | assert os.path.exists(dirpath)
146 | assert os.path.isdir(dirpath)
147 | outputfilepath: str = os.path.join(dirpath, self.filename)
148 | return self.save(outputfilepath, strict_validation)
149 |
150 | def __repr__(self) -> str:
151 | """
152 | Produce a handy repr-string.
153 | """
154 | return """<%s.%s %s>""" % (
155 | self.__class__.__module__,
156 | self.__class__.__name__,
157 | self.url,
158 | )
159 |
--------------------------------------------------------------------------------
/jenkinsapi/command_line/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | __init__,py for commandline module
3 | """
4 |
--------------------------------------------------------------------------------
/jenkinsapi/command_line/jenkins_invoke.py:
--------------------------------------------------------------------------------
1 | """
2 | jenkinsapi class for invoking Jenkins
3 | """
4 |
5 | import os
6 | import sys
7 | import logging
8 | import optparse
9 | from jenkinsapi import jenkins
10 |
11 | log = logging.getLogger(__name__)
12 |
13 |
14 | class JenkinsInvoke(object):
15 | """
16 | JenkinsInvoke object implements class to call from command line
17 | """
18 |
19 | @classmethod
20 | def mkparser(cls):
21 | parser = optparse.OptionParser()
22 | DEFAULT_BASEURL = os.environ.get(
23 | "JENKINS_URL", "http://localhost/jenkins"
24 | )
25 | parser.help_text = (
26 | "Execute a number of jenkins jobs on the server of your choice."
27 | + " Optionally block until the jobs are complete."
28 | )
29 | parser.add_option(
30 | "-J",
31 | "--jenkinsbase",
32 | dest="baseurl",
33 | help="Base URL for the Jenkins server, default is %s"
34 | % DEFAULT_BASEURL,
35 | type="str",
36 | default=DEFAULT_BASEURL,
37 | )
38 | parser.add_option(
39 | "--username",
40 | "-u",
41 | dest="username",
42 | help="Username for jenkins authentification",
43 | type="str",
44 | default=None,
45 | )
46 | parser.add_option(
47 | "--password",
48 | "-p",
49 | dest="password",
50 | help="password for jenkins user auth",
51 | type="str",
52 | default=None,
53 | )
54 | parser.add_option(
55 | "-b",
56 | "--block",
57 | dest="block",
58 | action="store_true",
59 | default=False,
60 | help="Block until each of the jobs is complete.",
61 | )
62 | parser.add_option(
63 | "-t",
64 | "--token",
65 | dest="token",
66 | help="Optional security token.",
67 | default=None,
68 | )
69 | return parser
70 |
71 | @classmethod
72 | def main(cls):
73 | parser = cls.mkparser()
74 | options, args = parser.parse_args()
75 | try:
76 | assert args, "Need to specify at least one job name"
77 | except AssertionError as err:
78 | log.critical(err.message)
79 | parser.print_help()
80 | sys.exit(1)
81 | invoker = cls(options, args)
82 | invoker()
83 |
84 | def __init__(self, options, jobs):
85 | self.options = options
86 | self.jobs = jobs
87 | self.api = self._get_api(
88 | baseurl=options.baseurl,
89 | username=options.username,
90 | password=options.password,
91 | )
92 |
93 | def _get_api(self, baseurl, username, password):
94 | return jenkins.Jenkins(baseurl, username, password)
95 |
96 | def __call__(self):
97 | for job in self.jobs:
98 | self.invokejob(
99 | job, block=self.options.block, token=self.options.token
100 | )
101 |
102 | def invokejob(self, jobname, block, token):
103 | assert isinstance(block, bool)
104 | assert isinstance(jobname, str)
105 | assert token is None or isinstance(token, str)
106 | job = self.api.get_job(jobname)
107 | job.invoke(securitytoken=token, block=block)
108 |
109 |
110 | def main():
111 | logging.basicConfig()
112 | logging.getLogger("").setLevel(logging.INFO)
113 | JenkinsInvoke.main()
114 |
--------------------------------------------------------------------------------
/jenkinsapi/command_line/jenkinsapi_version.py:
--------------------------------------------------------------------------------
1 | """ jenkinsapi.command_line.jenkinsapi_version
2 | """
3 |
4 | import jenkinsapi
5 | import sys
6 |
7 |
8 | def main():
9 | sys.stdout.write(jenkinsapi.__version__)
10 |
11 |
12 | if __name__ == "__main__":
13 | main()
14 |
--------------------------------------------------------------------------------
/jenkinsapi/config.py:
--------------------------------------------------------------------------------
1 | """
2 | Jenkins configuration
3 | """
4 |
5 | JENKINS_API = r"api/python"
6 |
7 | LOAD_TIMEOUT = 30
8 |
--------------------------------------------------------------------------------
/jenkinsapi/constants.py:
--------------------------------------------------------------------------------
1 | """
2 | Constants for jenkinsapi
3 | """
4 |
5 | import re
6 |
7 | STATUS_FAIL = "FAIL"
8 | STATUS_ERROR = "ERROR"
9 | STATUS_ABORTED = "ABORTED"
10 | STATUS_REGRESSION = "REGRESSION"
11 | STATUS_SUCCESS = "SUCCESS"
12 |
13 | STATUS_FIXED = "FIXED"
14 | STATUS_PASSED = "PASSED"
15 |
16 | RESULTSTATUS_FAILURE = "FAILURE"
17 | RESULTSTATUS_FAILED = "FAILED"
18 | RESULTSTATUS_SKIPPED = "SKIPPED"
19 |
20 | STR_RE_SPLIT_VIEW = "(.*)/view/([^/]*)/?"
21 | RE_SPLIT_VIEW_URL = re.compile(STR_RE_SPLIT_VIEW)
22 |
--------------------------------------------------------------------------------
/jenkinsapi/custom_exceptions.py:
--------------------------------------------------------------------------------
1 | """Module for custom_exceptions.
2 |
3 | Where possible we try to throw exceptions with non-generic,
4 | meaningful names.
5 | """
6 |
7 |
8 | class JenkinsAPIException(Exception):
9 | """Base class for all errors"""
10 |
11 | pass
12 |
13 |
14 | class NotFound(JenkinsAPIException):
15 | """Resource cannot be found"""
16 |
17 | pass
18 |
19 |
20 | class ArtifactsMissing(NotFound):
21 | """Cannot find a build with all of the required artifacts."""
22 |
23 | pass
24 |
25 |
26 | class UnknownJob(KeyError, NotFound):
27 | """Jenkins does not recognize the job requested."""
28 |
29 | pass
30 |
31 |
32 | class UnknownView(KeyError, NotFound):
33 | """Jenkins does not recognize the view requested."""
34 |
35 | pass
36 |
37 |
38 | class UnknownNode(KeyError, NotFound):
39 | """Jenkins does not recognize the node requested."""
40 |
41 | pass
42 |
43 |
44 | class UnknownQueueItem(KeyError, NotFound):
45 | """Jenkins does not recognize the requested queue item"""
46 |
47 | pass
48 |
49 |
50 | class UnknownPlugin(KeyError, NotFound):
51 | """Jenkins does not recognize the plugin requested."""
52 |
53 | pass
54 |
55 |
56 | class NoBuildData(NotFound):
57 | """A job has no build data."""
58 |
59 | pass
60 |
61 |
62 | class NotBuiltYet(NotFound):
63 | """A job has no build data."""
64 |
65 | pass
66 |
67 |
68 | class ArtifactBroken(JenkinsAPIException):
69 | """An artifact is broken, wrong"""
70 |
71 | pass
72 |
73 |
74 | class TimeOut(JenkinsAPIException):
75 | """Some jobs have taken too long to complete."""
76 |
77 | pass
78 |
79 |
80 | class NoResults(JenkinsAPIException):
81 | """A build did not publish any results."""
82 |
83 | pass
84 |
85 |
86 | class FailedNoResults(NoResults):
87 | """A build did not publish any results because it failed"""
88 |
89 | pass
90 |
91 |
92 | class BadURL(ValueError, JenkinsAPIException):
93 | """A URL appears to be broken"""
94 |
95 | pass
96 |
97 |
98 | class NotAuthorized(JenkinsAPIException):
99 | """Not Authorized to access resource"""
100 |
101 | # Usually thrown when we get a 403 returned
102 | pass
103 |
104 |
105 | class NotSupportSCM(JenkinsAPIException):
106 | """
107 | It's a SCM that does not supported by current version of jenkinsapi
108 | """
109 |
110 | pass
111 |
112 |
113 | class NotConfiguredSCM(JenkinsAPIException):
114 | """It's a job that doesn't have configured SCM"""
115 |
116 | pass
117 |
118 |
119 | class NotInQueue(JenkinsAPIException):
120 | """It's a job that is not in the queue"""
121 |
122 | pass
123 |
124 |
125 | class PostRequired(JenkinsAPIException):
126 | """Method requires POST and not GET"""
127 |
128 | pass
129 |
130 |
131 | class BadParams(JenkinsAPIException):
132 | """Invocation was given bad or inappropriate params"""
133 |
134 | pass
135 |
136 |
137 | class AlreadyExists(JenkinsAPIException):
138 | """
139 | Method requires POST and not GET
140 | """
141 |
142 | pass
143 |
--------------------------------------------------------------------------------
/jenkinsapi/executor.py:
--------------------------------------------------------------------------------
1 | """
2 | Module for jenkinsapi Executer class
3 | """
4 |
5 | from __future__ import annotations
6 |
7 | from jenkinsapi.jenkinsbase import JenkinsBase
8 | import logging
9 |
10 | log = logging.getLogger(__name__)
11 |
12 |
13 | class Executor(JenkinsBase):
14 | """
15 | Class to hold information on nodes that are attached as slaves to the
16 | master jenkins instance
17 | """
18 |
19 | def __init__(
20 | self, baseurl: str, nodename: str, jenkins_obj: "Jenkins", number: int
21 | ) -> None:
22 | """
23 | Init a node object by providing all relevant pointers to it
24 | :param baseurl: basic url for querying information on a node
25 | :param nodename: hostname of the node
26 | :param jenkins_obj: ref to the jenkins obj
27 | :return: Node obj
28 | """
29 | self.nodename: str = nodename
30 | self.number: int = number
31 | self.jenkins: "Jenkins" = jenkins_obj
32 | self.baseurl: str = baseurl
33 | JenkinsBase.__init__(self, baseurl)
34 |
35 | def __str__(self) -> str:
36 | return f"{self.nodename} {self.number}"
37 |
38 | def get_jenkins_obj(self) -> "Jenkins":
39 | return self.jenkins
40 |
41 | def get_progress(self) -> str:
42 | """Returns percentage"""
43 | return self.poll(tree="progress")["progress"]
44 |
45 | def get_number(self) -> int:
46 | """
47 | Get Executor number.
48 | """
49 | return self.poll(tree="number")["number"]
50 |
51 | def is_idle(self) -> bool:
52 | """
53 | Returns Boolean: whether Executor is idle or not.
54 | """
55 | return self.poll(tree="idle")["idle"]
56 |
57 | def likely_stuck(self) -> bool:
58 | """
59 | Returns Boolean: whether Executor is likely stuck or not.
60 | """
61 | return self.poll(tree="likelyStuck")["likelyStuck"]
62 |
63 | def get_current_executable(self) -> str:
64 | """
65 | Returns the current Queue.Task this executor is running.
66 | """
67 | return self.poll(tree="currentExecutable")["currentExecutable"]
68 |
--------------------------------------------------------------------------------
/jenkinsapi/executors.py:
--------------------------------------------------------------------------------
1 | """
2 | This module implements the Executors class, which is intended to be a
3 | container-like interface for all of the executors defined on a single
4 | Jenkins node.
5 | """
6 |
7 | from __future__ import annotations
8 |
9 | import logging
10 | from typing import Iterator
11 |
12 | from jenkinsapi.executor import Executor
13 | from jenkinsapi.jenkinsbase import JenkinsBase
14 |
15 | log: logging.Logger = logging.getLogger(__name__)
16 |
17 |
18 | class Executors(JenkinsBase):
19 | """
20 | This class provides a container-like API which gives
21 | access to all executors on a Jenkins node.
22 |
23 | Returns a list of Executor Objects.
24 | """
25 |
26 | def __init__(
27 | self, baseurl: str, nodename: str, jenkins: "Jenkins"
28 | ) -> None:
29 | self.nodename: str = nodename
30 | self.jenkins: str = jenkins
31 | JenkinsBase.__init__(self, baseurl)
32 | self.count: int = self._data["numExecutors"]
33 |
34 | def __str__(self) -> str:
35 | return f"Executors @ {self.baseurl}"
36 |
37 | def get_jenkins_obj(self) -> "Jenkins":
38 | return self.jenkins
39 |
40 | def __iter__(self) -> Iterator[Executor]:
41 | for index in range(self.count):
42 | executor_url = "%s/executors/%s" % (self.baseurl, index)
43 | yield Executor(executor_url, self.nodename, self.jenkins, index)
44 |
--------------------------------------------------------------------------------
/jenkinsapi/fingerprint.py:
--------------------------------------------------------------------------------
1 | """
2 | Module for jenkinsapi Fingerprint
3 | """
4 |
5 | from __future__ import annotations
6 |
7 | import re
8 | import logging
9 | from typing import Any
10 |
11 | import requests
12 |
13 | from jenkinsapi.jenkinsbase import JenkinsBase
14 | from jenkinsapi.custom_exceptions import ArtifactBroken
15 |
16 | log: logging.Logger = logging.getLogger(__name__)
17 |
18 |
19 | class Fingerprint(JenkinsBase):
20 | """
21 | Represents a jenkins fingerprint on a single artifact file ??
22 | """
23 |
24 | RE_MD5 = re.compile("^([0-9a-z]{32})$")
25 |
26 | def __init__(self, baseurl: str, id_: str, jenkins_obj: "Jenkins") -> None:
27 | self.jenkins_obj: "Jenkins" = jenkins_obj
28 | assert self.RE_MD5.search(id_), (
29 | "%s does not look like " "a valid id" % id_
30 | )
31 | url: str = f"{baseurl}/fingerprint/{id_}/"
32 |
33 | JenkinsBase.__init__(self, url, poll=False)
34 | self.id_: str = id_
35 | self.unknown: bool = False # Previously uninitialized in ctor
36 |
37 | def get_jenkins_obj(self) -> "Jenkins":
38 | return self.jenkins_obj
39 |
40 | def __str__(self) -> str:
41 | return self.id_
42 |
43 | def valid(self) -> bool:
44 | """
45 | Return True / False if valid. If returns True, self.unknown is
46 | set to either True or False, and can be checked if we have
47 | positive validity (fingerprint known at server) or negative
48 | validity (fingerprint not known at server, but not really an
49 | error).
50 | """
51 | try:
52 | self.poll()
53 | self.unknown = False
54 | except requests.exceptions.HTTPError as err:
55 | # We can't really say anything about the validity of
56 | # fingerprints not found -- but the artifact can still
57 | # exist, so it is not possible to definitely say they are
58 | # valid or not.
59 | # The response object is of type: requests.models.Response
60 | # extract the status code from it
61 | response_obj: Any = err.response
62 | if response_obj.status_code == 404:
63 | logging.warning(
64 | "MD5 cannot be checked if fingerprints are not enabled"
65 | )
66 | self.unknown = True
67 | return True
68 |
69 | return False
70 |
71 | return True
72 |
73 | def validate_for_build(self, filename: str, job: str, build: int) -> bool:
74 | if not self.valid():
75 | log.info("Fingerprint is not known to jenkins.")
76 | return False
77 | if self.unknown:
78 | # not request error, but unknown to jenkins
79 | return True
80 | if self._data["original"] is not None:
81 | if self._data["original"]["name"] == job:
82 | if self._data["original"]["number"] == build:
83 | return True
84 | if self._data["fileName"] != filename:
85 | log.info(
86 | msg="Filename from jenkins (%s) did not match provided (%s)"
87 | % (self._data["fileName"], filename)
88 | )
89 | return False
90 | for usage_item in self._data["usage"]:
91 | if usage_item["name"] == job:
92 | for range_ in usage_item["ranges"]["ranges"]:
93 | if range_["start"] <= build <= range_["end"]:
94 | msg = (
95 | "This artifact was generated by %s "
96 | "between build %i and %i"
97 | % (
98 | job,
99 | range_["start"],
100 | range_["end"],
101 | )
102 | )
103 | log.info(msg=msg)
104 | return True
105 | return False
106 |
107 | def validate(self) -> bool:
108 | try:
109 | assert self.valid()
110 | except AssertionError as ae:
111 | raise ArtifactBroken(
112 | "Artifact %s seems to be broken, check %s"
113 | % (self.id_, self.baseurl)
114 | ) from ae
115 | except requests.exceptions.HTTPError:
116 | raise ArtifactBroken(
117 | "Unable to validate artifact id %s using %s"
118 | % (self.id_, self.baseurl)
119 | )
120 | return True
121 |
122 | def get_info(self):
123 | """
124 | Returns a tuple of build-name, build# and artifact filename
125 | for a good build.
126 | """
127 | self.poll()
128 | return (
129 | self._data["original"]["name"],
130 | self._data["original"]["number"],
131 | self._data["fileName"],
132 | )
133 |
--------------------------------------------------------------------------------
/jenkinsapi/jenkinsbase.py:
--------------------------------------------------------------------------------
1 | """
2 | Module for JenkinsBase class
3 | """
4 |
5 | from __future__ import annotations
6 |
7 | import ast
8 | import pprint
9 | import logging
10 | from urllib.parse import quote
11 | from jenkinsapi import config
12 | from jenkinsapi.custom_exceptions import JenkinsAPIException
13 |
14 | logger = logging.getLogger(__name__)
15 |
16 |
17 | class JenkinsBase(object):
18 | """
19 | This appears to be the base object that all other jenkins objects are
20 | inherited from
21 | """
22 |
23 | def __repr__(self):
24 | return """<%s.%s %s>""" % (
25 | self.__class__.__module__,
26 | self.__class__.__name__,
27 | str(self),
28 | )
29 |
30 | def __str__(self):
31 | raise NotImplementedError
32 |
33 | def __init__(self, baseurl: str, poll: bool = True):
34 | """
35 | Initialize a jenkins connection
36 | """
37 | self._data = None
38 | self.baseurl = self.strip_trailing_slash(baseurl)
39 | if poll:
40 | self.poll()
41 |
42 | def get_jenkins_obj(self):
43 | raise NotImplementedError(
44 | "Please implement this method on %s" % self.__class__.__name__
45 | )
46 |
47 | def __eq__(self, other) -> bool:
48 | """
49 | Return true if the other object represents a connection to the
50 | same server
51 | """
52 | if not isinstance(other, self.__class__):
53 | return False
54 | return other.baseurl == self.baseurl
55 |
56 | @classmethod
57 | def strip_trailing_slash(cls, url: str) -> str:
58 | while url.endswith("/"):
59 | url = url[:-1]
60 | return url
61 |
62 | def poll(self, tree=None):
63 | data = self._poll(tree=tree)
64 | if "jobs" in data:
65 | data["jobs"] = self.resolve_job_folders(data["jobs"])
66 | if not tree:
67 | self._data = data
68 |
69 | return data
70 |
71 | def _poll(self, tree=None):
72 | url = self.python_api_url(self.baseurl)
73 | return self.get_data(url, tree=tree)
74 |
75 | def get_data(self, url, params=None, tree=None):
76 | requester = self.get_jenkins_obj().requester
77 | if tree:
78 | if not params:
79 | params = {"tree": tree}
80 | else:
81 | params.update({"tree": tree})
82 |
83 | response = requester.get_url(url, params)
84 | if response.status_code != 200:
85 | logger.error(
86 | "Failed request at %s with params: %s %s",
87 | url,
88 | params,
89 | tree if tree else "",
90 | )
91 | response.raise_for_status()
92 | try:
93 | return ast.literal_eval(response.text)
94 | except Exception:
95 | logger.exception("Inappropriate content found at %s", url)
96 | raise JenkinsAPIException("Cannot parse %s" % response.content)
97 |
98 | def pprint(self):
99 | """
100 | Print out all the data in this object for debugging.
101 | """
102 | pprint.pprint(self._data)
103 |
104 | def resolve_job_folders(self, jobs):
105 | for job in list(jobs):
106 | if "color" not in job.keys():
107 | jobs.remove(job)
108 | jobs += self.process_job_folder(job, self.baseurl)
109 |
110 | return jobs
111 |
112 | def process_job_folder(self, folder, folder_path):
113 | logger.debug("Processing folder %s in %s", folder["name"], folder_path)
114 | folder_path += "/job/%s" % quote(folder["name"])
115 | data = self.get_data(
116 | self.python_api_url(folder_path), tree="jobs[name,color]"
117 | )
118 |
119 | result = []
120 | for job in data.get("jobs", []):
121 | if "color" not in job.keys():
122 | result += self.process_job_folder(job, folder_path)
123 | else:
124 | job["url"] = "%s/job/%s" % (folder_path, quote(job["name"]))
125 | result.append(job)
126 |
127 | return result
128 |
129 | @classmethod
130 | def python_api_url(cls, url: str) -> str:
131 | if url.endswith(config.JENKINS_API):
132 | return url
133 | else:
134 | if url.endswith(r"/"):
135 | fmt = "%s%s"
136 | else:
137 | fmt = "%s/%s"
138 | return fmt % (url, config.JENKINS_API)
139 |
--------------------------------------------------------------------------------
/jenkinsapi/label.py:
--------------------------------------------------------------------------------
1 | """
2 | Module for jenkinsapi labels
3 | """
4 |
5 | from jenkinsapi.jenkinsbase import JenkinsBase
6 | import logging
7 |
8 | log = logging.getLogger(__name__)
9 |
10 |
11 | class Label(JenkinsBase):
12 | """
13 | Class to hold information on labels that tied to a collection of jobs
14 | """
15 |
16 | def __init__(self, baseurl, labelname, jenkins_obj):
17 | """
18 | Init a label object by providing all relevant pointers to it
19 | :param baseurl: basic url for querying information on a node
20 | :param labelname: name of the label
21 | :param jenkins_obj: ref to the jenkins obj
22 | :return: Label obj
23 | """
24 | self.labelname = labelname
25 | self.jenkins = jenkins_obj
26 | self.baseurl = baseurl
27 | JenkinsBase.__init__(self, baseurl)
28 |
29 | def __str__(self):
30 | return "%s" % (self.labelname)
31 |
32 | def get_jenkins_obj(self):
33 | return self.jenkins
34 |
35 | def is_online(self):
36 | return not self.poll(tree="offline")["offline"]
37 |
38 | def get_tied_jobs(self):
39 | """
40 | Get a list of jobs.
41 | """
42 | if self.get_tied_job_names():
43 | for job in self.get_tied_job_names():
44 | yield self.get_jenkins_obj().get_job(job["name"])
45 |
46 | def get_tied_job_names(self):
47 | """
48 | Get a list of the name of tied jobs.
49 | """
50 | return self.poll(tree="tiedJobs[name]")["tiedJobs"]
51 |
--------------------------------------------------------------------------------
/jenkinsapi/mutable_jenkins_thing.py:
--------------------------------------------------------------------------------
1 | """
2 | Module for MutableJenkinsThing
3 | """
4 |
5 |
6 | class MutableJenkinsThing(object):
7 | """
8 | A mixin for certain mutable objects which can be renamed and deleted.
9 | """
10 |
11 | def get_delete_url(self) -> str:
12 | return f"{self.baseurl}/doDelete"
13 |
14 | def get_rename_url(self) -> str:
15 | return f"{self.baseurl}/doRename"
16 |
--------------------------------------------------------------------------------
/jenkinsapi/plugin.py:
--------------------------------------------------------------------------------
1 | """
2 | Module for jenkinsapi Plugin
3 | """
4 |
5 | from __future__ import annotations
6 |
7 | from typing import Union
8 |
9 |
10 | class Plugin(object):
11 | """
12 | Plugin class
13 | """
14 |
15 | def __init__(self, plugin_dict: Union[dict, str]) -> None:
16 | if isinstance(plugin_dict, dict):
17 | self.__dict__ = plugin_dict
18 | else:
19 | self.__dict__ = self.to_plugin(plugin_dict)
20 | self.shortName: str = self.__dict__["shortName"]
21 | self.version: str = self.__dict__.get("version", "Unknown")
22 |
23 | def to_plugin(self, plugin_string: str) -> dict:
24 | plugin_string = str(plugin_string)
25 | if "@" not in plugin_string or len(plugin_string.split("@")) != 2:
26 | usage_err: str = (
27 | "plugin specification must be a string like "
28 | '"plugin-name@version", not "{0}"'
29 | )
30 | usage_err = usage_err.format(plugin_string)
31 | raise ValueError(usage_err)
32 |
33 | shortName, version = plugin_string.split("@")
34 | return {"shortName": shortName, "version": version}
35 |
36 | def __eq__(self, other) -> bool:
37 | return self.__dict__ == other.__dict__
38 |
39 | def __str__(self) -> str:
40 | return self.shortName
41 |
42 | def __repr__(self) -> str:
43 | return "<%s.%s %s>" % (
44 | self.__class__.__module__,
45 | self.__class__.__name__,
46 | str(self),
47 | )
48 |
49 | def get_attributes(self) -> str:
50 | """
51 | Used by Plugins object to install plugins in Jenkins
52 | """
53 | return ' ' % (
54 | self.shortName,
55 | self.version,
56 | )
57 |
58 | def is_latest(self, update_center_dict: dict) -> bool:
59 | """
60 | Used by Plugins object to determine if plugin can be
61 | installed through the update center (when plugin version is
62 | latest version), or must be installed by uploading
63 | the plugin hpi file.
64 | """
65 | if self.version == "latest":
66 | return True
67 | center_plugin = update_center_dict["plugins"][self.shortName]
68 | return center_plugin["version"] == self.version
69 |
70 | def get_download_link(self, update_center_dict) -> str:
71 | latest_version = update_center_dict["plugins"][self.shortName][
72 | "version"
73 | ]
74 | latest_url = update_center_dict["plugins"][self.shortName]["url"]
75 | return latest_url.replace(
76 | "/".join((self.shortName, latest_version)),
77 | "/".join((self.shortName, self.version)),
78 | )
79 |
--------------------------------------------------------------------------------
/jenkinsapi/queue.py:
--------------------------------------------------------------------------------
1 | """
2 | Queue module for jenkinsapi
3 | """
4 |
5 | from __future__ import annotations
6 |
7 | from typing import Iterator, Tuple
8 | import logging
9 | import time
10 | from requests import HTTPError
11 | from jenkinsapi.jenkinsbase import JenkinsBase
12 | from jenkinsapi.custom_exceptions import UnknownQueueItem, NotBuiltYet
13 |
14 | log: logging.Logger = logging.getLogger(__name__)
15 |
16 |
17 | class Queue(JenkinsBase):
18 | """
19 | Class that represents the Jenkins queue
20 | """
21 |
22 | def __init__(self, baseurl: str, jenkins_obj: "Jenkins") -> None:
23 | """
24 | Init the Jenkins queue object
25 | :param baseurl: basic url for the queue
26 | :param jenkins_obj: ref to the jenkins obj
27 | """
28 | self.jenkins: "Jenkins" = jenkins_obj
29 | JenkinsBase.__init__(self, baseurl)
30 |
31 | def __str__(self) -> str:
32 | return self.baseurl
33 |
34 | def get_jenkins_obj(self) -> "Jenkins":
35 | return self.jenkins
36 |
37 | def iteritems(self) -> Iterator[Tuple[str, "QueueItem"]]:
38 | for item in self._data["items"]:
39 | queue_id = item["id"]
40 | item_baseurl = "%s/item/%i" % (self.baseurl, queue_id)
41 | yield item["id"], QueueItem(
42 | baseurl=item_baseurl, jenkins_obj=self.jenkins
43 | )
44 |
45 | def iterkeys(self) -> Iterator[str]:
46 | for item in self._data["items"]:
47 | yield item["id"]
48 |
49 | def itervalues(self) -> Iterator["QueueItem"]:
50 | for item in self._data["items"]:
51 | yield QueueItem(self.jenkins, **item)
52 |
53 | def keys(self) -> list[str]:
54 | return list(self.iterkeys())
55 |
56 | def values(self) -> list["QueueItem"]:
57 | return list(self.itervalues())
58 |
59 | def __len__(self) -> int:
60 | return len(self._data["items"])
61 |
62 | def __getitem__(self, item_id: str) -> "QueueItem":
63 | self_as_dict = dict(self.iteritems())
64 | if item_id in self_as_dict:
65 | return self_as_dict[item_id]
66 | else:
67 | raise UnknownQueueItem(item_id)
68 |
69 | def _get_queue_items_for_job(self, job_name: str) -> Iterator["QueueItem"]:
70 | for item in self._data["items"]:
71 | if "name" in item["task"] and item["task"]["name"] == job_name:
72 | yield QueueItem(
73 | self.get_queue_item_url(item), jenkins_obj=self.jenkins
74 | )
75 |
76 | def get_queue_items_for_job(self, job_name: str):
77 | return list(self._get_queue_items_for_job(job_name))
78 |
79 | def get_queue_item_url(self, item: str) -> str:
80 | return "%s/item/%i" % (self.baseurl, item["id"])
81 |
82 | def delete_item(self, queue_item: "QueueItem"):
83 | self.delete_item_by_id(queue_item.queue_id)
84 |
85 | def delete_item_by_id(self, item_id: str):
86 | deleteurl: str = "%s/cancelItem?id=%s" % (self.baseurl, item_id)
87 | self.get_jenkins_obj().requester.post_url(deleteurl)
88 |
89 |
90 | class QueueItem(JenkinsBase):
91 | """An individual item in the queue"""
92 |
93 | def __init__(self, baseurl: str, jenkins_obj: "Jenkins") -> None:
94 | self.jenkins: "Jenkins" = jenkins_obj
95 | JenkinsBase.__init__(self, baseurl)
96 |
97 | @property
98 | def queue_id(self):
99 | return self._data["id"]
100 |
101 | @property
102 | def name(self):
103 | return self._data["task"]["name"]
104 |
105 | @property
106 | def why(self):
107 | return self._data.get("why")
108 |
109 | def get_jenkins_obj(self) -> "Jenkins":
110 | return self.jenkins
111 |
112 | def get_job(self) -> "Job":
113 | """
114 | Return the job associated with this queue item
115 | """
116 | return self.jenkins.get_job_by_url(
117 | self._data["task"]["url"],
118 | self._data["task"]["name"],
119 | )
120 |
121 | def get_parameters(self):
122 | """returns parameters of queue item"""
123 | actions = self._data.get("actions", [])
124 | for action in actions:
125 | if isinstance(action, dict) and "parameters" in action:
126 | parameters = action["parameters"]
127 | return dict(
128 | [(x["name"], x.get("value", None)) for x in parameters]
129 | )
130 | return []
131 |
132 | def __repr__(self) -> str:
133 | return "<%s.%s %s>" % (
134 | self.__class__.__module__,
135 | self.__class__.__name__,
136 | str(self),
137 | )
138 |
139 | def __str__(self) -> str:
140 | return "%s Queue #%i" % (self.name, self.queue_id)
141 |
142 | def get_build(self) -> "Build":
143 | build_number = self.get_build_number()
144 | job = self.get_job()
145 | return job[build_number]
146 |
147 | def block_until_complete(self, delay=5):
148 | build = self.block_until_building(delay)
149 | return build.block_until_complete(delay=delay)
150 |
151 | def block_until_building(self, delay=5):
152 | while True:
153 | try:
154 | self.poll()
155 | return self.get_build()
156 | except NotBuiltYet:
157 | time.sleep(delay)
158 | continue
159 | except HTTPError as http_error:
160 | log.debug(str(http_error))
161 | time.sleep(delay)
162 | continue
163 |
164 | def is_running(self) -> bool:
165 | """Return True if this queued item is running."""
166 | try:
167 | return self.get_build().is_running()
168 | except NotBuiltYet:
169 | return False
170 |
171 | def is_queued(self) -> bool:
172 | """Return True if this queued item is queued."""
173 | try:
174 | self.get_build()
175 | except NotBuiltYet:
176 | return True
177 | else:
178 | return False
179 |
180 | def get_build_number(self) -> int:
181 | try:
182 | return self._data["executable"]["number"]
183 | except (KeyError, TypeError):
184 | raise NotBuiltYet()
185 |
186 | def get_job_name(self) -> str:
187 | try:
188 | return self._data["task"]["name"]
189 | except KeyError:
190 | raise NotBuiltYet()
191 |
--------------------------------------------------------------------------------
/jenkinsapi/result.py:
--------------------------------------------------------------------------------
1 | """
2 | Module for jenkinsapi Result
3 | """
4 |
5 |
6 | class Result(object):
7 | """
8 | Result class
9 | """
10 |
11 | def __init__(self, **kwargs):
12 | self.__dict__.update(kwargs)
13 |
14 | def __str__(self):
15 | return f"{self.className} {self.name} {self.status}"
16 |
17 | def __repr__(self) -> str:
18 | module_name = self.__class__.__module__
19 | class_name = self.__class__.__name__
20 | self_str = str(self)
21 | return "<%s.%s %s>" % (module_name, class_name, self_str)
22 |
23 | def identifier(self) -> str:
24 | """
25 | Calculate an ID for this object.
26 | """
27 | return f"{self.className}.{self.name}"
28 |
--------------------------------------------------------------------------------
/jenkinsapi/result_set.py:
--------------------------------------------------------------------------------
1 | """
2 | Module for jenkinsapi ResultSet
3 | """
4 |
5 | from __future__ import annotations
6 |
7 | from jenkinsapi.jenkinsbase import JenkinsBase
8 | from jenkinsapi.result import Result
9 |
10 |
11 | class ResultSet(JenkinsBase):
12 | """
13 | Represents a result from a completed Jenkins run.
14 | """
15 |
16 | def __init__(self, url: str, build: "Build") -> None:
17 | """
18 | Init a resultset
19 | :param url: url for a build, str
20 | :param build: build obj
21 | """
22 | self.build: "Build" = build
23 | JenkinsBase.__init__(self, url)
24 |
25 | def get_jenkins_obj(self) -> "Jenkins":
26 | return self.build.job.get_jenkins_obj()
27 |
28 | def __str__(self) -> str:
29 | return "Test Result for %s" % str(self.build)
30 |
31 | @property
32 | def name(self):
33 | return str(self)
34 |
35 | def keys(self) -> list[str]:
36 | return [a[0] for a in self.iteritems()]
37 |
38 | def items(self):
39 | return [a for a in self.iteritems()]
40 |
41 | def iteritems(self):
42 | for suite in self._data.get("suites", []):
43 | for case in suite["cases"]:
44 | result = Result(**case)
45 | yield result.identifier(), result
46 |
47 | for report_set in self._data.get("childReports", []):
48 | if report_set["result"]:
49 | for suite in report_set["result"]["suites"]:
50 | for case in suite["cases"]:
51 | result = Result(**case)
52 | yield result.identifier(), result
53 |
54 | def __len__(self):
55 | return len(self.items())
56 |
57 | def __getitem__(self, key):
58 | self_as_dict = dict(self.iteritems())
59 | return self_as_dict[key]
60 |
--------------------------------------------------------------------------------
/jenkinsapi/utils/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | Module __init__ for utils
3 | """
4 |
--------------------------------------------------------------------------------
/jenkinsapi/utils/crumb_requester.py:
--------------------------------------------------------------------------------
1 | # Code from https://github.com/ros-infrastructure/ros_buildfarm
2 | # (c) Open Source Robotics Foundation
3 | import ast
4 | import logging
5 | from jenkinsapi.utils.requester import Requester
6 |
7 | logger = logging.getLogger(__name__)
8 |
9 |
10 | class CrumbRequester(Requester):
11 | """Adapter for Requester inserting the crumb in every request."""
12 |
13 | def __init__(self, *args, **kwargs):
14 | super(CrumbRequester, self).__init__(*args, **kwargs)
15 | self._baseurl = kwargs["baseurl"]
16 | self._last_crumb_data = None
17 |
18 | def post_url(
19 | self,
20 | url,
21 | params=None,
22 | data=None,
23 | files=None,
24 | headers=None,
25 | allow_redirects=True,
26 | **kwargs,
27 | ):
28 | if self._last_crumb_data:
29 | # first try request with previous crumb if available
30 | response = self._post_url_with_crumb(
31 | self._last_crumb_data,
32 | url,
33 | params,
34 | data,
35 | files,
36 | headers,
37 | allow_redirects,
38 | **kwargs,
39 | )
40 | # code 403 might indicate that the crumb is not valid anymore
41 | if response.status_code != 403:
42 | return response
43 |
44 | # fetch new crumb (if server has crumbs enabled)
45 | if self._last_crumb_data is not False:
46 | self._last_crumb_data = self._get_crumb_data()
47 |
48 | return self._post_url_with_crumb(
49 | self._last_crumb_data,
50 | url,
51 | params,
52 | data,
53 | files,
54 | headers,
55 | allow_redirects,
56 | **kwargs,
57 | )
58 |
59 | def _get_crumb_data(self):
60 | response = self.get_url(self._baseurl + "/crumbIssuer/api/python")
61 | if response.status_code in [404]:
62 | logger.warning("The Jenkins master does not require a crumb")
63 | return False
64 | if response.status_code not in [200]:
65 | raise RuntimeError("Failed to fetch crumb: %s" % response.text)
66 | crumb_issuer_response = ast.literal_eval(response.text)
67 | crumb_request_field = crumb_issuer_response["crumbRequestField"]
68 | crumb = crumb_issuer_response["crumb"]
69 | logger.debug("Fetched crumb: %s", crumb)
70 | return {crumb_request_field: crumb}
71 |
72 | def _post_url_with_crumb(
73 | self,
74 | crumb_data,
75 | url,
76 | params,
77 | data,
78 | files,
79 | headers,
80 | allow_redirects,
81 | **kwargs,
82 | ):
83 | if crumb_data:
84 | if headers is None:
85 | headers = crumb_data
86 | else:
87 | headers.update(crumb_data)
88 |
89 | return super(CrumbRequester, self).post_url(
90 | url, params, data, files, headers, allow_redirects, **kwargs
91 | )
92 |
--------------------------------------------------------------------------------
/jenkinsapi/utils/jsonp_to_json.py:
--------------------------------------------------------------------------------
1 | """
2 | Module for converting jsonp to json.
3 | """
4 |
5 |
6 | def jsonp_to_json(jsonp):
7 | try:
8 | l_index = jsonp.index("(") + 1
9 | r_index = jsonp.rindex(")")
10 | except ValueError:
11 | print("Input is not in jsonp format.")
12 | return None
13 |
14 | res = jsonp[l_index:r_index]
15 | return res
16 |
--------------------------------------------------------------------------------
/jenkinsapi/utils/krb_requester.py:
--------------------------------------------------------------------------------
1 | """
2 | Kerberos aware Requester
3 | """
4 |
5 | from jenkinsapi.utils.requester import Requester
6 | from requests_kerberos import HTTPKerberosAuth, OPTIONAL
7 |
8 |
9 | # pylint: disable=W0222
10 | class KrbRequester(Requester):
11 | """
12 | A class which carries out HTTP requests with Kerberos/GSSAPI
13 | authentication.
14 | """
15 |
16 | def __init__(self, *args, **kwargs):
17 | """
18 | :param ssl_verify: flag indicating if server certificate
19 | in HTTPS requests should be verified
20 | :param baseurl: Jenkins' base URL
21 | :param mutual_auth: type of mutual authentication, use one of
22 | REQUIRED, OPTIONAL or DISABLED
23 | from requests_kerberos package
24 | """
25 |
26 | super(KrbRequester, self).__init__(*args, **kwargs)
27 | self.mutual_auth = (
28 | kwargs["mutual_auth"] if "mutual_auth" in kwargs else OPTIONAL
29 | )
30 |
31 | def get_request_dict(
32 | self, params=None, data=None, files=None, headers=None, **kwargs
33 | ):
34 | req_dict = super(KrbRequester, self).get_request_dict(
35 | params=params, data=data, files=files, headers=headers, **kwargs
36 | )
37 | if self.mutual_auth:
38 | auth = HTTPKerberosAuth(self.mutual_auth)
39 | else:
40 | auth = HTTPKerberosAuth()
41 |
42 | req_dict["auth"] = auth
43 | return req_dict
44 |
--------------------------------------------------------------------------------
/jenkinsapi/utils/manifest.py:
--------------------------------------------------------------------------------
1 | """
2 | This module enables Manifest file parsing.
3 | Copied from
4 | https://chromium.googlesource.com/external/googleappengine/python/+/master
5 | /google/appengine/tools/jarfile.py
6 | """
7 |
8 | import zipfile
9 |
10 | _MANIFEST_NAME = "META-INF/MANIFEST.MF"
11 |
12 |
13 | class InvalidJarError(Exception):
14 | """
15 | InvalidJar exception class
16 | """
17 |
18 | pass
19 |
20 |
21 | class Manifest(object):
22 | """The parsed manifest from a jar file.
23 | Attributes:
24 | main_section: a dict representing the main (first)
25 | section of the manifest.
26 | Each key is a string that is an attribute, such as
27 | 'Manifest-Version', and the corresponding value is a string that
28 | is the value of the attribute, such as '1.0'.
29 | sections: a dict representing the other sections of the manifest.
30 | Each key is a string that is the value of the 'Name' attribute for
31 | the section, and the corresponding value is a dict like the
32 | main_section one, for the other attributes.
33 | """
34 |
35 | def __init__(self, main_section, sections):
36 | self.main_section = main_section
37 | self.sections = sections
38 |
39 |
40 | def read_manifest(jar_file_name):
41 | """Read and parse the manifest out of the given jar.
42 | Args:
43 | jar_file_name: the name of the jar from which the manifest is to be read.
44 | Returns:
45 | A parsed Manifest object, or None if the jar has no manifest.
46 | Raises:
47 | IOError: if the jar does not exist or cannot be read.
48 | """
49 | with zipfile.ZipFile(jar_file_name) as jar:
50 | try:
51 | manifest_string = jar.read(_MANIFEST_NAME).decode("UTF-8")
52 | except KeyError:
53 | return None
54 | return _parse_manifest(manifest_string)
55 |
56 |
57 | def _parse_manifest(manifest_string):
58 | """Parse a Manifest object out of the given string.
59 | Args:
60 | manifest_string: a str or unicode that is the manifest contents.
61 | Returns:
62 | A Manifest object parsed out of the string.
63 | Raises:
64 | InvalidJarError: if the manifest is not well-formed.
65 | """
66 | manifest_string = "\n".join(manifest_string.splitlines()).rstrip("\n")
67 | section_strings = manifest_string.split("\n\n")
68 | parsed_sections = [_parse_manifest_section(s) for s in section_strings]
69 | main_section = parsed_sections[0]
70 | sections = dict()
71 | try:
72 | for entry in parsed_sections[1:]:
73 | sections[entry["Name"]] = entry
74 | except KeyError:
75 | raise InvalidJarError(
76 | "Manifest entry has no Name attribute: %s" % entry
77 | )
78 | return Manifest(main_section, sections)
79 |
80 |
81 | def _parse_manifest_section(section):
82 | """Parse a dict out of the given manifest section string.
83 | Args:
84 | section: a str or unicode that is the manifest section.
85 | It looks something like this (without the >):
86 | > Name: section-name
87 | > Some-Attribute: some value
88 | > Another-Attribute: another value
89 | Returns:
90 | A dict where the keys are the attributes (here, 'Name', 'Some-Attribute',
91 | 'Another-Attribute'), and the values are the corresponding
92 | attribute values.
93 | Raises:
94 | InvalidJarError: if the manifest section is not well-formed.
95 | """
96 | section = section.replace("\n ", "")
97 | try:
98 | return dict(line.split(": ", 1) for line in section.split("\n"))
99 | except ValueError:
100 | raise InvalidJarError("Invalid manifest %r" % section)
101 |
--------------------------------------------------------------------------------
/jenkinsapi/utils/simple_post_logger.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | try:
4 | from SimpleHTTPServer import SimpleHTTPRequestHandler
5 | except ImportError:
6 | from http.server import SimpleHTTPRequestHandler
7 |
8 | try:
9 | import SocketServer as socketserver
10 | except ImportError:
11 | import socketserver
12 |
13 | import logging
14 | import cgi
15 |
16 | PORT = 8080
17 |
18 |
19 | class ServerHandler(SimpleHTTPRequestHandler):
20 | def do_GET(self):
21 | logging.error(self.headers)
22 | super().do_GET()
23 |
24 | def do_POST(self):
25 | logging.error(self.headers)
26 | form = cgi.FieldStorage(
27 | fp=self.rfile,
28 | headers=self.headers,
29 | environ={
30 | "REQUEST_METHOD": "POST",
31 | "CONTENT_TYPE": self.headers["Content-Type"],
32 | },
33 | )
34 | for item in form.list:
35 | logging.error(item)
36 | super().do_GET()
37 |
38 |
39 | if __name__ == "__main__":
40 | Handler = ServerHandler
41 |
42 | httpd = socketserver.TCPServer(("", PORT), Handler)
43 |
44 | print("serving at port", PORT)
45 | httpd.serve_forever()
46 |
--------------------------------------------------------------------------------
/jenkinsapi/views.py:
--------------------------------------------------------------------------------
1 | """
2 | Module for jenkinsapi Views
3 | """
4 |
5 | import logging
6 | import json
7 | from jenkinsapi.view import View
8 | from jenkinsapi.custom_exceptions import JenkinsAPIException
9 |
10 | log = logging.getLogger(__name__)
11 |
12 |
13 | class Views(object):
14 | """
15 | An abstraction on a Jenkins object's views
16 | """
17 |
18 | LIST_VIEW = "hudson.model.ListView"
19 | NESTED_VIEW = "hudson.plugins.nested_view.NestedView"
20 | CATEGORIZED_VIEW = (
21 | "org.jenkinsci.plugins.categorizedview.CategorizedJobsView"
22 | )
23 | MY_VIEW = "hudson.model.MyView"
24 | DASHBOARD_VIEW = "hudson.plugins.view.dashboard.Dashboard"
25 | PIPELINE_VIEW = (
26 | "au.com.centrumsystems.hudson."
27 | "plugin.buildpipeline.BuildPipelineView"
28 | )
29 |
30 | def __init__(self, jenkins):
31 | self.jenkins = jenkins
32 | self._data = None
33 |
34 | def poll(self, tree=None):
35 | self._data = self.jenkins.poll(
36 | tree="views[name,url]" if tree is None else tree
37 | )
38 |
39 | def __len__(self):
40 | return len(self.keys())
41 |
42 | def __delitem__(self, view_name):
43 | if view_name == "All":
44 | raise ValueError("Cannot delete this view: %s" % view_name)
45 |
46 | if view_name in self:
47 | self[view_name].delete()
48 | self.poll()
49 |
50 | def __setitem__(self, view_name, job_names_list):
51 | new_view = self.create(view_name)
52 | if isinstance(job_names_list, str):
53 | job_names_list = [job_names_list]
54 | for job_name in job_names_list:
55 | if not new_view.add_job(job_name):
56 | # Something wrong - delete view
57 | del self[new_view]
58 | raise TypeError("Job %s does not exist in Jenkins" % job_name)
59 |
60 | def __getitem__(self, view_name):
61 | self.poll()
62 | for row in self._data.get("views", []):
63 | if row["name"] == view_name:
64 | return View(row["url"], row["name"], self.jenkins)
65 |
66 | raise KeyError("View %s not found" % view_name)
67 |
68 | def iteritems(self):
69 | """
70 | Get the names & objects for all views
71 | """
72 | self.poll()
73 | for row in self._data.get("views", []):
74 | name = row["name"]
75 | url = row["url"]
76 |
77 | yield name, View(url, name, self.jenkins)
78 |
79 | def __contains__(self, view_name):
80 | """
81 | True if view_name is the name of a defined view
82 | """
83 | return view_name in self.keys()
84 |
85 | def iterkeys(self):
86 | """
87 | Get the names of all available views
88 | """
89 | self.poll()
90 | for row in self._data.get("views", []):
91 | yield row["name"]
92 |
93 | def keys(self):
94 | """
95 | Return a list of the names of all views
96 | """
97 | return list(self.iterkeys())
98 |
99 | def create(self, view_name, view_type=LIST_VIEW, config=None):
100 | """
101 | Create a view
102 | :param view_name: name of new view, str
103 | :param view_type: type of the view, one of the constants in Views, str
104 | :param config: XML configuration of the new view
105 | :return: new View obj or None if view was not created
106 | """
107 | log.info('Creating "%s" view "%s"', view_type, view_name)
108 |
109 | if view_name in self:
110 | log.warning('View "%s" already exists', view_name)
111 | return self[view_name]
112 |
113 | url = "%s/createView" % self.jenkins.baseurl
114 |
115 | if view_type == self.CATEGORIZED_VIEW:
116 | if not config:
117 | raise JenkinsAPIException(
118 | "Job XML config cannot be empty for CATEGORIZED_VIEW"
119 | )
120 |
121 | params = {"name": view_name}
122 |
123 | self.jenkins.requester.post_xml_and_confirm_status(
124 | url, data=config, params=params
125 | )
126 | else:
127 | headers = {"Content-Type": "application/x-www-form-urlencoded"}
128 | data = {
129 | "name": view_name,
130 | "mode": view_type,
131 | "Submit": "OK",
132 | "json": json.dumps({"name": view_name, "mode": view_type}),
133 | }
134 |
135 | self.jenkins.requester.post_and_confirm_status(
136 | url, data=data, headers=headers
137 | )
138 |
139 | self.poll()
140 | return self[view_name]
141 |
--------------------------------------------------------------------------------
/jenkinsapi_tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycontribs/jenkinsapi/ffb35b6dca607b9aa5a65d4e49aa1ca76946adbf/jenkinsapi_tests/__init__.py
--------------------------------------------------------------------------------
/jenkinsapi_tests/conftest.py:
--------------------------------------------------------------------------------
1 | import os
2 | import logging
3 |
4 |
5 | logging.basicConfig(
6 | format="%(module)s.%(funcName)s %(levelname)s: %(message)s",
7 | level=logging.INFO,
8 | )
9 |
10 | level = (
11 | logging.WARNING
12 | if "LOG_LEVEL" not in os.environ
13 | else os.environ["LOG_LEVEL"].upper().strip()
14 | )
15 |
16 | modules = [
17 | "requests.packages.urllib3.connectionpool",
18 | "requests",
19 | "urllib3",
20 | "urllib3.connectionpool",
21 | ]
22 |
23 | for module_name in modules:
24 | logger = logging.getLogger(module_name)
25 | logger.setLevel(level)
26 |
--------------------------------------------------------------------------------
/jenkinsapi_tests/systests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycontribs/jenkinsapi/ffb35b6dca607b9aa5a65d4e49aa1ca76946adbf/jenkinsapi_tests/systests/__init__.py
--------------------------------------------------------------------------------
/jenkinsapi_tests/systests/config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 1.0
5 | 2
6 | NORMAL
7 | true
8 |
9 |
10 |
11 | ${JENKINS_HOME}/workspace/${ITEM_FULLNAME}
12 | ${ITEM_ROOTDIR}/builds
13 |
14 |
15 |
16 |
17 |
18 | 0
19 |
20 |
21 |
22 | All
23 | false
24 | false
25 |
26 |
27 |
28 | All
29 | 0
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/jenkinsapi_tests/systests/get-jenkins-war.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #JENKINS_WAR_URL="http://mirrors.jenkins-ci.org/war/latest/jenkins.war"
3 |
4 | if [[ "$#" -ne 3 ]]; then
5 | echo "Usage: $0 jenkins_url path_to_store_jenkins war_filename"
6 | exit 1
7 | fi
8 |
9 | readonly JENKINS_WAR_URL=$1
10 | readonly JENKINS_PATH=$2
11 | readonly WAR_FILENAME=$3
12 |
13 | echo "Downloading $JENKINS_WAR_URL to ${JENKINS_PATH}"
14 | if [[ $(type -t wget) ]]; then wget -O ${JENKINS_PATH}/${WAR_FILENAME} -q $JENKINS_WAR_URL
15 | elif [[ $(type -t curl) ]]; then curl -sSL -o ${JENKINS_PATH}/${WAR_FILENAME} $JENKINS_WAR_URL
16 | else
17 | echo "Could not find wget or curl"
18 | exit 1
19 | fi
20 |
21 | echo "Jenkins downloaded"
22 |
--------------------------------------------------------------------------------
/jenkinsapi_tests/systests/jenkins_home.tar.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycontribs/jenkinsapi/ffb35b6dca607b9aa5a65d4e49aa1ca76946adbf/jenkinsapi_tests/systests/jenkins_home.tar.gz
--------------------------------------------------------------------------------
/jenkinsapi_tests/systests/test_authentication.py:
--------------------------------------------------------------------------------
1 | """
2 | System tests for authentication functionality
3 | """
4 |
5 | import pytest
6 | from jenkinsapi.utils.requester import Requester
7 | from requests import HTTPError as REQHTTPError
8 | from jenkinsapi.jenkins import Jenkins
9 |
10 |
11 | def test_normal_authentication(jenkins_admin_admin):
12 | # No problem with the righ user/pass
13 | jenkins_user = Jenkins(
14 | jenkins_admin_admin.baseurl,
15 | jenkins_admin_admin.username,
16 | jenkins_admin_admin.password,
17 | )
18 |
19 | assert jenkins_user is not None
20 |
21 | # We cannot connect if no user/pass
22 | with pytest.raises(REQHTTPError) as http_excep:
23 | Jenkins(jenkins_admin_admin.baseurl)
24 |
25 | assert Requester.AUTH_COOKIE is None
26 | assert http_excep.value.response.status_code == 403
27 |
28 |
29 | # def test_auth_cookie(jenkins_admin_admin):
30 | # initial_cookie_value = None
31 | # final_cookie_value = "JSESSIONID"
32 | # assert initial_cookie_value == Requester.AUTH_COOKIE
33 | #
34 | # jenkins_admin_admin.use_auth_cookie()
35 | #
36 | # result = Requester.AUTH_COOKIE
37 | # assert result is not None
38 | # assert final_cookie_value in result
39 | #
40 | #
41 | # def test_wrongauth_cookie(jenkins_admin_admin):
42 | # initial_cookie_value = None
43 | # assert initial_cookie_value == Requester.AUTH_COOKIE
44 | #
45 | # jenkins_admin_admin.username = "fakeuser"
46 | # jenkins_admin_admin.password = "fakepass"
47 | #
48 | # with pytest.raises(HTTPError) as http_excep:
49 | # jenkins_admin_admin.use_auth_cookie()
50 | #
51 | # assert Requester.AUTH_COOKIE is None
52 | # assert http_excep.value.code == 401
53 | #
54 | #
55 | # def test_verify_cookie_isworking(jenkins_admin_admin):
56 | # initial_cookie_value = None
57 | # final_cookie_value = "JSESSIONID"
58 | # assert initial_cookie_value == Requester.AUTH_COOKIE
59 | #
60 | # # Remove requester user/pass
61 | # jenkins_admin_admin.requester.username = None
62 | # jenkins_admin_admin.requester.password = None
63 | #
64 | # # Verify that we cannot connect
65 | # with pytest.raises(REQHTTPError) as http_excep:
66 | # jenkins_admin_admin.poll()
67 | #
68 | # assert Requester.AUTH_COOKIE is None
69 | # assert http_excep.value.response.status_code == 403
70 | #
71 | # # Retrieve the auth cookie, we can because we
72 | # # have right values for jenkins_admin_admin.username
73 | # # and jenkins_admin_admin.password
74 | # jenkins_admin_admin.use_auth_cookie()
75 | #
76 | # result = Requester.AUTH_COOKIE
77 | # assert result is not None
78 | # assert final_cookie_value in result
79 | #
80 | # # Verify that we can connect even with no requester user/pass
81 | # # If we have the cookie the requester user/pass is not needed
82 | # jenkins_admin_admin.poll()
83 |
--------------------------------------------------------------------------------
/jenkinsapi_tests/systests/test_credentials.py:
--------------------------------------------------------------------------------
1 | """
2 | System tests for `jenkinsapi.jenkins` module.
3 | """
4 |
5 | import logging
6 | import pytest
7 | from jenkinsapi_tests.test_utils.random_strings import random_string
8 | from jenkinsapi.credentials import Credentials
9 | from jenkinsapi.credentials import UsernamePasswordCredential
10 | from jenkinsapi.credentials import SecretTextCredential
11 | from jenkinsapi.credential import SSHKeyCredential
12 |
13 | log = logging.getLogger(__name__)
14 |
15 |
16 | def test_get_credentials(jenkins):
17 | creds = jenkins.credentials
18 | assert isinstance(creds, Credentials) is True
19 |
20 |
21 | def test_delete_inexistant_credential(jenkins):
22 | with pytest.raises(KeyError):
23 | creds = jenkins.credentials
24 |
25 | del creds[random_string()]
26 |
27 |
28 | def test_create_user_pass_credential(jenkins):
29 | creds = jenkins.credentials
30 |
31 | cred_descr = random_string()
32 | cred_dict = {
33 | "description": cred_descr,
34 | "userName": "userName",
35 | "password": "password",
36 | }
37 | creds[cred_descr] = UsernamePasswordCredential(cred_dict)
38 |
39 | assert cred_descr in creds
40 |
41 | cred = creds[cred_descr]
42 | assert isinstance(cred, UsernamePasswordCredential) is True
43 | assert cred.password == ""
44 | assert cred.description == cred_descr
45 |
46 | del creds[cred_descr]
47 |
48 |
49 | def test_update_user_pass_credential(jenkins):
50 | creds = jenkins.credentials
51 |
52 | cred_descr = random_string()
53 | cred_dict = {
54 | "description": cred_descr,
55 | "userName": "userName",
56 | "password": "password",
57 | }
58 | creds[cred_descr] = UsernamePasswordCredential(cred_dict)
59 |
60 | cred = creds[cred_descr]
61 | cred.userName = "anotheruser"
62 | cred.password = "password2"
63 |
64 | cred = creds[cred_descr]
65 | assert isinstance(cred, UsernamePasswordCredential) is True
66 | assert cred.userName == "anotheruser"
67 | assert cred.password == "password2"
68 |
69 |
70 | def test_create_ssh_credential(jenkins):
71 | creds = jenkins.credentials
72 |
73 | cred_descr = random_string()
74 | cred_dict = {
75 | "description": cred_descr,
76 | "userName": "userName",
77 | "passphrase": "",
78 | "private_key": "-----BEGIN RSA PRIVATE KEY-----",
79 | }
80 | creds[cred_descr] = SSHKeyCredential(cred_dict)
81 |
82 | assert cred_descr in creds
83 |
84 | cred = creds[cred_descr]
85 | assert isinstance(cred, SSHKeyCredential) is True
86 | assert cred.description == cred_descr
87 |
88 | del creds[cred_descr]
89 |
90 | cred_dict = {
91 | "description": cred_descr,
92 | "userName": "userName",
93 | "passphrase": "",
94 | "private_key": "/tmp/key",
95 | }
96 | with pytest.raises(ValueError):
97 | creds[cred_descr] = SSHKeyCredential(cred_dict)
98 |
99 | cred_dict = {
100 | "description": cred_descr,
101 | "userName": "userName",
102 | "passphrase": "",
103 | "private_key": "~/.ssh/key",
104 | }
105 | with pytest.raises(ValueError):
106 | creds[cred_descr] = SSHKeyCredential(cred_dict)
107 |
108 | cred_dict = {
109 | "description": cred_descr,
110 | "userName": "userName",
111 | "passphrase": "",
112 | "private_key": "invalid",
113 | }
114 | with pytest.raises(ValueError):
115 | creds[cred_descr] = SSHKeyCredential(cred_dict)
116 |
117 |
118 | def test_delete_credential(jenkins):
119 | creds = jenkins.credentials
120 |
121 | cred_descr = random_string()
122 | cred_dict = {
123 | "description": cred_descr,
124 | "userName": "userName",
125 | "password": "password",
126 | }
127 | creds[cred_descr] = UsernamePasswordCredential(cred_dict)
128 |
129 | assert cred_descr in creds
130 | del creds[cred_descr]
131 | assert cred_descr not in creds
132 |
133 |
134 | def test_create_secret_text_credential(jenkins):
135 | """
136 | Tests the creation of a secret text.
137 | """
138 | creds = jenkins.credentials
139 |
140 | cred_descr = random_string()
141 | cred_dict = {"description": cred_descr, "secret": "newsecret"}
142 | creds[cred_descr] = SecretTextCredential(cred_dict)
143 |
144 | assert cred_descr in creds
145 | cred = creds[cred_descr]
146 | assert isinstance(cred, SecretTextCredential) is True
147 | assert cred.secret is None
148 | assert cred.description == cred_descr
149 |
150 | del creds[cred_descr]
151 |
--------------------------------------------------------------------------------
/jenkinsapi_tests/systests/test_crumbs_requester.py:
--------------------------------------------------------------------------------
1 | import io
2 | import time
3 | import json
4 | import logging
5 | import pytest
6 |
7 | from urllib.parse import urljoin
8 | from jenkinsapi.jenkins import Jenkins
9 | from jenkinsapi.utils.crumb_requester import CrumbRequester
10 | from jenkinsapi_tests.test_utils.random_strings import random_string
11 | from jenkinsapi_tests.systests.job_configs import JOB_WITH_FILE
12 |
13 |
14 | log = logging.getLogger(__name__)
15 |
16 | DEFAULT_JENKINS_PORT = 8080
17 |
18 | ENABLE_CRUMBS_CONFIG = {
19 | "hudson-security-csrf-GlobalCrumbIssuerConfiguration": {
20 | "csrf": {
21 | "issuer": {
22 | "value": "0",
23 | "stapler-class": "hudson.security.csrf.DefaultCrumbIssuer",
24 | "$class": "hudson.security.csrf.DefaultCrumbIssuer",
25 | "excludeClientIPFromCrumb": False,
26 | }
27 | }
28 | }
29 | }
30 |
31 | DISABLE_CRUMBS_CONFIG = {
32 | "hudson-security-csrf-GlobalCrumbIssuerConfiguration": {},
33 | }
34 |
35 | SECURITY_SETTINGS = {
36 | "": "0",
37 | "markupFormatter": {
38 | "stapler-class": "hudson.markup.EscapedMarkupFormatter",
39 | "$class": "hudson.markup.EscapedMarkupFormatter",
40 | },
41 | "org-jenkinsci-main-modules-sshd-SSHD": {
42 | "port": {"value": "", "type": "disabled"}
43 | },
44 | "jenkins-CLI": {"enabled": False},
45 | # This is not required if envinject plugin is not installed
46 | # but since it is installed for test suite - we must have this config
47 | # If this is not present - Jenkins will return error
48 | "org-jenkinsci-plugins-envinject-EnvInjectPluginConfiguration": {
49 | "enablePermissions": False,
50 | "hideInjectedVars": False,
51 | "enableLoadingFromMaster": False,
52 | },
53 | "jenkins-model-DownloadSettings": {"useBrowser": False},
54 | "slaveAgentPort": {"value": "", "type": "disable"},
55 | "agentProtocol": [
56 | "CLI-connect",
57 | "CLI2-connect",
58 | "JNLP-connect",
59 | "JNLP2-connect",
60 | "JNLP4-connect",
61 | ],
62 | "core:apply": "",
63 | }
64 |
65 |
66 | @pytest.fixture(scope="function")
67 | def crumbed_jenkins(jenkins):
68 | ENABLE_CRUMBS_CONFIG.update(SECURITY_SETTINGS)
69 | DISABLE_CRUMBS_CONFIG.update(SECURITY_SETTINGS)
70 |
71 | jenkins.requester.post_and_confirm_status(
72 | urljoin(jenkins.baseurl, "/configureSecurity/configure"),
73 | data={"Submit": "save", "json": json.dumps(ENABLE_CRUMBS_CONFIG)},
74 | headers={"Content-Type": "application/x-www-form-urlencoded"},
75 | )
76 | log.info("Enabled Jenkins security")
77 |
78 | crumbed = Jenkins(
79 | jenkins.baseurl, requester=CrumbRequester(baseurl=jenkins.baseurl)
80 | )
81 |
82 | yield crumbed
83 |
84 | crumbed.requester.post_and_confirm_status(
85 | jenkins.baseurl + "/configureSecurity/configure",
86 | data={"Submit": "save", "json": json.dumps(DISABLE_CRUMBS_CONFIG)},
87 | headers={"Content-Type": "application/x-www-form-urlencoded"},
88 | )
89 | log.info("Disabled Jenkins security")
90 |
91 |
92 | def test_invoke_job_with_file(crumbed_jenkins):
93 | file_data = random_string()
94 | param_file = io.BytesIO(file_data.encode("utf-8"))
95 |
96 | job_name = "create1_%s" % random_string()
97 | job = crumbed_jenkins.create_job(job_name, JOB_WITH_FILE)
98 |
99 | assert job.has_params()
100 | assert len(job.get_params_list())
101 |
102 | job.invoke(block=True, files={"file.txt": param_file})
103 |
104 | build = job.get_last_build()
105 | while build.is_running():
106 | time.sleep(0.25)
107 |
108 | artifacts = build.get_artifact_dict()
109 | assert isinstance(artifacts, dict) is True
110 | art_file = artifacts["file.txt"]
111 | assert art_file.get_data().decode("utf-8").strip() == file_data
112 |
--------------------------------------------------------------------------------
/jenkinsapi_tests/systests/test_downstream_upstream.py:
--------------------------------------------------------------------------------
1 | """
2 | System tests for `jenkinsapi.jenkins` module.
3 | """
4 |
5 | import time
6 | import logging
7 | import pytest
8 | from jenkinsapi.custom_exceptions import NoBuildData
9 |
10 | log = logging.getLogger(__name__)
11 |
12 | JOB_CONFIGS = {
13 | "A": """
14 |
15 |
16 |
17 | false
18 |
19 |
20 | true
21 | false
22 | false
23 | false
24 |
25 | false
26 |
27 |
28 |
29 | B
30 |
31 | SUCCESS
32 | 0
33 | BLUE
34 |
35 |
36 |
37 |
38 | """,
39 | "B": """
40 |
41 |
42 |
43 | false
44 |
45 |
46 | true
47 | false
48 | false
49 | false
50 |
51 | false
52 |
53 |
54 |
55 | C
56 |
57 | SUCCESS
58 | 0
59 | BLUE
60 |
61 |
62 |
63 |
64 | """,
65 | "C": """
66 |
67 |
68 |
69 | false
70 |
71 |
72 | true
73 | false
74 | false
75 | false
76 |
77 | false
78 |
79 |
80 |
81 | """,
82 | }
83 |
84 | DELAY = 10
85 |
86 |
87 | def test_stream_relationship(jenkins):
88 | """
89 | Can we keep track of the relationships between upstream & downstream jobs?
90 | """
91 | for job_name, job_config in JOB_CONFIGS.items():
92 | jenkins.create_job(job_name, job_config)
93 |
94 | time.sleep(1)
95 |
96 | jenkins["A"].invoke()
97 |
98 | for _ in range(10):
99 | try:
100 | jenkins["C"].get_last_completed_buildnumber() > 0
101 | except NoBuildData:
102 | log.info(
103 | "Waiting %i seconds for until the final job has run", DELAY
104 | )
105 | time.sleep(DELAY)
106 | else:
107 | break
108 | else:
109 | pytest.fail("Jenkins took too long to run these jobs")
110 |
111 | assert jenkins["C"].get_upstream_jobs() == [jenkins["B"]]
112 | assert jenkins["B"].get_upstream_jobs() == [jenkins["A"]]
113 |
114 | assert jenkins["A"].get_downstream_jobs() == [jenkins["B"]]
115 | assert jenkins["B"].get_downstream_jobs() == [jenkins["C"]]
116 |
--------------------------------------------------------------------------------
/jenkinsapi_tests/systests/test_env_vars.py:
--------------------------------------------------------------------------------
1 | """
2 | System tests for `jenkinsapi.jenkins` module.
3 | """
4 |
5 | import time
6 |
7 | from jenkinsapi_tests.systests.job_configs import JOB_WITH_ENV_VARS
8 | from jenkinsapi_tests.test_utils.random_strings import random_string
9 |
10 |
11 | def test_get_env_vars(jenkins):
12 | job_name = "get_env_vars_create1_%s" % random_string()
13 | job = jenkins.create_job(job_name, JOB_WITH_ENV_VARS)
14 | job.invoke(block=True)
15 | build = job.get_last_build()
16 | while build.is_running():
17 | time.sleep(0.25)
18 | data = build.get_env_vars()
19 | assert data["key1"] == "value1"
20 | assert data["key2"] == "value2"
21 |
--------------------------------------------------------------------------------
/jenkinsapi_tests/systests/test_executors.py:
--------------------------------------------------------------------------------
1 | """
2 | System tests for `jenkinsapi.jenkins` module.
3 | """
4 |
5 | import time
6 | import logging
7 | from jenkinsapi_tests.systests.job_configs import LONG_RUNNING_JOB
8 | from jenkinsapi_tests.test_utils.random_strings import random_string
9 |
10 | log = logging.getLogger(__name__)
11 |
12 |
13 | def test_get_executors(jenkins):
14 | node_name = random_string()
15 | node_dict = {
16 | "num_executors": 2,
17 | "node_description": "Test JNLP Node",
18 | "remote_fs": "/tmp",
19 | "labels": "systest_jnlp",
20 | "exclusive": True,
21 | }
22 | jenkins.nodes.create_node(node_name, node_dict)
23 | executors = jenkins.get_executors(node_name)
24 | assert executors.count == 2
25 |
26 | for count, execs in enumerate(executors):
27 | assert count == execs.get_number()
28 | assert execs.is_idle() is True
29 |
30 |
31 | def test_running_executor(jenkins):
32 | node_name = random_string()
33 | node_dict = {
34 | "num_executors": 1,
35 | "node_description": "Test JNLP Node",
36 | "remote_fs": "/tmp",
37 | "labels": "systest_jnlp",
38 | "exclusive": True,
39 | }
40 | jenkins.nodes.create_node(node_name, node_dict)
41 | job_name = "create_%s" % random_string()
42 | job = jenkins.create_job(job_name, LONG_RUNNING_JOB)
43 | qq = job.invoke()
44 | qq.block_until_building()
45 |
46 | if job.is_running() is False:
47 | time.sleep(1)
48 | executors = jenkins.get_executors(node_name)
49 | all_idle = True
50 | for execs in executors:
51 | if execs.is_idle() is False:
52 | all_idle = False
53 | assert execs.get_progress() != -1
54 | assert execs.get_current_executable() == qq.get_build_number()
55 | assert execs.likely_stuck() is False
56 | assert all_idle is True, "Executor should have been triggered."
57 |
58 |
59 | def test_idle_executors(jenkins):
60 | node_name = random_string()
61 | node_dict = {
62 | "num_executors": 1,
63 | "node_description": "Test JNLP Node",
64 | "remote_fs": "/tmp",
65 | "labels": "systest_jnlp",
66 | "exclusive": True,
67 | }
68 | jenkins.nodes.create_node(node_name, node_dict)
69 | executors = jenkins.get_executors(node_name)
70 |
71 | for execs in executors:
72 | assert execs.get_progress() == -1
73 | assert execs.get_current_executable() is None
74 | assert execs.likely_stuck() is False
75 | assert execs.is_idle() is True
76 |
--------------------------------------------------------------------------------
/jenkinsapi_tests/systests/test_generate_new_api_token.py:
--------------------------------------------------------------------------------
1 | """
2 | System tests for generation new api token for logged in user
3 | """
4 |
5 | import pytest
6 | import logging
7 | from jenkinsapi.utils.crumb_requester import CrumbRequester
8 |
9 |
10 | log = logging.getLogger(__name__)
11 |
12 |
13 | @pytest.mark.generate_new_api_token
14 | def test_generate_new_api_token(jenkins_admin_admin):
15 | jenkins_admin_admin.requester = CrumbRequester(
16 | baseurl=jenkins_admin_admin.baseurl,
17 | username=jenkins_admin_admin.username,
18 | password=jenkins_admin_admin.password,
19 | )
20 | jenkins_admin_admin.poll()
21 |
22 | new_token = (
23 | jenkins_admin_admin.generate_new_api_token()
24 | ) # generate new token
25 | log.info("newly generated token: %s", new_token)
26 | assert new_token is not None
27 |
--------------------------------------------------------------------------------
/jenkinsapi_tests/systests/test_invocation.py:
--------------------------------------------------------------------------------
1 | """
2 | System tests for `jenkinsapi.jenkins` module.
3 | """
4 |
5 | import time
6 | import logging
7 | import pytest
8 | from jenkinsapi.build import Build
9 | from jenkinsapi.queue import QueueItem
10 | from jenkinsapi_tests.test_utils.random_strings import random_string
11 | from jenkinsapi_tests.systests.job_configs import LONG_RUNNING_JOB
12 | from jenkinsapi_tests.systests.job_configs import SHORTISH_JOB, EMPTY_JOB
13 | from jenkinsapi.custom_exceptions import BadParams, NotFound
14 |
15 |
16 | log = logging.getLogger(__name__)
17 |
18 |
19 | def test_invocation_object(jenkins):
20 | job_name = "Acreate_%s" % random_string()
21 | job = jenkins.create_job(job_name, SHORTISH_JOB)
22 | qq = job.invoke()
23 | assert isinstance(qq, QueueItem)
24 | # Let Jenkins catchup
25 | qq.block_until_building()
26 | assert qq.get_build_number() == 1
27 |
28 |
29 | def test_get_block_until_build_running(jenkins):
30 | job_name = "Bcreate_%s" % random_string()
31 | job = jenkins.create_job(job_name, LONG_RUNNING_JOB)
32 | qq = job.invoke()
33 | time.sleep(3)
34 | bn = qq.block_until_building(delay=3).get_number()
35 | assert isinstance(bn, int)
36 |
37 | build = qq.get_build()
38 | assert isinstance(build, Build)
39 | assert build.is_running()
40 | build.stop()
41 | # if we call next line right away - Jenkins have no time to stop job
42 | # so we wait a bit
43 | time.sleep(1)
44 | assert not build.is_running()
45 | console = build.get_console()
46 | assert isinstance(console, str)
47 | assert "Started by user" in console
48 |
49 |
50 | def test_get_block_until_build_complete(jenkins):
51 | job_name = "Ccreate_%s" % random_string()
52 | job = jenkins.create_job(job_name, SHORTISH_JOB)
53 | qq = job.invoke()
54 | qq.block_until_complete()
55 | assert not qq.get_build().is_running()
56 |
57 |
58 | def test_mi_and_get_last_build(jenkins):
59 | job_name = "Dcreate_%s" % random_string()
60 |
61 | job = jenkins.create_job(job_name, SHORTISH_JOB)
62 |
63 | for _ in range(3):
64 | ii = job.invoke()
65 | ii.block_until_complete(delay=2)
66 |
67 | build_number = job.get_last_good_buildnumber()
68 | assert build_number == 3
69 |
70 | build = job.get_build(build_number)
71 | assert isinstance(build, Build)
72 |
73 | build = job.get_build_metadata(build_number)
74 | assert isinstance(build, Build)
75 |
76 |
77 | def test_mi_and_get_build_number(jenkins):
78 | job_name = "Ecreate_%s" % random_string()
79 |
80 | job = jenkins.create_job(job_name, EMPTY_JOB)
81 |
82 | for invocation in range(3):
83 | qq = job.invoke()
84 | qq.block_until_complete(delay=1)
85 | build_number = qq.get_build_number()
86 | assert build_number == invocation + 1
87 |
88 |
89 | def test_mi_and_delete_build(jenkins):
90 | job_name = "Ecreate_%s" % random_string()
91 |
92 | job = jenkins.create_job(job_name, EMPTY_JOB)
93 |
94 | for invocation in range(3):
95 | qq = job.invoke()
96 | qq.block_until_complete(delay=1)
97 | build_number = qq.get_build_number()
98 | assert build_number == invocation + 1
99 |
100 | # Delete build using Job.delete_build
101 | job.get_build(1)
102 | job.delete_build(1)
103 | with pytest.raises(NotFound):
104 | job.get_build(1)
105 |
106 | # Delete build using Job as dictionary of builds
107 | assert isinstance(job[2], Build)
108 | del job[2]
109 |
110 | with pytest.raises(NotFound):
111 | job.get_build(2)
112 |
113 | with pytest.raises(NotFound):
114 | job.delete_build(99)
115 |
116 |
117 | def test_give_params_on_non_parameterized_job(jenkins):
118 | job_name = "Ecreate_%s" % random_string()
119 | job = jenkins.create_job(job_name, EMPTY_JOB)
120 | with pytest.raises(BadParams):
121 | job.invoke(build_params={"foo": "bar", "baz": 99})
122 |
123 |
124 | def test_keep_build_toggle(jenkins):
125 | job_name = "Ecreate_%s" % random_string()
126 |
127 | job = jenkins.create_job(job_name, EMPTY_JOB)
128 |
129 | qq = job.invoke()
130 | qq.block_until_complete(delay=1)
131 |
132 | build = job.get_last_build()
133 | assert not build.is_kept_forever()
134 | build.toggle_keep()
135 | assert build.is_kept_forever()
136 |
137 | build_number = job.get_last_buildnumber()
138 | job.toggle_keep_build(build_number)
139 | build = job.get_last_build()
140 | assert not build.is_kept_forever()
141 |
--------------------------------------------------------------------------------
/jenkinsapi_tests/systests/test_jenkins.py:
--------------------------------------------------------------------------------
1 | """
2 | System tests for `jenkinsapi.jenkins` module.
3 | """
4 |
5 | import pytest
6 | from jenkinsapi.job import Job
7 | from jenkinsapi.jobs import Jobs
8 | from jenkinsapi.build import Build
9 | from jenkinsapi.queue import QueueItem
10 | from jenkinsapi_tests.systests.job_configs import EMPTY_JOB
11 | from jenkinsapi_tests.test_utils.random_strings import random_string
12 | from jenkinsapi.custom_exceptions import UnknownJob
13 |
14 |
15 | def job_present(jenkins, name):
16 | jenkins.poll()
17 | assert name in jenkins, "Job %r is absent in jenkins." % name
18 | assert isinstance(jenkins.get_job(name), Job) is True
19 | assert isinstance(jenkins[name], Job) is True
20 |
21 |
22 | def job_absent(jenkins, name):
23 | jenkins.poll()
24 | assert name not in jenkins, "Job %r is present in jenkins." % name
25 |
26 |
27 | def test_create_job(jenkins):
28 | job_name = "create_%s" % random_string()
29 | jenkins.create_job(job_name, EMPTY_JOB)
30 | job_present(jenkins, job_name)
31 |
32 |
33 | def test_create_job_with_plus(jenkins):
34 | job_name = "create+%s" % random_string()
35 | jenkins.create_job(job_name, EMPTY_JOB)
36 | job_present(jenkins, job_name)
37 | job = jenkins[job_name]
38 | assert job_name in job.url
39 |
40 |
41 | def test_create_dup_job(jenkins):
42 | job_name = "create_%s" % random_string()
43 | old_job = jenkins.create_job(job_name, EMPTY_JOB)
44 | job_present(jenkins, job_name)
45 | new_job = jenkins.create_job(job_name, EMPTY_JOB)
46 | assert new_job == old_job
47 |
48 |
49 | def test_get_jobs_info(jenkins):
50 | job_name = "create_%s" % random_string()
51 | job = jenkins.create_job(job_name, EMPTY_JOB)
52 |
53 | jobs_info = list(jenkins.get_jobs_info())
54 | assert len(jobs_info) == 1
55 | for url, name in jobs_info:
56 | assert url == job.url
57 | assert name == job.name
58 |
59 |
60 | def test_create_job_through_jobs_dict(jenkins):
61 | job_name = "create_%s" % random_string()
62 | jenkins.jobs[job_name] = EMPTY_JOB
63 | job_present(jenkins, job_name)
64 |
65 |
66 | def test_enable_disable_job(jenkins):
67 | job_name = "create_%s" % random_string()
68 | jenkins.create_job(job_name, EMPTY_JOB)
69 | job_present(jenkins, job_name)
70 |
71 | j = jenkins[job_name]
72 | j.invoke(block=True) # run this at least once
73 | # Ensure job begins as enabled
74 | assert j.is_enabled() is True, "An enabled job is reporting incorrectly"
75 |
76 | j.disable()
77 | assert j.is_enabled() is False, "A disabled job is reporting incorrectly"
78 |
79 | j.enable()
80 | assert j.is_enabled() is True, "An enabled job is reporting incorrectly"
81 |
82 |
83 | def test_get_job_and_update_config(jenkins):
84 | job_name = "config_%s" % random_string()
85 | jenkins.create_job(job_name, EMPTY_JOB)
86 | job_present(jenkins, job_name)
87 | config = jenkins[job_name].get_config()
88 | assert config.strip() == EMPTY_JOB.strip()
89 | jenkins[job_name].update_config(EMPTY_JOB)
90 |
91 |
92 | def test_invoke_job(jenkins):
93 | job_name = "create_%s" % random_string()
94 | job = jenkins.create_job(job_name, EMPTY_JOB)
95 | job.invoke(block=True)
96 | assert isinstance(job.get_build(1), Build)
97 |
98 |
99 | def test_invocation_object(jenkins):
100 | job_name = "create_%s" % random_string()
101 | job = jenkins.create_job(job_name, EMPTY_JOB)
102 | ii = job.invoke()
103 | assert isinstance(ii, QueueItem) is True
104 |
105 |
106 | def test_get_jobs_list(jenkins):
107 | job1_name = "first_%s" % random_string()
108 | job2_name = "second_%s" % random_string()
109 |
110 | jenkins.create_job(job1_name, EMPTY_JOB)
111 | jenkins.create_job(job2_name, EMPTY_JOB)
112 | assert len(jenkins.jobs) >= 2
113 | job_list = jenkins.get_jobs_list()
114 | assert [job1_name, job2_name] == job_list
115 |
116 |
117 | def test_get_job(jenkins):
118 | job1_name = "first_%s" % random_string()
119 |
120 | jenkins.create_job(job1_name, EMPTY_JOB)
121 | job = jenkins[job1_name]
122 | assert isinstance(job, Job) is True
123 | assert job.name == job1_name
124 |
125 |
126 | def test_get_jobs(jenkins):
127 | job1_name = "first_%s" % random_string()
128 | job2_name = "second_%s" % random_string()
129 |
130 | jenkins.create_job(job1_name, EMPTY_JOB)
131 | jenkins.create_job(job2_name, EMPTY_JOB)
132 | jobs = jenkins.jobs
133 | assert isinstance(jobs, Jobs) is True
134 | assert len(jobs) >= 2
135 | for job_name, job in jobs.iteritems():
136 | assert isinstance(job_name, str) is True
137 | assert isinstance(job, Job) is True
138 |
139 |
140 | def test_get_job_that_does_not_exist(jenkins):
141 | with pytest.raises(UnknownJob):
142 | jenkins["doesnot_exist"]
143 |
144 |
145 | def test_has_job(jenkins):
146 | job1_name = "first_%s" % random_string()
147 | jenkins.create_job(job1_name, EMPTY_JOB)
148 | assert jenkins.has_job(job1_name) is True
149 | assert job1_name in jenkins
150 |
151 |
152 | def test_has_no_job(jenkins):
153 | assert jenkins.has_job("doesnt_exist") is False
154 | assert "doesnt_exist" not in jenkins
155 |
156 |
157 | def test_delete_job(jenkins):
158 | job1_name = "delete_me_%s" % random_string()
159 |
160 | jenkins.create_job(job1_name, EMPTY_JOB)
161 | jenkins.delete_job(job1_name)
162 | job_absent(jenkins, job1_name)
163 |
164 |
165 | def test_rename_job(jenkins):
166 | job1_name = "A__%s" % random_string()
167 | job2_name = "B__%s" % random_string()
168 |
169 | jenkins.create_job(job1_name, EMPTY_JOB)
170 | jenkins.rename_job(job1_name, job2_name)
171 | job_absent(jenkins, job1_name)
172 | job_present(jenkins, job2_name)
173 |
174 |
175 | def test_copy_job(jenkins):
176 | template_job_name = "TPL%s" % random_string()
177 | copied_job_name = "CPY%s" % random_string()
178 |
179 | jenkins.create_job(template_job_name, EMPTY_JOB)
180 | j = jenkins.copy_job(template_job_name, copied_job_name)
181 | job_present(jenkins, template_job_name)
182 | job_present(jenkins, copied_job_name)
183 | assert isinstance(j, Job) is True
184 | assert j.name == copied_job_name
185 |
186 |
187 | def test_get_master_data(jenkins):
188 | master_data = jenkins.get_master_data()
189 | assert master_data["totalExecutors"] == 2
190 |
191 |
192 | def test_run_groovy_script(jenkins):
193 | expected_result = "Hello world!"
194 | result = jenkins.run_groovy_script('print "%s"' % expected_result)
195 | assert result.strip() == "Hello world!"
196 |
--------------------------------------------------------------------------------
/jenkinsapi_tests/systests/test_jenkins_artifacts.py:
--------------------------------------------------------------------------------
1 | """
2 | System tests for `jenkinsapi.jenkins` module.
3 | """
4 |
5 | import os
6 | from posixpath import join
7 | import re
8 | import time
9 | import gzip
10 | import shutil
11 | import tempfile
12 | import logging
13 | from jenkinsapi_tests.systests.job_configs import JOB_WITH_ARTIFACTS
14 | from jenkinsapi_tests.test_utils.random_strings import random_string
15 |
16 | log = logging.getLogger(__name__)
17 |
18 |
19 | def test_artifacts(jenkins):
20 | job_name = "create_%s" % random_string()
21 | job = jenkins.create_job(job_name, JOB_WITH_ARTIFACTS)
22 | job.invoke(block=True)
23 |
24 | build = job.get_last_build()
25 |
26 | while build.is_running():
27 | time.sleep(1)
28 |
29 | artifacts = build.get_artifact_dict()
30 | assert isinstance(artifacts, dict) is True
31 |
32 | text_artifact = artifacts["out.txt"]
33 | binary_artifact = artifacts["out.gz"]
34 |
35 | tempDir = tempfile.mkdtemp()
36 |
37 | try:
38 | # Verify that we can handle text artifacts
39 | text_artifact.save_to_dir(tempDir, strict_validation=True)
40 | text_file_path = join(tempDir, text_artifact.filename)
41 | assert os.path.exists(text_file_path)
42 | with open(text_file_path, "rb") as f:
43 | read_back_text = f.read().strip()
44 | read_back_text = read_back_text.decode("ascii")
45 | log.info("Text artifact: %s", read_back_text)
46 | assert (
47 | re.match(r"^PING \S+ \(127.0.0.1\)", read_back_text)
48 | is not None
49 | )
50 | assert read_back_text.endswith("ms") is True
51 |
52 | # Verify that we can hande binary artifacts
53 | binary_artifact.save_to_dir(tempDir, strict_validation=True)
54 | bin_file_path = join(tempDir, binary_artifact.filename)
55 | assert os.path.exists(bin_file_path)
56 | with gzip.open(bin_file_path, "rb") as f:
57 | read_back_text = f.read().strip()
58 | read_back_text = read_back_text.decode("ascii")
59 | assert (
60 | re.match(r"^PING \S+ \(127.0.0.1\)", read_back_text)
61 | is not None
62 | )
63 | assert read_back_text.endswith("ms") is True
64 | finally:
65 | shutil.rmtree(tempDir)
66 |
--------------------------------------------------------------------------------
/jenkinsapi_tests/systests/test_jenkins_matrix.py:
--------------------------------------------------------------------------------
1 | """
2 | System tests for `jenkinsapi.jenkins` module.
3 | """
4 |
5 | import re
6 | import time
7 | from jenkinsapi_tests.systests.job_configs import MATRIX_JOB
8 | from jenkinsapi_tests.test_utils.random_strings import random_string
9 |
10 |
11 | def test_invoke_matrix_job(jenkins):
12 | job_name = "create_%s" % random_string()
13 | job = jenkins.create_job(job_name, MATRIX_JOB)
14 | queueItem = job.invoke()
15 | queueItem.block_until_complete()
16 |
17 | build = job.get_last_build()
18 |
19 | while build.is_running():
20 | time.sleep(1)
21 |
22 | set_of_groups = set()
23 | for run in build.get_matrix_runs():
24 | assert run.get_number() == build.get_number()
25 | assert run.get_upstream_build() == build
26 | match_result = re.search("\xbb (.*) #\\d+$", run.name)
27 | assert match_result is not None
28 | set_of_groups.add(match_result.group(1))
29 | build.get_master_job_name()
30 |
31 | assert set_of_groups == set(["one", "two", "three"])
32 |
--------------------------------------------------------------------------------
/jenkinsapi_tests/systests/test_nodes.py:
--------------------------------------------------------------------------------
1 | """
2 | System tests for `jenkinsapi.jenkins` module.
3 | """
4 |
5 | import logging
6 | import pytest
7 | from jenkinsapi.node import Node
8 | from jenkinsapi.credential import SSHKeyCredential
9 | from jenkinsapi_tests.test_utils.random_strings import random_string
10 |
11 | log = logging.getLogger(__name__)
12 |
13 | TOOL_KEY = "hudson.tasks.Maven$MavenInstallation$DescriptorImpl@Maven 3.0.5"
14 |
15 |
16 | def test_online_offline(jenkins):
17 | """
18 | Can we flip the online / offline state of the master node.
19 | """
20 | # Master node name should be case insensitive
21 | # mn0 = jenkins.get_node('MaStEr')
22 | mn = jenkins.get_node("Built-In Node")
23 | # self.assertEqual(mn, mn0)
24 |
25 | mn.set_online() # It should already be online, hence no-op
26 | assert mn.is_online() is True
27 |
28 | mn.set_offline() # We switch that suckah off
29 | mn.set_offline() # This should be a no-op
30 | assert mn.is_online() is False
31 |
32 | mn.set_online() # Switch it back on
33 | assert mn.is_online() is True
34 |
35 |
36 | def test_create_jnlp_node(jenkins):
37 | node_name = random_string()
38 | node_dict = {
39 | "num_executors": 1,
40 | "node_description": "Test JNLP Node",
41 | "remote_fs": "/tmp",
42 | "labels": "systest_jnlp",
43 | "exclusive": True,
44 | "tool_location": [
45 | {
46 | "key": TOOL_KEY,
47 | "home": "/home/apache-maven-3.0.5/",
48 | },
49 | ],
50 | }
51 | node = jenkins.nodes.create_node(node_name, node_dict)
52 | assert isinstance(node, Node) is True
53 |
54 | del jenkins.nodes[node_name]
55 |
56 |
57 | def test_create_ssh_node(jenkins):
58 | node_name = random_string()
59 | creds = jenkins.get_credentials()
60 |
61 | cred_descr = random_string()
62 | cred_dict = {
63 | "description": cred_descr,
64 | "userName": "username",
65 | "passphrase": "",
66 | "private_key": "-----BEGIN RSA PRIVATE KEY-----",
67 | }
68 | creds[cred_descr] = SSHKeyCredential(cred_dict)
69 | node_dict = {
70 | "num_executors": 1,
71 | "node_description": "Description %s" % node_name,
72 | "remote_fs": "/tmp",
73 | "labels": node_name,
74 | "exclusive": False,
75 | "host": "127.0.0.1",
76 | "port": 22,
77 | "credential_description": cred_descr,
78 | "jvm_options": "",
79 | "java_path": "",
80 | "prefix_start_slave_cmd": "",
81 | "suffix_start_slave_cmd": "",
82 | "retention": "ondemand",
83 | "ondemand_delay": 0,
84 | "ondemand_idle_delay": 5,
85 | "tool_location": [
86 | {
87 | "key": TOOL_KEY,
88 | "home": "/home/apache-maven-3.0.5/",
89 | },
90 | ],
91 | }
92 | node = jenkins.nodes.create_node(node_name, node_dict)
93 | assert isinstance(node, Node) is True
94 | del jenkins.nodes[node_name]
95 |
96 | jenkins.nodes[node_name] = node_dict
97 | assert isinstance(jenkins.nodes[node_name], Node) is True
98 | del jenkins.nodes[node_name]
99 |
100 |
101 | def test_delete_node(jenkins):
102 | node_name = random_string()
103 | node_dict = {
104 | "num_executors": 1,
105 | "node_description": "Test JNLP Node",
106 | "remote_fs": "/tmp",
107 | "labels": "systest_jnlp",
108 | "exclusive": True,
109 | }
110 | jenkins.nodes.create_node(node_name, node_dict)
111 | del jenkins.nodes[node_name]
112 |
113 | with pytest.raises(KeyError):
114 | jenkins.nodes[node_name]
115 |
116 | with pytest.raises(KeyError):
117 | del jenkins.nodes["not_exist"]
118 |
119 |
120 | def test_delete_all_nodes(jenkins):
121 | nodes = jenkins.nodes
122 |
123 | for name in nodes.keys():
124 | del nodes[name]
125 |
126 | assert len(jenkins.nodes) == 1
127 |
128 |
129 | def test_get_node_labels(jenkins):
130 | node_name = random_string()
131 | node_labels = "LABEL1 LABEL2"
132 | node_dict = {
133 | "num_executors": 1,
134 | "node_description": "Test Node with Labels",
135 | "remote_fs": "/tmp",
136 | "labels": node_labels,
137 | "exclusive": True,
138 | }
139 | node = jenkins.nodes.create_node(node_name, node_dict)
140 | assert node.get_labels() == node_labels
141 |
142 | del jenkins.nodes[node_name]
143 |
144 |
145 | def test_get_executors(jenkins):
146 | node_name = random_string()
147 | node_labels = "LABEL1 LABEL2"
148 | node_dict = {
149 | "num_executors": 1,
150 | "node_description": "Test Node with Labels",
151 | "remote_fs": "/tmp",
152 | "labels": node_labels,
153 | "exclusive": True,
154 | }
155 | node = jenkins.nodes.create_node(node_name, node_dict)
156 |
157 | with pytest.raises(AttributeError):
158 | assert node.get_config_element("executors") == "1"
159 |
160 | assert node.get_config_element("numExecutors") == "1"
161 |
162 | del jenkins.nodes[node_name]
163 |
164 |
165 | def test_set_executors(jenkins):
166 | node_name = random_string()
167 | node_labels = "LABEL1 LABEL2"
168 | node_dict = {
169 | "num_executors": 1,
170 | "node_description": "Test Node with Labels",
171 | "remote_fs": "/tmp",
172 | "labels": node_labels,
173 | "exclusive": True,
174 | }
175 | node = jenkins.nodes.create_node(node_name, node_dict)
176 |
177 | assert node.set_config_element("numExecutors", "5") is None
178 |
179 | assert node.get_config_element("numExecutors") == "5"
180 |
181 | del jenkins.nodes[node_name]
182 |
183 |
184 | def test_set_master_executors(jenkins):
185 | node = jenkins.nodes["Built-In Node"]
186 |
187 | assert node.get_num_executors() == 2
188 |
189 | node.set_num_executors(5)
190 | assert node.get_num_executors() == 5
191 |
192 | node.set_num_executors(2)
193 |
194 |
195 | def test_offline_reason(jenkins):
196 | node_name = random_string()
197 | node_labels = "LABEL1 LABEL2"
198 | node_dict = {
199 | "num_executors": 1,
200 | "node_description": "Test Node with Labels",
201 | "remote_fs": "/tmp",
202 | "labels": node_labels,
203 | "exclusive": True,
204 | }
205 | node = jenkins.nodes.create_node(node_name, node_dict)
206 |
207 | node.toggle_temporarily_offline("test1")
208 | node.poll()
209 | assert node.offline_reason() == "test1"
210 |
211 | node.update_offline_reason("test2")
212 | node.poll()
213 | assert node.offline_reason() == "test2"
214 |
215 | del jenkins.nodes[node_name]
216 |
--------------------------------------------------------------------------------
/jenkinsapi_tests/systests/test_parameterized_builds.py:
--------------------------------------------------------------------------------
1 | """
2 | System tests for `jenkinsapi.jenkins` module.
3 | """
4 |
5 | import io
6 | import time
7 |
8 | from jenkinsapi_tests.test_utils.random_strings import random_string
9 | from jenkinsapi_tests.systests.job_configs import JOB_WITH_FILE
10 | from jenkinsapi_tests.systests.job_configs import JOB_WITH_FILE_AND_PARAMS
11 | from jenkinsapi_tests.systests.job_configs import JOB_WITH_PARAMETERS
12 |
13 |
14 | def test_invoke_job_with_file(jenkins):
15 | file_data = random_string()
16 | param_file = io.BytesIO(file_data.encode("utf-8"))
17 |
18 | job_name = "create1_%s" % random_string()
19 | job = jenkins.create_job(job_name, JOB_WITH_FILE)
20 |
21 | assert job.has_params() is True
22 | assert len(job.get_params_list()) != 0
23 |
24 | job.invoke(block=True, files={"file.txt": param_file})
25 |
26 | build = job.get_last_build()
27 | while build.is_running():
28 | time.sleep(0.25)
29 |
30 | artifacts = build.get_artifact_dict()
31 | assert isinstance(artifacts, dict) is True
32 | art_file = artifacts["file.txt"]
33 | assert art_file.get_data().decode("utf-8").strip() == file_data
34 |
35 |
36 | def test_invoke_job_parameterized(jenkins):
37 | param_B = random_string()
38 |
39 | job_name = "create2_%s" % random_string()
40 | job = jenkins.create_job(job_name, JOB_WITH_PARAMETERS)
41 | job.invoke(block=True, build_params={"B": param_B})
42 | build = job.get_last_build()
43 |
44 | artifacts = build.get_artifact_dict()
45 | artB = artifacts["b.txt"]
46 | assert artB.get_data().decode("UTF-8", "replace").strip() == param_B
47 |
48 | assert param_B in build.get_console()
49 |
50 |
51 | def test_parameterized_job_build_queuing(jenkins):
52 | """
53 | Accept multiple builds of parameterized jobs with unique parameters.
54 | """
55 | job_name = "create_%s" % random_string()
56 | job = jenkins.create_job(job_name, JOB_WITH_PARAMETERS)
57 |
58 | # Latest Jenkins schedules builds to run right away, so remove all
59 | # executors from master node to investigate queue
60 | master = jenkins.nodes["Built-In Node"]
61 | num_executors = master.get_num_executors()
62 | master.set_num_executors(0)
63 |
64 | for i in range(3):
65 | param_B = random_string()
66 | params = {"B": param_B}
67 | job.invoke(build_params=params)
68 |
69 | assert job.has_queued_build(params) is True
70 |
71 | master.set_num_executors(num_executors)
72 |
73 | while job.has_queued_build(params):
74 | time.sleep(0.25)
75 |
76 | build = job.get_last_build()
77 | while build.is_running():
78 | time.sleep(0.25)
79 |
80 | artifacts = build.get_artifact_dict()
81 | assert isinstance(artifacts, dict) is True
82 | artB = artifacts["b.txt"]
83 | assert artB.get_data().decode("utf-8").strip() == param_B
84 |
85 | assert param_B in build.get_console()
86 |
87 |
88 | def test_parameterized_multiple_builds_get_the_same_queue_item(jenkins):
89 | """
90 | Multiple attempts to run the same parameterized
91 | build will get the same queue item.
92 | """
93 | job_name = "create_%s" % random_string()
94 | job = jenkins.create_job(job_name, JOB_WITH_PARAMETERS)
95 |
96 | # Latest Jenkins schedules builds to run right away, so remove all
97 | # executors from master node to investigate queue
98 | master = jenkins.nodes["Built-In Node"]
99 | num_executors = master.get_num_executors()
100 | master.set_num_executors(0)
101 |
102 | for i in range(3):
103 | params = {"B": random_string()}
104 | qq0 = job.invoke(build_params=params)
105 |
106 | qq1 = job.invoke(build_params=params)
107 | assert qq0 == qq1
108 |
109 | master.set_num_executors(num_executors)
110 |
111 |
112 | def test_invoke_job_with_file_and_params(jenkins):
113 | file_data = random_string()
114 | param_data = random_string()
115 | param_file = io.BytesIO(file_data.encode("utf-8"))
116 |
117 | job_name = "create_%s" % random_string()
118 | job = jenkins.create_job(job_name, JOB_WITH_FILE_AND_PARAMS)
119 |
120 | assert job.has_params() is True
121 | assert len(job.get_params_list()) != 0
122 |
123 | qi = job.invoke(
124 | block=True,
125 | files={"file.txt": param_file},
126 | build_params={"B": param_data},
127 | )
128 |
129 | build = qi.get_build()
130 | artifacts = build.get_artifact_dict()
131 | assert isinstance(artifacts, dict) is True
132 | art_file = artifacts["file.txt"]
133 | assert art_file.get_data().decode("utf-8").strip() == file_data
134 | art_param = artifacts["file1.txt"]
135 | assert art_param.get_data().decode("utf-8").strip() == param_data
136 |
--------------------------------------------------------------------------------
/jenkinsapi_tests/systests/test_plugins.py:
--------------------------------------------------------------------------------
1 | """
2 | System tests for `jenkinsapi.plugins` module.
3 | """
4 |
5 | import logging
6 | import pytest
7 | from jenkinsapi_tests.test_utils.random_strings import random_string
8 | from jenkinsapi.plugin import Plugin
9 |
10 | log = logging.getLogger(__name__)
11 |
12 |
13 | def test_plugin_data(jenkins):
14 | # It takes time to get plugins json from remote
15 | timeout = jenkins.requester.timeout
16 | jenkins.requester.timeout = 60
17 | jenkins.plugins.check_updates_server()
18 | jenkins.requester.timeout = timeout
19 |
20 | assert "workflow-api" in jenkins.plugins
21 |
22 |
23 | def test_get_missing_plugin(jenkins):
24 | plugins = jenkins.get_plugins()
25 | with pytest.raises(KeyError):
26 | plugins["lsdajdaslkjdlkasj"] # this plugin surely does not exist!
27 |
28 |
29 | def test_get_single_plugin(jenkins):
30 | plugins = jenkins.get_plugins()
31 | plugin_name, plugin = next(plugins.iteritems())
32 |
33 | assert isinstance(plugin_name, str)
34 | assert isinstance(plugin, Plugin)
35 |
36 |
37 | def test_get_single_plugin_depth_2(jenkins):
38 | plugins = jenkins.get_plugins(depth=2)
39 | _, plugin = next(plugins.iteritems())
40 |
41 | assert isinstance(plugin, Plugin)
42 |
43 |
44 | def test_delete_inexistant_plugin(jenkins):
45 | with pytest.raises(KeyError):
46 | del jenkins.plugins[random_string()]
47 |
48 |
49 | # def test_install_uninstall_plugin(jenkins):
50 | # plugin_name = "suppress-stack-trace"
51 | #
52 | # plugin_dict = {
53 | # "shortName": plugin_name,
54 | # "version": "latest",
55 | # }
56 | # jenkins.plugins[plugin_name] = Plugin(plugin_dict)
57 | #
58 | # assert plugin_name in jenkins.plugins
59 | #
60 | # plugin = jenkins.get_plugins()[plugin_name]
61 | # assert isinstance(plugin, Plugin)
62 | # assert plugin.shortName == plugin_name
63 | #
64 | # del jenkins.plugins[plugin_name]
65 | # assert jenkins.plugins[plugin_name].deleted
66 | #
67 | #
68 | # def test_install_multiple_plugins(jenkins):
69 | # plugin_one_name = "keyboard-shortcuts-plugin"
70 | # plugin_one_version = "latest"
71 | # plugin_one = "@".join((plugin_one_name, plugin_one_version))
72 | # plugin_two = Plugin(
73 | # {"shortName": "emotional-jenkins-plugin", "version": "latest"}
74 | # )
75 | #
76 | # assert isinstance(plugin_two, Plugin)
77 | #
78 | # plugin_list = [plugin_one, plugin_two]
79 | #
80 | # jenkins.install_plugins(plugin_list)
81 | #
82 | # assert plugin_one_name in jenkins.plugins
83 | # assert plugin_two.shortName in jenkins.plugins
84 | #
85 | # del jenkins.plugins["keyboard-shortcuts-plugin"]
86 | # del jenkins.plugins["emotional-jenkins-plugin"]
87 | #
88 | #
89 | # def test_downgrade_plugin(jenkins):
90 | # plugin_name = "console-badge"
91 | # plugin_version = "latest"
92 | # plugin = Plugin({"shortName": plugin_name, "version": plugin_version})
93 | #
94 | # assert isinstance(plugin, Plugin)
95 | #
96 | # # Need to restart when not installing the latest version
97 | # jenkins.install_plugins([plugin])
98 | #
99 | # installed_plugin = jenkins.plugins[plugin_name]
100 | #
101 | # assert installed_plugin.version == "1.1"
102 | #
103 | # older_plugin = Plugin({"shortName": plugin_name, "version": "1.0"})
104 | # jenkins.install_plugins(
105 | # [older_plugin], restart=True, wait_for_reboot=True)
106 | # installed_older_plugin = jenkins.plugins[plugin_name]
107 | #
108 | # assert installed_older_plugin.version == "1.0"
109 | #
110 | # del jenkins.plugins[plugin_name]
111 |
--------------------------------------------------------------------------------
/jenkinsapi_tests/systests/test_queue.py:
--------------------------------------------------------------------------------
1 | """
2 | All kinds of testing on Jenkins Queues
3 | """
4 |
5 | import time
6 | import logging
7 | import pytest
8 | from jenkinsapi.queue import Queue
9 | from jenkinsapi.queue import QueueItem
10 | from jenkinsapi.job import Job
11 | from jenkinsapi_tests.test_utils.random_strings import random_string
12 | from jenkinsapi_tests.systests.job_configs import LONG_RUNNING_JOB
13 |
14 | log = logging.getLogger(__name__)
15 |
16 |
17 | @pytest.fixture(scope="function")
18 | def no_executors(jenkins, request):
19 | master = jenkins.nodes["Built-In Node"]
20 | num_executors = master.get_num_executors()
21 | master.set_num_executors(0)
22 |
23 | def restore():
24 | master.set_num_executors(num_executors)
25 |
26 | request.addfinalizer(restore)
27 |
28 | return num_executors
29 |
30 |
31 | def test_get_queue(jenkins):
32 | qq = jenkins.get_queue()
33 | assert isinstance(qq, Queue) is True
34 |
35 |
36 | def test_invoke_many_jobs(jenkins, no_executors):
37 | job_names = [random_string() for _ in range(5)]
38 | jobs = []
39 |
40 | while len(jenkins.get_queue()) != 0:
41 | log.info("Sleeping to get queue empty...")
42 | time.sleep(1)
43 |
44 | for job_name in job_names:
45 | j = jenkins.create_job(job_name, LONG_RUNNING_JOB)
46 | jobs.append(j)
47 | j.invoke()
48 |
49 | assert j.is_queued_or_running() is True
50 |
51 | queue = jenkins.get_queue()
52 |
53 | reprString = repr(queue)
54 | assert queue.baseurl in reprString
55 | assert len(queue) == 5, queue.keys()
56 | assert isinstance(queue[queue.keys()[0]].get_job(), Job) is True
57 | items = queue.get_queue_items_for_job(job_names[2])
58 | assert isinstance(items, list) is True
59 | assert len(items) == 1
60 | assert isinstance(items[0], QueueItem) is True
61 | assert items[0].get_parameters() == []
62 |
63 | for _, item in queue.iteritems():
64 | queue.delete_item(item)
65 |
66 | queue.poll()
67 |
68 | assert len(queue) == 0
69 |
70 |
71 | def test_start_and_stop_long_running_job(jenkins):
72 | job_name = random_string()
73 | j = jenkins.create_job(job_name, LONG_RUNNING_JOB)
74 | j.invoke()
75 | time.sleep(1)
76 | assert j.is_queued_or_running() is True
77 |
78 | while j.is_queued():
79 | time.sleep(0.5)
80 |
81 | if j.is_running():
82 | time.sleep(1)
83 |
84 | j.get_first_build().stop()
85 | time.sleep(1)
86 | assert j.is_queued_or_running() is False
87 |
88 |
89 | def test_queueitem_for_why_field(jenkins, no_executors):
90 | job_names = [random_string() for _ in range(2)]
91 |
92 | jobs = []
93 | for job_name in job_names:
94 | j = jenkins.create_job(job_name, LONG_RUNNING_JOB)
95 | jobs.append(j)
96 | j.invoke()
97 |
98 | queue = jenkins.get_queue()
99 | for _, item in queue.iteritems():
100 | assert isinstance(item.why, str) is True
101 |
102 | # Clean up after ourselves
103 | for _, item in queue.iteritems():
104 | queue.delete_item(item)
105 |
106 |
107 | def test_queueitem_from_job(jenkins, no_executors):
108 | job_name = random_string()
109 | j = jenkins.create_job(job_name, LONG_RUNNING_JOB)
110 | j.invoke()
111 |
112 | qi = j.get_queue_item()
113 | assert isinstance(qi, QueueItem)
114 | assert qi.get_job() == j
115 | assert qi.get_job_name() == job_name
116 | assert qi.name == job_name
117 | assert qi.is_queued()
118 | assert not qi.is_running()
119 |
--------------------------------------------------------------------------------
/jenkinsapi_tests/systests/test_quiet_down.py:
--------------------------------------------------------------------------------
1 | """
2 | System tests for setting jenkins in quietDown mode
3 | """
4 |
5 | import logging
6 |
7 |
8 | log = logging.getLogger(__name__)
9 |
10 |
11 | def test_quiet_down_and_cancel_quiet_down(jenkins):
12 | jenkins.poll() # jenkins should be alive
13 |
14 | jenkins.quiet_down() # put Jenkins in quietDown mode
15 | # is_quieting_down = jenkins.is_quieting_down
16 | assert jenkins.is_quieting_down is True
17 |
18 | jenkins.poll() # jenkins should be alive
19 |
20 | jenkins.cancel_quiet_down() # leave quietDown mode
21 |
22 | # is_quieting_down = jenkins_api['quietingDown']
23 | assert jenkins.is_quieting_down is False
24 |
25 | jenkins.poll() # jenkins should be alive
26 |
--------------------------------------------------------------------------------
/jenkinsapi_tests/systests/test_restart.py:
--------------------------------------------------------------------------------
1 | """
2 | System tests for restarting jenkins
3 |
4 | NB: this test will be very time consuming because
5 | after restart it will wait for jenkins to boot
6 | """
7 |
8 | import time
9 | import logging
10 | import pytest
11 | from requests import HTTPError, ConnectionError
12 |
13 | log = logging.getLogger(__name__)
14 |
15 |
16 | def wait_for_restart(jenkins):
17 | wait = 15
18 | count = 0
19 | max_count = 30
20 | success = False
21 | msg = (
22 | "Jenkins has not restarted yet! (This is try %s of %s, "
23 | "waited %s seconds so far) "
24 | "Sleeping %s seconds and trying again..."
25 | )
26 |
27 | while count < max_count or not success:
28 | time.sleep(wait)
29 | try:
30 | jenkins.poll()
31 | log.info("Jenkins restarted successfully.")
32 | success = True
33 | break
34 | except HTTPError as ex:
35 | log.info(ex)
36 | except ConnectionError as ex:
37 | log.info(ex)
38 |
39 | log.info(msg, count + 1, max_count, count * wait, wait)
40 | count += 1
41 |
42 | if not success:
43 | msg = (
44 | "Jenkins did not come back from safe restart! "
45 | "Waited {0} seconds altogether. This "
46 | "failure may cause other failures."
47 | )
48 | log.critical(msg.format(count * wait))
49 | pytest.fail(msg)
50 |
51 |
52 | def test_safe_restart_wait(jenkins):
53 | jenkins.poll() # jenkins should be alive
54 | jenkins.safe_restart() # restart and wait for reboot (default)
55 | jenkins.poll() # jenkins should be alive again
56 |
57 |
58 | def test_safe_restart_dont_wait(jenkins):
59 | jenkins.poll() # jenkins should be alive
60 | jenkins.safe_restart(wait_for_reboot=False)
61 | # Jenkins sleeps for 10 seconds before actually restarting
62 | time.sleep(11)
63 | with pytest.raises((HTTPError, ConnectionError)):
64 | # this is a 503: jenkins is still restarting
65 | jenkins.poll()
66 | # the test is now complete, but other tests cannot run until
67 | # jenkins has finished restarted. to avoid cascading failure
68 | # we have to wait for reboot to finish.
69 | wait_for_restart(jenkins)
70 |
--------------------------------------------------------------------------------
/jenkinsapi_tests/systests/test_safe_exit.py:
--------------------------------------------------------------------------------
1 | """
2 | System tests for `jenkinsapi.jenkins` module.
3 | """
4 |
5 | import time
6 | import logging
7 | from jenkinsapi.build import Build
8 | from jenkinsapi_tests.test_utils.random_strings import random_string
9 | from jenkinsapi_tests.systests.job_configs import LONG_RUNNING_JOB
10 |
11 |
12 | log = logging.getLogger(__name__)
13 |
14 |
15 | def test_safe_exit(jenkins):
16 | job_name = "Bcreate_%s" % random_string()
17 | job = jenkins.create_job(job_name, LONG_RUNNING_JOB)
18 | qq = job.invoke()
19 | time.sleep(3)
20 | bn = qq.block_until_building(delay=3).get_number()
21 | assert isinstance(bn, int)
22 |
23 | build = qq.get_build()
24 | assert isinstance(build, Build)
25 | assert build.is_running()
26 |
27 | # A job is now running and safe_exit should await running jobs
28 | # Call, but wait only for 5 seconds then cancel exit
29 | jenkins.safe_exit(wait_for_exit=False)
30 | time.sleep(5)
31 |
32 | jenkins.cancel_quiet_down() # leave quietDown mode
33 | assert jenkins.is_quieting_down is False
34 |
35 | build.stop()
36 | # if we call next line right away - Jenkins have no time to stop job
37 | # so we wait a bit
38 | while build.is_running():
39 | time.sleep(0.5)
40 |
41 | console = build.get_console()
42 | assert isinstance(console, str)
43 | assert "Started by user" in console
44 |
--------------------------------------------------------------------------------
/jenkinsapi_tests/systests/test_scm.py:
--------------------------------------------------------------------------------
1 | # '''
2 | # System tests for `jenkinsapi.jenkins` module.
3 | # '''
4 | # To run unittests on python 2.6 please use unittest2 library
5 | # try:
6 | # import unittest2 as unittest
7 | # except ImportError:
8 | # import unittest
9 | # from jenkinsapi_tests.systests.base import BaseSystemTest
10 | # from jenkinsapi_tests.test_utils.random_strings import random_string
11 | # from jenkinsapi_tests.systests.job_configs import SCM_GIT_JOB
12 |
13 | # # Maybe have a base class for all SCM test activites?
14 | # class TestSCMGit(BaseSystemTest):
15 | # # Maybe it makes sense to move plugin dependencies outside the code.
16 | # # Have a config to dependencies mapping from the launcher can use
17 | # # to install plugins.
18 | # def test_get_revision(self):
19 | # job_name = 'git_%s' % random_string()
20 | # job = self.jenkins.create_job(job_name, SCM_GIT_JOB)
21 | # ii = job.invoke()
22 | # ii.block(until='completed')
23 | # self.assertFalse(ii.is_running())
24 | # b = ii.get_build()
25 | # try:
26 | # self.assertIsInstance(b.get_revision(), basestring)
27 | # except NameError:
28 | # # Python3
29 | # self.assertIsInstance(b.get_revision(), str)
30 |
31 | # if __name__ == '__main__':
32 | # unittest.main()
33 |
--------------------------------------------------------------------------------
/jenkinsapi_tests/systests/test_views.py:
--------------------------------------------------------------------------------
1 | """
2 | System tests for `jenkinsapi.jenkins` module.
3 | """
4 |
5 | import logging
6 | from jenkinsapi.view import View
7 | from jenkinsapi.views import Views
8 | from jenkinsapi.job import Job
9 | from jenkinsapi.api import get_view_from_url
10 | from jenkinsapi_tests.systests.job_configs import EMPTY_JOB
11 | from jenkinsapi_tests.systests.view_configs import VIEW_WITH_FILTER_AND_REGEX
12 | from jenkinsapi_tests.test_utils.random_strings import random_string
13 |
14 | log = logging.getLogger(__name__)
15 |
16 |
17 | def create_job(jenkins, job_name="whatever"):
18 | job = jenkins.create_job(job_name, EMPTY_JOB)
19 | return job
20 |
21 |
22 | def test_make_views(jenkins):
23 | view_name = random_string()
24 | assert view_name not in jenkins.views
25 | new_view = jenkins.views.create(view_name)
26 | assert view_name in jenkins.views
27 | assert isinstance(new_view, View) is True
28 | assert view_name == str(new_view)
29 |
30 | # Can we create a view that already exists?
31 | existing = jenkins.views.create(view_name)
32 | assert existing == new_view
33 |
34 | # Can we use the API convenience methods
35 | new_view_1 = get_view_from_url(new_view.baseurl)
36 | assert new_view == new_view_1
37 |
38 | del jenkins.views[view_name]
39 |
40 |
41 | def test_add_job_to_view(jenkins):
42 | job_name = random_string()
43 | create_job(jenkins, job_name)
44 |
45 | view_name = random_string()
46 | assert view_name not in jenkins.views
47 | new_view = jenkins.views.create(view_name)
48 | assert view_name in jenkins.views
49 | assert isinstance(new_view, View) is True
50 |
51 | assert job_name not in new_view
52 | assert new_view.add_job(job_name) is True
53 | assert job_name in new_view
54 | assert isinstance(new_view[job_name], Job) is True
55 |
56 | assert len(new_view) == 1
57 | for j_name, j in new_view.iteritems():
58 | assert j_name == job_name
59 | assert isinstance(j, Job) is True
60 |
61 | for j in new_view.values():
62 | assert isinstance(j, Job) is True
63 |
64 | jobs = new_view.items()
65 | assert isinstance(jobs, list) is True
66 | assert isinstance(jobs[0], tuple) is True
67 |
68 | assert new_view.add_job(job_name) is False
69 | assert new_view.add_job("unknown") is False
70 |
71 | del jenkins.views[view_name]
72 |
73 |
74 | def test_create_and_delete_views(jenkins):
75 | view1_name = random_string()
76 | new_view = jenkins.views.create(view1_name)
77 | assert isinstance(new_view, View) is True
78 | assert view1_name in jenkins.views
79 | del jenkins.views[view1_name]
80 | assert view1_name not in jenkins.views
81 |
82 |
83 | def test_create_and_delete_views_by_url(jenkins):
84 | view1_name = random_string()
85 | new_view = jenkins.views.create(view1_name)
86 | assert isinstance(new_view, View) is True
87 | assert view1_name in jenkins.views
88 |
89 | view_url = new_view.baseurl
90 | view_by_url = jenkins.get_view_by_url(view_url)
91 | assert isinstance(view_by_url, View) is True
92 | jenkins.delete_view_by_url(view_url)
93 |
94 | assert view1_name not in jenkins.views
95 |
96 |
97 | def test_delete_view_which_does_not_exist(jenkins):
98 | view1_name = random_string()
99 | assert view1_name not in jenkins.views
100 | del jenkins.views[view1_name]
101 |
102 |
103 | def test_update_view_config(jenkins):
104 | view_name = random_string()
105 | new_view = jenkins.views.create(view_name)
106 | assert isinstance(new_view, View) is True
107 | assert view_name in jenkins.views
108 |
109 | config = jenkins.views[view_name].get_config().strip()
110 | new_view_config = VIEW_WITH_FILTER_AND_REGEX % view_name
111 | assert config != new_view_config
112 |
113 | jenkins.views[view_name].update_config(new_view_config)
114 | config = jenkins.views[view_name].get_config().strip()
115 | assert config == new_view_config
116 |
117 |
118 | def test_make_nested_views(jenkins):
119 | job = create_job(jenkins)
120 | top_view_name = random_string()
121 | sub1_view_name = random_string()
122 | sub2_view_name = random_string()
123 |
124 | assert top_view_name not in jenkins.views
125 | tv = jenkins.views.create(top_view_name, Views.NESTED_VIEW)
126 | assert top_view_name in jenkins.views
127 | assert isinstance(tv, View) is True
128 |
129 | # Empty sub view
130 | sv1 = tv.views.create(sub1_view_name)
131 | assert sub1_view_name in tv.views
132 | assert isinstance(sv1, View) is True
133 |
134 | # Sub view with job in it
135 | tv.views[sub2_view_name] = job.name
136 | assert sub2_view_name in tv.views
137 | sv2 = tv.views[sub2_view_name]
138 | assert isinstance(sv2, View) is True
139 | assert job.name in sv2
140 |
141 | # Can we use the API convenience methods
142 | new_view = get_view_from_url(sv2.baseurl)
143 | assert new_view == sv2
144 |
145 |
146 | def test_add_to_view_after_copy(jenkins):
147 | # This test is for issue #291
148 | job = create_job(jenkins)
149 | new_job_name = random_string()
150 | view_name = random_string()
151 | new_view = jenkins.views.create(view_name)
152 | new_view = jenkins.views[view_name]
153 | new_job = jenkins.copy_job(job.name, new_job_name)
154 | assert new_view.add_job(new_job.name) is True
155 | assert new_job.name in new_view
156 |
157 |
158 | def test_get_job_config(jenkins):
159 | # This test is for issue #301
160 | job = create_job(jenkins)
161 | view_name = random_string()
162 | new_view = jenkins.views.create(view_name)
163 |
164 | assert new_view.add_job(job.name) is True
165 |
166 | assert "
7 |
8 | %s
9 | true
10 | true
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | regex
26 | false
27 |
28 | """.strip()
29 |
--------------------------------------------------------------------------------
/jenkinsapi_tests/test_utils/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycontribs/jenkinsapi/ffb35b6dca607b9aa5a65d4e49aa1ca76946adbf/jenkinsapi_tests/test_utils/__init__.py
--------------------------------------------------------------------------------
/jenkinsapi_tests/test_utils/random_strings.py:
--------------------------------------------------------------------------------
1 | import random
2 | import string
3 |
4 |
5 | def random_string(length=10):
6 | return "".join(
7 | random.choice(string.ascii_lowercase) for i in range(length)
8 | )
9 |
10 |
11 | if __name__ == "__main__":
12 | print(random_string())
13 |
--------------------------------------------------------------------------------
/jenkinsapi_tests/unittests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycontribs/jenkinsapi/ffb35b6dca607b9aa5a65d4e49aa1ca76946adbf/jenkinsapi_tests/unittests/__init__.py
--------------------------------------------------------------------------------
/jenkinsapi_tests/unittests/test_build_scm_git.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | from . import configs
3 | from jenkinsapi.build import Build
4 | from jenkinsapi.job import Job
5 |
6 |
7 | @pytest.fixture(scope="function")
8 | def jenkins(mocker):
9 | return mocker.MagicMock()
10 |
11 |
12 | @pytest.fixture(scope="function")
13 | def job(monkeypatch, jenkins):
14 | def fake_poll(cls, tree=None): # pylint: disable=unused-argument
15 | return configs.JOB_DATA
16 |
17 | monkeypatch.setattr(Job, "_poll", fake_poll)
18 |
19 | fake_job = Job("http://", "Fake_Job", jenkins)
20 | return fake_job
21 |
22 |
23 | @pytest.fixture(scope="function")
24 | def build(job, monkeypatch):
25 | def fake_poll(cls, tree=None): # pylint: disable=unused-argument
26 | return configs.BUILD_SCM_DATA
27 |
28 | monkeypatch.setattr(Build, "_poll", fake_poll)
29 |
30 | return Build("http://", 97, job)
31 |
32 |
33 | def test_git_scm(build):
34 | """
35 | Can we extract git build revision data from a build object?
36 | """
37 | assert isinstance(build.get_revision(), str)
38 | assert build.get_revision() == "7def9ed6e92580f37d00e4980c36c4d36e68f702"
39 |
40 |
41 | def test_git_revision_branch(build):
42 | """
43 | Can we extract git build branch from a build object?
44 | """
45 | assert isinstance(build.get_revision_branch(), list)
46 | assert len(build.get_revision_branch()) == 1
47 | assert isinstance(build.get_revision_branch()[0], dict)
48 | assert (
49 | build.get_revision_branch()[0]["SHA1"]
50 | == "7def9ed6e92580f37d00e4980c36c4d36e68f702"
51 | )
52 | assert build.get_revision_branch()[0]["name"] == "origin/unstable"
53 |
54 |
55 | def test_git_repo_url(build):
56 | """
57 | Can we Extract git repo url for a given build
58 | """
59 | assert isinstance(build.get_repo_url(), str)
60 | assert (
61 | build.get_repo_url()
62 | == "https://github.com/salimfadhley/jenkinsapi.git"
63 | )
64 |
--------------------------------------------------------------------------------
/jenkinsapi_tests/unittests/test_fingerprint.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | import hashlib
3 | from jenkinsapi.jenkins import Jenkins
4 | from jenkinsapi.jenkinsbase import JenkinsBase
5 | from jenkinsapi.fingerprint import Fingerprint
6 | from jenkinsapi.utils.requester import Requester
7 | from requests.exceptions import HTTPError
8 |
9 |
10 | @pytest.fixture(scope="function")
11 | def jenkins(monkeypatch):
12 | def fake_poll(cls, tree=None): # pylint: disable=unused-argument
13 | return {}
14 |
15 | monkeypatch.setattr(Jenkins, "_poll", fake_poll)
16 |
17 | return Jenkins(
18 | "http://localhost:8080", username="foouser", password="foopassword"
19 | )
20 |
21 |
22 | @pytest.fixture(scope="module")
23 | def dummy_md5():
24 | md = hashlib.md5()
25 | md.update("some dummy string".encode("ascii"))
26 | return md.hexdigest()
27 |
28 |
29 | def test_object_creation(jenkins, dummy_md5, monkeypatch):
30 | def fake_poll(cls, tree=None): # pylint: disable=unused-argument
31 | return {}
32 |
33 | monkeypatch.setattr(JenkinsBase, "_poll", fake_poll)
34 | fp_instance = Fingerprint("http://foo:8080", dummy_md5, jenkins)
35 |
36 | assert isinstance(fp_instance, Fingerprint)
37 | assert str(fp_instance) == dummy_md5
38 | assert fp_instance.valid()
39 |
40 |
41 | def test_valid_for_404(jenkins, dummy_md5, monkeypatch):
42 | class FakeResponse(object):
43 | status_code = 404
44 | text = "{}"
45 |
46 | class FakeHTTPError(HTTPError):
47 | def __init__(self):
48 | self.response = FakeResponse()
49 |
50 | def fake_poll(cls, tree=None): # pylint: disable=unused-argument
51 | raise FakeHTTPError()
52 |
53 | monkeypatch.setattr(JenkinsBase, "_poll", fake_poll)
54 |
55 | def fake_get_url(
56 | url, # pylint: disable=unused-argument
57 | params=None, # pylint: disable=unused-argument
58 | headers=None, # pylint: disable=unused-argument
59 | allow_redirects=True, # pylint: disable=unused-argument
60 | stream=False,
61 | ): # pylint: disable=unused-argument
62 |
63 | return FakeResponse()
64 |
65 | monkeypatch.setattr(Requester, "get_url", fake_get_url)
66 |
67 | fingerprint = Fingerprint("http://foo:8080", dummy_md5, jenkins)
68 | assert fingerprint.valid() is True
69 |
70 |
71 | def test_invalid_for_401(jenkins, dummy_md5, monkeypatch):
72 | class FakeResponse(object):
73 | status_code = 401
74 | text = "{}"
75 |
76 | class FakeHTTPError(HTTPError):
77 | def __init__(self):
78 | self.response = FakeResponse()
79 |
80 | def fake_poll(cls, tree=None): # pylint: disable=unused-argument
81 | raise FakeHTTPError()
82 |
83 | monkeypatch.setattr(JenkinsBase, "_poll", fake_poll)
84 |
85 | def fake_get_url(
86 | url, # pylint: disable=unused-argument
87 | params=None, # pylint: disable=unused-argument
88 | headers=None, # pylint: disable=unused-argument
89 | allow_redirects=True, # pylint: disable=unused-argument
90 | stream=False,
91 | ): # pylint: disable=unused-argument
92 |
93 | return FakeResponse()
94 |
95 | monkeypatch.setattr(Requester, "get_url", fake_get_url)
96 |
97 | fingerprint = Fingerprint("http://foo:8080", dummy_md5, jenkins)
98 | assert fingerprint.valid() is not True
99 |
--------------------------------------------------------------------------------
/jenkinsapi_tests/unittests/test_label.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | from jenkinsapi.label import Label
3 |
4 |
5 | DATA = {
6 | "actions": [],
7 | "busyExecutors": 0,
8 | "clouds": [],
9 | "description": None,
10 | "idleExecutors": 0,
11 | "loadStatistics": {},
12 | "name": "jenkins-slave",
13 | "nodes": [],
14 | "offline": True,
15 | "tiedJobs": [
16 | {
17 | "name": "test_job1",
18 | "url": "http://jtest:8080/job/test_job1/",
19 | "color": "blue",
20 | },
21 | {
22 | "name": "test_job2",
23 | "url": "http://jtest:8080/job/test_job2/",
24 | "color": "blue",
25 | },
26 | {
27 | "name": "test_job3",
28 | "url": "http://jtest:8080/job/test_job3/",
29 | "color": "blue",
30 | },
31 | {
32 | "name": "test_job4",
33 | "url": "http://jtest:8080/job/test_job4/",
34 | "color": "blue",
35 | },
36 | ],
37 | "totalExecutors": 0,
38 | "propertiesList": [],
39 | }
40 |
41 | DATA_JOB_NAMES = {
42 | "tiedJobs": [
43 | {"name": "test_job1"},
44 | {"name": "test_job2"},
45 | {"name": "test_job3"},
46 | {"name": "test_job4"},
47 | ]
48 | }
49 |
50 | DATA_JOBS = [
51 | {
52 | "url": "http://jtest:8080/job/test_job1/",
53 | "color": "blue",
54 | "name": "test_job1",
55 | },
56 | {
57 | "url": "http://jtest:8080/job/test_job2/",
58 | "color": "blue",
59 | "name": "test_job2",
60 | },
61 | {
62 | "url": "http://jtest:8080/job/test_job3/",
63 | "color": "blue",
64 | "name": "test_job3",
65 | },
66 | {
67 | "url": "http://jtest:8080/job/test_job4/",
68 | "color": "blue",
69 | "name": "test_job4",
70 | },
71 | ]
72 |
73 |
74 | @pytest.fixture(scope="function")
75 | def label(monkeypatch, mocker):
76 | def fake_poll(cls, tree=None): # pylint: disable=unused-argument
77 | return DATA
78 |
79 | monkeypatch.setattr(Label, "_poll", fake_poll)
80 | jenkins = mocker.MagicMock()
81 |
82 | return Label("http://foo:8080", "jenkins-slave", jenkins)
83 |
84 |
85 | def test_repr(label):
86 | # Can we produce a repr string for this object
87 | repr(label)
88 |
89 |
90 | def test_name(label):
91 | with pytest.raises(AttributeError):
92 | label.id()
93 | assert label.labelname == "jenkins-slave"
94 |
95 |
96 | def test_get_tied_job_names(label):
97 | assert label.get_tied_job_names() == DATA_JOBS
98 |
99 |
100 | def test_online(label):
101 | assert label.is_online() is False
102 |
--------------------------------------------------------------------------------
/jenkinsapi_tests/unittests/test_misc.py:
--------------------------------------------------------------------------------
1 | import jenkinsapi
2 |
3 |
4 | def test_jenkinsapi_version():
5 | """Verify that we can get the jenkinsapi version number from the
6 | package's __version__ property.
7 | """
8 | version = jenkinsapi.__version__
9 | # only first two parts must be interger, 1.0.dev5 being a valid version.
10 | parts = [int(x) for x in version.split(".")[0:2]]
11 | for part in parts:
12 | assert part >= 0, "Implausible version number: %r" % version
13 |
--------------------------------------------------------------------------------
/jenkinsapi_tests/unittests/test_node.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | from jenkinsapi.node import Node
3 |
4 |
5 | DATA = {
6 | "actions": [],
7 | "displayName": "bobnit",
8 | "executors": [{}],
9 | "icon": "computer.png",
10 | "idle": True,
11 | "jnlpAgent": False,
12 | "launchSupported": True,
13 | "loadStatistics": {},
14 | "manualLaunchAllowed": True,
15 | "monitorData": {
16 | "hudson.node_monitors.SwapSpaceMonitor": {
17 | "availablePhysicalMemory": 7681417216,
18 | "availableSwapSpace": 12195983360,
19 | "totalPhysicalMemory": 8374497280,
20 | "totalSwapSpace": 12195983360,
21 | },
22 | "hudson.node_monitors.ArchitectureMonitor": "Linux (amd64)",
23 | "hudson.node_monitors.ResponseTimeMonitor": {"average": 64},
24 | "hudson.node_monitors.TemporarySpaceMonitor": {
25 | "path": "/tmp",
26 | "size": 250172776448,
27 | },
28 | "hudson.node_monitors.DiskSpaceMonitor": {
29 | "path": "/home/sal/jenkins",
30 | "size": 170472026112,
31 | },
32 | "hudson.node_monitors.ClockMonitor": {"diff": 6736},
33 | },
34 | "numExecutors": 1,
35 | "offline": False,
36 | "offlineCause": None,
37 | "oneOffExecutors": [],
38 | "temporarilyOffline": False,
39 | }
40 |
41 |
42 | @pytest.fixture(scope="function")
43 | def node(monkeypatch, mocker):
44 | def fake_poll(cls, tree=None): # pylint: disable=unused-argument
45 | return DATA
46 |
47 | monkeypatch.setattr(Node, "_poll", fake_poll)
48 | jenkins = mocker.MagicMock()
49 |
50 | return Node(jenkins, "http://foo:8080", "bobnit", {})
51 |
52 |
53 | def test_repr(node):
54 | # Can we produce a repr string for this object
55 | repr(node)
56 |
57 |
58 | def test_name(node):
59 | with pytest.raises(AttributeError):
60 | node.id()
61 | assert node.name == "bobnit"
62 |
63 |
64 | def test_online(node):
65 | assert node.is_online() is True
66 |
67 |
68 | def test_available_physical_memory(node):
69 | monitor = DATA["monitorData"]["hudson.node_monitors.SwapSpaceMonitor"]
70 | expected_value = monitor["availablePhysicalMemory"]
71 | assert node.get_available_physical_memory() == expected_value
72 |
73 |
74 | def test_available_swap_space(node):
75 | monitor = DATA["monitorData"]["hudson.node_monitors.SwapSpaceMonitor"]
76 | expected_value = monitor["availableSwapSpace"]
77 | assert node.get_available_swap_space() == expected_value
78 |
79 |
80 | def test_total_physical_memory(node):
81 | monitor = DATA["monitorData"]["hudson.node_monitors.SwapSpaceMonitor"]
82 | expected_value = monitor["totalPhysicalMemory"]
83 | assert node.get_total_physical_memory() == expected_value
84 |
85 |
86 | def test_total_swap_space(node):
87 | monitor = DATA["monitorData"]["hudson.node_monitors.SwapSpaceMonitor"]
88 | expected_value = monitor["totalSwapSpace"]
89 | assert node.get_total_swap_space() == expected_value
90 |
91 |
92 | def test_workspace_path(node):
93 | monitor = DATA["monitorData"]["hudson.node_monitors.DiskSpaceMonitor"]
94 | expected_value = monitor["path"]
95 | assert node.get_workspace_path() == expected_value
96 |
97 |
98 | def test_workspace_size(node):
99 | monitor = DATA["monitorData"]["hudson.node_monitors.DiskSpaceMonitor"]
100 | expected_value = monitor["size"]
101 | assert node.get_workspace_size() == expected_value
102 |
103 |
104 | def test_temp_path(node):
105 | monitor = DATA["monitorData"]["hudson.node_monitors.TemporarySpaceMonitor"]
106 | expected_value = monitor["path"]
107 | assert node.get_temp_path() == expected_value
108 |
109 |
110 | def test_temp_size(node):
111 | monitor = DATA["monitorData"]["hudson.node_monitors.TemporarySpaceMonitor"]
112 | expected_value = monitor["size"]
113 | assert node.get_temp_size() == expected_value
114 |
115 |
116 | def test_architecture(node):
117 | expected_value = DATA["monitorData"][
118 | "hudson.node_monitors.ArchitectureMonitor"
119 | ]
120 | assert node.get_architecture() == expected_value
121 |
122 |
123 | def test_response_time(node):
124 | monitor = DATA["monitorData"]["hudson.node_monitors.ResponseTimeMonitor"]
125 | expected_value = monitor["average"]
126 | assert node.get_response_time() == expected_value
127 |
128 |
129 | def test_clock_difference(node):
130 | monitor = DATA["monitorData"]["hudson.node_monitors.ClockMonitor"]
131 | expected_value = monitor["diff"]
132 | assert node.get_clock_difference() == expected_value
133 |
--------------------------------------------------------------------------------
/jenkinsapi_tests/unittests/test_result_set.py:
--------------------------------------------------------------------------------
1 | import mock
2 |
3 | # To run unittests on python 2.6 please use unittest2 library
4 | try:
5 | import unittest2 as unittest
6 | except ImportError:
7 | import unittest
8 |
9 | from jenkinsapi.result_set import ResultSet
10 | from jenkinsapi.result import Result
11 |
12 |
13 | class TestResultSet(unittest.TestCase):
14 |
15 | DATA = {
16 | "duration": 0.0,
17 | "failCount": 2,
18 | "passCount": 0,
19 | "skipCount": 0,
20 | "suites": [
21 | {
22 | "cases": [
23 | {
24 | "age": 1,
25 | "className": ":setup",
31 | "skipped": False,
32 | "status": "FAILED",
33 | "stderr": None,
34 | "stdout": None,
35 | },
36 | {
37 | "age": 1,
38 | "className": "nose.failure.Failure",
39 | "duration": 0.0,
40 | "errorDetails": "No module named mock",
41 | "errorStackTrace": 'Traceback (most recent call last):\n File "/usr/lib/python2.7/unittest/case.py", line 332, in run\n testMethod()\n File "/usr/lib/python2.7/dist-packages/nose/loader.py", line 390, in loadTestsFromName\n addr.filename, addr.module)\n File "/usr/lib/python2.7/dist-packages/nose/importer.py", line 39, in importFromPath\n return self.importFromDir(dir_path, fqname)\n File "/usr/lib/python2.7/dist-packages/nose/importer.py", line 86, in importFromDir\n mod = load_module(part_fqname, fh, filename, desc)\n File "/var/lib/jenkins/jobs/test_jenkinsapi/workspace/jenkinsapi/src/jenkinsapi_tests/unittests/test_build.py", line 1, in \n import mock\nImportError: No module named mock\n', # noqa
42 | "failedSince": 88,
43 | "name": "runTest",
44 | "skipped": False,
45 | "status": "FAILED",
46 | "stderr": None,
47 | "stdout": None,
48 | },
49 | ],
50 | "duration": 0.0,
51 | "id": None,
52 | "name": "nosetests",
53 | "stderr": None,
54 | "stdout": None,
55 | "timestamp": None,
56 | }
57 | ],
58 | "childReports": [
59 | {"child": {"number": 1915, "url": "url1"}, "result": None},
60 | ],
61 | }
62 |
63 | @mock.patch.object(ResultSet, "_poll")
64 | def setUp(self, _poll):
65 | _poll.return_value = self.DATA
66 |
67 | # def __init__(self, url, build ):
68 |
69 | self.b = mock.MagicMock() # Build object
70 | self.b.__str__.return_value = "FooBuild"
71 | self.rs = ResultSet("http://", self.b)
72 |
73 | def testRepr(self):
74 | # Can we produce a repr string for this object
75 | repr(self.rs)
76 |
77 | def testName(self):
78 | with self.assertRaises(AttributeError):
79 | self.rs.id()
80 |
81 | self.assertEqual(self.rs.name, "Test Result for FooBuild")
82 |
83 | def testBuildComponents(self):
84 | self.assertTrue(self.rs.items())
85 | for k, v in self.rs.items():
86 | self.assertIsInstance(k, str)
87 | self.assertIsInstance(v, Result)
88 | self.assertIsInstance(v.identifier(), str)
89 |
90 |
91 | if __name__ == "__main__":
92 | unittest.main()
93 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = ["flit_core >=3.2,<4"]
3 | build-backend = "flit_core.buildapi"
4 |
5 | [project]
6 | name = "jenkinsapi"
7 | version = "0.3.14"
8 | authors = [
9 | {name = "Salim Fadhley", email = "salimfadhley@gmail.com"},
10 | {name = "Aleksey Maksimov", email = "ctpeko3a@gmail.com"},
11 | {name = "Clinton Steiner", email = "clintonsteiner@gmail.com"},
12 | ]
13 | maintainers = [
14 | {name = "Aleksey Maksimov", email = "ctpeko3a@gmail.com"},
15 | {name = "Clinton Steiner", email = "clintonsteiner@gmail.com"},
16 | ]
17 | description = "A Python API for accessing resources on a Jenkins continuous-integration server."
18 | readme = "README.rst"
19 | license = {text = "MIT license"}
20 | classifiers = [
21 | "Development Status :: 5 - Production/Stable",
22 | "Environment :: Console",
23 | "Intended Audience :: Developers",
24 | "Intended Audience :: Information Technology",
25 | "Intended Audience :: System Administrators",
26 | "License :: OSI Approved :: MIT License",
27 | "Natural Language :: English",
28 | "Operating System :: OS Independent",
29 | "Programming Language :: Python",
30 | "Programming Language :: Python :: 3",
31 | "Programming Language :: Python :: 3.8",
32 | "Programming Language :: Python :: 3.9",
33 | "Programming Language :: Python :: 3.10",
34 | "Programming Language :: Python :: 3.11",
35 | "Programming Language :: Python :: 3.12",
36 | "Programming Language :: Python :: 3.13",
37 | "Topic :: Software Development :: Testing",
38 | "Topic :: Utilities",
39 | ]
40 | requires-python = ">=3.8"
41 | dependencies = [
42 | "pytz>=2014.4",
43 | "requests>=2.3.0",
44 | ]
45 |
46 | [tool.setuptools]
47 | packages = ["jenkinsapi", "jenkinsapi_utils", "jenkinsapi_tests"]
48 | include-package-data = false
49 |
50 | [tool.pbr]
51 | warnerrors = "True"
52 |
53 | [project.scripts]
54 | jenkins_invoke = "jenkinsapi.command_line.jenkins_invoke:main"
55 | jenkinsapi_version = "jenkinsapi.command_line.jenkinsapi_version:main"
56 |
57 | [tool.build_sphinx]
58 | source-dir = "doc/source"
59 | build-dir = "doc/build"
60 | all_files = "1"
61 |
62 | [tool.upload_sphinx]
63 | upload-dir = "doc/build/html"
64 |
65 | [tool.distutils.bdist_wheel]
66 | universal = 1
67 |
68 | [tool.pycodestyle]
69 | exclude = ".tox,doc/source/conf.py,build,.venv,.eggs"
70 | max-line-length = "99"
71 |
72 | [dependency-groups]
73 | dev = [
74 | "pytest-mock>=3.14.0",
75 | "pytest>=8.3.4",
76 | "pytest-cov>=4.0.0",
77 | "pycodestyle>=2.3.1",
78 | "astroid>=1.4.8",
79 | "pylint>=1.7.1",
80 | "tox>=2.3.1",
81 | "mock>=5.1.0",
82 | "codecov>=2.1.13",
83 | "requests-kerberos>=0.15.0",
84 | "ruff>=0.9.6",
85 | ]
86 | docs = [
87 | "docutils>=0.20.1",
88 | "furo>=2024.8.6",
89 | "myst-parser>=3.0.1",
90 | "pygments>=2.19.1",
91 | "sphinx>=7.1.2",
92 | ]
93 |
94 | [tool.ruff]
95 | line-length = 79
96 |
97 | [tool.ruff.lint]
98 | select = ["E9", "F63", "F7", "F82"] # Equivalent to flake8’s default rules
99 | ignore = ["F821"] #, "W503", "W504"
100 |
--------------------------------------------------------------------------------