├── .dockerignore ├── .github └── workflows │ └── main.yml ├── .gitignore ├── .gitlab-ci.yml ├── Dockerfile ├── Dockerfile.multi ├── README.md ├── _config-examples ├── multi-stage │ ├── .gitlab-ci.yml │ ├── circle.yml │ ├── compose │ │ ├── .gitlab-ci.yml │ │ ├── circle.yml │ │ └── github.yml │ └── github.yml └── single-stage │ ├── .gitlab-ci.yml │ ├── circle.yml │ ├── compose │ ├── .gitlab-ci.yml │ ├── circle.yml │ └── github.yml │ └── github.yml ├── circle.yml ├── docker-compose.multi.yml ├── docker-compose.yml ├── manage.py ├── project ├── __init__.py ├── api │ ├── models.py │ └── users.py ├── config.py ├── db │ ├── Dockerfile │ └── create.sql └── tests │ ├── __init__.py │ ├── base.py │ └── test_users.py └── requirements.txt /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | .github 3 | .gitignore 4 | .gitlab-ci.yml 5 | Dockerfile 6 | Dockerfile.multi 7 | README.md 8 | _config-examples 9 | circle.yml 10 | docker-compose.multi.yml 11 | docker-compose.yml 12 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Docker Build 2 | 3 | on: [push] 4 | 5 | env: 6 | CACHE_IMAGE: mjhea0/docker-ci-cache 7 | DOCKER_BUILDKIT: 1 8 | 9 | jobs: 10 | build: 11 | name: Build Docker Image 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout master 15 | uses: actions/checkout@v4 16 | - name: Log in to docker hub 17 | run: docker login -u ${{ secrets.REGISTRY_USER }} -p ${{ secrets.REGISTRY_PASS }} 18 | - name: Build from dockerfile 19 | run: | 20 | docker build \ 21 | --cache-from $CACHE_IMAGE:latest \ 22 | --tag $CACHE_IMAGE:latest \ 23 | --build-arg BUILDKIT_INLINE_CACHE=1 \ 24 | "." 25 | - name: Push to docker hub 26 | run: docker push $CACHE_IMAGE:latest 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | image: docker:stable 2 | services: 3 | - docker:dind 4 | 5 | variables: 6 | DOCKER_DRIVER: overlay2 7 | CACHE_IMAGE: mjhea0/docker-ci-cache 8 | DOCKER_BUILDKIT: 1 9 | 10 | stages: 11 | - build 12 | 13 | docker-build: 14 | stage: build 15 | before_script: 16 | - docker login -u $REGISTRY_USER -p $REGISTRY_PASS 17 | script: 18 | - docker build 19 | --cache-from $CACHE_IMAGE:latest 20 | --tag $CACHE_IMAGE:latest 21 | --file ./Dockerfile 22 | --build-arg BUILDKIT_INLINE_CACHE=1 23 | "." 24 | after_script: 25 | - docker push $CACHE_IMAGE:latest 26 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # pull base image 2 | FROM python:3.12.2-slim 3 | 4 | # install netcat 5 | RUN apt-get update && \ 6 | apt-get -y install netcat-traditional && \ 7 | apt-get clean 8 | 9 | # set working directory 10 | WORKDIR /usr/src/app 11 | 12 | # install requirements 13 | COPY ./requirements.txt . 14 | RUN pip install -r requirements.txt 15 | 16 | # add app 17 | COPY project ./project 18 | COPY manage.py . 19 | 20 | # run server 21 | CMD gunicorn -b 0.0.0.0:5000 manage:app 22 | -------------------------------------------------------------------------------- /Dockerfile.multi: -------------------------------------------------------------------------------- 1 | # base 2 | FROM python:3.12.2 as base 3 | COPY ./requirements.txt / 4 | RUN pip wheel --no-cache-dir --no-deps --wheel-dir /wheels -r requirements.txt 5 | 6 | # stage 7 | FROM python:3.12.2-slim 8 | RUN apt-get update && \ 9 | apt-get -y install netcat-traditional && \ 10 | apt-get clean 11 | WORKDIR /usr/src/app 12 | COPY --from=base /wheels /wheels 13 | COPY --from=base requirements.txt . 14 | RUN pip install --no-cache /wheels/* 15 | COPY . /usr/src/app 16 | CMD gunicorn -b 0.0.0.0:5000 manage:app 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [Faster CI Builds with Docker Cache](https://testdriven.io/blog/faster-ci-builds-with-docker-cache/) 2 | -------------------------------------------------------------------------------- /_config-examples/multi-stage/.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | # _config-examples/multi-stage/.gitlab-ci.yml 2 | 3 | image: docker:stable 4 | services: 5 | - docker:dind 6 | 7 | 8 | variables: 9 | DOCKER_DRIVER: overlay2 10 | CACHE_IMAGE: mjhea0/docker-ci-cache 11 | DOCKER_BUILDKIT: 1 12 | 13 | stages: 14 | - build 15 | 16 | docker-build: 17 | stage: build 18 | before_script: 19 | - docker login -u $REGISTRY_USER -p $REGISTRY_PASS 20 | script: 21 | - docker build 22 | --target base 23 | --cache-from $CACHE_IMAGE:base 24 | --tag $CACHE_IMAGE:base 25 | --file ./Dockerfile.multi 26 | --build-arg BUILDKIT_INLINE_CACHE=1 27 | "." 28 | - docker build 29 | --cache-from $CACHE_IMAGE:base 30 | --cache-from $CACHE_IMAGE:stage 31 | --tag $CACHE_IMAGE:stage 32 | --file ./Dockerfile.multi 33 | --build-arg BUILDKIT_INLINE_CACHE=1 34 | "." 35 | after_script: 36 | - docker push $CACHE_IMAGE:latest 37 | -------------------------------------------------------------------------------- /_config-examples/multi-stage/circle.yml: -------------------------------------------------------------------------------- 1 | # _config-examples/multi-stage/circle.yml 2 | 3 | version: 2.1 4 | 5 | jobs: 6 | build: 7 | machine: 8 | image: ubuntu-2204:2024.01.1 9 | environment: 10 | CACHE_IMAGE: mjhea0/docker-ci-cache 11 | DOCKER_BUILDKIT: 1 12 | steps: 13 | - checkout 14 | - setup_remote_docker 15 | - run: 16 | name: Log in to docker hub 17 | command: docker login -u $REGISTRY_USER -p $REGISTRY_PASS 18 | - run: 19 | name: Build base from dockerfile 20 | command: | 21 | docker build \ 22 | --target base \ 23 | --cache-from $CACHE_IMAGE:base \ 24 | --tag $CACHE_IMAGE:base \ 25 | --file ./Dockerfile.multi \ 26 | --build-arg BUILDKIT_INLINE_CACHE=1 \ 27 | "." 28 | - run: 29 | name: Build stage from dockerfile 30 | command: | 31 | docker build \ 32 | --cache-from $CACHE_IMAGE:base \ 33 | --cache-from $CACHE_IMAGE:stage \ 34 | --tag $CACHE_IMAGE:stage \ 35 | --file ./Dockerfile.multi \ 36 | --build-arg BUILDKIT_INLINE_CACHE=1 \ 37 | "." 38 | - run: 39 | name: Push base image to docker hub 40 | command: docker push $CACHE_IMAGE:base 41 | - run: 42 | name: Push stage image to docker hub 43 | command: docker push $CACHE_IMAGE:stage 44 | -------------------------------------------------------------------------------- /_config-examples/multi-stage/compose/.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | # _config-examples/multi-stage/compose/.gitlab-ci.yml 2 | 3 | image: docker/compose:latest 4 | services: 5 | - docker:dind 6 | 7 | variables: 8 | DOCKER_DRIVER: overlay 9 | CACHE_IMAGE: mjhea0/docker-ci-cache 10 | DOCKER_BUILDKIT: 1 11 | COMPOSE_DOCKER_CLI_BUILD: 1 12 | 13 | stages: 14 | - build 15 | 16 | docker-build: 17 | stage: build 18 | before_script: 19 | - docker login -u $REGISTRY_USER -p $REGISTRY_PASS 20 | script: 21 | - docker build 22 | --target base 23 | --cache-from $CACHE_IMAGE:base 24 | --tag $CACHE_IMAGE:base 25 | --file ./Dockerfile.multi 26 | --build-arg BUILDKIT_INLINE_CACHE=1 27 | "." 28 | - docker-compose -f docker-compose.multi.yml build --build-arg BUILDKIT_INLINE_CACHE=1 29 | after_script: 30 | - docker push $CACHE_IMAGE:base 31 | - docker push $CACHE_IMAGE:stage 32 | -------------------------------------------------------------------------------- /_config-examples/multi-stage/compose/circle.yml: -------------------------------------------------------------------------------- 1 | # _config-examples/multi-stage/compose/circle.yml 2 | 3 | version: 2.1 4 | 5 | jobs: 6 | build: 7 | machine: 8 | image: ubuntu-2204:2024.01.1 9 | environment: 10 | CACHE_IMAGE: mjhea0/docker-ci-cache 11 | DOCKER_BUILDKIT: 1 12 | COMPOSE_DOCKER_CLI_BUILD: 1 13 | steps: 14 | - checkout 15 | - run: 16 | name: Log in to docker hub 17 | command: docker login -u $REGISTRY_USER -p $REGISTRY_PASS 18 | - run: 19 | name: Build base from dockerfile 20 | command: | 21 | docker build \ 22 | --target base \ 23 | --cache-from $CACHE_IMAGE:base \ 24 | --tag $CACHE_IMAGE:base \ 25 | --file ./Dockerfile.multi \ 26 | --build-arg BUILDKIT_INLINE_CACHE=1 \ 27 | "." 28 | - run: 29 | name: Build Docker images 30 | command: docker-compose -f docker-compose.multi.yml build --build-arg BUILDKIT_INLINE_CACHE=1 31 | - run: 32 | name: Push base image to docker hub 33 | command: docker push $CACHE_IMAGE:base 34 | - run: 35 | name: Push stage image to docker hub 36 | command: docker push $CACHE_IMAGE:stage 37 | -------------------------------------------------------------------------------- /_config-examples/multi-stage/compose/github.yml: -------------------------------------------------------------------------------- 1 | # _config-examples/multi-stage/compose/github.yml 2 | 3 | name: Docker Build 4 | 5 | on: [push] 6 | 7 | env: 8 | CACHE_IMAGE: mjhea0/docker-ci-cache 9 | DOCKER_BUILDKIT: 1 10 | COMPOSE_DOCKER_CLI_BUILD: 1 11 | 12 | jobs: 13 | build: 14 | name: Build Docker Image 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout master 18 | uses: actions/checkout@v4 19 | - name: Log in to docker hub 20 | run: docker login -u ${{ secrets.REGISTRY_USER }} -p ${{ secrets.REGISTRY_PASS }} 21 | - name: Build base from dockerfile 22 | run: | 23 | docker build \ 24 | --target base \ 25 | --cache-from $CACHE_IMAGE:base \ 26 | --tag $CACHE_IMAGE:base \ 27 | --file ./Dockerfile.multi \ 28 | --build-arg BUILDKIT_INLINE_CACHE=1 \ 29 | "." 30 | - name: Build images 31 | run: docker-compose -f docker-compose.multi.yml build --build-arg BUILDKIT_INLINE_CACHE=1 32 | - name: Push base image to docker hub 33 | run: docker push $CACHE_IMAGE:base 34 | - name: Push stage image to docker hub 35 | run: docker push $CACHE_IMAGE:stage 36 | -------------------------------------------------------------------------------- /_config-examples/multi-stage/github.yml: -------------------------------------------------------------------------------- 1 | # _config-examples/multi-stage/github.yml 2 | 3 | name: Docker Build 4 | 5 | on: [push] 6 | 7 | env: 8 | CACHE_IMAGE: mjhea0/docker-ci-cache 9 | 10 | jobs: 11 | build: 12 | name: Build Docker Image 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout master 16 | uses: actions/checkout@v4 17 | - name: Log in to docker hub 18 | run: docker login -u ${{ secrets.REGISTRY_USER }} -p ${{ secrets.REGISTRY_PASS }} 19 | - name: Build base from dockerfile 20 | run: | 21 | docker build \ 22 | --target base \ 23 | --cache-from $CACHE_IMAGE:base \ 24 | --tag $CACHE_IMAGE:base \ 25 | --file ./Dockerfile.multi \ 26 | --build-arg BUILDKIT_INLINE_CACHE=1 27 | "." 28 | - name: Build stage from dockerfile 29 | run: | 30 | docker build \ 31 | --cache-from $CACHE_IMAGE:base \ 32 | --cache-from $CACHE_IMAGE:stage \ 33 | --tag $CACHE_IMAGE:stage \ 34 | --file ./Dockerfile.multi \ 35 | --build-arg BUILDKIT_INLINE_CACHE=1 36 | "." 37 | - name: Push base image to docker hub 38 | run: docker push $CACHE_IMAGE:base 39 | - name: Push stage image to docker hub 40 | run: docker push $CACHE_IMAGE:stage 41 | -------------------------------------------------------------------------------- /_config-examples/single-stage/.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | # _config-examples/single-stage/.gitlab-ci.yml 2 | 3 | image: docker:stable 4 | services: 5 | - docker:dind 6 | 7 | variables: 8 | DOCKER_DRIVER: overlay2 9 | CACHE_IMAGE: mjhea0/docker-ci-cache 10 | DOCKER_BUILDKIT: 1 11 | 12 | stages: 13 | - build 14 | 15 | docker-build: 16 | stage: build 17 | before_script: 18 | - docker login -u $REGISTRY_USER -p $REGISTRY_PASS 19 | script: 20 | - docker build 21 | --cache-from $CACHE_IMAGE:latest 22 | --tag $CACHE_IMAGE:latest 23 | --file ./Dockerfile 24 | --build-arg BUILDKIT_INLINE_CACHE=1 25 | "." 26 | after_script: 27 | - docker push $CACHE_IMAGE:latest 28 | -------------------------------------------------------------------------------- /_config-examples/single-stage/circle.yml: -------------------------------------------------------------------------------- 1 | # _config-examples/single-stage/circle.yml 2 | 3 | version: 2.1 4 | 5 | jobs: 6 | build: 7 | machine: 8 | image: ubuntu-2204:2024.01.1 9 | environment: 10 | CACHE_IMAGE: mjhea0/docker-ci-cache 11 | DOCKER_BUILDKIT: 1 12 | steps: 13 | - checkout 14 | - run: 15 | name: Log in to docker hub 16 | command: docker login -u $REGISTRY_USER -p $REGISTRY_PASS 17 | - run: 18 | name: Build from dockerfile 19 | command: | 20 | docker build \ 21 | --cache-from $CACHE_IMAGE:latest \ 22 | --tag $CACHE_IMAGE:latest \ 23 | --build-arg BUILDKIT_INLINE_CACHE=1 \ 24 | "." 25 | - run: 26 | name: Push to docker hub 27 | command: docker push $CACHE_IMAGE:latest 28 | -------------------------------------------------------------------------------- /_config-examples/single-stage/compose/.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | # _config-examples/single-stage/compose/.gitlab-ci.yml 2 | 3 | image: docker/compose:latest 4 | services: 5 | - docker:dind 6 | 7 | variables: 8 | DOCKER_DRIVER: overlay2 9 | CACHE_IMAGE: mjhea0/docker-ci-cache 10 | DOCKER_BUILDKIT: 1 11 | COMPOSE_DOCKER_CLI_BUILD: 1 12 | 13 | stages: 14 | - build 15 | 16 | docker-build: 17 | stage: build 18 | before_script: 19 | - docker login -u $REGISTRY_USER -p $REGISTRY_PASS 20 | script: 21 | - docker-compose build --build-arg BUILDKIT_INLINE_CACHE=1 22 | after_script: 23 | - docker push $CACHE_IMAGE:latest 24 | -------------------------------------------------------------------------------- /_config-examples/single-stage/compose/circle.yml: -------------------------------------------------------------------------------- 1 | # _config-examples/single-stage/compose/circle.yml 2 | 3 | version: 2.1 4 | 5 | jobs: 6 | build: 7 | machine: 8 | image: ubuntu-2204:2024.01.1 9 | environment: 10 | CACHE_IMAGE: mjhea0/docker-ci-cache 11 | DOCKER_BUILDKIT: 1 12 | COMPOSE_DOCKER_CLI_BUILD: 1 13 | steps: 14 | - checkout 15 | - run: 16 | name: Log in to docker hub 17 | command: docker login -u $REGISTRY_USER -p $REGISTRY_PASS 18 | - run: 19 | name: Build images 20 | command: docker-compose build --build-arg BUILDKIT_INLINE_CACHE=1 21 | - run: 22 | name: Push to docker hub 23 | command: docker push $CACHE_IMAGE:latest 24 | -------------------------------------------------------------------------------- /_config-examples/single-stage/compose/github.yml: -------------------------------------------------------------------------------- 1 | # _config-examples/single-stage/compose/github.yml 2 | 3 | name: Docker Build 4 | 5 | on: [push] 6 | 7 | env: 8 | CACHE_IMAGE: mjhea0/docker-ci-cache 9 | DOCKER_BUILDKIT: 1 10 | COMPOSE_DOCKER_CLI_BUILD: 1 11 | 12 | jobs: 13 | build: 14 | name: Build Docker Image 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout master 18 | uses: actions/checkout@v4 19 | - name: Log in to docker hub 20 | run: docker login -u ${{ secrets.REGISTRY_USER }} -p ${{ secrets.REGISTRY_PASS }} 21 | - name: Build Docker images 22 | run: docker-compose build --build-arg BUILDKIT_INLINE_CACHE=1 23 | - name: Push to docker hub 24 | run: docker push $CACHE_IMAGE:latest 25 | -------------------------------------------------------------------------------- /_config-examples/single-stage/github.yml: -------------------------------------------------------------------------------- 1 | # _config-examples/single-stage/github.yml 2 | 3 | name: Docker Build 4 | 5 | on: [push] 6 | 7 | env: 8 | CACHE_IMAGE: mjhea0/docker-ci-cache 9 | DOCKER_BUILDKIT: 1 10 | 11 | jobs: 12 | build: 13 | name: Build Docker Image 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout master 17 | uses: actions/checkout@v4 18 | - name: Log in to docker hub 19 | run: docker login -u ${{ secrets.REGISTRY_USER }} -p ${{ secrets.REGISTRY_PASS }} 20 | - name: Build from dockerfile 21 | run: | 22 | docker build \ 23 | --cache-from $CACHE_IMAGE:latest \ 24 | --tag $CACHE_IMAGE:latest \ 25 | --build-arg BUILDKIT_INLINE_CACHE=1 \ 26 | "." 27 | - name: Push to docker hub 28 | run: docker push $CACHE_IMAGE:latest 29 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | jobs: 4 | build: 5 | machine: 6 | image: ubuntu-2204:2024.01.1 7 | environment: 8 | CACHE_IMAGE: mjhea0/docker-ci-cache 9 | DOCKER_BUILDKIT: 1 10 | steps: 11 | - checkout 12 | - run: 13 | name: Log in to docker hub 14 | command: docker login -u $REGISTRY_USER -p $REGISTRY_PASS 15 | - run: 16 | name: Build from dockerfile 17 | command: | 18 | docker build \ 19 | --cache-from $CACHE_IMAGE:latest \ 20 | --tag $CACHE_IMAGE:latest \ 21 | --build-arg BUILDKIT_INLINE_CACHE=1 \ 22 | "." 23 | - run: 24 | name: Push to docker hub 25 | command: docker push $CACHE_IMAGE:latest 26 | -------------------------------------------------------------------------------- /docker-compose.multi.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | 5 | web: 6 | build: 7 | context: . 8 | cache_from: 9 | - mjhea0/docker-ci-cache:stage 10 | image: mjhea0/docker-ci-cache:stage 11 | ports: 12 | - 5001:5000 13 | environment: 14 | - FLASK_DEBUG=1 15 | - APP_SETTINGS=project.config.DevelopmentConfig 16 | - DATABASE_URL=postgresql://postgres:postgres@db:5432/users 17 | - DATABASE_TEST_URL=postgresql://postgres:postgres@db:5432/users_test 18 | depends_on: 19 | - db 20 | 21 | db: 22 | build: 23 | context: ./project/db 24 | dockerfile: Dockerfile 25 | expose: 26 | - 5432 27 | environment: 28 | - POSTGRES_USER=postgres 29 | - POSTGRES_PASSWORD=postgres 30 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | 5 | web: 6 | build: 7 | context: . 8 | cache_from: 9 | - mjhea0/docker-ci-cache:latest 10 | image: mjhea0/docker-ci-cache:latest 11 | ports: 12 | - 5001:5000 13 | environment: 14 | - FLASK_DEBUG=1 15 | - APP_SETTINGS=project.config.DevelopmentConfig 16 | - DATABASE_URL=postgresql://postgres:postgres@db:5432/users 17 | - DATABASE_TEST_URL=postgresql://postgres:postgres@db:5432/users_test 18 | depends_on: 19 | - db 20 | 21 | db: 22 | build: 23 | context: ./project/db 24 | dockerfile: Dockerfile 25 | expose: 26 | - 5432 27 | environment: 28 | - POSTGRES_USER=postgres 29 | - POSTGRES_PASSWORD=postgres 30 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import unittest 3 | 4 | from flask.cli import FlaskGroup 5 | 6 | from project import create_app, db 7 | from project.api.models import User 8 | 9 | app = create_app() 10 | cli = FlaskGroup(create_app=create_app) 11 | 12 | 13 | @cli.command("recreate_db") 14 | def recreate_db(): 15 | db.drop_all() 16 | db.create_all() 17 | db.session.commit() 18 | 19 | 20 | @cli.command("seed_db") 21 | def seed_db(): 22 | """Seeds the database.""" 23 | db.session.add(User(username="michael", email="michael@notreal.com",)) 24 | db.session.commit() 25 | 26 | 27 | @cli.command("test") 28 | def test(): 29 | """Runs the tests without code coverage""" 30 | tests = unittest.TestLoader().discover("project/tests", pattern="test*.py") 31 | result = unittest.TextTestRunner(verbosity=2).run(tests) 32 | if result.wasSuccessful(): 33 | return 0 34 | sys.exit(result) 35 | 36 | 37 | if __name__ == "__main__": 38 | cli() 39 | -------------------------------------------------------------------------------- /project/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from flask import Flask 4 | from flask_migrate import Migrate 5 | from flask_sqlalchemy import SQLAlchemy 6 | 7 | # instantiate the extensions 8 | db = SQLAlchemy() 9 | migrate = Migrate() 10 | 11 | 12 | def create_app(script_info=None): 13 | 14 | # instantiate the app 15 | app = Flask(__name__) 16 | 17 | # set config 18 | app_settings = os.getenv("APP_SETTINGS") 19 | app.config.from_object(app_settings) 20 | 21 | # set up extensions 22 | db.init_app(app) 23 | migrate.init_app(app, db) 24 | 25 | # register blueprints 26 | from project.api.users import users_blueprint 27 | 28 | app.register_blueprint(users_blueprint) 29 | 30 | # shell context for flask cli 31 | app.shell_context_processor({"app": app, "db": db}) 32 | return app 33 | -------------------------------------------------------------------------------- /project/api/models.py: -------------------------------------------------------------------------------- 1 | from project import db 2 | 3 | 4 | class User(db.Model): 5 | __tablename__ = "users" 6 | 7 | id = db.Column(db.Integer, primary_key=True, autoincrement=True) 8 | username = db.Column(db.String(128), unique=True, nullable=False) 9 | email = db.Column(db.String(128), unique=True, nullable=False) 10 | active = db.Column(db.Boolean, default=True, nullable=False) 11 | admin = db.Column(db.Boolean, default=False, nullable=False) 12 | 13 | def __init__(self, username, email): 14 | self.username = username 15 | self.email = email 16 | 17 | def to_json(self): 18 | return { 19 | "id": self.id, 20 | "username": self.username, 21 | "email": self.email, 22 | "active": self.active, 23 | "admin": self.admin, 24 | } 25 | -------------------------------------------------------------------------------- /project/api/users.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint, jsonify 2 | 3 | from project.api.models import User 4 | 5 | users_blueprint = Blueprint("users", __name__, template_folder="./templates") 6 | 7 | 8 | @users_blueprint.route("/users/ping", methods=["GET"]) 9 | def ping_pong(): 10 | return jsonify({"status": "success", "message": "pong!"}) 11 | 12 | 13 | @users_blueprint.route("/users", methods=["GET"]) 14 | def get_all_users(): 15 | """Get all users""" 16 | response_object = { 17 | "status": "success", 18 | "data": {"users": [user.to_json() for user in User.query.all()]}, 19 | } 20 | return jsonify(response_object), 200 21 | -------------------------------------------------------------------------------- /project/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | class BaseConfig: 5 | """Base configuration""" 6 | 7 | DEBUG = False 8 | TESTING = False 9 | SQLALCHEMY_TRACK_MODIFICATIONS = False 10 | 11 | 12 | class DevelopmentConfig(BaseConfig): 13 | """Development onfiguration""" 14 | 15 | SQLALCHEMY_DATABASE_URI = os.environ.get("DATABASE_URL") 16 | 17 | 18 | class TestingConfig(BaseConfig): 19 | """Testing configuration""" 20 | 21 | TESTING = True 22 | SQLALCHEMY_DATABASE_URI = os.environ.get("DATABASE_TEST_URL") 23 | -------------------------------------------------------------------------------- /project/db/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM postgres:13-alpine 2 | 3 | # run create.sql on init 4 | ADD create.sql /docker-entrypoint-initdb.d 5 | -------------------------------------------------------------------------------- /project/db/create.sql: -------------------------------------------------------------------------------- 1 | CREATE DATABASE users; 2 | CREATE DATABASE users_test; 3 | -------------------------------------------------------------------------------- /project/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdrivenio/docker-ci-cache/6e6ee68fdb6c397c249b468043673c0d541de7c7/project/tests/__init__.py -------------------------------------------------------------------------------- /project/tests/base.py: -------------------------------------------------------------------------------- 1 | from flask_testing import TestCase 2 | 3 | from project import create_app, db 4 | 5 | app = create_app() 6 | 7 | 8 | class BaseTestCase(TestCase): 9 | def create_app(self): 10 | app.config.from_object("project.config.TestingConfig") 11 | return app 12 | 13 | def setUp(self): 14 | db.create_all() 15 | db.session.commit() 16 | 17 | def tearDown(self): 18 | db.session.remove() 19 | db.drop_all() 20 | -------------------------------------------------------------------------------- /project/tests/test_users.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from project import db 4 | from project.api.models import User 5 | from project.tests.base import BaseTestCase 6 | 7 | 8 | class TestUserService(BaseTestCase): 9 | """Tests for the Users Service.""" 10 | 11 | def test_users_ping(self): 12 | """Ensure the /ping route behaves correctly.""" 13 | with self.client: 14 | response = self.client.get("/users/ping") 15 | data = json.loads(response.data.decode()) 16 | self.assertEqual(response.status_code, 200) 17 | self.assertIn("pong!", data["message"]) 18 | self.assertIn("success", data["status"]) 19 | 20 | def test_add_user(self): 21 | """Ensure a new user can be added to the database.""" 22 | with self.client: 23 | user = User(username="test", email="test@test.com") 24 | db.session.add(user) 25 | db.session.commit() 26 | response = self.client.get("/users") 27 | data = json.loads(response.data.decode())["data"] 28 | self.assertEqual(response.status_code, 200) 29 | self.assertTrue(data["users"][0]["active"]) 30 | self.assertFalse(data["users"][0]["admin"]) 31 | self.assertEqual(data["users"][0]["email"], "test@test.com") 32 | self.assertEqual(data["users"][0]["username"], "test") 33 | self.assertEqual(data["users"][0]["id"], 1) 34 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | flake8==7.0.0 2 | Flask==3.0.2 3 | Flask-Migrate==4.0.6 4 | Flask-SQLAlchemy==3.1.1 5 | Flask-Testing==0.8.1 6 | gunicorn==21.2.0 7 | google-cloud-pubsub==2.20.1 8 | pandas==2.2.1 9 | psycopg2-binary==2.9.9 10 | --------------------------------------------------------------------------------