Feature/measurement creation #142
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Deploy | |
| on: | |
| push: | |
| branches: | |
| - master | |
| pull_request: | |
| branches: | |
| - master | |
| release: | |
| types: [published] | |
| issue_comment: | |
| types: [created] | |
| workflow_dispatch: | |
| inputs: | |
| environment: | |
| description: "Environment to deploy" | |
| required: true | |
| type: choice | |
| options: | |
| - test | |
| - master | |
| - release | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| issues: write | |
| env: | |
| REGISTRY: ghcr.io | |
| IMAGE_NAME_BASE: universalscientifictechnologies/dosportal | |
| jobs: | |
| backend-tests: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v6 | |
| - name: Build backend image | |
| run: docker compose -p "ci-${{ github.run_id }}" -f docker-compose.yml build backend | |
| - name: Create .env file for tests | |
| run: cp .env.example .env | |
| - name: Start dependencies | |
| run: docker compose -p "ci-${{ github.run_id }}" -f docker-compose.yml up -d db_dosportal redis minio | |
| - name: Run backend tests | |
| run: docker compose -p "ci-${{ github.run_id }}" -f docker-compose.yml run --rm --entrypoint "pytest" backend DOSPORTAL/tests/ | |
| - name: Shutdown services | |
| if: always() | |
| run: docker compose -p "ci-${{ github.run_id }}" -f docker-compose.yml down -v | |
| build-check: | |
| runs-on: ubuntu-latest | |
| if: github.event_name == 'pull_request' | |
| strategy: | |
| matrix: | |
| include: | |
| - component: backend | |
| dockerfile: backend.Dockerfile | |
| - component: frontend | |
| dockerfile: frontend.Dockerfile | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v6 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Build Docker image (${{ matrix.component }}) | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: . | |
| file: ${{ matrix.dockerfile }} | |
| push: false | |
| build-args: | | |
| GIT_COMMIT=${{ github.sha }} | |
| GIT_BRANCH=${{ github.head_ref }} | |
| # Determine deployment environment | |
| prepare: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| issues: write | |
| deployments: write | |
| outputs: | |
| should_deploy: ${{ steps.check.outputs.should_deploy }} | |
| environment: ${{ steps.check.outputs.environment }} | |
| image_tag: ${{ steps.check.outputs.image_tag }} | |
| app_dir: ${{ steps.check.outputs.app_dir }} | |
| project_name: ${{ steps.check.outputs.project_name }} | |
| comment_id: ${{ steps.react.outputs.comment_id }} | |
| deployment_id: ${{ steps.create_deployment.outputs.deployment_id }} | |
| steps: | |
| - name: React to PR comment | |
| if: github.event_name == 'issue_comment' | |
| id: react | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| await github.rest.reactions.createForIssueComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: context.payload.comment.id, | |
| content: 'eyes' | |
| }); | |
| core.setOutput('comment_id', context.payload.comment.id); | |
| - name: Check deployment conditions | |
| id: check | |
| env: | |
| COMMENT_BODY: ${{ github.event.comment.body }} | |
| run: | | |
| SHOULD_DEPLOY="false" | |
| ENVIRONMENT="" | |
| IMAGE_TAG="" | |
| APP_DIR="" | |
| PROJECT_NAME="" | |
| # Release (tag or published release) | |
| if [[ "${{ github.event_name }}" == "release" || ( "${{ github.event_name }}" == "push" && "${{ github.ref }}" == refs/tags/v* ) ]]; then | |
| SHOULD_DEPLOY="true" | |
| ENVIRONMENT="release" | |
| if [[ "${{ github.event_name }}" == "release" ]]; then | |
| IMAGE_TAG="${{ github.event.release.tag_name }}" | |
| else | |
| IMAGE_TAG="${GITHUB_REF#refs/tags/}" | |
| fi | |
| APP_DIR="/data/ust/dosportal/release" | |
| PROJECT_NAME="dosportal-release" | |
| # Master branch (master or dev) | |
| elif [[ "${{ github.event_name }}" == "push" && ( "${{ github.ref }}" == "refs/heads/master" || "${{ github.ref }}" == "refs/heads/dev" ) ]]; then | |
| SHOULD_DEPLOY="true" | |
| ENVIRONMENT="master" | |
| IMAGE_TAG="master" | |
| APP_DIR="/data/ust/dosportal/master" | |
| PROJECT_NAME="dosportal-master" | |
| # PR comment with /test-me | |
| elif [[ "${{ github.event_name }}" == "issue_comment" ]]; then | |
| if [[ "$COMMENT_BODY" == *"/test-me"* ]] && [[ "${{ github.event.issue.pull_request }}" != "" ]]; then | |
| SHOULD_DEPLOY="true" | |
| ENVIRONMENT="test" | |
| IMAGE_TAG="pr-${{ github.event.issue.number }}" | |
| APP_DIR="/data/ust/dosportal/test" | |
| PROJECT_NAME="dosportal-test" | |
| fi | |
| # Manual workflow dispatch | |
| elif [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then | |
| SHOULD_DEPLOY="true" | |
| ENVIRONMENT="${{ inputs.environment }}" | |
| if [[ "$ENVIRONMENT" == "release" ]]; then | |
| IMAGE_TAG="latest" | |
| APP_DIR="/data/ust/dosportal/release" | |
| PROJECT_NAME="dosportal-release" | |
| elif [[ "$ENVIRONMENT" == "master" ]]; then | |
| IMAGE_TAG="master" | |
| APP_DIR="/data/ust/dosportal/master" | |
| PROJECT_NAME="dosportal-master" | |
| elif [[ "$ENVIRONMENT" == "test" ]]; then | |
| IMAGE_TAG="test" | |
| APP_DIR="/data/ust/dosportal/test" | |
| PROJECT_NAME="dosportal-test" | |
| fi | |
| fi | |
| echo "should_deploy=$SHOULD_DEPLOY" >> $GITHUB_OUTPUT | |
| echo "environment=$ENVIRONMENT" >> $GITHUB_OUTPUT | |
| echo "image_tag=$IMAGE_TAG" >> $GITHUB_OUTPUT | |
| echo "app_dir=$APP_DIR" >> $GITHUB_OUTPUT | |
| echo "project_name=$PROJECT_NAME" >> $GITHUB_OUTPUT | |
| echo "Deployment decision:" | |
| echo " Should deploy: $SHOULD_DEPLOY" | |
| echo " Environment: $ENVIRONMENT" | |
| echo " Image tag: $IMAGE_TAG" | |
| - name: Create GitHub deployment | |
| if: steps.check.outputs.should_deploy == 'true' && steps.check.outputs.environment == 'test' && github.event_name == 'issue_comment' | |
| id: create_deployment | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const deployment = await github.rest.repos.createDeployment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| ref: 'refs/pull/${{ github.event.issue.number }}/head', | |
| environment: 'test', | |
| description: 'Test deployment for PR #${{ github.event.issue.number }}', | |
| auto_merge: false, | |
| required_contexts: [] | |
| }); | |
| core.setOutput('deployment_id', deployment.data.id); | |
| await github.rest.repos.createDeploymentStatus({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| deployment_id: deployment.data.id, | |
| state: 'in_progress', | |
| description: 'Deployment in progress' | |
| }); | |
| build-and-push: | |
| runs-on: ubuntu-latest | |
| needs: [prepare, backend-tests] | |
| if: needs.prepare.outputs.should_deploy == 'true' | |
| permissions: | |
| contents: read | |
| packages: write | |
| strategy: | |
| matrix: | |
| include: | |
| - component: backend | |
| dockerfile: backend.Dockerfile | |
| - component: frontend | |
| dockerfile: frontend.Dockerfile | |
| steps: | |
| - name: Get PR info if needed | |
| if: github.event_name == 'issue_comment' | |
| id: pr_info | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const pr = await github.rest.pulls.get({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| pull_number: context.issue.number | |
| }); | |
| core.setOutput('ref', pr.data.head.ref); | |
| core.setOutput('sha', pr.data.head.sha); | |
| core.setOutput('repo', pr.data.head.repo.full_name); | |
| - name: Checkout repository | |
| uses: actions/checkout@v6 | |
| with: | |
| repository: ${{ github.event_name == 'issue_comment' && steps.pr_info.outputs.repo || github.repository }} | |
| ref: ${{ github.event_name == 'issue_comment' && steps.pr_info.outputs.ref || github.ref }} | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Log in to the Container registry | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ${{ env.REGISTRY }} | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Extract metadata (tags, labels) for Docker | |
| id: meta | |
| uses: docker/metadata-action@v5 | |
| with: | |
| images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_BASE }}-${{ matrix.component }} | |
| tags: | | |
| type=ref,event=branch | |
| type=ref,event=tag | |
| type=semver,pattern={{version}} | |
| type=raw,value=${{ needs.prepare.outputs.image_tag }} | |
| - name: Build and push Docker image (${{ matrix.component }}) | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: . | |
| file: ${{ matrix.dockerfile }} | |
| push: true | |
| tags: ${{ steps.meta.outputs.tags }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| build-args: | | |
| GIT_COMMIT=${{ github.event_name == 'issue_comment' && steps.pr_info.outputs.sha || github.sha }} | |
| GIT_BRANCH=${{ github.event_name == 'issue_comment' && steps.pr_info.outputs.ref || github.ref_name }} | |
| cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME_BASE }}-${{ matrix.component }}:buildcache | |
| cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME_BASE }}-${{ matrix.component }}:buildcache,mode=max | |
| deploy: | |
| name: Deploy to ${{ needs.prepare.outputs.environment }} | |
| needs: [prepare, build-and-push] | |
| if: needs.prepare.outputs.should_deploy == 'true' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| deployments: write | |
| environment: | |
| name: ${{ needs.prepare.outputs.environment }} | |
| url: ${{ needs.prepare.outputs.environment == 'release' && 'https://portal.dos.ust.cz' || (needs.prepare.outputs.environment == 'master' && 'https://master.portal.dos.ust.cz' || '') }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v6 | |
| - name: Read docker-compose file | |
| id: compose_file | |
| run: | | |
| COMPOSE_CONTENT=$(cat docker-compose.prod.yml | base64 -w 0) | |
| echo "content=$COMPOSE_CONTENT" >> $GITHUB_OUTPUT | |
| - name: Deploy via SSH | |
| uses: appleboy/ssh-action@v1.0.3 | |
| env: | |
| APP_DIR: ${{ needs.prepare.outputs.app_dir }} | |
| ENV_FILE: ${{ needs.prepare.outputs.app_dir }}/env/${{ needs.prepare.outputs.environment }}.env | |
| COMPOSE_FILE: docker-compose.prod.yml | |
| PROJECT_NAME: ${{ needs.prepare.outputs.project_name }} | |
| REGISTRY: ${{ env.REGISTRY }} | |
| IMAGE_NAME_BASE: ${{ env.IMAGE_NAME_BASE }} | |
| IMAGE_TAG: ${{ needs.prepare.outputs.image_tag }} | |
| COMPOSE_CONTENT: ${{ steps.compose_file.outputs.content }} | |
| with: | |
| host: ${{ secrets.DEPLOY_HOST }} | |
| username: ${{ secrets.DEPLOY_USER }} | |
| key: ${{ secrets.DEPLOY_SSH_KEY }} | |
| envs: APP_DIR,ENV_FILE,COMPOSE_FILE,PROJECT_NAME,REGISTRY,IMAGE_NAME_BASE,IMAGE_TAG,COMPOSE_CONTENT | |
| script: | | |
| set -euo pipefail | |
| echo "==> Deploying to environment: $PROJECT_NAME" | |
| echo "==> Using image tag: $IMAGE_TAG" | |
| echo "==> Preparing app dir" | |
| mkdir -p "$APP_DIR/env" | |
| cd "$APP_DIR" | |
| echo "==> Creating docker-compose file" | |
| echo "$COMPOSE_CONTENT" | base64 -d > "$COMPOSE_FILE" | |
| echo "==> Sanity checks" | |
| test -f "$COMPOSE_FILE" || (echo "Missing compose file: $APP_DIR/$COMPOSE_FILE" && exit 1) | |
| if [ ! -f "$ENV_FILE" ]; then | |
| echo "Creating dummy env file at $ENV_FILE" | |
| touch "$ENV_FILE" | |
| fi | |
| docker version >/dev/null | |
| docker compose version >/dev/null | |
| echo "==> Remove old images with same tag to force fresh pull" | |
| docker rmi "$REGISTRY/$IMAGE_NAME_BASE-backend:$IMAGE_TAG" 2>/dev/null || true | |
| docker rmi "$REGISTRY/$IMAGE_NAME_BASE-frontend:$IMAGE_TAG" 2>/dev/null || true | |
| echo "==> Pull images (explicit, public GHCR)" | |
| docker pull "$REGISTRY/$IMAGE_NAME_BASE-backend:$IMAGE_TAG" | |
| docker pull "$REGISTRY/$IMAGE_NAME_BASE-frontend:$IMAGE_TAG" | |
| echo "==> Check for override file" | |
| COMPOSE_ARGS="-f $COMPOSE_FILE" | |
| if [ -f "docker-compose.override.yml" ]; then | |
| echo "Found docker-compose.override.yml, applying it" | |
| COMPOSE_ARGS="$COMPOSE_ARGS -f docker-compose.override.yml" | |
| fi | |
| echo "==> Compose pull" | |
| docker compose -p "$PROJECT_NAME" $COMPOSE_ARGS --env-file "$ENV_FILE" pull | |
| echo "==> Up (force recreate to apply env changes)" | |
| docker compose -p "$PROJECT_NAME" $COMPOSE_ARGS --env-file "$ENV_FILE" up -d --force-recreate --remove-orphans | |
| echo "==> Initialize DOSPORTAL (migrations + fixtures + MinIO)" | |
| docker exec "${PROJECT_NAME}-backend-1" python manage.py init_dosportal | |
| echo "==> Show status" | |
| docker compose -p "$PROJECT_NAME" $COMPOSE_ARGS ps | |
| echo "==> Cleanup old images (keep recent ones)" | |
| docker image prune -af --filter "until=168h" | |
| echo "==> Done" | |
| - name: Mark deployment as successful | |
| if: needs.prepare.outputs.environment == 'test' && github.event_name == 'issue_comment' && needs.prepare.outputs.deployment_id | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| await github.rest.repos.createDeploymentStatus({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| deployment_id: ${{ needs.prepare.outputs.deployment_id }}, | |
| state: 'success', | |
| environment_url: 'https://test.portal.dos.ust.cz', | |
| description: 'Deployment completed successfully' | |
| }); | |
| - name: Update PR comment with deployment info | |
| if: needs.prepare.outputs.environment == 'test' && github.event_name == 'issue_comment' && needs.prepare.outputs.comment_id | |
| continue-on-error: true | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| try { | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| body: '✅ **Test deployment completed!**\n🔗 Available at: https://test.portal.dos.ust.cz' | |
| }); | |
| } catch (error) { | |
| console.error('Could not create comment:', error.message); | |
| } |