diff --git a/.github/workflows/housekeeping.yml b/.github/workflows/housekeeping.yml new file mode 100644 index 0000000..452c958 --- /dev/null +++ b/.github/workflows/housekeeping.yml @@ -0,0 +1,20 @@ +name: Housekeeping +on: + schedule: + - cron: "0 0 1 * *" +jobs: + clean-ghcr: + name: Delete old container images + runs-on: ubuntu-latest + steps: + - name: Delete old releases + uses: snok/container-retention-policy@v2 + with: + image-names: aimaas/aimaas-api, aimaas/aimaas-ui + cut-off: one month ago UTC + timestamp-to-use: updated_at + account-type: org + org-name: suse + keep-at-least: 3 + skip-tags: latest + token: ${{ secrets.PAT }} diff --git a/.github/workflows/on_tag.yml b/.github/workflows/on_tag.yml new file mode 100644 index 0000000..8f13b0c --- /dev/null +++ b/.github/workflows/on_tag.yml @@ -0,0 +1,71 @@ +name: Build & Test +on: + release: + types: [created] + workflow_dispatch: +jobs: + build-api: + runs-on: ubuntu-latest + steps: + - name: 'Checkout GitHub Action' + uses: actions/checkout@main + - name: 'Login to GitHub Container Registry' + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{github.actor}} + password: ${{secrets.GITHUB_TOKEN}} + - name: 'Build API image' + id: image + run: | + docker build backend/ \ + --tag ghcr.io/${GITHUB_REPOSITORY,,}/aimaas-api:${GITHUB_REF_NAME,,} \ + --tag ghcr.io/${GITHUB_REPOSITORY,,}/aimaas-api:latest + docker push ghcr.io/${GITHUB_REPOSITORY,,}/aimaas-api:${GITHUB_REF_NAME,,} + docker push ghcr.io/${GITHUB_REPOSITORY,,}/aimaas-api:latest + build-ui: + runs-on: ubuntu-latest + steps: + - name: 'Checkout GitHub Action' + uses: actions/checkout@main + - name: 'Login to GitHub Container Registry' + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{github.actor}} + password: ${{secrets.GITHUB_TOKEN}} + - name: 'Build UI image' + run: | + docker build frontend/ \ + --tag ghcr.io/${GITHUB_REPOSITORY,,}/aimaas-ui:${GITHUB_REF_NAME,,} \ + --tag ghcr.io/${GITHUB_REPOSITORY,,}/aimaas-ui:latest + docker push ghcr.io/${GITHUB_REPOSITORY,,}/aimaas-ui:${GITHUB_REF_NAME,,} + docker push ghcr.io/${GITHUB_REPOSITORY,,}/aimaas-ui:latest + test-api: + needs: build-api + runs-on: ubuntu-latest + services: + postgres: + image: postgres:latest + env: + POSTGRES_DB: test_aimaas + POSTGRES_USER: aimaas + POSTGRES_PASSWORD: password + ports: + - 5432:5432 + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 + steps: + - uses: actions/checkout@v2 + - name: Test + run: | + docker run --rm --network host -e PG_USER=$PG_USER -e PG_PASSWORD=$PG_PASSWORD \ + -e PG_HOST=$PG_HOST -e PG_PORT=$PG_PORT -e PG_DB=$PG_DB --entrypoint "" \ + -v `pwd`:/src \ + ghcr.io/${GITHUB_REPOSITORY,,}/aimaas-api:${GITHUB_REF_NAME,,} \ + bash -c 'pip3 install -r /src/backend/requirements_test.txt; pytest /opt/aimaas/backend/tests/' + env: + PG_USER: aimaas + PG_PASSWORD: password + PG_HOST: localhost + PG_PORT: 5432 + PG_DB: aimaas diff --git a/alembic.ini b/alembic.ini deleted file mode 100644 index 9093145..0000000 --- a/alembic.ini +++ /dev/null @@ -1,100 +0,0 @@ -# A generic, single database configuration. - -[alembic] -# path to migration scripts -script_location = backend/alembic - -# template used to generate migration files -# file_template = %%(rev)s_%%(slug)s - -# sys.path path, will be prepended to sys.path if present. -# defaults to the current working directory. -prepend_sys_path = . - -# timezone to use when rendering the date within the migration file -# as well as the filename. -# If specified, requires the python-dateutil library that can be -# installed by adding `alembic[tz]` to the pip requirements -# string value is passed to dateutil.tz.gettz() -# leave blank for localtime -# timezone = - -# max length of characters to apply to the -# "slug" field -# truncate_slug_length = 40 - -# set to 'true' to run the environment during -# the 'revision' command, regardless of autogenerate -# revision_environment = false - -# set to 'true' to allow .pyc and .pyo files without -# a source .py file to be detected as revisions in the -# versions/ directory -# sourceless = false - -# version location specification; This defaults -# to alembic/versions. When using multiple version -# directories, initial revisions must be specified with --version-path. -# The path separator used here should be the separator specified by "version_path_separator" -# version_locations = %(here)s/bar:%(here)s/bat:alembic/versions - -# version path separator; As mentioned above, this is the character used to split -# version_locations. Valid values are: -# -# version_path_separator = : -# version_path_separator = ; -# version_path_separator = space -version_path_separator = os # default: use os.pathsep - -# the output encoding used when revision files -# are written from script.py.mako -# output_encoding = utf-8 - -sqlalchemy.url = postgresql://user:pass@localhost/dbname - - -[post_write_hooks] -# post_write_hooks defines scripts or Python functions that are run -# on newly generated revision scripts. See the documentation for further -# detail and examples - -# format using "black" - use the console_scripts runner, against the "black" entrypoint -# hooks = black -# black.type = console_scripts -# black.entrypoint = black -# black.options = -l 79 REVISION_SCRIPT_FILENAME - -# Logging configuration -[loggers] -keys = root,sqlalchemy,alembic - -[handlers] -keys = console - -[formatters] -keys = generic - -[logger_root] -level = WARN -handlers = console -qualname = - -[logger_sqlalchemy] -level = WARN -handlers = -qualname = sqlalchemy.engine - -[logger_alembic] -level = INFO -handlers = -qualname = alembic - -[handler_console] -class = StreamHandler -args = (sys.stderr,) -level = NOTSET -formatter = generic - -[formatter_generic] -format = %(levelname)-5.5s [%(name)s] %(message)s -datefmt = %H:%M:%S diff --git a/alembic.ini b/alembic.ini new file mode 120000 index 0000000..3b75df4 --- /dev/null +++ b/alembic.ini @@ -0,0 +1 @@ +backend/alembic/alembic.ini \ No newline at end of file diff --git a/backend/Dockerfile b/backend/Dockerfile index 42dc174..7903ee9 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,14 +1,15 @@ -FROM python:3.10-slim +FROM python:3.12-slim -COPY . /opt/aimaas -RUN pip3 install -r /opt/aimaas/requirements.txt; \ +COPY . /opt/aimaas/backend +RUN pip3 install -r /opt/aimaas/backend/requirements.txt; \ pip3 cache purge -WORKDIR /opt +WORKDIR /opt/aimaas ENV WORKERS=1 ENV ROOT_PATH=/ +ENV ALEMBIC_CONFIG=/opt/aimaas/backend/alembic/alembic.ini EXPOSE 8000 -ENTRYPOINT ["/bin/bash", "-c", "uvicorn aimaas.main:app --port 8000 --host 0.0.0.0 --workers ${WORKERS} --root-path ${ROOT_PATH}"] +ENTRYPOINT ["/bin/bash", "-c", "uvicorn backend.main:app --port 8000 --host 0.0.0.0 --workers ${WORKERS} --root-path ${ROOT_PATH}"] diff --git a/backend/alembic/alembic.ini b/backend/alembic/alembic.ini new file mode 100644 index 0000000..9093145 --- /dev/null +++ b/backend/alembic/alembic.ini @@ -0,0 +1,100 @@ +# A generic, single database configuration. + +[alembic] +# path to migration scripts +script_location = backend/alembic + +# template used to generate migration files +# file_template = %%(rev)s_%%(slug)s + +# sys.path path, will be prepended to sys.path if present. +# defaults to the current working directory. +prepend_sys_path = . + +# timezone to use when rendering the date within the migration file +# as well as the filename. +# If specified, requires the python-dateutil library that can be +# installed by adding `alembic[tz]` to the pip requirements +# string value is passed to dateutil.tz.gettz() +# leave blank for localtime +# timezone = + +# max length of characters to apply to the +# "slug" field +# truncate_slug_length = 40 + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + +# set to 'true' to allow .pyc and .pyo files without +# a source .py file to be detected as revisions in the +# versions/ directory +# sourceless = false + +# version location specification; This defaults +# to alembic/versions. When using multiple version +# directories, initial revisions must be specified with --version-path. +# The path separator used here should be the separator specified by "version_path_separator" +# version_locations = %(here)s/bar:%(here)s/bat:alembic/versions + +# version path separator; As mentioned above, this is the character used to split +# version_locations. Valid values are: +# +# version_path_separator = : +# version_path_separator = ; +# version_path_separator = space +version_path_separator = os # default: use os.pathsep + +# the output encoding used when revision files +# are written from script.py.mako +# output_encoding = utf-8 + +sqlalchemy.url = postgresql://user:pass@localhost/dbname + + +[post_write_hooks] +# post_write_hooks defines scripts or Python functions that are run +# on newly generated revision scripts. See the documentation for further +# detail and examples + +# format using "black" - use the console_scripts runner, against the "black" entrypoint +# hooks = black +# black.type = console_scripts +# black.entrypoint = black +# black.options = -l 79 REVISION_SCRIPT_FILENAME + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/backend/tests/conftest.py b/backend/tests/conftest.py index 2079fd0..4a55402 100644 --- a/backend/tests/conftest.py +++ b/backend/tests/conftest.py @@ -1,4 +1,5 @@ from datetime import datetime, timezone, timedelta +import os from alembic import command from alembic.config import Config @@ -306,7 +307,8 @@ def engine(): with engine.connect() as conn: conn.execute(text("drop table if exists alembic_version")) conn.commit() - cfg = Config("alembic.ini") + cfg = Config(os.getenv("ALEMBIC_CONFIG", + os.path.join(os.path.dirname(__file__), "../alembic/alembic.ini"))) command.upgrade(cfg, "head") yield engine