diff --git a/.coveragerc b/.coveragerc index 2d586d5..f5572e2 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,7 +1,7 @@ [paths] source = moldesign - + */moldesign [run] source = moldesign diff --git a/DockerMakefiles/Moldesign.yml b/DockerMakefiles/Moldesign.yml index 2d40dbc..8b3c51c 100644 --- a/DockerMakefiles/Moldesign.yml +++ b/DockerMakefiles/Moldesign.yml @@ -5,7 +5,6 @@ moldesign_py_build: requires: - python_deploy build_directory: .. - ignorefile: buildfiles/moldesign/moldesign.dockerignore build: | COPY . /opt/molecular-design-toolkit WORKDIR /opt/molecular-design-toolkit diff --git a/codeship-services.yml b/codeship-services.yml index ab5f69b..62dc1d5 100644 --- a/codeship-services.yml +++ b/codeship-services.yml @@ -2,16 +2,27 @@ image_builder: &build-base build: context: . dockerfile: ./deployment/build-env.dockerfile - volumes: - - ./tmp/dist:/opt/molecular-design-toolkit/dist add_docker: true cached: true working_dir: /opt/molecular-design-toolkit + environment: &repo + REPO: "docker.io/autodesk/moldesign:" + + +coverage_pusher: + <<: *build-base + cached: false + encrypted_env_file: ./deployment/test-tokens.crypt + volumes: + - ./tmp/reports:/opt/reports publisher: <<: *build-base + cached: false encrypted_env_file: ./deployment/deploy-tokens.crypt + volumes: + - ./tmp/dist:/opt/dist test_moldesign_minimal: &test-base @@ -26,6 +37,7 @@ test_moldesign_minimal: &test-base args: baseimage: moldesign_minimal environment: + <<: *repo TESTENV: minimal PYVERSION: 3 OPENMM_CPU_THREADS: 1 @@ -38,6 +50,7 @@ test_moldesign_minimal_py2: args: baseimage: moldesign_minimal_py2 environment: + <<: *repo TESTENV: minimal PYVERSION: 2 OPENMM_CPU_THREADS: 1 @@ -50,6 +63,7 @@ test_moldesign_complete: args: baseimage: moldesign_complete environment: + <<: *repo TESTENV: complete PYVERSION: 3 OPENMM_CPU_THREADS: 1 @@ -62,6 +76,7 @@ test_moldesign_complete_py2: args: baseimage: moldesign_complete_py2 environment: + <<: *repo TESTENV: complete PYVERSION: 2 OPENMM_CPU_THREADS: 1 diff --git a/codeship-steps.yml b/codeship-steps.yml index a6b8fa1..8cb5feb 100644 --- a/codeship-steps.yml +++ b/codeship-steps.yml @@ -1,13 +1,21 @@ +# Pull previous builds from DockerHub to use as a cache. +# These images are tagged as [imagename]-cache - name: pull-cache service: image_builder command: deployment/pull-cache.sh +# Build the python package to be tested (and uploaded to PyPI if this is a release) +# The package is built in the "autodesk/moldesign:moldesign_py_build-[branch name]" image - name: build-sdist service: image_builder - command: docker-make moldesign_py_build + command: bash -c "docker-make moldesign_py_build + --tag dev --keep-build-tags - -f DockerMakefiles/DockerMake.yml --tag dev + -f DockerMakefiles/DockerMake.yml" +# Build docker images for all test environments. They will be tagged as +# [imagename]-dev +# (For the sake of parallelism, we also download other images that the tests depend on) - name: build-and-pull-images service: image_builder type: parallel @@ -15,49 +23,50 @@ - name: pull-dependent-images command: deployment/pull-chemdocker.sh - name: build_py3 - command: - docker-make moldesign_minimal moldesign_complete + command: bash -c + "docker-make moldesign_minimal moldesign_complete -f DockerMakefiles/DockerMake.yml - --tag dev --keep-build-tags - --cache-repo moldesign --cache-tag cache + --tag dev + --keep-build-tags + --cache-tag cache" - name: build_py2 - command: - docker-make moldesign_minimal_py2 moldesign_complete_py2 + command: bash -c + "docker-make moldesign_minimal_py2 moldesign_complete_py2 -f DockerMakefiles/DockerMake.yml - --tag dev --keep-build-tags - --cache-repo moldesign --cache-tag cache + --tag dev + --keep-build-tags + --cache-tag cache" -- name: push-images + +# Push build artifacts to Dockerhub. +# These are tagged as autodesk/moldesign:[imagename]-[branchame]-devbuild +# For testing purposes, they are also tagged as +# autodesk/moldesign:[imagename]-[branchname] (this is the tag that will be pushed +# to dockerhub for successful releases) +- name: tag-and-push-images type: parallel steps: - name: push-artifacts - command: deployment/push.sh moldesign_minimal moldesign_py2 - moldesign_complete moldesign_complete_py2 + command: deployment/push-and-tag.sh + moldesign_minimal moldesign_minimal_py2 moldesign_py_build + moldesign_complete moldesign_complete_py2 service: publisher - type: serial name: make-notebook steps: - name: build-notebook service: image_builder - command: docker-make moldesign_notebook + command: bash -c + "docker-make moldesign_notebook -f DockerMakefiles/DockerMake.yml - --tag dev --cache-repo moldesign --cache-tag cache + --tag dev + --cache-tag cache" - name: push-notebook service: publisher - command: deployment/push.sh moldesign_notebook - + command: deployment/push-and-tag.sh moldesign_notebook -- name: print-environments - type: parallel - services: - - test_moldesign_complete - - test_moldesign_complete_py2 - - test_moldesign_minimal - - test_moldesign_minimal_py2 - steps: - - command: deployment/print-environment.sh - name: env-report +# Run the tests in each environment - name: run-tests type: serial services: @@ -66,12 +75,33 @@ - test_moldesign_minimal_py2 - test_moldesign_minimal steps: + - command: deployment/print-environment.sh + name: environment - command: deployment/run-ci-tests.sh - name: testrunner + name: tests + + +# Upload coverage results +- name: push-coverage + service: coverage_pusher + command: deployment/push-coverage.sh -- name: publish-python - service: publisher + +# If this build is tagged with a PEP440-compliant version number AND the tests have passed, +# upload the package to PyPI, and push the docker images to dockerhub as +# autodesk/moldesign:[imagename]-[version-number] +- name: release # matches tags that are valid PEP440 versions + type: serial tag: '^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)((a|rc|b)(0|[1-9]\d*))?$' - command: deployment/publish.sh + steps: + - type: parallel + services: + - test_moldesign_complete + - test_moldesign_complete_py2 + steps: + - command: deployment/test-version-number.sh + - name: upload + command: deployment/publish.sh + service: publisher diff --git a/deployment/README.md b/deployment/README.md index 0602eb8..44507d8 100644 --- a/deployment/README.md +++ b/deployment/README.md @@ -1 +1,5 @@ -Files in this directory are for use with our Codeship CI/CD setup, and aren't generally useful outside of that context. \ No newline at end of file +Files in this directory are for use with our Codeship CI/CD setup, +and aren't generally useful outside of that context. + + +The build process is annotated in ../codeship-steps.yml \ No newline at end of file diff --git a/deployment/build-env.dockerfile b/deployment/build-env.dockerfile index 9f0a20f..9252040 100644 --- a/deployment/build-env.dockerfile +++ b/deployment/build-env.dockerfile @@ -10,6 +10,8 @@ RUN curl -fsSLO https://get.docker.com/builds/Linux/x86_64/docker-17.04.0-ce.tgz && mv docker/docker /usr/local/bin \ && rm -r docker docker-17.04.0-ce.tgz -ADD . /opt/molecular-design-toolkit -RUN pip install -r /opt/molecular-design-toolkit/DockerMakefiles/requirements.txt +ADD ./DockerMakefiles/requirements.txt /tmp/reqs.txt +RUN pip install -r /tmp/reqs.txt \ + && pip install coveralls twine +ADD . /opt/molecular-design-toolkit diff --git a/deployment/publish.sh b/deployment/publish.sh index 8756aaa..85ba9f8 100755 --- a/deployment/publish.sh +++ b/deployment/publish.sh @@ -8,18 +8,25 @@ # fail immediately if any command fails: set -e -pyversion=$(python -m moldesign version | tail -n 1) +echo "Now deploying moldesign-${CI_BRANCH}" -if [ "${pyversion}" == "${CI_BRANCH}" ]; then - echo "Deploying version ${CI_BRANCH}" -else - echo "Can't publish - moldesign package version '${pyversion}' differs from its Git tag '${CI_BRANCH}'" - exit 1 -fi +docker login -u ${DOCKERHUB_USER} -p ${DOCKERHUB_PASSWORD} -# Copy build artifacts -sdist=moldesign-${pyversion}.tar.gz -docker run moldesign_py_build:dev -v ./tmp/dists:/hostdists cp dist/${sdist} /hostdists +# Copy python package out of the docker image +sdist=moldesign-${CI_BRANCH}.tar.gz +docker run moldesign_py_build:dev cat dist/${sdist} > /opt/dist/${sdist} + +# Push images to dockerhub +for img in moldesign_minimal \ + moldesign_minimal_py2 \ + moldesign_complete \ + moldesign_complete_py2 \ + moldesign_notebook; do + docker push ${REPO}${img}-${CI_BRANCH} | tee -a push.log | egrep -i 'pull|already' +done + + +# Push python package to PyPI echo "Uploading version ${CI_BRANCH} to PyPI:" -twine upload -u ${PYPI_USER} -p ${PYPI_PASSWORD} ./tmp/dists/moldesign-${pyversion}.tar.gz +twine upload -u ${PYPI_USER} -p ${PYPI_PASSWORD} /opt/dist/${sdist} diff --git a/deployment/pull-cache.sh b/deployment/pull-cache.sh index 737a51c..362aa1a 100755 --- a/deployment/pull-cache.sh +++ b/deployment/pull-cache.sh @@ -14,7 +14,7 @@ function run-pull(){ # pull an image. If successful, retags the image with the "cache" tag img=$1 tag=$2 - imgpath="autodesk/moldesign:${img}-${tag}" + imgpath="${REPO}${img}-${tag}" echocmd docker pull ${imgpath} | tee -a pull.log | egrep -i 'pull|already'; @@ -22,7 +22,7 @@ function run-pull(){ if [ "$success" -ne 0 ]; then return ${success}; else - docker tag ${imgpath} moldesign/${img}:cache + docker tag ${imgpath} ${img}:cache fi } @@ -34,6 +34,7 @@ for img in moldesign_minimal \ moldesign_minimal_py2 \ moldesign_complete \ moldesign_complete_py2 \ + moldesign_py_build \ moldesign_notebook; do run-pull ${img} ${CI_BRANCH}-devbuild || run-pull ${img} master || \ echo " --> Failed to pull cache for ${img}" diff --git a/deployment/push.sh b/deployment/push-and-tag.sh similarity index 54% rename from deployment/push.sh rename to deployment/push-and-tag.sh index 53a54e4..9655ec6 100755 --- a/deployment/push.sh +++ b/deployment/push-and-tag.sh @@ -1,5 +1,7 @@ #!/bin/bash +set -e + if [ -z ${CI_BRANCH} ]; then echo "\$CI_BRANCH" var not set. exit 1 @@ -18,8 +20,11 @@ function echocmd() { docker login -u ${DOCKERHUB_USER} -p ${DOCKERHUB_PASSWORD} for img in $@; do - remote_img=autodesk/moldesign:${img}-${CI_BRANCH}-devbuild + build_img=${img}:dev + release_tag=${REPO}${img}-${CI_BRANCH} + artifact_tag=${release_tag}-devbuild - echocmd docker tag ${img}:dev ${remote_img} - echocmd docker push ${remote_img} | tee -a push.log | egrep -i 'push|already'; + echocmd docker tag ${build_img} ${release_tag} + echocmd docker tag ${build_img} ${artifact_tag} + echocmd docker push ${artifact_tag} | tee -a push.log | egrep -i 'push|already'; done \ No newline at end of file diff --git a/deployment/push-coverage.sh b/deployment/push-coverage.sh new file mode 100755 index 0000000..214da24 --- /dev/null +++ b/deployment/push-coverage.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +# combines coverage from all test environments and pushes it to coveralls.io + +set -e + +mkdir -p /opt/reports/env-coverage +if $(cp /opt/reports/env-coverage/coverage.* /opt/reports/); then + coverage combine /opt/reports/coverage.* + coveralls || echo "Failed to upload coverage to coveralls.io" +else + echo "No coverage files found, skipping coveralls upload" +fi diff --git a/deployment/run-ci-tests.sh b/deployment/run-ci-tests.sh index be6d0f3..a832708 100755 --- a/deployment/run-ci-tests.sh +++ b/deployment/run-ci-tests.sh @@ -1,15 +1,27 @@ #!/usr/bin/env bash +# Drives tests for our CI system. This looks for the following environment variables: +# Defined by codeship +# - CI_BRANCH +# - CI_COMMIT_MESSAGE +# - PROJECT_ID +# Defined in ../codeship-services.yml +# - TESTENV +# - PYVERSION set -e # fail immediately if any command fails +if [ -z "${CI_BRANCH}" ]; then + echo "FAILURE: Variable \$CI_BRANCH not defined." + exit 1 +fi + install_location=$(python -c "import moldesign, os; print(moldesign.__path__[0])") test_location=$(dirname "${install_location}") VERSION="${TESTENV}.py${PYVERSION}" -PYTESTFLAGS="moldesign/_tests/ -n 2 --spec --durations=20 --junit-xml=/opt/reports/junit.${VERSION}.xml --timeout=3600 --tb=short" -if [ "${VERSION}" == "complete.py3" ]; then - PYTESTFLAGS="${PYTESTFLAGS} --cov moldesign --cov-config /opt/molecular-design-toolkit/.coveragerc" -fi +PYTESTFLAGS="moldesign/_tests/ -n 2 --spec --durations=20 + --junit-xml=/opt/reports/junit.${VERSION}.xml --timeout=3600 --tb=short + --cov moldesign --cov-config /opt/molecular-design-toolkit/.coveragerc" function send_status_update(){ @@ -19,22 +31,36 @@ function send_status_update(){ function check_if_tests_should_run(){ echo "Should I run the tests in this environment?" - runthem=false - if [[ "${CI_COMMIT_MESSAGE}" == *"--fast-ci-tests"* && "${VERSION}" != "complete.py3" ]]; then + if [[ "${CI_COMMIT_MESSAGE}" == *"--no-tests"* ]]; then + echo "NO: found \"--skip-ci-tests\" flag in commit message; will not run any test suites" + exit 0 + fi + + if [[ "${CI_COMMIT_MESSAGE}" == *"--fast-tests"* && "${VERSION}" != "complete.py3" ]]; then echo "NO: found \"--fast-ci-tests\" flag in commit message; run complete.py3 only" exit 0 fi + if [[ "${CI_BRANCH}" =~ ^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)((a|rc|b)(0|[1-9]\d*))?$ ]] + then + echo "YES: this is a release version: \"${CI_BRANCH}\"" + return 0 + else # otherwise, point to the appropriate docker image tag + mkdir -p ~/.moldesign + echo "default_version_tag: ${CI_BRANCH}" >> ~/.moldesign/moldesign.yml + fi + + if [ "${TESTENV}" == "complete" ]; then - runthem=true echo "YES: always run in 'complete' environment" + return 0 fi case "${CI_BRANCH}" in master|deploy|dev) - runthem=true echo "YES: always run in branch \"${CI_BRANCH}\"" + return 0 ;; esac @@ -56,6 +82,7 @@ function run_tests(){ send_status_update "na" "Starting tests for ${VERSION}" cd ${test_location} + echo echo "Test command running in working dir '$(pwd)':" echo "py.test ${PYTESTFLAGS}" @@ -63,22 +90,17 @@ function run_tests(){ py.test ${PYTESTFLAGS} | tee /opt/reports/pytest.${VERSION}.log exitstat=${PIPESTATUS[0]} - statline="$(tail -n1 /opt/reports/pytest.${VERSION}.log)" + # Make a copy of the coverage report + mkdir -p /opt/reports/env-coverage/ + cp .coverage /opt/reports/env-coverage/coverage.${VERSION} + echo 'Test status:' echo ${statline} send_status_update "${exitstat}" "${statline}" - if [ "${VERSION}" == "complete.py3" ]; then - if [[ ${exitstat} == 0 && "$CI_BRANCH" != "" ]]; then - coveralls || echo "Failed to upload code coverage stats" - else - echo "Skipping coveralls upload: tests not passing or \$CI_COMMIT not set." - fi - fi - exit ${exitstat} } diff --git a/deployment/test-version-number.sh b/deployment/test-version-number.sh new file mode 100755 index 0000000..73ad170 --- /dev/null +++ b/deployment/test-version-number.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +set -e + +if [ -z "${CI_BRANCH}" ]; then + echo "Error: env var CI_BRANCH not set" + exit 1 +fi + + +pyversion=$(python -m moldesign version | tail -n 1) + +echo "Expecting moldesign==${CI_BRANCH}" +echo "Found moldesign==${pyversion}" + + +if [ "${pyversion}" == "${CI_BRANCH}" ]; then + echo "All good" + exit 0 +else + echo "Error: moldesign package version '${pyversion}' differs from its Git tag '${CI_BRANCH}'" + exit 1 +fi