10
.gitignore
vendored
10
.gitignore
vendored
@ -20,4 +20,12 @@ pyvenv.cfg
|
||||
|
||||
#Don't include the autogenerated Module docs
|
||||
docs/module/*
|
||||
docs/includes/*
|
||||
docs/includes/*
|
||||
|
||||
# Don't track JUnit Test or coverage Reports from unittests
|
||||
*.JUnit.xml
|
||||
.coverage*
|
||||
*,cover
|
||||
public/*
|
||||
htmlcov/*
|
||||
UnitTesting_coverage.xml
|
||||
222
.gitlab-ci.yml
222
.gitlab-ci.yml
@ -3,6 +3,8 @@
|
||||
|
||||
|
||||
stages:
|
||||
- Verify
|
||||
- Unit Testing
|
||||
- package
|
||||
- build
|
||||
- test
|
||||
@ -11,20 +13,189 @@ stages:
|
||||
- publish
|
||||
|
||||
|
||||
.PythonImageBuildModuleBefore_Script: &PythonImageBuildModuleBefore_Script |
|
||||
pip install --user --upgrade setuptools wheel
|
||||
pip install -r requirements.txt
|
||||
pip install -r test/requirements_unittest.pip
|
||||
apt-get update && apt-get install -y --no-install-recommends git
|
||||
git --version
|
||||
GIT_PYTHON_GIT_EXECUTABLE=$(which git)
|
||||
echo $GIT_PYTHON_GIT_EXECUTABLE
|
||||
echo $PATH
|
||||
PATH=$PATH:$GIT_PYTHON_GIT_EXECUTABLE
|
||||
|
||||
|
||||
PyLint:
|
||||
stage: Verify
|
||||
image: python:3.6.9-slim
|
||||
before_script:
|
||||
- *PythonImageBuildModuleBefore_Script
|
||||
- python3 setup.py egg_info sdist bdist_wheel
|
||||
script:
|
||||
- python3 -m pylint --exit-zero --output-format=pylint_gitlab.GitlabCodeClimateReporter gitlab_management test *.py > gl-code-quality-report.json
|
||||
- python3 -m pylint --exit-zero --output-format=pylint_gitlab.GitlabPagesHtmlReporter gitlab_management test *.py > gl-code-quality-report.html
|
||||
- PyPIScore=$(python3 -m pylint --exit-zero gitlab_management test *.py | sed -n 's/^Your code has been rated at \([-0-9./]*\).*/\1/p')
|
||||
- |
|
||||
echo "{
|
||||
\"PyLintScore\": \"$PyPIScore\"
|
||||
}
|
||||
" > badge_pylint.json
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG
|
||||
when: never
|
||||
- if: $CI_MERGE_REQUEST_IID
|
||||
when: never
|
||||
- if: '$CI_COMMIT_BRANCH'
|
||||
changes:
|
||||
- gitlab_management/*.py
|
||||
- test/*.py
|
||||
- ./*.py
|
||||
when: always
|
||||
artifacts:
|
||||
expire_in: 60 days
|
||||
paths:
|
||||
- gl-code-quality-report.json
|
||||
- gl-code-quality-report.html
|
||||
- badge_pylint.json
|
||||
reports:
|
||||
codequality: gl-code-quality-report.json
|
||||
|
||||
Unit Test:
|
||||
stage: Unit Testing
|
||||
image: python:3.6.9-slim
|
||||
before_script:
|
||||
- *PythonImageBuildModuleBefore_Script
|
||||
- python3 setup.py egg_info sdist bdist_wheel
|
||||
script:
|
||||
- coverage run --parallel-mode --branch --context=Unit_Testing --source gitlab_management test/test_unit.py
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG
|
||||
when: never
|
||||
- if: $CI_MERGE_REQUEST_IID
|
||||
when: never
|
||||
- if: '$CI_COMMIT_BRANCH'
|
||||
changes:
|
||||
- gitlab_management/*.py
|
||||
- test/test_*.py
|
||||
- setup.py
|
||||
when: always
|
||||
artifacts:
|
||||
expire_in: 3 days
|
||||
paths:
|
||||
- Unit.JUnit.xml
|
||||
- gitlab_management/*.cover
|
||||
- .coverage*
|
||||
reports:
|
||||
junit: Unit.JUnit.xml
|
||||
|
||||
|
||||
Function Test:
|
||||
stage: Unit Testing
|
||||
image: python:3.6.9-slim
|
||||
before_script:
|
||||
- *PythonImageBuildModuleBefore_Script
|
||||
- python3 setup.py egg_info sdist bdist_wheel
|
||||
script:
|
||||
- coverage run --parallel-mode --branch --context=Function_Testing --source gitlab_management test/test_function.py
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG
|
||||
when: never
|
||||
- if: $CI_MERGE_REQUEST_IID
|
||||
when: never
|
||||
- if: '$CI_COMMIT_BRANCH'
|
||||
changes:
|
||||
- gitlab_management/*.py
|
||||
- test/test_*.py
|
||||
- setup.py
|
||||
when: always
|
||||
artifacts:
|
||||
expire_in: 3 days
|
||||
paths:
|
||||
- Function.JUnit.xml
|
||||
- gitlab_management/*.cover
|
||||
- .coverage*
|
||||
reports:
|
||||
junit: Function.JUnit.xml
|
||||
|
||||
|
||||
Integration Test:
|
||||
stage: Unit Testing
|
||||
image: python:3.6.9-slim
|
||||
before_script:
|
||||
- *PythonImageBuildModuleBefore_Script
|
||||
- python3 setup.py egg_info sdist bdist_wheel
|
||||
script:
|
||||
- coverage run --parallel-mode --branch --context=Integration_Testing --source gitlab_management test/test_integration.py
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG
|
||||
when: never
|
||||
- if: $CI_MERGE_REQUEST_IID
|
||||
when: never
|
||||
- if: '$CI_COMMIT_BRANCH'
|
||||
changes:
|
||||
- gitlab_management/*.py
|
||||
- test/test_*.py
|
||||
- setup.py
|
||||
when: always
|
||||
artifacts:
|
||||
expire_in: 3 days
|
||||
paths:
|
||||
- Integration.JUnit.xml
|
||||
- gitlab_management/*.cover
|
||||
- .coverage*
|
||||
reports:
|
||||
junit: Integration.JUnit.xml
|
||||
|
||||
|
||||
|
||||
Coverage:
|
||||
stage: package
|
||||
image: python:3.6.9-slim
|
||||
variables:
|
||||
COVERAGE_DIR: public/$CI_COMMIT_BRANCH/coverage
|
||||
before_script:
|
||||
- *PythonImageBuildModuleBefore_Script
|
||||
- python3 setup.py egg_info sdist bdist_wheel
|
||||
script:
|
||||
- coverage combine --append
|
||||
- coverage report
|
||||
- coverage html --show-contexts -d public/$CI_COMMIT_BRANCH/coverage
|
||||
- coverage json -o badge_coverage.json
|
||||
- coverage xml -o UnitTesting_coverage.xml
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG
|
||||
when: never
|
||||
- if: $CI_MERGE_REQUEST_IID
|
||||
when: never
|
||||
- if: '$CI_COMMIT_BRANCH'
|
||||
changes:
|
||||
- gitlab_management/*.py
|
||||
- setup.py
|
||||
- test/*.py
|
||||
when: on_success
|
||||
dependencies:
|
||||
- Unit Test
|
||||
- Function Test
|
||||
- Integration Test
|
||||
artifacts:
|
||||
expire_in: 60 days
|
||||
when: on_success
|
||||
paths:
|
||||
- UnitTesting_coverage.xml
|
||||
- public/*
|
||||
- badge_coverage.json
|
||||
reports:
|
||||
cobertura: UnitTesting_coverage.xml
|
||||
|
||||
|
||||
gitlab-management_package:
|
||||
stage: package
|
||||
image: python:3.6.9-slim
|
||||
variables:
|
||||
GIT_PYTHON_GIT_EXECUTABLE: /bin/git
|
||||
before_script:
|
||||
- apt-get update && apt-get install -y --no-install-recommends git
|
||||
- *PythonImageBuildModuleBefore_Script
|
||||
- git --version
|
||||
- GIT_PYTHON_GIT_EXECUTABLE=$(which git)
|
||||
- echo $GIT_PYTHON_GIT_EXECUTABLE
|
||||
- echo $PATH
|
||||
- PATH=$PATH:$GIT_PYTHON_GIT_EXECUTABLE
|
||||
- python3 -m pip install --user --upgrade setuptools wheel
|
||||
- python3 -m pip install -r requirements.txt
|
||||
script:
|
||||
- python3 setup.py egg_info sdist bdist_wheel
|
||||
rules:
|
||||
@ -38,7 +209,7 @@ gitlab-management_package:
|
||||
- setup.py
|
||||
- README.md
|
||||
- CONTRIBUTING.md
|
||||
when: always
|
||||
when: on_success
|
||||
artifacts:
|
||||
expire_in: 3 days
|
||||
when: on_success
|
||||
@ -85,6 +256,31 @@ include:
|
||||
- template: SAST.gitlab-ci.yml
|
||||
|
||||
|
||||
|
||||
pages:
|
||||
stage: test
|
||||
dependencies:
|
||||
- Coverage
|
||||
script:
|
||||
- echo coverage reports to gitlab pages
|
||||
artifacts:
|
||||
paths:
|
||||
- public
|
||||
expire_in: 3 days
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG
|
||||
when: never
|
||||
- if: $CI_MERGE_REQUEST_IID
|
||||
when: never
|
||||
- if: '$CI_COMMIT_BRANCH'
|
||||
changes:
|
||||
- gitlab_management/*.py
|
||||
- setup.py
|
||||
- README.md
|
||||
- CONTRIBUTING.md
|
||||
when: on_success
|
||||
|
||||
|
||||
variables:
|
||||
SAST_DEFAULT_ANALYZERS: "bandit"
|
||||
|
||||
@ -134,7 +330,7 @@ container_scanning:
|
||||
- if: $CI_COMMIT_BRANCH
|
||||
when: never
|
||||
|
||||
|
||||
|
||||
gemnasium-python-dependency_scanning:
|
||||
variables:
|
||||
DS_PYTHON_VERSION: 3
|
||||
@ -151,8 +347,16 @@ gemnasium-python-dependency_scanning:
|
||||
changes:
|
||||
- gitlab_management/*.py
|
||||
- setup.py
|
||||
- requirements.txt
|
||||
- requirements.pip
|
||||
when: on_success
|
||||
allow_failure: false
|
||||
artifacts:
|
||||
paths:
|
||||
- gl-dependency-scanning-report.json
|
||||
reports:
|
||||
dependency_scanning: gl-dependency-scanning-report.json
|
||||
|
||||
|
||||
# to activate licence approvals: https://docs.gitlab.com/ee/user/application_security/#enabling-license-approvals-within-a-project
|
||||
license_scanning:
|
||||
|
||||
@ -48,6 +48,49 @@ python3 setup.py sdist bdist_wheel
|
||||
|
||||
>**Note:** you must increment the version in the build script (`buildinit.py`) prior to committing your final changes to the repo.
|
||||
|
||||
## running builds
|
||||
Prior to committing to the repo, test builds need to be conducted. we have designed this to replicate gitlabs CI. Each stage of the `.gitlab-ci.yml` can be run from the command line using the following docker command
|
||||
|
||||
|
||||
``` bash
|
||||
docker run --privileged -w $PWD -v $PWD:$PWD -v /var/run/docker.sock:/var/run/docker.sock gitlab/gitlab-runner:ubuntu-v13.0.0 exec docker ${Gitlab-CI Stage} -env "CI_COMMIT_BRANCH=$(git rev-parse --abbrev-ref HEAD)" --docker-privileged --docker-volumes '/var/run/docker.sock:/var/run/docker.sock'
|
||||
```
|
||||
>**tip:** substitute `{Gitlab-CI Stage}` with one of the available stages below before running this command
|
||||
|
||||
Tested and confirmed `.gitlab-ci.yml` tasks as working with the above command
|
||||
- **Verify**
|
||||
* PyLint
|
||||
- **Unit Testing**
|
||||
* Unit Test
|
||||
* Function Test
|
||||
- **package**
|
||||
* Coverage *- Not Working*
|
||||
* gitlab-management_package
|
||||
- **build**
|
||||
* ~~Docker_Build-Alpine~~ *- not working*
|
||||
- **test**
|
||||
* ~~pages~~ *- Only usable on GitLab*
|
||||
* ~~bandit-sast~~ *- Not Tested*
|
||||
* ~~Scan gitlab-management-Alpine~~ *- Not Tested*
|
||||
* ~~gemnasium-python-dependency_scanning~~ *- Not Tested*
|
||||
* ~~license_scanning~~ *- Not Tested*
|
||||
- **validate**
|
||||
* ~~Documentation~~ *- Not Working*
|
||||
**Tip:** use this command to build/test the docs
|
||||
```bash
|
||||
cd {Repo Directory}
|
||||
|
||||
rm -Rf build bin docs/_build gitlab_management.egg-info lib include pyvenv.cfg
|
||||
|
||||
CI_PROJECT_DIR=/Repository && docker run -e CI_PROJECT_DIR=$CI_PROJECT_DIR -w $CI_PROJECT_DIR -v $PWD:$CI_PROJECT_DIR readthedocs/build:latest bash test/validation-build-docs.sh
|
||||
```
|
||||
- **release**
|
||||
* ~~GitLab-Release~~ *- Only usable on GitLab*
|
||||
- **publish**
|
||||
* ~~Publish~~ *- Only usable on GitLab*
|
||||
|
||||
|
||||
|
||||
### Version Changes
|
||||
Every change, prior to being committed to the `development` branch, must have it's version incremented. In most cases only the `{newfeature}` number will need to be incremented.
|
||||
|
||||
@ -171,11 +214,6 @@ Clean-up the environment prior to building the documentation, with:
|
||||
rm -Rf build bin docs/_build gitlab_management.egg-info lib include pyvenv.cfg
|
||||
```
|
||||
|
||||
To build the documentation run:
|
||||
``` bash
|
||||
cd {Repo Directory}
|
||||
CI_PROJECT_DIR=/Repository && docker run -e CI_PROJECT_DIR=$CI_PROJECT_DIR -w $CI_PROJECT_DIR -v $PWD:$CI_PROJECT_DIR readthedocs/build:latest bash test/validation-build-docs.sh
|
||||
```
|
||||
|
||||
|
||||
>**Tip:**
|
||||
|
||||
16
README.md
16
README.md
@ -1,12 +1,22 @@
|
||||
# Python Gitlab Management
|
||||
|
||||
[](https://gitlab.com/nofusscomputing/projects/python-gitlab-management/)
|
||||
[](https://gitlab.com/nofusscomputing/projects/python-gitlab-management/)
|
||||

|
||||
[](https://pypi.org/project/gitlab-management/)
|
||||
[](https://gitlab.com/nofusscomputing/projects/python-gitlab-management/-/blob/master/LICENCE)
|
||||
|
||||
|
||||
|
||||
[](https://gitlab.com/nofusscomputing/projects/python-gitlab-management/)
|
||||
[](https://nofusscomputing.gitlab.io/projects/python-gitlab-management/master/coverage/)
|
||||
[](https://gitlab.com/nofusscomputing/projects/python-gitlab-management/-/jobs/artifacts/master/file/gl-code-quality-report.html?job=PyLint)
|
||||
[](https://python-gitlab-management.readthedocs.io/en/stable/)
|
||||
|
||||
|
||||
[](https://gitlab.com/nofusscomputing/projects/python-gitlab-management/)
|
||||
[](https://nofusscomputing.gitlab.io/projects/python-gitlab-management/development/coverage/)
|
||||
[](https://gitlab.com/nofusscomputing/projects/python-gitlab-management/-/jobs/artifacts/development/file/gl-code-quality-report.html?job=PyLint)
|
||||
[](https://python-gitlab-management.readthedocs.io/en/development/)
|
||||
|
||||
|
||||
Gitlab-management is python module that enables GitLab group configuration from code. By design it's intended to be setup to run on a schedule.
|
||||
|
||||
## How to Use
|
||||
|
||||
@ -15,7 +15,7 @@ __license__ = "GNU LGPLv3"
|
||||
__copyright__ = "(C) All Rights reserved"
|
||||
__source__ = ''
|
||||
__title__ = "gitlab-management"
|
||||
__version__ = "0.1.4"
|
||||
__version__ = "0.1.5"
|
||||
__doc__ = "https://gitlab.com/nofusscomputing/projects/python-gitlab-management"
|
||||
|
||||
CurrentDirectory = './'
|
||||
|
||||
@ -8,4 +8,5 @@ For assistance in using this module please see the sections below.
|
||||
|
||||
configuration
|
||||
docker
|
||||
labels
|
||||
labels
|
||||
unittesting
|
||||
26
docs/pages/unittesting.md
Normal file
26
docs/pages/unittesting.md
Normal file
@ -0,0 +1,26 @@
|
||||
# Unit Testing
|
||||
Unit testing is done on all classes and methods. Unit testing has been broken down into the following categories
|
||||
1. Unit
|
||||
1. Function
|
||||
1. Integration
|
||||
|
||||
Depending on what is type of item is being tested will depend on where the testing code will go.
|
||||
|
||||
## Unit
|
||||
The Unit test is the most basic of all of the tests. It's intent is to check the following items:
|
||||
1. Method exists `{Classname}.globals`
|
||||
1. all Inputs
|
||||
1. The retrun values
|
||||
1. Exceptions
|
||||
* Unexpected
|
||||
* Expected exceptions are thrown
|
||||
|
||||
whilst unit testing, all other methods or classes that the tested method is calling are to be mocked.
|
||||
|
||||
## Function
|
||||
Function testing is intended to test interoperability between classes and methods, including any user input(s). Function testing is to be limited to required dependancies.
|
||||
|
||||
The only items that are to be mocked dueing function testing are any methods that require interaction outside of the tested class methods, including dependant modules. *i.e. a http request.*
|
||||
|
||||
## Integration
|
||||
Integration testing is designed to confirm the classes, dependent modules and any required external services work as they were designed. If all tests upto and including this stage of testing pass, it is assumed that the module is ready for release.
|
||||
@ -1,3 +1,3 @@
|
||||
recommonmark
|
||||
m2r
|
||||
python-gitlab
|
||||
python-gitlab==2.2.0
|
||||
|
||||
@ -8,20 +8,68 @@ class GitlabManagement:
|
||||
No Fuss Computing's Gitlab Config Management python module.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
import gitlab as GitLabAPIWrapper
|
||||
import gitlab
|
||||
import gitlab.v4.objects
|
||||
import traceback
|
||||
import logging
|
||||
from enum import Enum
|
||||
|
||||
|
||||
GitlabSession:GitLabAPIWrapper.Gitlab = None
|
||||
_GitlabSession:gitlab.Gitlab = None
|
||||
|
||||
DesiredOutputLevel:int = None
|
||||
@property
|
||||
def GitlabSession(self) -> gitlab.Gitlab:
|
||||
|
||||
if self._GitlabSession == None:
|
||||
|
||||
Config:dict = None
|
||||
return None
|
||||
|
||||
else:
|
||||
|
||||
return self._GitlabSession
|
||||
|
||||
|
||||
@GitlabSession.setter
|
||||
def GitlabSession(self, oSession):
|
||||
|
||||
if type(oSession) is self.gitlab.Gitlab or type(oSession) is None:
|
||||
|
||||
self._GitlabSession = oSession
|
||||
|
||||
else:
|
||||
raise TypeError("GitlabSession can only be type 'None' or 'gitlab.Gitlab' [{}] is neither".format(str(oSession)))
|
||||
|
||||
|
||||
|
||||
_DesiredOutputLevel:int = None
|
||||
|
||||
@property
|
||||
def DesiredOutputLevel(self) -> int:
|
||||
|
||||
return self._DesiredOutputLevel
|
||||
|
||||
@DesiredOutputLevel.setter
|
||||
def DesiredOutputLevel(self, Object):
|
||||
|
||||
if type(Object) is not self.OutputSeverity:
|
||||
raise TypeError('{} must be of type OutputSeverity'.format(str(Object)))
|
||||
|
||||
self._DesiredOutputLevel = Object
|
||||
|
||||
_Config:dict = None
|
||||
|
||||
@property
|
||||
def Config(self):
|
||||
|
||||
if self._Config == None:
|
||||
if self.GetConfig():
|
||||
return self._Config
|
||||
else:
|
||||
raise RuntimeError('Unable to get config')
|
||||
|
||||
else:
|
||||
return self._Config
|
||||
|
||||
class OutputSeverity(Enum):
|
||||
|
||||
@ -29,26 +77,45 @@ class GitlabManagement:
|
||||
Alert:int = 1
|
||||
Critical:int = 2
|
||||
Error:int = 3
|
||||
Warnin:int = 4
|
||||
Warning:int = 4
|
||||
Notice:int = 5
|
||||
Informational:int = 6
|
||||
Debug:int = 7
|
||||
|
||||
GitlabObjectCache:dict = None
|
||||
_GitlabObjectCache:dict = None
|
||||
"""Cache the objects fetched via the GitLab API"""
|
||||
|
||||
@property
|
||||
def GitlabObjectCache(self) -> dict:
|
||||
"""Cache the objects fetched via the GitLab API
|
||||
|
||||
See Also
|
||||
--------
|
||||
`Dependent Methods`:
|
||||
Nil
|
||||
|
||||
`issue #8 <https://gitlab.com/nofusscomputing/projects/python-gitlab-management/-/issues/8>`_
|
||||
Feature: Groups now cached
|
||||
|
||||
"""
|
||||
|
||||
return self._GitlabObjectCache
|
||||
|
||||
def __init__(self, GitLab_URL:str, GitLab_PrivateToken:str):
|
||||
|
||||
|
||||
|
||||
def __init__(self, GitLab_URL:str, GitLab_PrivateToken:str, Authenticate:bool = True):
|
||||
|
||||
self.DesiredOutputLevel = self.OutputSeverity.Informational
|
||||
|
||||
if self.GitlabLoginAuthenticate(GitLab_URL, GitLab_PrivateToken):
|
||||
|
||||
if not self.GetConfig():
|
||||
if not self.GitlabLoginAuthenticate(GitLab_URL, GitLab_PrivateToken, Authenticate):
|
||||
self.Output(self.OutputSeverity.Critical, 'could not logon to ' + GitLab_URL)
|
||||
|
||||
if not self.GetConfig():
|
||||
|
||||
self.Output(self.OutputSeverity.Critical, "Couldn't load config yml")
|
||||
|
||||
else:
|
||||
else:
|
||||
|
||||
self.Output(self.OutputSeverity.Notice, 'config loaded')
|
||||
|
||||
@ -56,15 +123,10 @@ class GitlabManagement:
|
||||
|
||||
CacheINIT['Groups']:dict = None
|
||||
|
||||
self.GitlabObjectCache = CacheINIT
|
||||
|
||||
|
||||
else:
|
||||
|
||||
self.Output(self.OutputSeverity.Critical, 'could not logon to ' + GitLab_URL)
|
||||
self._GitlabObjectCache = CacheINIT
|
||||
|
||||
|
||||
def GitlabLoginAuthenticate(self, URL:str, PrivateToken:str) -> bool:
|
||||
def GitlabLoginAuthenticate(self, URL:str, PrivateToken:str, Authenticate:bool = True) -> bool:
|
||||
"""
|
||||
Establish the Gitlab instance to connect to and authenticate.
|
||||
|
||||
@ -80,7 +142,7 @@ class GitlabManagement:
|
||||
|
||||
Raises
|
||||
------
|
||||
GitLabAPIWrapper.GitlabAuthenticationError
|
||||
gitlab.GitlabAuthenticationError
|
||||
Returns text output of the failed authentication issue that occured when attemping to authenticate.
|
||||
|
||||
Exception
|
||||
@ -101,19 +163,18 @@ class GitlabManagement:
|
||||
GitlabLoginAuthenticate = False
|
||||
|
||||
try:
|
||||
GitlabLogin = self.GitLabAPIWrapper.Gitlab(URL, private_token=PrivateToken)
|
||||
|
||||
import gitlab_management
|
||||
|
||||
GitlabLogin.headers['User-Agent'] = "%s/%s %s" % (gitlab_management.__title__, gitlab_management.__version__, gitlab_management.__doc__)
|
||||
GitlabLogin = self.gitlab.Gitlab(URL, private_token=PrivateToken)
|
||||
|
||||
GitlabLogin.headers['User-Agent'] = "%s/%s %s" % (self.get_detail('title'), self.get_detail('version'), self.get_detail('doc'))
|
||||
|
||||
GitlabLogin.auth()
|
||||
if Authenticate:
|
||||
GitlabLogin.auth()
|
||||
|
||||
self.GitlabSession = GitlabLogin
|
||||
|
||||
GitlabLoginAuthenticate = True
|
||||
|
||||
except self.GitLabAPIWrapper.GitlabAuthenticationError as e:
|
||||
except self.gitlab.GitlabAuthenticationError as e:
|
||||
|
||||
self.Output(self.OutputSeverity.Error, str(e))
|
||||
|
||||
@ -196,12 +257,12 @@ class GitlabManagement:
|
||||
|
||||
try:
|
||||
|
||||
self.Config = dict(yaml.safe_load(stream))
|
||||
self._Config = dict(yaml.safe_load(stream))
|
||||
GetConfig = True
|
||||
|
||||
except yaml.YAMLError as exc:
|
||||
|
||||
self.Output(self.OutputSeverity.Error, exc)
|
||||
self.Output(self.OutputSeverity.Error, str(exc))
|
||||
|
||||
except Exception as e:
|
||||
self.Output(self.OutputSeverity.Critical, logging.error(traceback.format_exc()))
|
||||
@ -231,10 +292,10 @@ class GitlabManagement:
|
||||
Exception
|
||||
Generic catch all Exception is returned, however the exception will be printed to the console
|
||||
|
||||
GitLabAPIWrapper.GitlabHttpError
|
||||
gitlab.GitlabHttpError
|
||||
A http error occured, the output should denote what the issue is
|
||||
|
||||
GitLabAPIWrapper.GitlabCreateError
|
||||
gitlab.GitlabCreateError
|
||||
a check for '409: label exists', as no attempt should be made to create a label when the check has already been done.
|
||||
|
||||
Warning
|
||||
@ -258,7 +319,7 @@ class GitlabManagement:
|
||||
NewLabelString = {}
|
||||
CreateGroupLabel = False
|
||||
|
||||
if type(Group) == self.GitLabAPIWrapper.v4.objects.Group:
|
||||
if type(Group) == self.gitlab.v4.objects.Group:
|
||||
|
||||
try:
|
||||
|
||||
@ -278,11 +339,11 @@ class GitlabManagement:
|
||||
|
||||
CreateGroupLabel = True
|
||||
|
||||
except self.GitLabAPIWrapper.GitlabHttpError as e:
|
||||
except self.gitlab.GitlabHttpError as e:
|
||||
|
||||
self.Output(self.OutputSeverity.Critical, logging.error(traceback.format_exc()))
|
||||
|
||||
except self.GitLabAPIWrapper.GitlabCreateError as e:
|
||||
except self.gitlab.GitlabCreateError as e:
|
||||
|
||||
if e.response_code == 409:
|
||||
|
||||
@ -346,7 +407,7 @@ class GitlabManagement:
|
||||
|
||||
if self.GitlabObjectCache['Groups'] is None:
|
||||
|
||||
Groups = self.GitlabSession.groups.list(min_access_level = self.GitLabAPIWrapper.MAINTAINER_ACCESS)
|
||||
Groups = self.GitlabSession.groups.list(min_access_level = self.gitlab.MAINTAINER_ACCESS)
|
||||
|
||||
self.GitlabObjectCache['Groups'] = {}
|
||||
|
||||
@ -431,7 +492,7 @@ class GitlabManagement:
|
||||
|
||||
try:
|
||||
|
||||
if type(Group) is self.GitLabAPIWrapper.v4.objects.Group:
|
||||
if type(Group) is self.gitlab.v4.objects.Group:
|
||||
|
||||
CachedGroupLabels = self.GitlabObjectCache['Groups'][str(Group.attributes['id'])]['Labels']
|
||||
|
||||
@ -544,3 +605,10 @@ class GitlabManagement:
|
||||
self.Output(self.OutputSeverity.Critical, logging.error(traceback.format_exc()))
|
||||
|
||||
return ProcessConfigGroups
|
||||
|
||||
|
||||
def get_detail(self, ItemName:str):
|
||||
with open("gitlab_management/__init__.py") as f:
|
||||
for line in f:
|
||||
if line.startswith("__" + ItemName + "__"):
|
||||
return(str(line.split("=")[-1]).replace('"', '').replace("\n", '').replace(" ", ''))
|
||||
|
||||
@ -21,20 +21,23 @@ Example: python3 gitlab-management -T {GitlabAuthToken}
|
||||
"""
|
||||
|
||||
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
try:
|
||||
|
||||
full_cmd_arguments = sys.argv
|
||||
|
||||
if len(full_cmd_arguments) == 0:
|
||||
raise getopt.error('No Arguments specified')
|
||||
|
||||
argument_list = full_cmd_arguments[1:]
|
||||
|
||||
short_options = "H:T:hv:l"
|
||||
long_options = ["Host=", "Token=" "help", "verbose=", "Labels"]
|
||||
|
||||
arguments, values = getopt.getopt(argument_list, short_options, long_options)
|
||||
|
||||
|
||||
GitlabHost = None
|
||||
GitlabPrivateToken = None
|
||||
RunCLI = True
|
||||
@ -63,7 +66,6 @@ def main():
|
||||
# Setting the value to non-int will cause an un-caught exception
|
||||
|
||||
Verbose = current_value
|
||||
print('here: ' + str(type(current_value)))
|
||||
|
||||
elif current_argument in ("-h", "--help"):
|
||||
|
||||
@ -88,7 +90,10 @@ def main():
|
||||
|
||||
if Option_Labels:
|
||||
GitlabManagementObject.ProcessConfigLabels(GitlabManagementObject.Config['Group']['Labels'])
|
||||
|
||||
else:
|
||||
sys.exit(3)
|
||||
|
||||
|
||||
|
||||
|
||||
except getopt.error as err:
|
||||
@ -102,7 +107,7 @@ def main():
|
||||
except Exception as e:
|
||||
|
||||
print(logging.error(traceback.format_exc()))
|
||||
|
||||
|
||||
sys.exit(2)
|
||||
|
||||
|
||||
|
||||
@ -1,2 +1,3 @@
|
||||
python-gitlab
|
||||
gitpython
|
||||
python-gitlab==2.2.0
|
||||
GitPython==3.1.3
|
||||
PyYAML==5.3.1
|
||||
7
setup.py
7
setup.py
@ -13,7 +13,7 @@ def get_detail(ItemName:str):
|
||||
for line in f:
|
||||
if line.startswith("__" + ItemName + "__"):
|
||||
return(str(line.split("=")[-1]).replace('"', '').replace("\n", '').replace(" ", ''))
|
||||
|
||||
|
||||
setuptools.setup(
|
||||
name=get_detail('title'),
|
||||
version=get_detail('version'),
|
||||
@ -31,8 +31,9 @@ setuptools.setup(
|
||||
},
|
||||
packages=setuptools.find_packages(),
|
||||
install_requires=[
|
||||
'python-gitlab',
|
||||
'PyYAML'
|
||||
'GitPython==3.1.3',
|
||||
'python-gitlab==2.2.0',
|
||||
'PyYAML==5.3.1'
|
||||
],
|
||||
entry_points = {
|
||||
'console_scripts': ['gitlab-management=gitlab_management.cli:main'],
|
||||
|
||||
1
test/__init__.py
Normal file
1
test/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
|
||||
173
test/data/HTTPRequest_api_v4_groups_1_labels.json
Normal file
173
test/data/HTTPRequest_api_v4_groups_1_labels.json
Normal file
@ -0,0 +1,173 @@
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Bug",
|
||||
"color": "#FF0000",
|
||||
"description": "Used in discussion about a bug in comments or commits",
|
||||
"description_html": "Used in discussion about a bug in comments or commits",
|
||||
"text_color": "#FFFFFF",
|
||||
"subscribed": false
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "Bug::Reproducable",
|
||||
"color": "#FF0000",
|
||||
"description": "Given to an issue when the bug has been confirmed as re-producable. Can also be used in comments and commits.",
|
||||
"description_html": "Given to an issue when the bug has been confirmed as re-producable. Can also be used in comments and commits.",
|
||||
"text_color": "#FFFFFF",
|
||||
"subscribed": false
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"name": "Bug::Unable to Reproduce",
|
||||
"color": "#FF0000",
|
||||
"description": "Given to an issue when the bug that is not able to be reproduced. Can also be used in comments and commits.",
|
||||
"description_html": "Given to an issue when the bug that is not able to be reproduced. Can also be used in comments and commits.",
|
||||
"text_color": "#FFFFFF",
|
||||
"subscribed": false
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"name": "Category::Bug",
|
||||
"color": "#A295D6",
|
||||
"description": "Category for Issues and Merge Requests. Can also be used in discussion in comments and commits",
|
||||
"description_html": "Category for Issues and Merge Requests. Can also be used in discussion in comments and commits",
|
||||
"text_color": "#333333",
|
||||
"subscribed": false
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"name": "Category::Feature",
|
||||
"color": "#A295D6",
|
||||
"description": "Category for Issues and Merge Requests. Can also be used in discussion in comments and commits",
|
||||
"description_html": "Category for Issues and Merge Requests. Can also be used in discussion in comments and commits",
|
||||
"text_color": "#333333",
|
||||
"subscribed": false
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"name": "Code Review::Complete",
|
||||
"color": "#D9534F",
|
||||
"description": null,
|
||||
"description_html": "",
|
||||
"text_color": "#FFFFFF",
|
||||
"subscribed": false
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"name": "Code Review::Not Started",
|
||||
"color": "#D9534F",
|
||||
"description": null,
|
||||
"description_html": "",
|
||||
"text_color": "#FFFFFF",
|
||||
"subscribed": false
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"name": "Code Review::Rejected",
|
||||
"color": "#D9534F",
|
||||
"description": null,
|
||||
"description_html": "",
|
||||
"text_color": "#FFFFFF",
|
||||
"subscribed": false
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"name": "Code Review::Underway",
|
||||
"color": "#D9534F",
|
||||
"description": null,
|
||||
"description_html": "",
|
||||
"text_color": "#FFFFFF",
|
||||
"subscribed": false
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"name": "Documentation",
|
||||
"color": "#428BCA",
|
||||
"description": "Documentation items",
|
||||
"description_html": "Documentation items",
|
||||
"text_color": "#FFFFFF",
|
||||
"subscribed": false
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"name": "Documentation::Complete",
|
||||
"color": "#428BCA",
|
||||
"description": "Issues, Merge Requests",
|
||||
"description_html": "Issues, Merge Requests",
|
||||
"text_color": "#FFFFFF",
|
||||
"subscribed": false
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"name": "Documentation::No Change Required",
|
||||
"color": "#428BCA",
|
||||
"description": "Issues, Merge Requests and used to denote no documentation changes required",
|
||||
"description_html": "Issues, Merge Requests and used to denote no documentation changes required",
|
||||
"text_color": "#FFFFFF",
|
||||
"subscribed": false
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"name": "Documentation::Not Started",
|
||||
"color": "#428BCA",
|
||||
"description": "Issues, Merge Requests",
|
||||
"description_html": "Issues, Merge Requests",
|
||||
"text_color": "#FFFFFF",
|
||||
"subscribed": false
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"name": "Documentation::Stalled",
|
||||
"color": "#428BCA",
|
||||
"description": "Issues, Merge Requests",
|
||||
"description_html": "Issues, Merge Requests",
|
||||
"text_color": "#FFFFFF",
|
||||
"subscribed": false
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"name": "Documentation::Underway",
|
||||
"color": "#428BCA",
|
||||
"description": "Issues, Merge Requests",
|
||||
"description_html": "Issues, Merge Requests",
|
||||
"text_color": "#FFFFFF",
|
||||
"subscribed": false
|
||||
},
|
||||
{
|
||||
"id": 16,
|
||||
"name": "Feature",
|
||||
"color": "#A295D6",
|
||||
"description": "Used in discussion about a Feature in comments or commits",
|
||||
"description_html": "Used in discussion about a Feature in comments or commits",
|
||||
"text_color": "#333333",
|
||||
"subscribed": false
|
||||
},
|
||||
{
|
||||
"id": 17,
|
||||
"name": "stage::develop",
|
||||
"color": "#004E00",
|
||||
"description": "Stage for use in Issues and Merge Requests. Can also be used in discussion in comments and commits",
|
||||
"description_html": "Stage for use in Issues and Merge Requests. Can also be used in discussion in comments and commits",
|
||||
"text_color": "#FFFFFF",
|
||||
"subscribed": false
|
||||
},
|
||||
{
|
||||
"id": 18,
|
||||
"name": "stage::planning",
|
||||
"color": "#004E00",
|
||||
"description": "Stage for use in Issues and Merge Requests. Can also be used in discussion in comments and commits",
|
||||
"description_html": "Stage for use in Issues and Merge Requests. Can also be used in discussion in comments and commits",
|
||||
"text_color": "#FFFFFF",
|
||||
"subscribed": false
|
||||
},
|
||||
{
|
||||
"id": 19,
|
||||
"name": "stage::test",
|
||||
"color": "#004E00",
|
||||
"description": "Stage for use in Issues and Merge Requests. Can also be used in discussion in comments and commits",
|
||||
"description_html": "Stage for use in Issues and Merge Requests. Can also be used in discussion in comments and commits",
|
||||
"text_color": "#FFFFFF",
|
||||
"subscribed": false
|
||||
}
|
||||
]
|
||||
56
test/data/HTTPRequest_api_v4_groups_min_access_level.json
Normal file
56
test/data/HTTPRequest_api_v4_groups_min_access_level.json
Normal file
@ -0,0 +1,56 @@
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"web_url": "http://mock_gitlab_url/groups/documents",
|
||||
"name": "Documents",
|
||||
"path": "documents",
|
||||
"description": "Documents",
|
||||
"visibility": "private",
|
||||
"share_with_group_lock": false,
|
||||
"require_two_factor_authentication": false,
|
||||
"two_factor_grace_period": 48,
|
||||
"project_creation_level": "developer",
|
||||
"auto_devops_enabled": null,
|
||||
"subgroup_creation_level": "maintainer",
|
||||
"emails_disabled": null,
|
||||
"mentions_disabled": null,
|
||||
"lfs_enabled": true,
|
||||
"default_branch_protection": 2,
|
||||
"avatar_url": null,
|
||||
"request_access_enabled": true,
|
||||
"full_name": "Documents",
|
||||
"full_path": "documents",
|
||||
"created_at": "2020-05-10T03:59:02.143Z",
|
||||
"parent_id": 0,
|
||||
"ldap_cn": null,
|
||||
"ldap_access": null
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"web_url": "https://mock_gitlab_url/groups/projects",
|
||||
"name": "Projects",
|
||||
"path": "projects",
|
||||
"description": "Projects",
|
||||
"visibility": "public",
|
||||
"share_with_group_lock": false,
|
||||
"require_two_factor_authentication": false,
|
||||
"two_factor_grace_period": 48,
|
||||
"project_creation_level": "developer",
|
||||
"auto_devops_enabled": null,
|
||||
"subgroup_creation_level": "maintainer",
|
||||
"emails_disabled": null,
|
||||
"mentions_disabled": null,
|
||||
"lfs_enabled": true,
|
||||
"default_branch_protection": 2,
|
||||
"avatar_url": null,
|
||||
"request_access_enabled": true,
|
||||
"full_name": "Projects",
|
||||
"full_path": "projects",
|
||||
"created_at": "2020-05-31T10:41:37.648Z",
|
||||
"parent_id": 0,
|
||||
"ldap_cn": null,
|
||||
"ldap_access": null
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@ -13,3 +13,5 @@ readthedocs-sphinx-ext<1.1
|
||||
m2r==0.2.1
|
||||
sphinxcontrib-apidoc==0.3.0
|
||||
|
||||
|
||||
|
||||
|
||||
6
test/requirements_unittest.pip
Normal file
6
test/requirements_unittest.pip
Normal file
@ -0,0 +1,6 @@
|
||||
unittest-xml-reporting
|
||||
coverage
|
||||
requests_mock==1.8.0
|
||||
|
||||
pylint==2.5.2
|
||||
pylint-gitlab==0.1.0
|
||||
165
test/test_function.py
Normal file
165
test/test_function.py
Normal file
@ -0,0 +1,165 @@
|
||||
#!/usr/bin/env python3
|
||||
#-*- coding: utf-8 -*-
|
||||
import sys
|
||||
sys.path.insert(0, "../gitlab_management")
|
||||
sys.path.insert(0, "./gitlab_management")
|
||||
sys.path.insert(0, "./")
|
||||
|
||||
from base import GitlabManagement
|
||||
|
||||
import unittest, xmlrunner
|
||||
from enum import Enum
|
||||
import gitlab
|
||||
|
||||
import gitlab_management
|
||||
|
||||
from cli import main as cli
|
||||
from unittest.mock import patch
|
||||
|
||||
class GitlabManagement_Functional(unittest.TestCase):
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.ClassToTest = GitlabManagement
|
||||
|
||||
cls.Gitlab_Instance_URL = 'http://gitlab.com'
|
||||
cls.Gitlab_Instance_TOKEN = 'Token'
|
||||
|
||||
ConfigFileContents = """Group:
|
||||
Labels:
|
||||
-
|
||||
Group: "ExampleGroup1"
|
||||
Name: "Stage::Planning"
|
||||
Description: "example description for the label"
|
||||
Color: "#FF0000"
|
||||
|
||||
-
|
||||
Group:
|
||||
- "ExampleGroup1"
|
||||
- "ExampleGroup2"
|
||||
Name: "Stage::RequireFeedback"
|
||||
Description: "example description for the label"
|
||||
Color: "#FF0000"
|
||||
"""
|
||||
#create config.yml
|
||||
with open('config.yml', 'w') as ConfigFile:
|
||||
ConfigFile.write(ConfigFileContents)
|
||||
|
||||
|
||||
cls.ClassToTest = GitlabManagement(cls.Gitlab_Instance_URL, cls.Gitlab_Instance_TOKEN, False)
|
||||
|
||||
|
||||
|
||||
def test_cli_NoArgs(self):
|
||||
|
||||
#No args should display help and exit non-zero to denotee error
|
||||
with self.assertRaises(SystemExit, msg="When cli exits, an `ExitCode` must be present") as ExitCode:
|
||||
with unittest.mock.patch('sys.argv', []):
|
||||
cli()
|
||||
|
||||
self.assertEqual(ExitCode.exception.code, 1, msg="noargs should exit 1")
|
||||
|
||||
|
||||
def test_cli_Help(self):
|
||||
|
||||
with self.assertRaises(SystemExit, msg="When cli exits, an `ExitCode` must be present") as ExitCode:
|
||||
with unittest.mock.patch('sys.argv', ['DummyFileName', "-h"]):
|
||||
cli()
|
||||
|
||||
self.assertEqual(ExitCode.exception.code, 0, msg="Help should exit on 0")
|
||||
|
||||
# ToDo assert output matches
|
||||
|
||||
|
||||
def test_cliLogonSuccess(self):
|
||||
|
||||
|
||||
with unittest.mock.patch('gitlab_management.base.GitlabManagement.GitlabLoginAuthenticate') as mock_authenticate:
|
||||
# Set auth to true, as this method is to only return bool
|
||||
mock_authenticate.return_value = True
|
||||
|
||||
with unittest.mock.patch('gitlab_management.base.GitlabManagement._GitlabSession', unittest.mock.Mock()) as Session:
|
||||
Session = 'NotNoneMeansAuthenticated'
|
||||
|
||||
#Mock Config Labels
|
||||
with unittest.mock.patch('gitlab_management.base.GitlabManagement.ProcessConfigLabels', unittest.mock.Mock()):
|
||||
|
||||
# specify all parameters for a connection, including host, verbose 3 and process labels, should exit 0
|
||||
with unittest.mock.patch('sys.argv', ['DummyFileName', '-H', 'http://127.0.0.1', '-T', 'invalidtoken', '-v', '3', '-l']):
|
||||
with self.assertRaises(SystemExit, msg="When cli exits, an `ExitCode` must be present") as ExitCode:
|
||||
cli()
|
||||
self.assertEqual(ExitCode.exception.code, 0, msg="connection should exit with code 0")
|
||||
|
||||
# same test as above, however, don't specify host and http://gitlab.com will be used.
|
||||
with unittest.mock.patch('sys.argv', ['DummyFileName', '-T', 'invalidtoken', '-v', '3']):
|
||||
with self.assertRaises(SystemExit, msg="When cli exits, an `ExitCode` must be present") as ExitCode:
|
||||
cli()
|
||||
self.assertEqual(ExitCode.exception.code, 0, msg="connection should exit with code 0")
|
||||
|
||||
# same test as above, however, don't specify host and http://gitlab.com will be used and verbose above max of 7.
|
||||
with unittest.mock.patch('sys.argv', ['DummyFileName', '-T', 'invalidtoken', '-v', '8', '-l']):
|
||||
with self.assertRaises(SystemExit, msg="When cli exits, an `ExitCode` must be present") as ExitCode:
|
||||
cli()
|
||||
self.assertEqual(ExitCode.exception.code, 0, msg="connection should exit with code 0")
|
||||
|
||||
|
||||
# Test a failed authenticate.
|
||||
with unittest.mock.patch('gitlab.Gitlab') as GitLabNone:
|
||||
GitLabNone.return_value = None
|
||||
mock_authenticate.return_value = False
|
||||
|
||||
with unittest.mock.patch('sys.argv', ['DummyFileName', '-T', 'invalidtoken', '-v', '8', '-l']):
|
||||
with self.assertRaises(SystemExit, msg="When cli exits, an `ExitCode` must be present") as ExitCode:
|
||||
cli()
|
||||
|
||||
self.assertNotEqual(ExitCode.exception.code, 0, msg="failed login should exit non zero")
|
||||
|
||||
|
||||
@patch('gitlab_management.base.GitlabManagement.GitlabLoginAuthenticate')
|
||||
def test_cli_LogonFail(self, mock_authenticate):
|
||||
|
||||
#self.skipTest('ToDo: Implement an exit code for failed logon')
|
||||
|
||||
# Set auth to true, as this method is to only return bool
|
||||
mock_authenticate.return_value = False
|
||||
|
||||
# specify all parameters for a connection, including host, verbose 3 and process labels, should exit 0
|
||||
with self.assertRaises(SystemExit, msg="When cli exits, an `ExitCode` must be present") as ExitCode:
|
||||
with unittest.mock.patch('sys.argv', ['DummyFileName', '-H', 'http://127.0.0.1', '-T', 'invalidtoken']):
|
||||
cli()
|
||||
|
||||
#ToDo Assert output to notify user
|
||||
|
||||
self.assertNotEqual(ExitCode.exception.code, 0, msg="connection should exit with code non zero")
|
||||
|
||||
|
||||
@patch('gitlab_management.base.GitlabManagement.GitlabLoginAuthenticate')
|
||||
def test_cliProcessLabels(self, mock_authenticate):
|
||||
|
||||
# Set auth to true, as this method is to only return bool
|
||||
mock_authenticate.return_value = True
|
||||
|
||||
# specify all parameters for a connection, including host, verbose 3 and process labels, should exit 0
|
||||
with self.assertRaises(SystemExit, msg="When cli exits, an `ExitCode` must be present") as ExitCode:
|
||||
with unittest.mock.patch('sys.argv', ['DummyFileName', '-H', 'http://127.0.0.1', '-T', 'invalidtoken', '-l']):
|
||||
with unittest.mock.patch('gitlab_management.base.GitlabManagement.GitlabSession', unittest.mock.Mock()): # Must be a session available, so mock it
|
||||
with unittest.mock.patch('gitlab_management.base.GitlabManagement.ProcessConfigLabels', unittest.mock.Mock()): #ToDo, change this to patch decoration so that the return can be set as the cli should output success/failure and exitcode to match
|
||||
cli()
|
||||
|
||||
self.assertEqual(ExitCode.exception.code, 0, msg="connection should exit with code 0")
|
||||
|
||||
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
with open('Function.JUnit.xml', 'wb') as output:
|
||||
unittest.main(
|
||||
testRunner=xmlrunner.XMLTestRunner(output=output),
|
||||
failfast=False, buffer=False, catchbreak=False)
|
||||
108
test/test_integration.py
Normal file
108
test/test_integration.py
Normal file
@ -0,0 +1,108 @@
|
||||
#!/usr/bin/env python3
|
||||
#-*- coding: utf-8 -*-
|
||||
import os, sys
|
||||
sys.path.insert(0, "../gitlab_management")
|
||||
sys.path.insert(0, "./gitlab_management")
|
||||
sys.path.insert(0, "./")
|
||||
|
||||
from base import GitlabManagement
|
||||
|
||||
import unittest, xmlrunner
|
||||
from unittest.mock import patch
|
||||
import enum
|
||||
import gitlab
|
||||
|
||||
import requests_mock
|
||||
|
||||
import gitlab_management
|
||||
import yaml
|
||||
|
||||
class GitlabManagement_Unit(unittest.TestCase):
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
|
||||
cls.ConfigFileContents = """Group:
|
||||
Labels:
|
||||
-
|
||||
Group: "ExampleGroup1"
|
||||
Name: "Stage::Planning"
|
||||
Description: "example description for the label"
|
||||
Color: "#FF0000"
|
||||
|
||||
-
|
||||
Group:
|
||||
- "ExampleGroup1"
|
||||
- "ExampleGroup2"
|
||||
Name: "Stage::RequireFeedback"
|
||||
Description: "example description for the label"
|
||||
Color: "#FF0000"
|
||||
"""
|
||||
|
||||
cls.ConfigFileContents_InvalidYML = """Group:
|
||||
Labels:
|
||||
-
|
||||
Group "This Group label is missing the ':' so it's invalid"
|
||||
Name: "Stage::Planning"
|
||||
Description: "example description for the label"
|
||||
Color: "#FF0000"
|
||||
|
||||
-
|
||||
Group:
|
||||
- "ExampleGroup1"
|
||||
- "ExampleGroup2"
|
||||
Name: "Stage::RequireFeedback"
|
||||
Description: "example description for the label"
|
||||
Color: "#FF0000"
|
||||
"""
|
||||
|
||||
cls.Gitlab_Instance_URL = 'http://mock_gitlab_url'
|
||||
cls.Gitlab_Instance_TOKEN = 'Gitlab token'
|
||||
|
||||
with open('config.yml', 'w') as ConfigFile:
|
||||
ConfigFile.write(cls.ConfigFileContents)
|
||||
ConfigFile.close()
|
||||
|
||||
# login and class init needs to be done for each test
|
||||
#cls.ClassToTest = GitlabManagement(cls.Gitlab_Instance_URL, cls.Gitlab_Instance_TOKEN, False)
|
||||
#The integration tests will be done against a gitlab instance
|
||||
|
||||
|
||||
|
||||
def test_CreateGroupLabel(self):
|
||||
self.skipTest('implement')
|
||||
|
||||
|
||||
def test_GetGroupByName(self):
|
||||
|
||||
self.skipTest('implement')
|
||||
|
||||
|
||||
def test_GetLabelByName(self):
|
||||
|
||||
self.skipTest('implement')
|
||||
|
||||
|
||||
def test_GitlabLoginAuthenticate(self):
|
||||
|
||||
self.skipTest('implement')
|
||||
|
||||
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
#unittest.main()
|
||||
|
||||
with open('Integration.JUnit.xml', 'wb') as output:
|
||||
|
||||
unittest.main(
|
||||
testRunner=xmlrunner.XMLTestRunner(output=output),
|
||||
failfast=False, buffer=False, catchbreak=False)
|
||||
333
test/test_unit.py
Normal file
333
test/test_unit.py
Normal file
@ -0,0 +1,333 @@
|
||||
#!/usr/bin/env python3
|
||||
#-*- coding: utf-8 -*-
|
||||
import os, sys
|
||||
sys.path.insert(0, "../gitlab_management")
|
||||
sys.path.insert(0, "./gitlab_management")
|
||||
sys.path.insert(0, "./")
|
||||
|
||||
from base import GitlabManagement
|
||||
|
||||
import unittest, xmlrunner
|
||||
from unittest.mock import patch
|
||||
import enum
|
||||
import gitlab
|
||||
|
||||
import requests_mock
|
||||
|
||||
import gitlab_management
|
||||
import yaml
|
||||
|
||||
class GitlabManagement_Unit(unittest.TestCase):
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
|
||||
cls.ConfigFileContents = """Group:
|
||||
Labels:
|
||||
-
|
||||
Group: "ExampleGroup1"
|
||||
Name: "Stage::Planning"
|
||||
Description: "example description for the label"
|
||||
Color: "#FF0000"
|
||||
|
||||
-
|
||||
Group:
|
||||
- "ExampleGroup1"
|
||||
- "ExampleGroup2"
|
||||
Name: "Stage::RequireFeedback"
|
||||
Description: "example description for the label"
|
||||
Color: "#FF0000"
|
||||
"""
|
||||
|
||||
cls.ConfigFileContents_InvalidYML = """Group:
|
||||
Labels:
|
||||
-
|
||||
Group "This Group label is missing the ':' so it's invalid"
|
||||
Name: "Stage::Planning"
|
||||
Description: "example description for the label"
|
||||
Color: "#FF0000"
|
||||
|
||||
-
|
||||
Group:
|
||||
- "ExampleGroup1"
|
||||
- "ExampleGroup2"
|
||||
Name: "Stage::RequireFeedback"
|
||||
Description: "example description for the label"
|
||||
Color: "#FF0000"
|
||||
"""
|
||||
|
||||
cls.Gitlab_Instance_URL = 'http://mock_gitlab_url'
|
||||
cls.Gitlab_Instance_TOKEN = 'Gitlab token'
|
||||
|
||||
with open('config.yml', 'w') as ConfigFile:
|
||||
ConfigFile.write(cls.ConfigFileContents)
|
||||
ConfigFile.close()
|
||||
|
||||
cls.ClassToTest = GitlabManagement(cls.Gitlab_Instance_URL, cls.Gitlab_Instance_TOKEN, False)
|
||||
|
||||
|
||||
|
||||
def test___init__file(self):
|
||||
# Check __init__.py to make sure all of the attributes are available
|
||||
|
||||
self.assertTrue('__title__' in gitlab_management.__dict__, msg="__init__.py missing attribute __titl__")
|
||||
self.assertTrue('__version__' in gitlab_management.__dict__, msg="__init__.py missing attribute __version__")
|
||||
self.assertTrue('__doc__' in gitlab_management.__dict__, msg="__init__.py missing attribute __doc__")
|
||||
self.assertTrue('__author__' in gitlab_management.__dict__, msg="__init__.py missing attribute __author__")
|
||||
self.assertTrue('__email__' in gitlab_management.__dict__, msg="__init__.py missing attribute __email__")
|
||||
self.assertTrue('__license__' in gitlab_management.__dict__, msg="__init__.py missing attribute __license__")
|
||||
self.assertTrue('__copyright__' in gitlab_management.__dict__, msg="__init__.py missing attribute __copyright__")
|
||||
self.assertTrue('__source__' in gitlab_management.__dict__, msg="__init__.py missing attribute __source__")
|
||||
|
||||
|
||||
def test_Config(self):
|
||||
|
||||
#ToDo fix this variable test, class should not be init before testing, so that it can be confirmed as existing before creating
|
||||
|
||||
self.assertTrue('_Config' in self.ClassToTest.__dict__, msg="_Config Variable missing")
|
||||
|
||||
#Within init, Config should be called, which populates `Config` variable, so this value should not be none
|
||||
self.assertFalse(self.ClassToTest.Config is None, msg="Class has been initialized, value should not be `None`")
|
||||
|
||||
# Class has been init, config.yml loaded into `Config` variable.
|
||||
self.assertTrue(type(self.ClassToTest.Config) is dict, msg="Config is collected during class init, `Config` should be a `dict` of the config.yml file contents")
|
||||
|
||||
# should not be able to change value
|
||||
with self.assertRaises(AttributeError, msg="You should not be able to set this value"):
|
||||
self.ClassToTest.Config = "IWontSet"
|
||||
|
||||
#test the property config.getter.Valid yaml
|
||||
self.ClassToTest._Config = None
|
||||
self.assertTrue(self.ClassToTest._Config == None, msg="precursor test setting '_Config' to None for Config.getter test")
|
||||
|
||||
self.assertTrue(self.ClassToTest.Config == yaml.safe_load(self.ConfigFileContents), msg="yaml file didn't load")
|
||||
|
||||
#invalid yaml
|
||||
with open('config.yml', 'w') as ConfigFile:
|
||||
ConfigFile.write(self.ConfigFileContents_InvalidYML)
|
||||
ConfigFile.close()
|
||||
|
||||
#test the property config.getter. Invalid yaml
|
||||
self.ClassToTest._Config = None
|
||||
self.assertTrue(self.ClassToTest._Config == None, msg="precursor test setting '_Config' to None for Config.getter test")
|
||||
|
||||
with self.assertRaises(RuntimeError, msg="`RuntimeError` exception should have been thrown as the yaml was invalid"):
|
||||
self.ClassToTest.Config
|
||||
|
||||
#Return yaml file t be valid
|
||||
with open('config.yml', 'w') as ConfigFile:
|
||||
ConfigFile.write(self.ConfigFileContents)
|
||||
ConfigFile.close()
|
||||
|
||||
|
||||
def test_CreateGroupLabel(self):
|
||||
#self.skipTest('implement')
|
||||
|
||||
# not a group
|
||||
self.assertEqual(self.ClassToTest.CreateGroupLabel(None, Name='boo', Description='boo', Color='000000'), False, msg='Invalid group returns false')
|
||||
|
||||
#is a group
|
||||
with requests_mock.Mocker() as m:
|
||||
|
||||
with open('test/data/HTTPRequest_api_v4_groups_min_access_level.json', 'r') as Group_API_Call:
|
||||
|
||||
m.get(self.Gitlab_Instance_URL + '/api/v4/groups?min_access_level=40', text=Group_API_Call.read(), status_code=200)
|
||||
|
||||
GitLabGroup = self.ClassToTest.GetGroupByName('Documents')
|
||||
|
||||
with open('test/data/HTTPRequest_api_v4_groups_1_labels.json', 'r') as Group_API_Call:
|
||||
|
||||
m.get(self.Gitlab_Instance_URL + '/api/v4/groups/1/labels', text=Group_API_Call.read(), status_code=200)
|
||||
|
||||
with unittest.mock.patch('gitlab.v4.objects.GroupLabelManager.create', unittest.mock.Mock()) as Mock_resp:
|
||||
|
||||
self.assertEqual(self.ClassToTest.CreateGroupLabel(GitLabGroup, Name='feature', Description='boo', Color='000000'), True)
|
||||
|
||||
#Attempt to create a label that exists
|
||||
self.assertEqual(self.ClassToTest.CreateGroupLabel(GitLabGroup, Name='Bug', Description='Label exists', Color='000000'), True)
|
||||
|
||||
|
||||
def test_GetConfig(self):
|
||||
|
||||
try:
|
||||
|
||||
with open('config.yml', 'w') as ConfigFile:
|
||||
ConfigFile.write(self.ConfigFileContents_InvalidYML)
|
||||
ConfigFile.close()
|
||||
|
||||
self.assertFalse(self.ClassToTest.GetConfig(), msg="invalid yml files cause exception 'yaml.YAMLError' which needs to be handled by the method")
|
||||
|
||||
# make a valid Yaml file and test again
|
||||
with open('config.yml', 'w') as ConfigFile:
|
||||
ConfigFile.write(self.ConfigFileContents)
|
||||
ConfigFile.close()
|
||||
|
||||
self.assertTrue(self.ClassToTest.GetConfig(), msg="valid yml files are loaded with the method returning true.")
|
||||
|
||||
#confirm what is in the file is loaded to the '_config' variable
|
||||
self.assertTrue(self.ClassToTest._Config == yaml.safe_load(self.ConfigFileContents))
|
||||
|
||||
except yaml.YAMLError:
|
||||
self.assertFalse(True, msg="Exception `yaml.YAMLError` needs to be caught by the method")
|
||||
|
||||
except Exception:
|
||||
self.assertFalse(True, msg="Uncaught exceptions must be caught in the method")
|
||||
|
||||
|
||||
def test_GetGroupByName(self):
|
||||
#self.ClassToTest.GitlabObjectCache['Groups'] = None
|
||||
|
||||
#Group:gitlab.v4.objects.Group =
|
||||
with requests_mock.Mocker() as m:
|
||||
|
||||
with open('test/data/HTTPRequest_api_v4_groups_min_access_level.json', 'r') as Group_API_Call:
|
||||
|
||||
m.get(self.Gitlab_Instance_URL + '/api/v4/groups?min_access_level=40', text=Group_API_Call.read(), status_code=200)
|
||||
|
||||
self.assertEqual(self.ClassToTest.GetGroupByName('Documents').name, 'Documents', msg='Unable to fetch group "Documents" by name')
|
||||
|
||||
self.assertEqual(self.ClassToTest.GetGroupByName('A Random Name to fail'), None, msg="group doesn't exist, this test should = 'None'")
|
||||
|
||||
#self.ClassToTest.GitlabObjectCache['Groups'] = None
|
||||
|
||||
|
||||
def test_GetLabelByName(self):
|
||||
|
||||
try:
|
||||
with requests_mock.Mocker() as m:
|
||||
with open('test/data/HTTPRequest_api_v4_groups_1_labels.json', 'r') as Group_API_Call:
|
||||
|
||||
m.get(self.Gitlab_Instance_URL + '/api/v4/groups/1/labels', text=Group_API_Call.read(), status_code=200)
|
||||
|
||||
#GroupName = self.ClassToTest.GitlabObjectCache['Groups']['Projects']
|
||||
|
||||
self.assertEqual(self.ClassToTest.GetLabelByName(self.ClassToTest.GetGroupByName('Documents'), 'Bug').name, 'Bug', msg="Searching for an existing label failed when it should not have")
|
||||
|
||||
self.assertEqual(self.ClassToTest.GetLabelByName(self.ClassToTest.GetGroupByName('Documents'), 'Non existant label'), None, msg="Searching for a label that doesn't exist should return 'None'")
|
||||
except Exception:
|
||||
|
||||
self.assertFalse(True, msg="An uncaught exception occured when the method should have caught it")
|
||||
|
||||
|
||||
def test_GitlabLoginAuthenticate(self):
|
||||
|
||||
try:
|
||||
|
||||
with unittest.mock.patch('gitlab.Gitlab.auth', unittest.mock.Mock()):
|
||||
self.assertTrue(self.ClassToTest.GitlabLoginAuthenticate(self.Gitlab_Instance_URL, self.Gitlab_Instance_TOKEN))
|
||||
|
||||
with requests_mock.Mocker() as m:
|
||||
m.get(self.Gitlab_Instance_URL + '/api/v4/user', text='mock test, failed to auth', status_code=401)
|
||||
self.assertFalse(self.ClassToTest.GitlabLoginAuthenticate(self.Gitlab_Instance_URL, self.Gitlab_Instance_TOKEN))
|
||||
|
||||
except gitlab.GitlabAuthenticationError:
|
||||
self.assertFalse(True, msg="Exception 'gitlab.GitlabAuthenticationError' needs to be caught by the method")
|
||||
|
||||
except Exception:
|
||||
self.assertFalse(True, msg="Uncaught exceptions must be caught in the method")
|
||||
|
||||
|
||||
def test_GitlabSession(self):
|
||||
|
||||
self.assertTrue('_GitlabSession' in self.ClassToTest.__dict__, msg="_GitlabSession Variable missing")
|
||||
|
||||
#self.skipTest("functional")
|
||||
ClassNotInit = GitlabManagement
|
||||
|
||||
self.assertTrue(type(ClassNotInit.GitlabSession) is property, msg="Class not initalized, should be a property")
|
||||
|
||||
self.assertTrue(type(self.ClassToTest.GitlabSession) == gitlab.Gitlab, msg="property must be type 'gitlab.Gitlab'")
|
||||
|
||||
with self.assertRaises(TypeError, msg='Should not be able to set to different type'):
|
||||
boo:str = 'NotMe'
|
||||
self.ClassToTest.GitlabSession = boo
|
||||
|
||||
|
||||
def test_DesiredOutputLevel(self):
|
||||
|
||||
self.assertTrue('_DesiredOutputLevel' in self.ClassToTest.__dict__, msg="_DesiredOutputLevel Variable missing")
|
||||
|
||||
# Must be an int, however should be a GitlabManagement.OutputSeverity, can be none
|
||||
self.assertTrue(type(self.ClassToTest.DesiredOutputLevel) is self.ClassToTest.OutputSeverity, msg="Must be of type `self.ClassToTest.OutputSeverity`")
|
||||
|
||||
#Should not be able to change type
|
||||
with self.assertRaises(TypeError, msg="Type Error must be thrown if setting the type doens't equal `self.ClassToTest.OutputSeverity`"):
|
||||
self.ClassToTest.DesiredOutputLevel = str('NoString')
|
||||
|
||||
with self.assertRaises(TypeError, msg="Type Error must be thrown if setting the type doens't equal `self.ClassToTest.OutputSeverity`"):
|
||||
self.ClassToTest.DesiredOutputLevel = int(1)
|
||||
|
||||
self.assertFalse(self.ClassToTest.GitlabObjectCache is type(None), msg="during class init, a default value needs to be assigned")
|
||||
|
||||
|
||||
def test_GitlabObjectCache(self):
|
||||
|
||||
self.assertTrue('_GitlabObjectCache' in self.ClassToTest.__dict__, msg="_GitlabObjectCache Variable missing")
|
||||
|
||||
# During init, `dict` is created with the required keys
|
||||
self.assertNotIsInstance(self.ClassToTest.GitlabObjectCache, type(None), msg="Item should not be initialized with NoneType")
|
||||
|
||||
# Must be of type dict
|
||||
self.assertTrue(type(self.ClassToTest.GitlabObjectCache) is dict, msg="Property must be of type `dict`")
|
||||
|
||||
# Must be dict, with Groups key
|
||||
self.assertTrue('Groups' in self.ClassToTest.GitlabObjectCache, msg="Group Key Must exist in `dict`")
|
||||
|
||||
# ToDo: the line above test failed as now the getlabelby name makes a mock test. change this test to be a global check to see if the variable exists before the class has been init.
|
||||
|
||||
#Should not be able to change type
|
||||
with self.assertRaises(AttributeError, msg="Attribute error should be thrown as no setter is required nor should exist"):
|
||||
ListObject:list = []
|
||||
self.ClassToTest.GitlabObjectCache = ListObject
|
||||
|
||||
|
||||
def test_OutputSeverity_Class(self):
|
||||
|
||||
# Must be Enum
|
||||
self.assertTrue(type(self.ClassToTest.OutputSeverity) is enum.EnumMeta, msg="Class must be an `enum`")
|
||||
|
||||
# All Class Properties must be int
|
||||
self.assertTrue(type(self.ClassToTest.OutputSeverity.Emergency.value) is int, msg="Value must be an int")
|
||||
self.assertTrue(type(self.ClassToTest.OutputSeverity.Alert.value) is int, msg="Value must be an int")
|
||||
self.assertTrue(type(self.ClassToTest.OutputSeverity.Critical.value) is int, msg="Value must be an int")
|
||||
self.assertTrue(type(self.ClassToTest.OutputSeverity.Error.value) is int, msg="Value must be an int")
|
||||
self.assertTrue(type(self.ClassToTest.OutputSeverity.Warning.value) is int, msg="Value must be an int")
|
||||
self.assertTrue(type(self.ClassToTest.OutputSeverity.Notice.value) is int, msg="Value must be an int")
|
||||
self.assertTrue(type(self.ClassToTest.OutputSeverity.Informational.value) is int, msg="Value must be an int")
|
||||
self.assertTrue(type(self.ClassToTest.OutputSeverity.Debug.value) is int, msg="Value must be an int")
|
||||
|
||||
|
||||
def test_ProcessConfigLabels(self):
|
||||
|
||||
#self.skipTest('implement test')
|
||||
|
||||
with requests_mock.Mocker() as m:
|
||||
|
||||
with open('test/data/HTTPRequest_api_v4_groups_min_access_level.json', 'r') as Group_API_Call:
|
||||
|
||||
m.get(self.Gitlab_Instance_URL + '/api/v4/groups?min_access_level=40', text=Group_API_Call.read(), status_code=200)
|
||||
|
||||
|
||||
with unittest.mock.patch('gitlab_management.base.GitlabManagement.CreateGroupLabel', unittest.mock.Mock()):
|
||||
|
||||
self.assertEqual(self.ClassToTest.ProcessConfigLabels(self.ClassToTest.Config['Group']['Labels']), True, msg="Process label should return true")
|
||||
|
||||
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
#unittest.main()
|
||||
|
||||
with open('Unit.JUnit.xml', 'wb') as output:
|
||||
|
||||
unittest.main(
|
||||
testRunner=xmlrunner.XMLTestRunner(output=output),
|
||||
failfast=False, buffer=False, catchbreak=False)
|
||||
Reference in New Issue
Block a user