diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..415565666428bb46865793c121d4d28793d3c927 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,13 @@ +FROM "mcr.microsoft.com/devcontainers/python:1-3.12-bullseye" + +RUN apt-get update && \ + apt-get upgrade -y && \ + apt-get install -y \ + libev-dev \ + zlib1g-dev \ + libjpeg-dev + +RUN pip install --upgrade pip && \ + pip install bjoern tox setuptools + +CMD ["sleep", "infinity"] \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000000000000000000000000000000000000..233ca1055ca148b2a5db4381e36fcb010c12da25 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,19 @@ +{ + "name": "amivapi devcontainer setup", + "dockerComposeFile": "docker-compose.yml", + "service": "amivapi", + "workspaceFolder": "/api", + "features": { + "ghcr.io/devcontainers/features/git:1": {}, + "ghcr.io/devcontainers-extra/features/tox:2": {} + }, + "postCreateCommand": "pip install -r requirements.txt && pip install -e /api/.", + "customizations": { + "vscode": { + "extensions": [ + "ms-python.flake8", + "ms-python.python" + ] + } + } +} diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..e555a1545967e0eef1dbcb5b822becd9e4aa4bb0 --- /dev/null +++ b/.devcontainer/docker-compose.yml @@ -0,0 +1,36 @@ +version: "3" +services: + amivapi: + build: + context: . + ports: + - "5000:5000" + volumes: + - ../:/api:cached + environment: + - AMIVAPI_CONFIG=/api/dev_config.py + depends_on: + - mongodb + + mongodb: + image: mongo:5.0.14 + ports: + - "27017:27017" + volumes: + - ../dev_mongoinit.js:/docker-entrypoint-initdb.d/dev_mongoinit.js:ro + + admintool: + image: amiveth/admintool:local + ports: + - "9000:80" + + mongo-express: + image: mongo-express + ports: + - "8081:8081" + environment: + - ME_CONFIG_MONGODB_URL=mongodb://mongodb:27017 + - ME_CONFIG_BASICAUTH_USERNAME=admin + - ME_CONFIG_BASICAUTH_PASSWORD=admin + depends_on: + - mongodb diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 2785977fab461c1a1a322d40781b6154925f8a15..0000000000000000000000000000000000000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,30 +0,0 @@ -version: 2 -updates: - # Maintain dependencies for GitHub Actions - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "monthly" - groups: - all: - patterns: - - "*" - - package-ecosystem: "docker" - directory: "/" - schedule: - interval: "weekly" - - package-ecosystem: "pip" - directory: "/" - schedule: - interval: "weekly" - groups: - eve: - patterns: - - "eve" - - "flask" - - "pymongo" - test: - patterns: - - "pytest*" - - "tox" - - "flake8" diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml deleted file mode 100644 index 825f0cd2f17a97b2db234bf667cd24636e00c9e8..0000000000000000000000000000000000000000 --- a/.github/workflows/cd.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: CD - -on: - workflow_run: - workflows: [CI] - branches: [master] - types: - - completed - -jobs: - deploy: - runs-on: ubuntu-latest - container: amiveth/service-update-helper:latest - if: ${{ github.event.workflow_run.conclusion == 'success' }} - - strategy: - matrix: - deploy-url: - - https://deploy-cluster.amiv.ethz.ch - - https://deploy-fallback.amiv.ethz.ch - deploy-service: - - amivapi - - amivapi-cron - - amivapi-dev - - amivapi-dev-cron - exclude: - - deploy-url: 'https://deploy-fallback.amiv.ethz.ch' - deploy-service: 'amivapi-dev' - - deploy-url: 'https://deploy-fallback.amiv.ethz.ch' - deploy-service: 'amivapi-dev-cron' - - env: - CI_DEPLOY_URL: ${{ matrix.deploy-url }} - CI_DEPLOY_SERVICE: ${{ matrix.deploy-service }} - CI_DEPLOY_TOKEN: ${{ secrets.CI_DEPLOY_TOKEN }} - - steps: - - run: /update.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 3da3c301c1a690d9cf675e1c94c7f95534782424..0000000000000000000000000000000000000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,91 +0,0 @@ -name: CI - -on: - schedule: - - cron: '5 4 * * 1' - push: - branches: [ master ] - pull_request: - -jobs: - test: - # Due to drop of out-of-the-box support in ubuntu-22.04 for MongoDB 5.0, - # we cannot use ubuntu-latest. have to consider this in the future. as ubuntu-20.04 will be OBSOLETE in 2025. - runs-on: ubuntu-20.04 - strategy: - matrix: - python-version: ['3.10','3.12'] - - services: - mongodb: - image: mongo:5.0.14 - env: - MONGO_INITDB_DATABASE: amivapi - options: >- - --health-cmd mongo - --health-start-period 20s - --health-interval 10s - --health-timeout 5s - --health-retries 5 - ports: - - 27017:27017 - - steps: - - uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip - python -m pip install tox tox-gh-actions setuptools - - name: Create MongoDB User - run: mongo test_amivapi --eval 'db.createUser({user:"test_user",pwd:"test_pw",roles:["readWrite"]});' - - name: Test with tox - run: tox - - uses: codecov/codecov-action@v4 - with: - token: ${{ secrets.CODECOV_TOKEN }} - files: ./coverage.xml - flags: unittests - fail_ci_if_error: true - verbose: true - - - build: - runs-on: ubuntu-latest - needs: test - - env: - IMAGE_NAME: amiveth/amivapi - - steps: - - uses: actions/checkout@v4 - # Workaround: https://github.com/docker/build-push-action/issues/461 - - name: Setup Docker buildx - uses: docker/setup-buildx-action@v3 - # Login against a Docker registry except on PR - - name: Log into Docker Hub registry - if: github.event_name != 'pull_request' - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - # Extract metadata (tags, labels) for Docker - - name: Extract Docker metadata - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.IMAGE_NAME }} - tags: type=raw,value=latest - # Build and push Docker image with Buildx (don't push on PR) - - name: Build and push Docker image - id: build-and-push - uses: docker/build-push-action@v5 - with: - context: . - pull: true - push: ${{ github.event_name != 'pull_request' }} - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..3451d4baff6df87232ff2501241480e211b63056 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,42 @@ +stages: + - test + - build + - deploy + +test: + stage: test + image: python:3.12 + services: + - name: mongo:5.0.14 + alias: mongodb + before_script: + - pip install --upgrade pip + - pip install tox + script: + - tox + +build_master: + stage: build + image: docker:latest + before_script: + - echo "$CI_DOCKER_REGISTRY_TOKEN" | docker login -u "$CI_DOCKER_REGISTRY_USER" --password-stdin + script: + - docker build --pull -t "$CI_REGISTRY_IMAGE" ./ + - docker push "$CI_REGISTRY_IMAGE" + only: + - master + +build_dev: + stage: build + image: docker:stable + before_script: + - echo "$CI_DOCKER_REGISTRY_TOKEN_DEV" | docker login -u "$CI_DOCKER_REGISTRY_USER_DEV" --password-stdin + script: + - docker build --pull -t "$CI_REGISTRY_IMAGE_DEV" ./ + - docker push "$CI_REGISTRY_IMAGE_DEV" + +deploy: + stage: deploy + image: amiveth/ansible-ci-helper + script: + - python /main.py diff --git a/amivapi/tests/config.py b/amivapi/tests/config.py new file mode 100644 index 0000000000000000000000000000000000000000..771b0bf50cc79f0437aea21f9b12ced787daabe7 --- /dev/null +++ b/amivapi/tests/config.py @@ -0,0 +1,30 @@ +""" Config file used for tox tests. +It is called by the utils.py setUp function. """ +from passlib.context import CryptContext +import logging + +# Do not change this has to be the same as in tests/utils.py +MONGO_HOST = 'mongodb' +MONGO_PORT = 27017 +MONGO_DBNAME = 'test_amivapi' +MONGO_USERNAME = 'test_user' +MONGO_PASSWORD = 'test_pw' + +ROOT_PASSWORD = 'root' + +API_MAIL = 'api@test.ch' +SMTP_SERVER = '' +TESTING = True +DEBUG = True # This makes eve's error messages more helpful +LDAP_USERNAME = None # LDAP test require special treatment +LDAP_PASSWORD = None # LDAP test require special treatment +SENTRY_DSN = None +SENTRY_ENVIRONMENT = None +PASSWORD_CONTEXT = CryptContext( + schemes=["pbkdf2_sha256"], + pbkdf2_sha256__default_rounds=10, + # min_rounds is used to determine if a hash needs to be upgraded + pbkdf2_sha256__min_rounds=8, +) + +LOG_LEVEL = logging.DEBUG diff --git a/amivapi/tests/utils.py b/amivapi/tests/utils.py index 3e41fbac7da573fe7b734ec154ce9031311cc455..298bde1bd1c1b7ec50837963c6be87f79eaaefb2 100644 --- a/amivapi/tests/utils.py +++ b/amivapi/tests/utils.py @@ -16,7 +16,6 @@ from bson import ObjectId from flask import g from flask.testing import FlaskClient from flask.wrappers import Response -from passlib.context import CryptContext from pymongo import MongoClient from amivapi import bootstrap @@ -98,27 +97,6 @@ class WebTest(unittest.TestCase, FixtureMixin): Inspired by eve standard testing class. """ - # Test Config overwrites - test_config = { - 'MONGO_DBNAME': 'test_amivapi', - 'MONGO_USERNAME': 'test_user', - 'MONGO_PASSWORD': 'test_pw', - 'API_MAIL': 'api@test.ch', - 'SMTP_SERVER': '', - 'TESTING': True, - 'DEBUG': True, # This makes eve's error messages more helpful - 'LDAP_USERNAME': None, # LDAP test require special treatment - 'LDAP_PASSWORD': None, # LDAP test require special treatment - 'SENTRY_DSN': None, - 'SENTRY_ENVIRONMENT': None, - 'PASSWORD_CONTEXT': CryptContext( - schemes=["pbkdf2_sha256"], - pbkdf2_sha256__default_rounds=10, - # min_rounds is used to determine if a hash needs to be upgraded - pbkdf2_sha256__min_rounds=8, - ) - } - def setUp(self, **extra_config): """Set up the testing client and database connection. @@ -132,11 +110,14 @@ class WebTest(unittest.TestCase, FixtureMixin): if sys.version_info >= (3, 2): self.assertItemsEqual = self.assertCountEqual + # Initialize test user in mongodb + self.init_test_user() + # create eve app and test client config = {} - config.update(self.test_config) config.update(extra_config) - self.app = bootstrap.create_app(**config) + self.app = bootstrap.create_app( + config_file='amivapi/tests/config.py', **config) self.app.response_class = TestResponse self.app.test_client_class = TestClient self.app.test_mails = [] @@ -151,10 +132,29 @@ class WebTest(unittest.TestCase, FixtureMixin): authSource=self.app.config['MONGO_DBNAME']) self.db = self.connection[self.app.config['MONGO_DBNAME']] + def init_test_user(self): + """Initialize test user this is done statically here. + Skips user creation if he already exists. + """ + # Ensure the test user is created in the db + with MongoClient('mongodb', 27017) as client: + + db = client['test_amivapi'] + + # Check if the user already exists by querying usersInfo + user_info = db.command('usersInfo', 'test_user') + + # If no users are returned, create the user + if not user_info.get('users'): + db.command('createUser', + 'test_user', + pwd='test_pw', + roles=['readWrite']) + def tearDown(self): """Tear down after testing.""" # delete testing database - self.connection.drop_database(self.test_config['MONGO_DBNAME']) + self.connection.drop_database(self.app.config['MONGO_DBNAME']) # close database connection self.connection.close() diff --git a/dev_mongoinit.js b/dev_mongoinit.js index 46da2db6f8f36151d062779478e745da0317e60b..a437c58cf55dc7f82d3072d0e5be6f0a470c6e75 100644 --- a/dev_mongoinit.js +++ b/dev_mongoinit.js @@ -9,65 +9,4 @@ db.createUser( } ] } -); - -db.getSiblingDB('test_amivapi').createUser( - { - user: "test_user", - pwd: "test_pw", - roles: [ - { - role: "readWrite", - db: "test_amivapi" - } - ] - } -); - -// Use the following code to create default user, group and oauthclient for local development -// db = db.getSiblingDB('amivapi'); - -// // Create admin user with password admin -// let userId = db.users.insertOne({ -// nethz: 'admin', -// password: '$pbkdf2-sha256$5$OqfUmtNaq5UyRohxDuGckw$9H/UL5N5dA7JmUq7ohRPfmJ84OUnpRKjTgsMeuFilXM', -// email: "admin@example.com", -// membership: "regular", -// gender: "female", -// firstname: "ad", -// lastname: "min", -// _etag: "27f987fd9dd45d491e5aea3e27730israndom", -// }).insertedId; - -// // Create admin group with permissions on all resources -// let groupId = db.groups.insertOne({ -// name: 'admin', -// permissions: { -// apikeys: "readwrite", -// users: "readwrite", -// sessions: "readwrite", -// events: "readwrite", -// eventsignups: "readwrite", -// groups: "readwrite", -// groupmemberships: "readwrite", -// joboffers: "readwrite", -// beverages: "read", -// studydocuments: "readwrite", -// oauthclients: "readwrite", -// }, -// _etag: "27f987fd9dd45d491e5aea3e27730israndom", -// }).insertedId; - -// // Add admin to admin group -// db.groupmemberships.insertOne({ -// user: userId, -// group: groupId, -// _etag: "27f987fd9dd45d491e5aea3e27730israndom", -// }) - -// // Add Local Tool client for admin tool -// db.oauthclients.insertOne({ -// client_id: "Local Tool", -// redirect_uri: "http://localhost", -// _etag: "27f987fd9dd45d491e5aea3e27730israndom", -// }); \ No newline at end of file +); \ No newline at end of file diff --git a/tox.ini b/tox.ini index 57e93eedabc3335719a7db3e1745e5a4fbde9495..5528226d395b777fc440ab259a30102dbb10ef90 100644 --- a/tox.ini +++ b/tox.ini @@ -4,17 +4,16 @@ # and then run "tox" from this directory. [tox] -envlist = py38, py310, py312, flake8 +envlist = py312, flake8 [gh-actions] python = - 3.10: py310, flake8 3.12: py312, flake8 [testenv] # `-rs` shows summary on skipped tests by default -commands = py.test \ - --cov-report term-missing --cov-report xml:coverage.xml --cov=amivapi -rs {posargs} amivapi/tests +commands = + py.test --cov-report term-missing --cov-report xml:coverage.xml --cov=amivapi -rs {posargs} amivapi/tests install_command = pip install {opts} {packages} deps = -r requirements.txt