diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..eb61588 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,63 @@ +version: 2 +jobs: + build: + docker: + - image: circleci/python + steps: + - checkout + - setup_remote_docker + + - run: + name: Create version.json + command: | + printf '{"commit":"%s","version":"%s","source":"https://github.com/%s/%s","build":"%s"}\n' \ + "$CIRCLE_SHA1" \ + "$CIRCLE_TAG" \ + "$CIRCLE_PROJECT_USERNAME" \ + "$CIRCLE_PROJECT_REPONAME" \ + "$CIRCLE_BUILD_URL" | tee version.json + - store_artifacts: + path: version.json + + - run: + name: Build deployment container image + command: docker build -t app:build . + - run: + name: Test flake8 + command: docker run -it app:build test_flake8 + - run: + name: Test nose + command: docker run -it app:build test_nose + - run: + name: Functional tests + command: docker run -it app:build test_functional + - run: + name: Push to Dockerhub + command: | + if [ "${CIRCLE_BRANCH}" == "master" ]; then + bin/ci/deploy-dockerhub.sh latest + fi + if [[ "${CIRCLE_BRANCH}" == feature* ]] || [[ "${CIRCLE_BRANCH}" == dockerpush* ]]; then + bin/ci/deploy-dockerhub.sh "$CIRCLE_BRANCH" + fi + if [ -n "${CIRCLE_TAG}" ]; then + bin/ci/deploy-dockerhub.sh "$CIRCLE_TAG" + fi + +workflows: + version: 2 + + # workflow jobs are _not_ run in tag builds by default + # we use filters to whitelist jobs that should be run for tags + + # workflow jobs are run in _all_ branch builds by default + # we use filters to blacklist jobs that shouldn't be run for a branch + + # see: https://circleci.com/docs/2.0/workflows/#git-tag-job-execution + + build-test-push: + jobs: + - build: + filters: + tags: + only: /.*/ diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..6fb6758 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,8 @@ +*.pyc +local +*.egg-info +*.swp +\.coverage +*~ +nosetests.xml +syncserver.db diff --git a/Dockerfile b/Dockerfile index 7b3c614..1196a20 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:2.7-alpine3.7 +FROM python:2.7-alpine RUN addgroup -g 1001 app \ && adduser -u 1001 -S -D -G app -s /usr/sbin/nologin app @@ -17,11 +17,12 @@ RUN apk --no-cache update \ && pip install --upgrade --no-cache-dir -r dev-requirements.txt \ && apk del g++ -COPY ./syncserver /app/syncserver -COPY ./setup.py /app +COPY . /app RUN python ./setup.py develop # run as non priviledged user USER app -ENTRYPOINT ["/usr/bin/dumb-init", "--"] +# run the server by default +ENTRYPOINT ["/usr/bin/dumb-init", "/app/docker-entrypoint.sh"] +CMD ["server"] diff --git a/bin/ci/deploy-dockerhub.sh b/bin/ci/deploy-dockerhub.sh new file mode 100755 index 0000000..39587c7 --- /dev/null +++ b/bin/ci/deploy-dockerhub.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +# THIS IS MEANT TO BE RUN BY CI + +set -e + +# Usage: retry MAX CMD... +# Retry CMD up to MAX times. If it fails MAX times, returns failure. +# Example: retry 3 docker push "$DOCKERHUB_REPO:$TAG" +function retry() { + max=$1 + shift + count=1 + until "$@"; do + count=$((count + 1)) + if [[ $count -gt $max ]]; then + return 1 + fi + echo "$count / $max" + done + return 0 +} + +# configure docker creds +retry 3 echo "$DOCKER_PASS" | docker login -u="$DOCKER_USER" --password-stdin + +# docker tag and push git branch to dockerhub +if [ -n "$1" ]; then + [ "$1" == master ] && TAG=latest || TAG="$1" + docker tag app:build "$DOCKERHUB_REPO:$TAG" || + (echo "Couldn't tag app:build as $DOCKERHUB_REPO:$TAG" && false) + retry 3 docker push "$DOCKERHUB_REPO:$TAG" || + (echo "Couldn't push $DOCKERHUB_REPO:$TAG" && false) + echo "Pushed $DOCKERHUB_REPO:$TAG" +fi diff --git a/circle.yml b/circle.yml deleted file mode 100644 index 0c9d424..0000000 --- a/circle.yml +++ /dev/null @@ -1,75 +0,0 @@ -# These environment variables must be set in CircleCI UI -# -# DOCKERHUB_REPO - docker hub repo, format: / -# DOCKER_EMAIL - login info for docker hub -# DOCKER_USER -# DOCKER_PASS -# -machine: - services: - - docker - -dependencies: - # make sure to keep the docker cache dir - cache_directories: - - "~/docker" - - override: - - docker info - - # build the container, use circleci's docker cache workaround - # only use 1 image per day to keep the cache size from getting - # too big and slowing down the build - - I="image-$(date +%j).tar"; if [[ -e ~/docker/$I ]]; then echo "Loading $I"; docker load -i ~/docker/$I; fi - - # create version.json - - > - printf '{"commit":"%s","version":"%s","source":"https://github.com/%s/%s","build":"%s"}\n' - "$CIRCLE_SHA1" - "$CIRCLE_TAG" - "$CIRCLE_PROJECT_USERNAME" - "$CIRCLE_PROJECT_REPONAME" - "$CIRCLE_BUILD_URL" - > version.json - - cp version.json $CIRCLE_ARTIFACTS - - - docker build -t syncserver:build . - - - > - docker images --no-trunc | - awk '/^app/ {print $3}' | - tee $CIRCLE_ARTIFACTS/docker-image-shasum256.txt - - # Clean up any old images and save the new one - - I="image-$(date +%j).tar"; mkdir -p ~/docker; rm ~/docker/*; docker save syncserver:build > ~/docker/$I; ls -l ~/docker - -test: - override: - - docker run syncserver:build /bin/sh -c "flake8 syncserver && nosetests syncstorage.tests" - -# appropriately tag and push the container to dockerhub -deployment: - hub_latest: - # push certain branches, they are used by fxa-dev for deployment - branch: /^(master|feature.*|dockerpush.*)$/ - commands: - - "[ ! -z $DOCKERHUB_REPO ]" - - docker login -e "$DOCKER_EMAIL" -u "$DOCKER_USER" -p "$DOCKER_PASS" - - | - if [ $CIRCLE_BRANCH = "master" ]; then - CIRCLE_BRANCH=latest - fi - echo ${DOCKERHUB_REPO}:${CIRCLE_BRANCH} - docker tag syncserver:build ${DOCKERHUB_REPO}:${CIRCLE_BRANCH} - docker push ${DOCKERHUB_REPO}:${CIRCLE_BRANCH} - - - hub_releases: - # push all tags - tag: /.*/ - commands: - - "[ ! -z $DOCKERHUB_REPO ]" - - docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS - - "docker tag syncserver:build ${DOCKERHUB_REPO}:${CIRCLE_TAG}" - - "docker images" - - "docker push ${DOCKERHUB_REPO}:${CIRCLE_TAG}" diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh new file mode 100755 index 0000000..0a1b01c --- /dev/null +++ b/docker-entrypoint.sh @@ -0,0 +1,49 @@ +#!/bin/sh + +cd $(dirname $0) +case "$1" in + server) + export SYNCSERVER_SQLURI="${SYNCSERVER_SQLURI:-sqlite:///tmp/syncserver.db}" + exec gunicorn \ + --bind ${HOST-0.0.0.0}:${PORT-5000}\ + syncserver.wsgi_app + ;; + + test_all) + $0 test_flake8 + $0 test_nose + $0 test_functional + ;; + + test_flake8) + echo "test - flake8" + flake8 syncserver + ;; + + test_nose) + echo "test - nose" + nosetests --verbose --nocapture syncstorage.tests + ;; + + test_functional) + echo "test - functional" + # run functional tests + gunicorn --paste ./syncserver/tests.ini & + SERVER_PID=$! + sleep 2 + + $0 test_endpoint http://localhost:5000 + + kill $SERVER_PID + ;; + + test_endpoint) + exec python -m syncstorage.tests.functional.test_storage \ + --use-token-server $2/token/1.0/sync/1.5 + ;; + + *) + echo "Unknown CMD, $1" + exit 1 + ;; +esac diff --git a/syncserver/tests.ini b/syncserver/tests.ini index fc9ea4b..e1de5ca 100644 --- a/syncserver/tests.ini +++ b/syncserver/tests.ini @@ -13,7 +13,7 @@ use = egg:SyncServer public_url = http://localhost:5000/ # This defines the database in which to store all server data. -#sqluri = sqlite:////tmp/syncserver.db +sqluri = sqlite:///:memory: # This is a secret key used for signing authentication tokens. #secret = INSERT_SECRET_KEY_HERE