aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Johannes Christ <[email protected]>2019-08-06 20:26:43 +0200
committerGravatar GitHub <[email protected]>2019-08-06 20:26:43 +0200
commit9bcbdf974d157bc4cafb0e8f96e51beef167e5fe (patch)
treec3c3ef681e97ebe72392dffc00054127671f5b17
parentMerge pull request #35 from python-discord/readme (diff)
parentCI: output coverage report to stdout (diff)
Merge pull request #33 from python-discord/ci
CI Improvements
-rw-r--r--azure-pipelines.yml366
-rwxr-xr-xscripts/check_dockerfiles.sh86
-rwxr-xr-xscripts/dev.sh9
3 files changed, 316 insertions, 145 deletions
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index 98d64bf..424b1a3 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -1,144 +1,228 @@
# https://aka.ms/yaml
jobs:
-- job: test
- displayName: 'Lint & Test'
-
- pool:
- vmImage: 'Ubuntu-16.04'
-
- steps:
- - script: docker build -t pythondiscord/snekbox-base:latest -f docker/base.Dockerfile .
- displayName: 'Build Base Image'
-
- - script: |
- docker build -t pythondiscord/snekbox-venv:dev -f docker/venv.Dockerfile --build-arg DEV=1 .
- displayName: 'Build Development Image'
-
- - script: |
- docker run \
- -td \
- --name snekbox_test \
- --privileged \
- --network host \
- -h pdsnk-dev \
- -e PYTHONDONTWRITEBYTECODE=1 \
- -e PIPENV_PIPFILE="/snekbox/Pipfile" \
- -e ENV="${PWD}/scripts/.profile" \
- -v "${PWD}":"${PWD}" \
- -w "${PWD}"\
- --entrypoint /bin/ash \
- pythondiscord/snekbox-venv:dev
- displayName: 'Start Container'
-
- - script: |
- docker exec snekbox_test /bin/ash -c \
- 'pipenv run lint --format junit-xml --output-file test-lint.xml'
- displayName: 'Run Linter'
-
- - task: PublishTestResults@2
- condition: succeededOrFailed()
- displayName: 'Publish Lint Results'
- inputs:
- testResultsFiles: '**/test-lint.xml'
- testRunTitle: 'Lint Results'
-
- - script: sudo swapoff -a
- displayName: 'Disable swap memory'
-
- - script: |
- docker exec snekbox_test /bin/ash -c \
- 'pipenv run coverage run -m xmlrunner'
- displayName: 'Run Unit Tests'
-
- - task: PublishTestResults@2
- condition: succeededOrFailed()
- displayName: 'Publish Test Results'
- inputs:
- testResultsFiles: '**/TEST-*.xml'
- testRunTitle: 'Test Results'
-
- - script: |
- docker exec snekbox_test /bin/ash -c \
- 'pipenv run coverage xml'
- displayName: 'Generate Coverage Report'
-
- - task: PublishCodeCoverageResults@1
- displayName: 'Publish Coverage Results'
- condition: succeededOrFailed()
- inputs:
- codeCoverageTool: Cobertura
- summaryFileLocation: '**/coverage.xml'
-
-- job: build
- displayName: 'Build'
- dependsOn: test
-
- variables:
- BASE_CHANGED: true
- VENV_CHANGED: true
-
- steps:
- - task: Docker@1
- displayName: 'Login: Docker Hub'
-
- inputs:
- containerregistrytype: 'Container Registry'
- dockerRegistryEndpoint: 'DockerHub'
- command: 'login'
-
- - script: |
- REQUEST_URL="https://dev.azure.com/python-discord/${SYSTEM_TEAMPROJECTID}/_apis/build/builds?queryOrder=finishTimeDescending&resultFilter=succeeded&\$top=1&repositoryType=${BUILD_REPOSITORY_PROVIDER}&repositoryId=${BUILD_REPOSITORY_NAME}&branchName=${BUILD_SOURCEBRANCH}&api-version=5.0"
- echo "Retrieving previous build's commit using $REQUEST_URL"
- RESPONSE="$(curl -sSL "${REQUEST_URL}")"
-
- if [[ $BUILD_REASON = "PullRequest" ]]; then
- PREV_COMMIT="$(echo "${RESPONSE}" | grep -Po '"pr\.sourceSha"\s*:\s*"\K.*?[^\\](?="\s*[,}])')"
- if [[ -z $PREV_COMMIT ]]; then
- echo "Could not retrieve the previous build's commit. Falling back to the head of the target branch."
- PREV_COMMIT="origin/$SYSTEM_PULLREQUEST_TARGETBRANCH"
- fi
- else
- PREV_COMMIT="$(echo "${RESPONSE}" | grep -Po '"sourceVersion"\s*:\s*"\K.*?[^\\](?="\s*[,}])')"
- fi
-
- if [[ -n $PREV_COMMIT ]]; then
- echo "Using $PREV_COMMIT to compare diffs."
-
- if [[ -z "$(git diff $PREV_COMMIT -- docker/base.Dockerfile)" ]]; then
- echo "No changes detected in docker/base.Dockerfile. The base image will not be built."
- echo "##vso[task.setvariable variable=BASE_CHANGED]false"
- fi
-
- if [[ -z "$(git diff $PREV_COMMIT -- docker/venv.Dockerfile Pipfile*)" ]]; then
- echo "No changes detected in docker/venv.Dockerfile or the Pipfiles. The venv image will not be built."
- echo "##vso[task.setvariable variable=VENV_CHANGED]false"
- fi
- else
- echo "No previous commit was retrieved. Either the previous build is too old and was deleted or the branch was empty before this build. All images will be built."
- fi
- displayName: 'Check Changed Files'
-
- - script: docker build -t pythondiscord/snekbox-base:latest -f docker/base.Dockerfile .
- displayName: 'Build Base Image'
- condition: and(succeeded(), eq(variables.BASE_CHANGED, 'true'))
-
- - script: docker build -t pythondiscord/snekbox-venv:latest -f docker/venv.Dockerfile .
- displayName: 'Build Virtual Environment Image'
- condition: and(succeeded(), or(eq(variables.BASE_CHANGED, 'true'), eq(variables.VENV_CHANGED, 'true')))
-
- - script: docker build -t pythondiscord/snekbox:latest -f docker/Dockerfile .
- displayName: 'Build Final Image'
- condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'))
-
- - script: docker push pythondiscord/snekbox-base:latest
- displayName: 'Push Base Image to Dockerhub'
- condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'), eq(variables.BASE_CHANGED, 'true'))
-
- - script: docker push pythondiscord/snekbox-venv:latest
- displayName: 'Push Virtual Environment Image to Dockerhub'
- condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'), or(eq(variables.BASE_CHANGED, 'true'), eq(variables.VENV_CHANGED, 'true')))
-
- - script: docker push pythondiscord/snekbox:latest
- displayName: 'Push Final Image to Dockerhub'
- condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'))
+ - job: test
+ displayName: 'Lint & Test'
+
+ pool:
+ vmImage: 'ubuntu-16.04'
+
+ steps:
+ - task: ShellScript@2
+ displayName: 'Check If Images Need to Be Built'
+ name: check
+ inputs:
+ scriptPath: scripts/check_dockerfiles.sh
+
+ # Without a login the following Docker build tasks won't add image tags.
+ - task: Docker@2
+ displayName: 'Log into Docker Hub'
+ inputs:
+ command: login
+ containerRegistry: DockerHubV2
+
+ # The venv image depends on this image. Build it if it can't be pulled
+ # from Docker Hub, which will be the case if the base Dockerfile has had
+ # changes.
+ - task: Docker@2
+ displayName: 'Build Base Image'
+ condition: and(succeeded(), ne(variables['check.BASE_PULL'], True))
+ inputs:
+ command: build
+ repository: pythondiscord/snekbox-base
+ tags: latest
+ Dockerfile: docker/base.Dockerfile
+ buildContext: .
+
+ # The dev image is never pushed and therefore is always built.
+ - task: Docker@2
+ displayName: 'Build Development Image'
+ inputs:
+ command: build
+ repository: pythondiscord/snekbox-venv
+ tags: dev
+ Dockerfile: docker/venv.Dockerfile
+ buildContext: .
+ arguments: --build-arg DEV=1
+
+ # The linter and all tests run inside this container.
+ - script: |
+ docker run \
+ --tty \
+ --detach \
+ --name snekbox_test \
+ --privileged \
+ --network host \
+ --hostname pdsnk-dev \
+ -e PYTHONDONTWRITEBYTECODE=1 \
+ -e PIPENV_PIPFILE="/snekbox/Pipfile" \
+ -e ENV="${PWD}/scripts/.profile" \
+ --volume "${PWD}":"${PWD}" \
+ --workdir "${PWD}"\
+ --entrypoint /bin/ash \
+ pythondiscord/snekbox-venv:dev
+ displayName: 'Start Container'
+
+ - script: |
+ docker exec snekbox_test /bin/ash -c \
+ 'pipenv run lint --format junit-xml --output-file test-lint.xml'
+ displayName: 'Run Linter'
+
+ - task: PublishTestResults@2
+ displayName: 'Publish Lint Results'
+ condition: succeededOrFailed()
+ inputs:
+ testResultsFiles: '**/test-lint.xml'
+ testRunTitle: 'Lint Results'
+
+ # Memory limit tests would fail if this isn't disabled.
+ - script: sudo swapoff -a
+ displayName: 'Disable Swap Memory'
+
+ - script: |
+ docker exec snekbox_test /bin/ash -c \
+ 'pipenv run coverage run -m xmlrunner'
+ displayName: 'Run Unit Tests'
+
+ - task: PublishTestResults@2
+ displayName: 'Publish Test Results'
+ condition: succeededOrFailed()
+ inputs:
+ testResultsFiles: '**/TEST-*.xml'
+ testRunTitle: 'Test Results'
+
+ # Run report too because the XML report doesn't output to stdout.
+ - script: |
+ docker exec snekbox_test /bin/ash -c \
+ 'pipenv run /bin/ash -c "coverage report && coverage xml"'
+ displayName: 'Generate Coverage Report'
+
+ - task: PublishCodeCoverageResults@1
+ displayName: 'Publish Coverage Results'
+ condition: succeededOrFailed()
+ inputs:
+ codeCoverageTool: Cobertura
+ summaryFileLocation: '**/coverage.xml'
+
+ # When a pull request, only perform this job if images need to be built.
+ # It's always performed for non-PRs because the final image will always need
+ # to be built.
+ - job: build
+ displayName: 'Build'
+ condition: >
+ and(
+ succeeded(),
+ or(
+ ne(variables['Build.Reason'], 'PullRequest'),
+ eq(coalesce(dependencies.test.outputs['check.BASE_CHANGED'], True), True),
+ eq(coalesce(dependencies.test.outputs['check.VENV_CHANGED'], True), True)
+ )
+ )
+ dependsOn: test
+
+ # coalesce() gives variables default values if they are null (i.e. unset).
+ variables:
+ BASE_CHANGED: $[ coalesce(dependencies.test.outputs['check.BASE_CHANGED'], True) ]
+ VENV_CHANGED: $[ coalesce(dependencies.test.outputs['check.VENV_CHANGED'], True) ]
+ BASE_PULL: $[ coalesce(dependencies.test.outputs['check.BASE_PULL'], False) ]
+
+ steps:
+ - task: Docker@2
+ displayName: 'Log into Docker Hub'
+ inputs:
+ command: login
+ containerRegistry: DockerHubV2
+
+ # Because this is the base image for the venv image, if the venv needs to
+ # be built, this base image must also be present. Build it if it has
+ # changed or can't be pulled from Docker Hub.
+ - task: Docker@2
+ displayName: 'Build Base Image'
+ condition: >
+ and(
+ succeeded(),
+ ne(variables.BASE_PULL, True),
+ or(
+ eq(variables.BASE_CHANGED, True),
+ eq(variables.VENV_CHANGED, True)
+ )
+ )
+ inputs:
+ command: build
+ repository: pythondiscord/snekbox-base
+ tags: latest
+ Dockerfile: docker/base.Dockerfile
+ buildContext: .
+
+ # Also build this image if base has changed - even if this image hasn't.
+ - task: Docker@2
+ displayName: 'Build Virtual Environment Image'
+ condition: >
+ and(
+ succeeded(),
+ or(
+ eq(variables.BASE_CHANGED, True),
+ eq(variables.VENV_CHANGED, True)
+ )
+ )
+ inputs:
+ command: build
+ repository: pythondiscord/snekbox-venv
+ tags: latest
+ Dockerfile: docker/venv.Dockerfile
+ buildContext: .
+
+ # Always build this image unless it's for a pull request.
+ - task: Docker@2
+ displayName: 'Build Final Image'
+ condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'))
+ inputs:
+ command: build
+ repository: pythondiscord/snekbox
+ tags: latest
+ Dockerfile: docker/Dockerfile
+ buildContext: .
+
+ # Push images only after they've all successfully been built.
+ # These have the same conditions as the build tasks. However, for safety,
+ # a condition for not being a pull request is added.
+ - task: Docker@2
+ displayName: 'Push Base Image'
+ condition: >
+ and(
+ succeeded(),
+ ne(variables['Build.Reason'], 'PullRequest'),
+ ne(variables.BASE_PULL, True),
+ or(
+ eq(variables.BASE_CHANGED, True),
+ eq(variables.VENV_CHANGED, True)
+ )
+ )
+ inputs:
+ command: push
+ repository: pythondiscord/snekbox-base
+ tags: latest
+
+ - task: Docker@2
+ displayName: 'Push Virtual Environment Image'
+ condition: >
+ and(
+ succeeded(),
+ ne(variables['Build.Reason'], 'PullRequest'),
+ or(
+ eq(variables.BASE_CHANGED, True),
+ eq(variables.VENV_CHANGED, True)
+ )
+ )
+ inputs:
+ command: push
+ repository: pythondiscord/snekbox-venv
+ tags: latest
+
+ - task: Docker@2
+ displayName: 'Push Final Image'
+ condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'))
+ inputs:
+ command: push
+ repository: pythondiscord/snekbox
+ tags: latest
diff --git a/scripts/check_dockerfiles.sh b/scripts/check_dockerfiles.sh
new file mode 100755
index 0000000..c84c61f
--- /dev/null
+++ b/scripts/check_dockerfiles.sh
@@ -0,0 +1,86 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+exec 3>&1 # New file descriptor to stdout
+
+BASE_URL="https://dev.azure.com/\
+python-discord/${SYSTEM_TEAMPROJECTID}/_apis/build/builds?\
+queryOrder=finishTimeDescending&\
+resultFilter=succeeded&\
+\$top=1&\
+repositoryType=${BUILD_REPOSITORY_PROVIDER}&\
+repositoryId=${BUILD_REPOSITORY_NAME}&\
+api-version=5.0"
+
+get_build() {
+ set -e # Poor Ubuntu LTS doesn't have Bash 4.4's inherit_errexit
+
+ local branch="${1:?"get_build: argument 1 'branch' is unset"}"
+ local url="${BASE_URL}&branchName=${branch}"
+
+ printf '%s\n' "Retrieving the latest successful build using ${url}" >&3
+
+ local response
+ response="$(curl -sSL "${url}")"
+
+ if [[ -z "${response}" ]] \
+ || ! count="$(printf '%s' "${response}" | jq -re '.count')" \
+ || (( "${count}" < 1 ))
+ then
+ return 1
+ else
+ printf '%s' "${response}"
+ fi
+}
+
+# Get the previous commit
+if [[ "${BUILD_REASON}" = "PullRequest" ]]; then
+ if ! prev_commit="$(
+ get_build "${BUILD_SOURCEBRANCH}" \
+ | jq -re '.value[0].triggerInfo."pr.sourceSha"'
+ )"
+ then
+ echo \
+ "Could not retrieve the previous build's commit." \
+ "Falling back to the head of the target branch."
+
+ prev_commit="origin/${SYSTEM_PULLREQUEST_TARGETBRANCH}"
+ fi
+elif ! prev_commit="$(
+ get_build "${BUILD_SOURCEBRANCH}" \
+ | jq -re '.value[0].sourceVersion'
+ )"
+then
+ echo \
+ "No previous build was found." \
+ "Either the previous build is too old and was deleted" \
+ "or the branch was empty before this build." \
+ "All images will be built."
+ exit 0
+fi
+
+# Compare diffs
+head="$(git rev-parse HEAD)"
+printf '%s\n' "Comparing HEAD (${head}) against ${prev_commit}."
+
+if git diff --quiet "${prev_commit}" -- docker/base.Dockerfile; then
+ echo "No changes detected in docker/base.Dockerfile."
+ echo "##vso[task.setvariable variable=BASE_CHANGED;isOutput=true]False"
+else
+ # Always rebuild the venv if the base changes.
+ exit 0
+fi
+
+if git diff --quiet "${prev_commit}" -- docker/venv.Dockerfile Pipfile*; then
+ echo "No changes detected in docker/venv.Dockerfile or the Pipfiles."
+ echo "##vso[task.setvariable variable=VENV_CHANGED;isOutput=true]False"
+elif master_commit="$(
+ get_build "refs/heads/master" \
+ | jq -re '.value[0].sourceVersion'
+ )" \
+ && git diff --quiet "${master_commit}" -- docker/base.Dockerfile
+then
+ # Though base image hasn't changed, it's still needed to build the venv.
+ echo "Can pull base image from Docker Hub; no changes made since master."
+ echo "##vso[task.setvariable variable=BASE_PULL;isOutput=true]True"
+fi
diff --git a/scripts/dev.sh b/scripts/dev.sh
index 097690b..8f5b24f 100755
--- a/scripts/dev.sh
+++ b/scripts/dev.sh
@@ -34,16 +34,17 @@ fi
# The volume is mounted to same the path in the container as the source
# directory on the host to ensure coverage can find the source files.
docker run \
- -td \
+ --tty \
+ --detach \
--name snekbox_test \
--privileged \
--network host \
- -h pdsnk-dev \
+ --hostname pdsnk-dev \
-e PYTHONDONTWRITEBYTECODE=1 \
-e PIPENV_PIPFILE="/snekbox/Pipfile" \
-e ENV="${PWD}/scripts/.profile" \
- -v "${PWD}":"${PWD}" \
- -w "${PWD}"\
+ --volume "${PWD}":"${PWD}" \
+ --workdir "${PWD}"\
--entrypoint /bin/ash \
pythondiscord/snekbox-venv:dev \
>/dev/null \