diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index ce5ee70f5..3f445f771 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -10,29 +10,24 @@ jobs: deploy-staging: name: Deploy Staging runs-on: ubuntu-latest + container: + image: docker:27-cli if: github.ref == 'refs/heads/main' steps: - name: Checkout code - shell: bash + shell: sh run: | - set -euo pipefail - repo_url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git" - auth_header="AUTHORIZATION: basic $(printf 'x-access-token:%s' "$GITHUB_TOKEN" | base64 -w0)" - git init . - git remote add origin "$repo_url" - git -c http.https://api.xiaoxiajianji.com/.extraheader="$auth_header" fetch --depth=1 origin "$GITHUB_SHA" - git checkout --force FETCH_HEAD - - - name: Prepare deploy tooling - run: | - apt-get update - apt-get install -y docker.io curl + set -eu + archive_url="${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/archive/${GITHUB_SHA}.tar.gz" + wget --header="Authorization: token ${GITHUB_TOKEN}" -O /tmp/repo.tar.gz "$archive_url" + tar -xzf /tmp/repo.tar.gz --strip-components=1 -C . + rm -f /tmp/repo.tar.gz - name: Sync code to staging workspace - shell: bash + shell: sh run: | - set -euo pipefail + set -eu tar --exclude=.git -cf - . | docker run --rm -i \ -v /:/host \ alpine:3.20 \ @@ -45,82 +40,77 @@ jobs: ' - name: Verify staging env file + shell: sh run: | + set -eu docker run --rm -v /:/host alpine:3.20 test -f /host/var/lib/xiaoxia-saas-staging/.env - name: Prepare staging env + shell: sh run: | + set -eu docker run --rm -v /:/host alpine:3.20 sh -lc 'cp /host/var/lib/xiaoxia-saas-staging/.env /host/var/lib/xiaoxia-saas-staging/repo/.env' - name: Build staging images + shell: sh run: | - docker run --rm \ - -v /var/run/docker.sock:/var/run/docker.sock \ - -v /:/host \ - docker:27-cli sh -lc ' - docker build \ - -t xiaoxia-saas-api:${{ github.sha }} \ - -t xiaoxia-saas-api:staging \ - -f /host/var/lib/xiaoxia-saas-staging/repo/infra/docker/api.Dockerfile \ - /host/var/lib/xiaoxia-saas-staging/repo && \ - docker build \ - -t xiaoxia-saas-worker:${{ github.sha }} \ - -t xiaoxia-saas-worker:staging \ - -f /host/var/lib/xiaoxia-saas-staging/repo/infra/docker/worker.Dockerfile \ - /host/var/lib/xiaoxia-saas-staging/repo - ' + set -eu + docker build \ + -t xiaoxia-saas-api:${GITHUB_SHA} \ + -t xiaoxia-saas-api:staging \ + -f /var/lib/xiaoxia-saas-staging/repo/infra/docker/api.Dockerfile \ + /var/lib/xiaoxia-saas-staging/repo + docker build \ + -t xiaoxia-saas-worker:${GITHUB_SHA} \ + -t xiaoxia-saas-worker:staging \ + -f /var/lib/xiaoxia-saas-staging/repo/infra/docker/worker.Dockerfile \ + /var/lib/xiaoxia-saas-staging/repo - name: Deploy staging containers + shell: sh run: | - docker run --rm \ - -v /var/run/docker.sock:/var/run/docker.sock \ - -v /:/host \ - -w /host/var/lib/xiaoxia-saas-staging/repo/infra/docker \ - -e API_IMAGE=xiaoxia-saas-api:staging \ - -e WORKER_IMAGE=xiaoxia-saas-worker:staging \ - docker:27-cli sh -lc ' - docker compose up -d postgres redis api worker && \ - docker compose ps - ' + set -eu + cd /var/lib/xiaoxia-saas-staging/repo/infra/docker + API_IMAGE=xiaoxia-saas-api:staging \ + WORKER_IMAGE=xiaoxia-saas-worker:staging \ + docker compose up -d postgres redis api worker + docker compose ps - name: Verify staging health + shell: sh run: | - docker run --rm --network host curlimages/curl:8.8.0 sh -lc ' - for i in $(seq 1 30); do - if curl -fsS http://127.0.0.1:8000/api/v1/health; then - exit 0 - fi - sleep 2 - done - exit 1 - ' + set -eu + i=0 + while [ "$i" -lt 30 ]; do + if wget -qO- http://127.0.0.1:8000/api/v1/health; then + exit 0 + fi + i=$((i + 1)) + sleep 2 + done + exit 1 deploy-production: name: Deploy Production runs-on: ubuntu-latest + container: + image: docker:27-cli if: startsWith(github.ref, 'refs/tags/v') steps: - name: Checkout code - shell: bash + shell: sh run: | - set -euo pipefail - repo_url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git" - auth_header="AUTHORIZATION: basic $(printf 'x-access-token:%s' "$GITHUB_TOKEN" | base64 -w0)" - git init . - git remote add origin "$repo_url" - git -c http.https://api.xiaoxiajianji.com/.extraheader="$auth_header" fetch --depth=1 origin "$GITHUB_SHA" - git checkout --force FETCH_HEAD - - - name: Prepare deploy tooling - run: | - apt-get update - apt-get install -y docker.io curl + set -eu + archive_url="${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/archive/${GITHUB_SHA}.tar.gz" + wget --header="Authorization: token ${GITHUB_TOKEN}" -O /tmp/repo.tar.gz "$archive_url" + tar -xzf /tmp/repo.tar.gz --strip-components=1 -C . + rm -f /tmp/repo.tar.gz - name: Sync code to production workspace - shell: bash + shell: sh run: | - set -euo pipefail + set -eu tar --exclude=.git -cf - . | docker run --rm -i \ -v /:/host \ alpine:3.20 \ @@ -133,54 +123,54 @@ jobs: ' - name: Verify production env file + shell: sh run: | + set -eu docker run --rm -v /:/host alpine:3.20 test -f /host/var/lib/xiaoxia-saas-production/.env - name: Prepare production env + shell: sh run: | + set -eu docker run --rm -v /:/host alpine:3.20 sh -lc 'cp /host/var/lib/xiaoxia-saas-production/.env /host/var/lib/xiaoxia-saas-production/repo/.env' - name: Build production images + shell: sh run: | - docker run --rm \ - -v /var/run/docker.sock:/var/run/docker.sock \ - -v /:/host \ - docker:27-cli sh -lc ' - docker build \ - -t xiaoxia-saas-api:${{ github.sha }} \ - -t xiaoxia-saas-api:${{ github.ref_name }} \ - -t xiaoxia-saas-api:latest \ - -f /host/var/lib/xiaoxia-saas-production/repo/infra/docker/api.Dockerfile \ - /host/var/lib/xiaoxia-saas-production/repo && \ - docker build \ - -t xiaoxia-saas-worker:${{ github.sha }} \ - -t xiaoxia-saas-worker:${{ github.ref_name }} \ - -t xiaoxia-saas-worker:latest \ - -f /host/var/lib/xiaoxia-saas-production/repo/infra/docker/worker.Dockerfile \ - /host/var/lib/xiaoxia-saas-production/repo - ' + set -eu + docker build \ + -t xiaoxia-saas-api:${GITHUB_SHA} \ + -t xiaoxia-saas-api:${GITHUB_REF_NAME} \ + -t xiaoxia-saas-api:latest \ + -f /var/lib/xiaoxia-saas-production/repo/infra/docker/api.Dockerfile \ + /var/lib/xiaoxia-saas-production/repo + docker build \ + -t xiaoxia-saas-worker:${GITHUB_SHA} \ + -t xiaoxia-saas-worker:${GITHUB_REF_NAME} \ + -t xiaoxia-saas-worker:latest \ + -f /var/lib/xiaoxia-saas-production/repo/infra/docker/worker.Dockerfile \ + /var/lib/xiaoxia-saas-production/repo - name: Deploy production containers + shell: sh run: | - docker run --rm \ - -v /var/run/docker.sock:/var/run/docker.sock \ - -v /:/host \ - -w /host/var/lib/xiaoxia-saas-production/repo/infra/docker \ - -e API_IMAGE=xiaoxia-saas-api:latest \ - -e WORKER_IMAGE=xiaoxia-saas-worker:latest \ - docker:27-cli sh -lc ' - docker compose up -d postgres redis api worker && \ - docker compose ps - ' + set -eu + cd /var/lib/xiaoxia-saas-production/repo/infra/docker + API_IMAGE=xiaoxia-saas-api:latest \ + WORKER_IMAGE=xiaoxia-saas-worker:latest \ + docker compose up -d postgres redis api worker + docker compose ps - name: Verify production health + shell: sh run: | - docker run --rm --network host curlimages/curl:8.8.0 sh -lc ' - for i in $(seq 1 30); do - if curl -fsS http://127.0.0.1:8000/api/v1/health; then - exit 0 - fi - sleep 2 - done - exit 1 - ' + set -eu + i=0 + while [ "$i" -lt 30 ]; do + if wget -qO- http://127.0.0.1:8000/api/v1/health; then + exit 0 + fi + i=$((i + 1)) + sleep 2 + done + exit 1 diff --git a/.gitea/workflows/tests.yml b/.gitea/workflows/tests.yml index c28b527e6..23c1b3e2e 100644 --- a/.gitea/workflows/tests.yml +++ b/.gitea/workflows/tests.yml @@ -9,70 +9,111 @@ on: jobs: test: runs-on: ubuntu-latest + container: + image: python:3.12-slim steps: - name: Checkout code - shell: bash + shell: sh run: | - set -euo pipefail - repo_url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git" - auth_header="AUTHORIZATION: basic $(printf 'x-access-token:%s' "$GITHUB_TOKEN" | base64 -w0)" - git init . - git remote add origin "$repo_url" - git -c http.https://api.xiaoxiajianji.com/.extraheader="$auth_header" fetch --depth=1 origin "$GITHUB_SHA" - git checkout --force FETCH_HEAD + set -eu + python - <<'PY' + import io + import os + import tarfile + import urllib.request - - name: Install Python tooling + url = f"{os.environ['GITHUB_API_URL']}/repos/{os.environ['GITHUB_REPOSITORY']}/archive/{os.environ['GITHUB_SHA']}.tar.gz" + request = urllib.request.Request(url, headers={"Authorization": f"token {os.environ['GITHUB_TOKEN']}"}) + with urllib.request.urlopen(request, timeout=120) as response: + archive = response.read() + + with tarfile.open(fileobj=io.BytesIO(archive), mode='r:gz') as tar: + root_prefix = tar.getmembers()[0].name.split('/', 1)[0] + '/' + for member in tar.getmembers(): + name = member.name + if name == root_prefix[:-1]: + continue + if name.startswith(root_prefix): + member.name = name[len(root_prefix):] + if member.name: + tar.extract(member, '.') + PY + + - name: Show Python version + shell: sh run: | - apt-get update - apt-get install -y python3-pip python3-venv - python3 --version - python3 -m pip --version + set -eu + python --version + python -m pip --version - name: Install dependencies + shell: sh run: | - python3 -m pip install --break-system-packages --upgrade pip - python3 -m pip install --break-system-packages -r requirements.txt -r requirements-dev.txt + set -eu + python -m pip install --upgrade pip -i https://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com + python -m pip install -r requirements.txt -r requirements-dev.txt -i https://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com - name: Run tests + shell: sh run: | - python3 -m pytest tests/integration/ -v --cov=packages --cov=apps --cov-report=xml --cov-report=term + set -eu + python -m pytest tests/integration/ -v --cov=packages --cov=apps --cov-report=xml --cov-report=term lint: runs-on: ubuntu-latest + container: + image: python:3.12-slim steps: - name: Checkout code - shell: bash + shell: sh run: | - set -euo pipefail - repo_url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git" - auth_header="AUTHORIZATION: basic $(printf 'x-access-token:%s' "$GITHUB_TOKEN" | base64 -w0)" - git init . - git remote add origin "$repo_url" - git -c http.https://api.xiaoxiajianji.com/.extraheader="$auth_header" fetch --depth=1 origin "$GITHUB_SHA" - git checkout --force FETCH_HEAD + set -eu + python - <<'PY' + import io + import os + import tarfile + import urllib.request - - name: Install Python tooling - run: | - apt-get update - apt-get install -y python3-pip python3-venv - python3 --version - python3 -m pip --version + url = f"{os.environ['GITHUB_API_URL']}/repos/{os.environ['GITHUB_REPOSITORY']}/archive/{os.environ['GITHUB_SHA']}.tar.gz" + request = urllib.request.Request(url, headers={"Authorization": f"token {os.environ['GITHUB_TOKEN']}"}) + with urllib.request.urlopen(request, timeout=120) as response: + archive = response.read() - - name: Install linting tools + with tarfile.open(fileobj=io.BytesIO(archive), mode='r:gz') as tar: + root_prefix = tar.getmembers()[0].name.split('/', 1)[0] + '/' + for member in tar.getmembers(): + name = member.name + if name == root_prefix[:-1]: + continue + if name.startswith(root_prefix): + member.name = name[len(root_prefix):] + if member.name: + tar.extract(member, '.') + PY + + - name: Install dependencies + shell: sh run: | - python3 -m pip install --break-system-packages --upgrade pip - python3 -m pip install --break-system-packages -r requirements.txt -r requirements-dev.txt + set -eu + python -m pip install --upgrade pip -i https://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com + python -m pip install -r requirements.txt -r requirements-dev.txt -i https://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com - name: Run Black (check only) + shell: sh run: | - python3 -m black --check packages/ apps/ tests/ + set -eu + python -m black --check packages/ apps/ tests/ - name: Run Flake8 + shell: sh run: | - python3 -m flake8 packages/ apps/ tests/ --max-line-length=120 --extend-ignore=E203,W503 + set -eu + python -m flake8 packages/ apps/ tests/ --max-line-length=120 --extend-ignore=E203,W503 - name: Run MyPy + shell: sh run: | - python3 -m mypy packages/ apps/ --ignore-missing-imports + set -eu + python -m mypy packages/ apps/ --ignore-missing-imports