├── irisvmpy ├── __init__.py └── iris.py ├── tests ├── features │ ├── environment.py │ ├── iris.feature │ └── steps │ │ └── iris_steps.py └── test_iris.py ├── .behaverc ├── requirements.txt ├── setup.cfg ├── MANIFEST.in ├── requirements ├── prod.txt └── dev.txt ├── reports ├── unit_tests.xml └── acceptance.json ├── README.md ├── LICENSE ├── setup.py ├── .gitignore └── Jenkinsfile /irisvmpy/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/features/environment.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.behaverc: -------------------------------------------------------------------------------- 1 | [behave] 2 | paths=tests/features -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | -r requirements/prod.txt 2 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | license_file=LICENSE 3 | description_file=README.md 4 | 5 | [bdist_wheel] 6 | universal=1 -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | 3 | recursive-include tests * 4 | recursive-exclude * __pycache__ 5 | recursive-exclude * *.py[co] -------------------------------------------------------------------------------- /requirements/prod.txt: -------------------------------------------------------------------------------- 1 | 2 | Click==6.7 3 | 4 | # Data science tools 5 | numpy==1.14.3 6 | scipy==1.0.1 7 | scikit-learn==0.19.1 -------------------------------------------------------------------------------- /tests/features/iris.feature: -------------------------------------------------------------------------------- 1 | Feature: irissvmpy performance 2 | 3 | Scenario: run main program loop 4 | Given we have irisvmpy installed 5 | when we run program 6 | then Something will be displayed -------------------------------------------------------------------------------- /requirements/dev.txt: -------------------------------------------------------------------------------- 1 | -r prod.txt 2 | 3 | # Static code metrics 4 | # pyflakes==1.6.0 5 | # Flake8==3.5.0 6 | pylint==1.8.1 7 | Radon==2.1.1 8 | coverage==4.4.2 9 | 10 | # Testing 11 | pytest==3.5.1 12 | pytest-runner==4.2 13 | pytest-cov==2.5.1 14 | behave==1.2.6 -------------------------------------------------------------------------------- /reports/unit_tests.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jenkins python ci/cd 2 | 3 | Test repository for Jenkins based CI/CD of python project. 4 | 5 | 6 | # Quickstart 7 | 8 | 9 | Include `Jenkinsfile` in your project and connect it to the running Jenkins server. 10 | 11 | For more details refer to this blog post: 12 | 13 | [Setting Jenkins CI for python application](https://mdyzma.github.io/2017/10/14/python-app-and-jenkins/) 14 | -------------------------------------------------------------------------------- /tests/test_iris.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from click.testing import CliRunner 3 | 4 | from irisvmpy import iris 5 | 6 | class TestCLI(object): 7 | 8 | @pytest.fixture() 9 | def runner(self): 10 | return CliRunner() 11 | 12 | def test_print_help_succeeds(self, runner): 13 | result = runner.invoke(iris.cli, ['--help']) 14 | assert result.exit_code == 0 -------------------------------------------------------------------------------- /tests/features/steps/iris_steps.py: -------------------------------------------------------------------------------- 1 | from behave import given, when, then 2 | 3 | @given(u'we have irisvmpy installed') 4 | def step_impl(context): 5 | raise NotImplementedError(u'STEP: Given we have irisvmpy installed') 6 | 7 | @when(u'we run program') 8 | def step_impl(context): 9 | raise NotImplementedError(u'STEP: When we run program') 10 | 11 | @then(u' will be displayed') 12 | def step_impl(context): 13 | raise NotImplementedError(u'STEP: Then Something will be displayed') 14 | -------------------------------------------------------------------------------- /reports/acceptance.json: -------------------------------------------------------------------------------- 1 | Feature: irissvmpy performance # tests/features/iris.feature:1 2 | 3 | Scenario: run main program loop # tests/features/iris.feature:3 4 | Given we have irisvmpy installed # tests/features/steps/iris_steps.py:3 5 | Traceback (most recent call last): 6 | File "/usr/local/lib/python2.7/dist-packages/behave/model.py", line 1456, in run 7 | match.run(runner.context) 8 | File "/usr/local/lib/python2.7/dist-packages/behave/model.py", line 1903, in run 9 | self.func(context, *args, **kwargs) 10 | File "tests/features/steps/iris_steps.py", line 5, in step_impl 11 | raise NotImplementedError(u'STEP: Given we have irisvmpy installed') 12 | NotImplementedError: STEP: Given we have irisvmpy installed 13 | 14 | When we run program # None 15 | Then Something will be displayed # None 16 | 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Michal Dyzma 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 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # import codecs 2 | # try: 3 | # codecs.lookup('mbcs') 4 | # except LookupError: 5 | # ascii = codecs.lookup('ascii') 6 | # func = lambda name, enc=ascii: {True: enc}.get(name=='mbcs') 7 | # codecs.register(func) 8 | 9 | from setuptools import setup, find_packages 10 | 11 | 12 | requirements = [ 13 | 'scipy', 'numpy', 'scikit-learn', 'Click' 14 | ] 15 | 16 | test_requirements=[ 17 | 'behave' 18 | ] 19 | 20 | setup( 21 | name='irisvmpy', 22 | version='0.0.1', 23 | description='SVM classifier for iris data-set', 24 | author='Michal Dyzma', 25 | author_email='mdyzma@gmail.com', 26 | url ='https://github.com/mdyzma/jenkins-python-test', 27 | download_url='https://github.com/mdyzma/jenkins-python-test/archive/0.0.1.tar.gz', 28 | license='MIT', 29 | packages=find_packages(), 30 | install_requires=requirements, 31 | entry_points={ 32 | 'console_scripts': [ 33 | 'irisvmpy = irisvmpy.iris:cli', 34 | ], 35 | }, 36 | classifiers=[ 37 | 'Development Status :: 1 - Alpha', 38 | 'License :: OSI Approved :: MIT License', 39 | 'Programming Language :: Python :: 2.7', 40 | 'Programming Language :: Python :: 3.6', 41 | ], 42 | zip_safe=False 43 | ) -------------------------------------------------------------------------------- /irisvmpy/iris.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """Simple Iris data set classifier based on Support Vector Machines algorithm. 4 | """ 5 | 6 | from __future__ import print_function 7 | import time 8 | from sklearn import svm, datasets 9 | import click 10 | 11 | 12 | 13 | # import some data to play with 14 | iris = datasets.load_iris() 15 | 16 | X = iris.data[:, :2] # we only take the first two features. We could 17 | # avoid this ugly slicing by using a two-dim dataset 18 | y = iris.target 19 | 20 | 21 | svc = svm.SVC(kernel='rbf', C=1, gamma=0.7).fit(X, y) 22 | 23 | dimm_names = ['Petal Length', 'Petal Width', 'Sepal Length', 'Sepal Width'] 24 | 25 | 26 | @click.command() 27 | @click.argument('dimensions', nargs=4, type=float) 28 | def cli(dimensions): 29 | """Basic command line interface. 30 | 31 | Arguments: 32 | dimensions {list} -- list of flower dimensions: PL, PW, SL, SW 33 | """ 34 | click.echo("Iris Flower classifier\n") 35 | 36 | click.echo("Calculating result...") 37 | time.sleep(1) 38 | results = zip(dimm_names, dimensions) 39 | click.echo("Input data:") 40 | for i, j in results: 41 | click.echo("{:12} -> {}".format(i, j)) 42 | 43 | click.echo() 44 | click.echo("Your flower seems to be fine example of:") 45 | click.secho("{}".format("species"), fg='green', bold=True) 46 | 47 | 48 | if __name__ == "__main__": 49 | cli() 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | test-reports/ 50 | 51 | # Translations 52 | *.mo 53 | *.pot 54 | 55 | # Django stuff: 56 | *.log 57 | local_settings.py 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # dotenv 85 | .env 86 | 87 | # virtualenv 88 | .venv 89 | venv/ 90 | ENV/ 91 | 92 | # Spyder project settings 93 | .spyderproject 94 | .spyproject 95 | 96 | # Rope project settings 97 | .ropeproject 98 | 99 | # mkdocs documentation 100 | /site 101 | 102 | # mypy 103 | .mypy_cache/ 104 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent any 3 | 4 | triggers { 5 | pollSCM('*/5 * * * 1-5') 6 | } 7 | 8 | options { 9 | skipDefaultCheckout(true) 10 | // Keep the 10 most recent builds 11 | buildDiscarder(logRotator(numToKeepStr: '10')) 12 | timestamps() 13 | } 14 | 15 | environment { 16 | PATH="/var/lib/jenkins/miniconda3/bin:$PATH" 17 | } 18 | 19 | stages { 20 | 21 | stage ("Code pull"){ 22 | steps{ 23 | checkout scm 24 | } 25 | } 26 | 27 | stage('Build environment') { 28 | steps { 29 | echo "Building virtualenv" 30 | sh ''' conda create --yes -n ${BUILD_TAG} python 31 | source activate ${BUILD_TAG} 32 | pip install -r requirements/dev.txt 33 | ''' 34 | } 35 | } 36 | 37 | stage('Static code metrics') { 38 | steps { 39 | echo "Raw metrics" 40 | sh ''' source activate ${BUILD_TAG} 41 | radon raw --json irisvmpy > raw_report.json 42 | radon cc --json irisvmpy > cc_report.json 43 | radon mi --json irisvmpy > mi_report.json 44 | sloccount --duplicates --wide irisvmpy > sloccount.sc 45 | ''' 46 | echo "Test coverage" 47 | sh ''' source activate ${BUILD_TAG} 48 | coverage run irisvmpy/iris.py 1 1 2 3 49 | python -m coverage xml -o reports/coverage.xml 50 | ''' 51 | echo "Style check" 52 | sh ''' source activate ${BUILD_TAG} 53 | pylint irisvmpy || true 54 | ''' 55 | } 56 | post{ 57 | always{ 58 | step([$class: 'CoberturaPublisher', 59 | autoUpdateHealth: false, 60 | autoUpdateStability: false, 61 | coberturaReportFile: 'reports/coverage.xml', 62 | failNoReports: false, 63 | failUnhealthy: false, 64 | failUnstable: false, 65 | maxNumberOfBuilds: 10, 66 | onlyStable: false, 67 | sourceEncoding: 'ASCII', 68 | zoomCoverageChart: false]) 69 | } 70 | } 71 | } 72 | 73 | 74 | 75 | stage('Unit tests') { 76 | steps { 77 | sh ''' source activate ${BUILD_TAG} 78 | python -m pytest --verbose --junit-xml reports/unit_tests.xml 79 | ''' 80 | } 81 | post { 82 | always { 83 | // Archive unit tests for the future 84 | junit allowEmptyResults: true, testResults: 'reports/unit_tests.xml' 85 | } 86 | } 87 | } 88 | 89 | stage('Acceptance tests') { 90 | steps { 91 | sh ''' source activate ${BUILD_TAG} 92 | behave -f=formatters.cucumber_json:PrettyCucumberJSONFormatter -o ./reports/acceptance.json || true 93 | ''' 94 | } 95 | post { 96 | always { 97 | cucumber (buildStatus: 'SUCCESS', 98 | fileIncludePattern: '**/*.json', 99 | jsonReportDirectory: './reports/', 100 | parallelTesting: true, 101 | sortingMethod: 'ALPHABETICAL') 102 | } 103 | } 104 | } 105 | 106 | stage('Build package') { 107 | when { 108 | expression { 109 | currentBuild.result == null || currentBuild.result == 'SUCCESS' 110 | } 111 | } 112 | steps { 113 | sh ''' source activate ${BUILD_TAG} 114 | python setup.py bdist_wheel 115 | ''' 116 | } 117 | post { 118 | always { 119 | // Archive unit tests for the future 120 | archiveArtifacts allowEmptyArchive: true, artifacts: 'dist/*whl', fingerprint: true 121 | } 122 | } 123 | } 124 | 125 | // stage("Deploy to PyPI") { 126 | // steps { 127 | // sh """twine upload dist/* 128 | // """ 129 | // } 130 | // } 131 | } 132 | 133 | post { 134 | always { 135 | sh 'conda remove --yes -n ${BUILD_TAG} --all' 136 | } 137 | failure { 138 | emailext ( 139 | subject: "FAILED: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]'", 140 | body: """

FAILED: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]':

141 |

Check console output at "${env.JOB_NAME} [${env.BUILD_NUMBER}]"

""", 142 | recipientProviders: [[$class: 'DevelopersRecipientProvider']]) 143 | } 144 | } 145 | } --------------------------------------------------------------------------------