Skip to content

Commit

Permalink
support to manage domain for credential (#91)
Browse files Browse the repository at this point in the history
* add domain class
  • Loading branch information
joelee2012 authored Aug 30, 2023
1 parent d7ccaf7 commit 1cac13f
Show file tree
Hide file tree
Showing 18 changed files with 181 additions and 71 deletions.
10 changes: 10 additions & 0 deletions .readthedocs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
version: 2

build:
os: "ubuntu-22.04"
tools:
python: "3.11"

formats:
- pdf
- epub
10 changes: 7 additions & 3 deletions HISTORY.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
Release History
===============
1.15.0 (2023-08-30)
-------------------
- Support to manage domains for credential

1.14.1 (2023-07-24)
-------------------
- Support additional session headers
- Support additional session headers

1.14 (2022-11-28)
-----------------
Expand Down Expand Up @@ -31,7 +35,7 @@ Release History
- set dependency version

1.9.1 (2022-03-29)
-----------------
------------------
- change OrganizationFolder to inherit from WorkflowMultiBranchProject
- `Jenkins.get_job` return consistent result

Expand Down Expand Up @@ -59,7 +63,7 @@ Release History
- bugfix for `queue.get_build`

1.5.1 (2021-05-11)
-----------------
------------------
- Bugfix for nodes.iter_builds

1.5 (2021-04-29)
Expand Down
5 changes: 2 additions & 3 deletions api4jenkins/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,14 +284,13 @@ def plugins(self):
@property
def version(self):
'''Version of Jenkins'''
return self.handle_req('GET', '').headers['X-Jenkins']
return self.handle_req('HEAD', '').headers['X-Jenkins']

@property
def credentials(self):
'''An object for managing credentials.
see :class:`Credentials <api4jenkins.credential.Credentials>`'''
return Credentials(self,
f'{self.url}credentials/store/system/domain/_/')
return Credentials(self, f'{self.url}credentials/store/system/')

@property
def views(self):
Expand Down
2 changes: 1 addition & 1 deletion api4jenkins/__version__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# encoding: utf-8
__version__ = '1.14.1'
__version__ = '1.15.0'
__title__ = 'api4jenkins'
__description__ = 'Jenkins Python Client'
__url__ = 'https://github.com/joelee2012/api4jenkins'
Expand Down
33 changes: 29 additions & 4 deletions api4jenkins/credential.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,34 @@

class Credentials(Item):

def get(self, name):
for key in self.api_json(tree='domains[urlName]')['domains'].keys():
if key == name:
return Domain(self.jenkins, f'{self.url}domain/{key}/')
return None

def create(self, xml):
self.handle_req('POST', 'createDomain',
headers=self.headers, data=xml)

def __iter__(self):
for key in self.api_json(tree='domains[urlName]')['domains'].keys():
yield Domain(self.jenkins, f'{self.url}domain/{key}/')

def __getitem__(self, name):
return self.get(name)

@property
def global_domain(self):
return self['_']


class Domain(Item, ConfigurationMixIn, DeletionMixIn):

def get(self, id):
for item in self.api_json(tree='credentials[id]')['credentials']:
if item['id'] == id:
return Credential(self.jenkins,
f'{self.url}credential/{id}/')
return Credential(self.jenkins, f'{self.url}credential/{id}/')
return None

def create(self, xml):
Expand All @@ -20,8 +43,10 @@ def create(self, xml):

def __iter__(self):
for item in self.api_json(tree='credentials[id]')['credentials']:
yield Credential(self.jenkins,
f'{self.url}credential/{item["id"]}/')
yield Credential(self.jenkins, f'{self.url}credential/{item["id"]}/')

def __getitem__(self, id):
return self.get(id)


class Credential(Item, ConfigurationMixIn, DeletionMixIn):
Expand Down
3 changes: 1 addition & 2 deletions api4jenkins/job.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,7 @@ def views(self):

@property
def credentials(self):
return Credentials(self.jenkins,
f'{self.url}credentials/store/folder/domain/_/')
return Credentials(self.jenkins, f'{self.url}credentials/store/folder/')

def __iter__(self):
yield from self.iter()
Expand Down
4 changes: 2 additions & 2 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@

sys.path.insert(0, os.path.abspath('../../'))
# -- Project information -----------------------------------------------------
import api4jenkins

project = 'api4jenkins'
copyright = '2021, Joe Lee'
copyright = '2023, Joe Lee'
author = 'Joe Lee'

import api4jenkins
# The full version, including alpha/beta/rc tags
release = api4jenkins.__version__

Expand Down
12 changes: 7 additions & 5 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ Jenkins Python Client



`Python3 <https://www.python.org/>`_ client library for `Jenkins API <https://wiki.jenkins.io/display/JENKINS/Remote+access+API>`_.
`Python3 <https://www.python.org/>`_ client library for
`Jenkins API <https://wiki.jenkins.io/display/JENKINS/Remote+access+API>`_.


Features
Expand All @@ -36,7 +37,8 @@ Features
Quick start
----------------------------------------

Here is an example to create and build job, then monitor progressive output until it's done.
Here is an example to create and build job, then monitor progressive output
until it's done.


>>> from api4jenkins import Jenkins
Expand Down Expand Up @@ -86,9 +88,9 @@ Here is an example to create and build job, then monitor progressive output unti
:maxdepth: 2
:caption: Contents:

user/install.rst
user/example.rst
user/api.rst
user/install
user/example
user/api


Indices and tables
Expand Down
5 changes: 0 additions & 5 deletions docs/source/user/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,6 @@ API reference
:undoc-members:
:inherited-members:

.. automodule:: api4jenkins.utils
:members:
:undoc-members:
:inherited-members:

.. automodule:: api4jenkins.requester
:members:
:undoc-members:
Expand Down
66 changes: 37 additions & 29 deletions docs/source/user/example.rst
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,7 @@ Call `j.dynamic_attrs` to get the dynamic attributes of an Item::
['_class', 'mode', 'node_description', 'node_name', 'num_executors', 'description', 'quieting_down', 'slave_agent_port', 'use_crumbs', 'use_security']

With Jenkins object you can manage many Items including: `Job`_, `Credential`_,
`Node`_, `View`_, `Queue`_, `Plugin`_, `System`_ and so on. let's start with
`Job`_ management.
`Node`_, `View`_, `Queue`_, `Plugin`_, `System`_ and so on. let's start with `Job`_ management.

create job with `j.create_job()`::

Expand Down Expand Up @@ -521,8 +520,8 @@ and abort input::

>>> build.get_pending_input().abort()

WorkflowRun supports `archive artfacts <https://www.jenkins.io/doc/pipeline/steps/core/#archiveartifacts-archive-the-artifacts>`_,
you can also process with api4jenkins::
`WorkflowRun` supports `archive artfacts <https://www.jenkins.io/doc/pipeline/steps/core/#archiveartifacts-archive-the-artifacts>`_,
you can also process with api4jenkins

save file you interest::

Expand All @@ -537,33 +536,38 @@ save artifacts as zip::

Credential
-------------
Credential is for saving secret data, `api4jenkins` support to manage system
and folder based credentials, all credentials must be in default domain(_).
more detail can be found: `using credentials <https://www.jenkins.io/doc/book/using/using-credentials/>`_
and `credentials plugin user.doc <https://github.com/jenkinsci/credentials-plugin/blob/master/docs/user.adoc>`_
Credential is for saving secret data, `api4jenkins` support to manage :class:`Jenkins <api4jenkins.Jenkins.credentials>`
and :class:`Folder <api4jenkins.job.Folder.credentials>` based domains and credentials.

create/get domain::

>>> xml = '''<com.cloudbees.plugins.credentials.domains.Domain>
... <name>testing</name>
... <description>Credentials for use against the *.test.example.com hosts</description>
... <specifications>
... <com.cloudbees.plugins.credentials.domains.HostnameSpecification>
... <includes>*.test.example.com</includes>
... <excludes></excludes>
... </com.cloudbees.plugins.credentials.domains.HostnameSpecification>
... </specifications>
... </com.cloudbees.plugins.credentials.domains.Domain>'''
>>> folder.credentials.create(xml)
>>> domain = folder.credentials.get('testing')

create/get folder based credential::
.. note::

>>> xml = '''<com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl>
... <id>user-id</id>
... <username>user-name</username>
... <password>user-password</password>
... <description>user id for testing</description>
... </com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl>'''
>>> folder.credentials.create(xml)
>>> credential = folder.credentials.get('user-id')
:class:`global_domain <api4jenkins.credential.Credentials.global_domain>` is shortcut of domain (_)

create system based credential::
create/get credential in domain::

>>> xml = '''<com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl>
... <scope>GLOBAL</scope>
... <id>user-id</id>
... <username>user-name</username>
... <password>user-password</password>
... <description>user id for testing</description>
... </com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl>'''
>>> j.credentials.create(xml)
>>> credential = j.credentials.get('user-id')
>>> domain.create(xml)
>>> credential = domain.get('user-id')

get/update configuration of credential::

Expand All @@ -576,16 +580,20 @@ delete credential::
>>> credential.exists()
False

iterate folder credentials::
iterate domain in `Folder` or `Jenkins`::

>>> for c in folder.credentials:
... print(c)
>>> for domain in folder:
... print(domain)

iterate system credentials::
iterate credentials in `Domain`::

>>> for c in j.credentials:
>>> for c in domain:
... print(c)

.. seealso::

more detail can be found: `using credentials <https://www.jenkins.io/doc/book/using/using-credentials/>`_
and `credentials plugin user.doc <https://github.com/jenkinsci/credentials-plugin/blob/master/docs/user.adoc>`_

View
-------
Expand Down Expand Up @@ -801,9 +809,9 @@ run groovy script

>>> j.system.run_script('println "this is test"')

it also supports to manage `jcasc <https://www.jenkins.io/projects/jcasc/>`_ ::
it also supports to manage `jcasc <https://www.jenkins.io/projects/jcasc/>`_

to reload jcase
to reload jcasc::

>>> j.system.reload_jcasc()

Expand Down Expand Up @@ -1012,7 +1020,7 @@ iterate all case in test report and filter by status ::
... print(case)

Coverage report
-----
---------------

Access coverage report generated by `JaCoCo <https://plugins.jenkins.io/jacoco/>`_, avaliable types are 'branchCoverage', 'classCoverage', 'complexityScore', 'instructionCoverage', 'lineCoverage', 'methodCoverage'::

Expand Down
5 changes: 4 additions & 1 deletion tests/integration/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,12 @@ def folder(jenkins):


@pytest.fixture(autouse=True)
def setup_folder(jenkins, folder_xml):
def setup_folder(jenkins, folder_xml, credential_xml):
jenkins.create_job('Level1_Folder1', folder_xml)
jenkins.create_job('Level1_Folder1/Level2_Folder1', folder_xml)
folder = jenkins.get_job('Level1_Folder1')
folder.credentials.global_domain.create(credential_xml)
folder.credentials.create(load_xml('domain.xml'))
yield
jenkins.delete_job('Level1_Folder1')

Expand Down
15 changes: 15 additions & 0 deletions tests/integration/test_credential.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
class TestCredentials:
def test_credentials_iter(self, folder):
assert len(list(folder.credentials)) == 2

def test_credential_get(self, folder):
assert folder.credentials.global_domain is not None
assert folder.credentials.get('not exists') is None

def test_domain_get(self, folder):
c = folder.credentials.global_domain['user-id']
assert c.id == 'user-id'
assert folder.credentials.global_domain['not exists'] is None

def test_domain_iter(self, folder):
assert len(list(folder.credentials.global_domain)) == 1
16 changes: 8 additions & 8 deletions tests/integration/test_jenkins.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,14 @@ def test_iter_jobs(self, jenkins):
assert len(list(jenkins)) == 1
assert len(list(jenkins(2))) == 2

def test_credential(self, jenkins, credential_xml):
assert len(list(jenkins.credentials)) == 0
jenkins.credentials.create(credential_xml)
assert len(list(jenkins.credentials)) == 1
c = jenkins.credentials.get('user-id')
assert c.id == 'user-id'
c.delete()
assert c.exists() == False
# def test_credential(self, jenkins, credential_xml):
# assert len(list(jenkins.credentials)) == 1
# jenkins.credentials.global_domain.create(credential_xml)
# assert len(list(jenkins.credentials.global_domain)) == 1
# c = jenkins.credentials.get('user-id')
# assert c.id == 'user-id'
# c.delete()
# assert c.exists() == False

def test_view(self, jenkins, view_xml):
assert len(list(jenkins.views)) == 1
Expand Down
10 changes: 10 additions & 0 deletions tests/integration/tests_data/domain.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<com.cloudbees.plugins.credentials.domains.Domain>
<name>testing</name>
<description>Credentials for use against the *.test.example.com hosts</description>
<specifications>
<com.cloudbees.plugins.credentials.domains.HostnameSpecification>
<includes>*.test.example.com</includes>
<excludes></excludes>
</com.cloudbees.plugins.credentials.domains.HostnameSpecification>
</specifications>
</com.cloudbees.plugins.credentials.domains.Domain>
4 changes: 3 additions & 1 deletion tests/unit/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from api4jenkins.job import WorkflowJob, WorkflowMultiBranchProject
from api4jenkins.build import WorkflowRun, FreeStyleBuild
from api4jenkins import Credentials
from api4jenkins.credential import Credential
from api4jenkins.credential import Credential, Domain
from api4jenkins import PluginsManager
from api4jenkins import Queue
from api4jenkins.queue import QueueItem
Expand All @@ -31,6 +31,8 @@ def _api_json(self, tree='', depth=0):
elif isinstance(self, FreeStyleBuild):
return load_json('run/freestylebuild.json')
elif isinstance(self, Credentials):
return load_json('credential/domains.json')
elif isinstance(self, Domain):
return load_json('credential/credentials.json')
elif isinstance(self, Credential):
return load_json('credential/user_psw.json')
Expand Down
Loading

0 comments on commit 1cac13f

Please sign in to comment.