diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 22968f1531b4..b6eb6cbe580e 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -73,18 +73,7 @@ Prerequisites: 4. ... ### Testserver States -> [!NOTE] -> These badges show the state of the test servers. -> Green = Currently available, Red = Currently locked -> Click on the badges to get to the test servers. - -[![](https://byob.yarr.is/ls1intum/Artemis/artemis-test1)](https://artemis-test1.artemis.cit.tum.de) -[![](https://byob.yarr.is/ls1intum/Artemis/artemis-test2)](https://artemis-test2.artemis.cit.tum.de) -[![](https://byob.yarr.is/ls1intum/Artemis/artemis-test3)](https://artemis-test3.artemis.cit.tum.de) -[![](https://byob.yarr.is/ls1intum/Artemis/artemis-test4)](https://artemis-test4.artemis.cit.tum.de) -[![](https://byob.yarr.is/ls1intum/Artemis/artemis-test5)](https://artemis-test5.artemis.cit.tum.de) -[![](https://byob.yarr.is/ls1intum/Artemis/artemis-test6)](https://artemis-test6.artemis.cit.tum.de) -[![](https://byob.yarr.is/ls1intum/Artemis/artemis-test9)](https://artemis-test9.artemis.cit.tum.de) +You can manage test servers using [Helios](https://helios.aet.cit.tum.de/). Check environment statuses in the [environment list](https://helios.aet.cit.tum.de/repo/69562331/environment/list). To deploy to a test server, go to the [CI/CD](https://helios.aet.cit.tum.de/repo/69562331/ci-cd) page, find your PR or branch, and trigger the deployment. ### Review Progress @@ -111,7 +100,7 @@ Prerequisites: - [ ] Test 2 ### Test Coverage - + diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 18aa94ff1059..f29fa7b2cfaa 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -57,7 +57,7 @@ updates: # Check for version updates for Python dependencies (coverage) - package-ecosystem: "pip" - directory: "/supporting_scripts/generate_code_cov_table" + directory: "/supporting_scripts/code-coverage/generate_code_cov_table" schedule: interval: "weekly" reviewers: diff --git a/.github/issue-labeler.yml b/.github/issue-labeler.yml index 34ca1e28321c..8fc424b1e785 100644 --- a/.github/issue-labeler.yml +++ b/.github/issue-labeler.yml @@ -111,7 +111,6 @@ programming: - build plan - code hint - \b(?> $GITHUB_OUTPUT + echo "release_url=${{ github.event.release.upload_url }}" >> $GITHUB_OUTPUT + echo "release_path=build/libs/Artemis-${{ github.event.release.tag_name }}.war" >> $GITHUB_OUTPUT + echo "release_name=Artemis.war" >> $GITHUB_OUTPUT + echo "release_type=application/x-webarchive" >> $GITHUB_OUTPUT + else + echo "release_upload=false" >> $GITHUB_OUTPUT + fi - docker: - name: Build and Push Docker Image - if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'ls1intum/Artemis' }} - runs-on: ubuntu-latest - steps: - - name: Compute Tag - uses: actions/github-script@v7 - id: compute-tag - with: - result-encoding: string - script: | - if (context.eventName === "pull_request") { - return "pr-" + context.issue.number; - } - if (context.eventName === "release") { - return "latest"; - } - if (context.eventName === "push") { - if (context.ref.startsWith("refs/tags/")) { - return context.ref.slice(10); + - name: Set Docker Build Flag + id: set-docker-build + run: | + if [[ ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'ls1intum/Artemis' }} ]]; then + echo "docker_build=true" >> $GITHUB_OUTPUT + else + echo "docker_build=false" >> $GITHUB_OUTPUT + fi + + - name: Set Docker ref + if: ${{ steps.set-docker-build.outputs.docker_build == 'true' }} + id: set-docker-ref + run: | + if [[ "${{ github.event_name }}" == "pull_request" ]]; then + # Checkout pull request HEAD commit instead of merge commit + # this is done to include the correct branch and git information inside the build + echo "docker_ref=${{ github.event.pull_request.head.ref }}" >> $GITHUB_OUTPUT + elif [[ "${{ github.event_name }}" == "push" ]]; then + echo "docker_ref=${{ github.ref_name }}" >> $GITHUB_OUTPUT + fi + + - name: Compute Docker Tag + if: ${{ steps.set-docker-build.outputs.docker_build == 'true' }} + uses: actions/github-script@v7 + id: compute-tag + with: + result-encoding: string + script: | + if (context.eventName === "pull_request") { + return "pr-" + context.issue.number; + } + if (context.eventName === "release") { + return "latest"; } - if (context.ref === "refs/heads/develop") { - return "develop"; + if (context.eventName === "push") { + if (context.ref.startsWith("refs/tags/")) { + return context.ref.slice(10); + } + if (context.ref === "refs/heads/develop") { + return "develop"; + } } - } - return "FALSE"; - - name: Git Checkout for PRs - if: ${{ github.event_name == 'pull_request' }} - # Checkout pull request HEAD commit instead of merge commit - # this is done to include the correct branch and git information inside the build - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.ref }} - - name: Git Checkout for push actions - if: ${{ github.event_name == 'push' }} - uses: actions/checkout@v4 - with: - ref: ${{ github.ref_name }} - - name: Git Checkout for push actions - if: ${{ github.event_name == 'release' }} - uses: actions/checkout@v4 - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - # Build and Push to GitHub Container Registry - - name: Login to GitHub Container Registry - uses: docker/login-action@v3 - if: ${{ steps.compute-tag.outputs.result != 'FALSE' }} - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Build and Push to GitHub Container Registry - uses: docker/build-push-action@v5 - if: ${{ steps.compute-tag.outputs.result != 'FALSE' }} - with: - # beware that the linux/arm64 build from the registry is using an amd64 compiled .war file as - # the GitHub runners don't support arm64 and QEMU takes too long for emulating the build - platforms: linux/amd64,linux/arm64 - file: ./docker/artemis/Dockerfile - context: . - tags: ghcr.io/ls1intum/artemis:${{ steps.compute-tag.outputs.result }} - push: true - cache-from: type=gha - cache-to: type=gha,mode=min + return "FALSE"; + + - name: Set Docker Tag + id: set-docker-tag + run: | + if [[ ${{ steps.compute-tag.outputs.result != 'FALSE' }} ]]; then + echo "docker_build_tag=${{ steps.compute-tag.outputs.result }}" >> $GITHUB_OUTPUT + fi - # TODO: Push to Docker Hub (develop + tag) - # TODO: Push to Chair Harbour (??) + call-build-workflow: + name: Call Build Workflow + needs: define-inputs + uses: ./.github/workflows/reusable-build.yml + with: + build_war: true + release_upload: ${{ needs.define-inputs.outputs.release_upload == 'true' }} + release_url: ${{ needs.define-inputs.outputs.release_url }} + release_path: ${{ needs.define-inputs.outputs.release_path }} + release_name: ${{ needs.define-inputs.outputs.release_name }} + release_type: ${{ needs.define-inputs.outputs.release_type }} + docker: ${{ needs.define-inputs.outputs.docker_build == 'true' }} + docker_ref: ${{ needs.define-inputs.outputs.docker_ref }} + docker_build_tag: ${{ needs.define-inputs.outputs.docker_build_tag }} diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 785d5f58fd7b..081cb2ccae03 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -30,7 +30,7 @@ on: # Keep in sync with build.yml and test.yml and analysis-of-endpoint-connections.yml env: CI: true - node: 20 + node: 22 java: 21 diff --git a/.github/workflows/pullrequest-closed.yml b/.github/workflows/pullrequest-closed.yml index 62f25e00de09..256332e88f9c 100644 --- a/.github/workflows/pullrequest-closed.yml +++ b/.github/workflows/pullrequest-closed.yml @@ -18,79 +18,3 @@ jobs: token: ${{ secrets.GH_TOKEN_ADD_TO_PROJECT }} tag: pr-${{ github.event.pull_request.number }} untagged-older-than: 28 - - # If a PR is closed the testserver lock should be removed and corresponding badges updated - process_labels: - name: Process labels - runs-on: ubuntu-latest - outputs: - labels: ${{ steps.process.outputs.labels }} - badges: ${{ steps.process.outputs.badges }} - steps: - - name: Process labels - id: process - uses: actions/github-script@v7 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - script: | - const labelsToRemove = []; - const labelsToProcess = []; - - // Get the PR number - const prNumber = context.payload.pull_request.number; - - // Iterate through labels on the PR - for (const label of context.payload.pull_request.labels) { - const labelName = label.name; - const regex = /^lock:artemis-test(\d+)$/; - - if (regex.test(labelName)) { - // Extract the part after "lock:" using capture groups - const extractedLabel = labelName.match(regex)[1]; - labelsToProcess.push(extractedLabel); - labelsToRemove.push(labelName); - } - } - - // Do something with the extracted labels - console.log('Badges to process:', labelsToProcess); - console.log('Labels to remove:', labelsToRemove); - - // Use the labelsToRemove array to remove the matching labels - core.setOutput('badges', JSON.stringify(labelsToProcess)); - core.setOutput('labels', labelsToRemove.join(', ')); - - - remove_labels: - name: Remove labels - needs: process_labels - runs-on: ubuntu-latest - if: ${{ needs.process_labels.outputs.labels != '' }} - - steps: - - name: Remove labels - uses: actions-ecosystem/action-remove-labels@v1 - with: - labels: ${{ needs.process_labels.outputs.labels }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - update_badges: - name: Update badges - needs: process_labels - runs-on: ubuntu-latest - strategy: - matrix: - badge: ${{ fromJson(needs.process_labels.outputs.badges) }} - if: ${{ needs.process_labels.outputs.labels != '' }} - - steps: - - name: Update badge - uses: RubbaBoy/BYOB@v1.3.0 - with: - NAME: "artemis-test${{ matrix.badge }}" - LABEL: "artemis-test${{ matrix.badge }}.artemis.cit.tum.de" - STATUS: ${{ github.event.pull_request.head.ref }} - COLOR: green - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/pullrequest-unlabeled.yml b/.github/workflows/pullrequest-unlabeled.yml deleted file mode 100644 index 1ceffae87430..000000000000 --- a/.github/workflows/pullrequest-unlabeled.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: Pull Request Label Removal - -on: - pull_request_target: - types: - - unlabeled - -jobs: - update_badges: - name: Update test server badges - runs-on: ubuntu-latest - if: startsWith(github.event.label.name, 'lock:artemis-test') - - steps: - - name: Get badge id - id: env - uses: actions/github-script@v7 - with: - script: | - const labelName = context.payload.label.name; - const badge = labelName.replace(/^lock:artemis-test/, ''); - core.setOutput('BADGE', badge); - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Update badge - uses: RubbaBoy/BYOB@v1.3.0 - with: - NAME: "artemis-test${{ steps.env.outputs.BADGE }}" - LABEL: "artemis-test${{ steps.env.outputs.BADGE }}.artemis.cit.tum.de" - STATUS: ${{ github.event.pull_request.head.ref }} - COLOR: green - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/reusable-build.yml b/.github/workflows/reusable-build.yml new file mode 100644 index 000000000000..6960bf6c5b45 --- /dev/null +++ b/.github/workflows/reusable-build.yml @@ -0,0 +1,193 @@ +name: Build + +on: + workflow_call: + inputs: + # Build job inputs + build_war: + description: "Whether to build and upload the .war artifact." + required: false + default: false + type: boolean + build_ref: + description: "Branch name, tag, or commit SHA to use for the build job. If not provided, it falls back to the default behavior of actions/checkout." + required: false + default: '' + type: string + + # Upload Release Artifact job inputs + release_upload: + description: "Whether to upload the release artifact." + required: false + default: false + type: boolean + release_url: + description: "URL to upload the release artifact to." + required: false + default: '' + type: string + release_path: + description: "Path to the release artifact." + required: false + default: '' + type: string + release_name: + description: "Name of the release artifact." + required: false + default: '' + type: string + release_type: + description: "Content type of the release artifact." + required: false + default: '' + type: string + + # Docker job inputs + docker: + description: "Whether to build and push a Docker image." + required: false + default: false + type: boolean + docker_ref: + description: "Branch name, tag, or commit SHA to use for the Docker job. If not provided, it falls back to the default behavior of actions/checkout." + required: false + default: '' + type: string + docker_build_tag: + description: "Tag to use when building Docker image." + required: false + default: '' + type: string + +# Keep in sync with codeql-analysis.yml and test.yml and analysis-of-endpoint-connections.yml +env: + CI: true + node: 22 + java: 21 + +jobs: + validate-inputs: + name: Validate Inputs + runs-on: ubuntu-latest + steps: + - name: Validate Inputs + run: | + # Check release related inputs + if [[ "${{ github.event.inputs.release_upload }}" ]]; then + # List of required release inputs + missing_inputs=() + + # Check each required input + [[ -z "${{ inputs.release_url }}" || "${{ inputs.release_url }}" == '' ]] && missing_inputs+=("release_url") + [[ -z "${{ inputs.release_path }}" || "${{ inputs.release_path }}" == '' ]] && missing_inputs+=("release_path") + [[ -z "${{ inputs.release_name }}" || "${{ inputs.release_name }}" == '' ]] && missing_inputs+=("release_name") + [[ -z "${{ inputs.release_type }}" || "${{ inputs.release_type }}" == '' ]] && missing_inputs+=("release_type") + + if [[ "${#missing_inputs[@]}" -gt 0 ]]; then + echo "::error::Release upload is set to true, but the following inputs are missing: ${missing_inputs[*]}" + exit 1 + fi + fi + + # Check Docker related inputs + if [[ "${{ github.event.inputs.docker }}" ]]; then + # Check whether all Docker inputs are set + if [[ "${{ github.event.inputs.docker_build_tag }}" == '' ]]; then + echo "::error::Docker build is set to true, but Docker build tag is not set." + exit 1 + fi + fi + + + build: + name: Build .war artifact + if: ${{ inputs.build_war }} + needs: validate-inputs + runs-on: ubuntu-latest + steps: + # Git Checkout + - name: Git Checkout to the specific ref (if build_ref is set) + uses: actions/checkout@v4 + if: ${{ inputs.build_ref != '' }} + with: + ref: ${{ inputs.build_ref }} + - name: Git Checkout (default) + uses: actions/checkout@v4 + if: ${{ inputs.build_ref == '' }} + # Setup Node.js, Java and Gradle + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '${{ env.node }}' + cache: 'npm' + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '${{ env.java }}' + cache: 'gradle' + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v3 + # Build + - name: Production Build + run: ./gradlew -Pprod -Pwar clean bootWar + # Upload Artifact + - name: Upload Artifact + uses: actions/upload-artifact@v4 + with: + name: Artemis.war + path: build/libs/Artemis-*.war + # Upload Artifact (Release) + - name: Upload Release Artifact + if: ${{ inputs.release_upload }} + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ inputs.release_url }} + asset_path: ${{ inputs.release_path }} + asset_name: ${{ inputs.release_name }} + asset_content_type: ${{ inputs.release_type }} + + docker: + name: Build and Push Docker Image + if: ${{ inputs.docker }} + needs: validate-inputs + runs-on: ubuntu-latest + steps: + # Git Checkout + - name: Git Checkout to the specific ref (if docker_ref is set) + uses: actions/checkout@v4 + if: ${{ inputs.docker_ref != '' }} + with: + ref: ${{ inputs.docker_ref }} + - name: Git Checkout (default) + uses: actions/checkout@v4 + if: ${{ inputs.docker_ref == '' }} + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + # Build and Push to GitHub Container Registry + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Build and Push to GitHub Container Registry + uses: docker/build-push-action@v5 + with: + # beware that the linux/arm64 build from the registry is using an amd64 compiled .war file as + # the GitHub runners don't support arm64 and QEMU takes too long for emulating the build + platforms: linux/amd64,linux/arm64 + file: ./docker/artemis/Dockerfile + context: . + tags: ghcr.io/ls1intum/artemis:${{ inputs.docker_build_tag }} + push: true + cache-from: type=gha + cache-to: type=gha,mode=min + + # TODO: Push to Docker Hub (develop + tag) + + # TODO: Push to Chair Harbour (??) diff --git a/.github/workflows/staging-deployment.yml b/.github/workflows/staging-deployment.yml new file mode 100644 index 000000000000..23e89c909afa --- /dev/null +++ b/.github/workflows/staging-deployment.yml @@ -0,0 +1,251 @@ +name: Artemis Staging Deployment + +on: + workflow_dispatch: + inputs: + branch_name: + description: 'Branch to deploy' + required: true + commit_sha: + description: 'Commit SHA to deploy' + required: true + environment_name: + description: 'Environment to deploy to' + required: true + type: choice + options: + - artemis-staging-localci.artemis.cit.tum.de + +concurrency: ${{ github.event.inputs.environment_name }} + +env: + build_workflow_name: build.yml + +jobs: + check-build-status: + runs-on: ubuntu-latest + outputs: + build_workflow_run_id: ${{ steps.set_build_workflow_id.outputs.workflow_id }} + steps: + - name: Print inputs + run: | + echo "Branch: ${{ github.event.inputs.branch_name }}" + echo "Commit SHA: ${{ github.event.inputs.commit_sha }}" + echo "Environment: ${{ github.event.inputs.environment_name }}" + + - name: Fetch workflow runs by branch and commit + id: get_workflow_run + uses: octokit/request-action@v2.x + with: + route: GET /repos/${{ github.repository }}/actions/workflows/${{ env.build_workflow_name }}/runs?branch=${{ github.event.inputs.branch_name }}&head_sha=${{ github.event.inputs.commit_sha }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract workflow ID + id: set_build_workflow_id + run: | + WORKFLOW_DATA='${{ steps.get_workflow_run.outputs.data }}' + + WORKFLOW_ID=$(echo "$WORKFLOW_DATA" | jq -r ' + .workflow_runs[0].id // empty + ') + + if [ -z "$WORKFLOW_ID" ]; then + echo "::error::No build found for commit ${{ github.event.inputs.commit_sha }} on branch ${{ github.event.inputs.branch_name }}" + exit 1 + fi + + echo "Found build workflow ID: $WORKFLOW_ID for commit ${{ github.event.inputs.commit_sha }} on branch ${{ github.event.inputs.branch_name }}" + echo "workflow_id=$WORKFLOW_ID" >> $GITHUB_OUTPUT + + - name: Check for war artifact + id: verify_artifact + uses: octokit/request-action@v2.x + with: + route: GET /repos/${{ github.repository }}/actions/runs/${{ steps.set_build_workflow_id.outputs.workflow_id }}/artifacts?name=Artemis.war + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Verify artifact exists + id: check_result + run: | + TOTAL_COUNT=$(echo '${{ steps.verify_artifact.outputs.data }}' | jq -r '.total_count') + + if [ "$TOTAL_COUNT" -gt 0 ]; then + echo "Found Artemis.war artifact in build for commit ${{ github.event.inputs.commit_sha }}" + else + echo "::error::No Artemis.war artifact found in build for commit ${{ github.event.inputs.commit_sha }}!" + exit 1 + fi + + deploy: + needs: check-build-status + runs-on: [self-hosted, ase-large-ubuntu] + environment: + name: ${{ github.event.inputs.environment_name }} + url: ${{ vars.DEPLOYMENT_URL }} + env: + DEPLOYMENT_HOSTS_PRIMARY: ${{ vars.DEPLOYMENT_HOSTS_PRIMARY }} + DEPLOYMENT_HOSTS_SECONDARY: ${{ vars.DEPLOYMENT_HOSTS_SECONDARY }} + DEPLOYMENT_USER: ${{ vars.DEPLOYMENT_USER }} + DEPLOYMENT_FOLDER: ${{ vars.DEPLOYMENT_FOLDER }} + HEALTH_CHECK_URL: "${{ vars.DEPLOYMENT_URL }}/management/health" + WORKFLOW_RUN_ID: ${{ needs.check-build-status.outputs.build_workflow_run_id }} + + steps: + - name: Clean workspace + run: | + echo "[INFO] Cleaning workspace..." + rm -rf artifacts/ + rm -rf ./* + mkdir -p artifacts + + - name: Download artifact + uses: actions/download-artifact@v4 + with: + name: Artemis.war + path: artifacts + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ env.WORKFLOW_RUN_ID }} + + - name: Setup SSH and Known Hosts + env: + DEPLOYMENT_SSH_KEY: ${{ secrets.DEPLOYMENT_SSH_KEY }} + DEPLOYMENT_HOST_PUBLIC_KEYS: ${{ vars.DEPLOYMENT_HOST_PUBLIC_KEYS }} + run: | + mkdir -p ~/.ssh + chmod 700 ~/.ssh + + # Write private key + echo "$DEPLOYMENT_SSH_KEY" | sed 's/\\n/\n/g' > ~/.ssh/id_rsa + chmod 600 ~/.ssh/id_rsa + + # Write known hosts + echo "$DEPLOYMENT_HOST_PUBLIC_KEYS" > ~/.ssh/known_hosts + chmod 644 ~/.ssh/known_hosts + + - name: Phase 1 - Stop Secondary Nodes + run: | + HOSTS_SPACE_SEPARATED=$(echo "$DEPLOYMENT_HOSTS_SECONDARY" | tr -d '\r' | tr '\n' ' ' | awk '{$1=$1};1') + echo "Debug: Hosts list: $HOSTS_SPACE_SEPARATED" + for node in $HOSTS_SPACE_SEPARATED + do + SSH="ssh -i ~/.ssh/id_rsa -l $DEPLOYMENT_USER $node" + echo "[INFO] Stop artemis.service on ${node} ..." + $SSH sudo systemctl stop artemis + done + + - name: Phase 1 - Deploy to Primary Node + run: | + echo "[INFO] Deploy on $DEPLOYMENT_HOSTS_PRIMARY ..." + SSH="ssh -o LogLevel=ERROR -i ~/.ssh/id_rsa -l $DEPLOYMENT_USER $DEPLOYMENT_HOSTS_PRIMARY" + + # Store the war file name + WAR_FILE=$(ls -1 artifacts/*.war | head -n 1) + + # Check if artifacts directory contains the WAR file + echo "[INFO] Checking local artifacts..." + ls -la artifacts/ + if [ ! -f "$WAR_FILE" ]; then + echo "Error: No WAR file found in artifacts directory" + exit 1 + fi + + # Check remote directory exists and is writable + echo "[INFO] Checking remote directory..." + $SSH "if [ ! -d /opt/artemis ]; then echo 'Error: /opt/artemis directory does not exist'; exit 1; fi" + $SSH "if [ ! -w /opt/artemis ]; then echo 'Error: /opt/artemis directory is not writable'; exit 1; fi" + + # Remove old backup if exists + echo "[INFO] Remove old artemis.war ..." + $SSH "rm -f /opt/artemis/artemis.war.old" + + # Copy new artemis.war to node + echo "[INFO] Copy new artemis.war ..." + scp -v -i ~/.ssh/id_rsa "$WAR_FILE" $DEPLOYMENT_USER@$DEPLOYMENT_HOSTS_PRIMARY:/opt/artemis/artemis.war.new + if [ $? -ne 0 ]; then + echo "Error: Failed to copy WAR file" + exit 1 + fi + + # Verify the file was copied successfully + echo "[INFO] Verify new WAR file..." + $SSH ' + if [ ! -f /opt/artemis/artemis.war.new ]; then + echo "Error: No WAR file found at /opt/artemis/artemis.war.new" + exit 1 + fi + ' + + # Stop Artemis-Service on node + echo "[INFO] Stop artemis.service ..." + $SSH sudo systemctl stop artemis + + # Replace old artemis.war + echo "[INFO] Rename old artemis.war ..." + $SSH mv /opt/artemis/artemis.war /opt/artemis/artemis.war.old || true + echo "[INFO] Rename new artemis.war ..." + $SSH mv /opt/artemis/artemis.war.new /opt/artemis/artemis.war + + # Start Artemis-Service on node + echo "[INFO] Start artemis.service ..." + $SSH sudo systemctl start artemis + + - name: Verify Primary Node Deployment + id: verify_deployment + timeout-minutes: 10 + run: | + while true; do + echo "Performing health check..." + + RESPONSE=$(curl -s -f $HEALTH_CHECK_URL || echo '{"status":"DOWN"}') + STATUS=$(echo $RESPONSE | grep -o '"status":"[^"]*"' | cut -d'"' -f4) + + if [ "$STATUS" = "UP" ]; then + echo "Health check passed! Application is UP" + exit 0 + else + echo "Health check failed. Status: $STATUS" + echo "Waiting 10 seconds before next attempt..." + sleep 10 + fi + done + + - name: Phase 2 - Deploy to Secondary Nodes + run: | + HOSTS_SPACE_SEPARATED=$(echo "$DEPLOYMENT_HOSTS_SECONDARY" | tr -d '\r' | tr '\n' ' ' | awk '{$1=$1};1') + WAR_FILE=$(ls -1 artifacts/*.war | head -n 1) + + echo "Debug: Hosts list: $HOSTS_SPACE_SEPARATED" + + for node in $HOSTS_SPACE_SEPARATED + do + echo "##################################################################################################" + echo "[INFO] Deploy on $node ..." + echo "##################################################################################################" + + # Build SSH-command + SSH="ssh -o LogLevel=ERROR -i ~/.ssh/id_rsa -l $DEPLOYMENT_USER $node" + + # Remove old artemis.war + echo "[INFO] Remove old artemis.war ..." + $SSH "rm -f /opt/artemis/artemis.war.old" + + # Copy new artemis.war to node + echo "[INFO] Copy new artemis.war ..." + scp -i ~/.ssh/id_rsa "$WAR_FILE" "$DEPLOYMENT_USER@$node:/opt/artemis/artemis.war.new" + + # Stop Artemis-Service on node + echo "[INFO] Stop artemis.service ..." + $SSH "sudo systemctl stop artemis" + + # Replace old artemis.war + echo "[INFO] Rename old artemis.war ..." + $SSH "mv /opt/artemis/artemis.war /opt/artemis/artemis.war.old || true" + echo "[INFO] Rename new artemis.war ..." + $SSH "mv /opt/artemis/artemis.war.new /opt/artemis/artemis.war" + + # Start Artemis-Service on node + echo "[INFO] Start artemis.service ..." + $SSH "sudo systemctl start artemis" + done diff --git a/.github/workflows/test-android.yml b/.github/workflows/test-android.yml index 47b150e960fb..2841a15b2884 100644 --- a/.github/workflows/test-android.yml +++ b/.github/workflows/test-android.yml @@ -69,7 +69,7 @@ jobs: # Run artemis server in a detached mode, and store the pid in variable steps.run-artemis.outputs.pid - name: Run Artemis Server id: run-artemis - run: ./main-repo/gradlew bootRun --args='--spring.profiles.active=dev,artemis,scheduling --artemis.user-management.use-external=false --artemis.user-management.internal-admin.username=artemis_admin --artemis.user-management.internal-admin.password=artemis_admin --artemis.user-management.registration.enabled=true --artemis.user-management.registration.allowed-email-pattern=.*' & echo "pid=$!" >> "$GITHUB_OUTPUT" + run: ./main-repo/gradlew bootRun --args='--spring.profiles.active=dev,artemis,scheduling,atlas --artemis.user-management.use-external=false --artemis.user-management.internal-admin.username=artemis_admin --artemis.user-management.internal-admin.password=artemis_admin --artemis.user-management.registration.enabled=true --artemis.user-management.registration.allowed-email-pattern=.*' & echo "pid=$!" >> "$GITHUB_OUTPUT" # For debug purposes or if anything goes wrong, and we have to manually kill the process on the VM - name: Print Pid diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ccdd88feed03..06617001ab6d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -40,8 +40,10 @@ concurrency: # Keep in sync with codeql-analysis.yml and build.yml env: CI: true - node: 20 + node: 22 java: 21 + # Run all tests for non-draft pull-requests or the default branch. Otherwise, only module-affected tests are run. + RUN_ALL_TESTS: ${{ (github.event_name == 'pull_request' && github.event.pull_request.draft == false) || github.event.repository.default_branch == github.ref_name }} jobs: @@ -59,8 +61,44 @@ jobs: 17 ${{ env.java }} cache: 'gradle' - - name: Java Tests - run: set -o pipefail && ./gradlew --console=plain test jacocoTestReport -x webapp jacocoTestCoverageVerification | tee tests.log + - name: Java Tests (module-affected) + # Not a non-draft pull-request and not the default branch + if: ${{ !env.RUN_ALL_TESTS }} + run: | + set -o pipefail + + DEFAULT_BRANCH="${{ github.event.repository.default_branch }}" + + # Explicitly fetch as the clone action only clones the current branch + git fetch origin "$DEFAULT_BRANCH" + + chmod +x ./supporting_scripts/get_changed_modules.sh + CHANGED_MODULES=$(./supporting_scripts/get_changed_modules.sh "origin/$DEFAULT_BRANCH") + + # Restrict executed tests to changed modules if there is diff between this and the base branch + if [ -n "${CHANGED_MODULES}" ]; then + IFS=, + TEST_MODULE_TAGS=$(echo "-DincludeModules=${CHANGED_MODULES[*]}") + + echo "Executing tests for modules: $CHANGED_MODULES" + ./gradlew --console=plain test jacocoTestReport -x webapp jacocoTestCoverageVerification "$TEST_MODULE_TAGS" | tee tests.log + exit 0 + fi + + echo "Executing all tests" + ./gradlew --console=plain test jacocoTestReport -x webapp jacocoTestCoverageVerification | tee tests.log + - name: Java Tests (All) + # Non-draft pull-request or default branch + if: ${{ env.RUN_ALL_TESTS }} + run: | + set -o pipefail + ./gradlew --console=plain test jacocoTestReport -x webapp jacocoTestCoverageVerification | tee tests.log + - name: Upload JUnit Test Results + if: success() || failure() + uses: actions/upload-artifact@v4 + with: + name: JUnit Test Results + path: build/test-results/test/*.xml - name: Print failed tests if: failure() run: grep "Test >.* FAILED\$" tests.log || echo "No failed tests." @@ -68,7 +106,7 @@ jobs: uses: codacy/codacy-coverage-reporter-action@master with: project-token: ${{ secrets.CODACY_PROJECT_TOKEN }} - coverage-reports: build/reports/jacoco/test/jacocoTestReport.xml + coverage-reports: build/reports/jacoco/aggregated/jacocoTestReport.xml if: (github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name) && (success() || failure()) && github.event.pull_request.user.login != 'dependabot[bot]' - name: Annotate Server Test Results uses: ashley-taylor/junit-report-annotations-action@f9c1a5cbe28479439f82b80a5402a6d3aa1990ac @@ -92,8 +130,13 @@ jobs: uses: actions/upload-artifact@v4 with: name: Coverage Report Server Tests - path: build/reports/jacoco/test/html/ - + path: build/reports/jacoco/aggregated/html + - name: Append Per-Module Coverage to Job Summary + if: success() || failure() + run: | + AGGREGATED_REPORT_FILE=./module_coverage_report.md + python3 ./supporting_scripts/code-coverage/per_module_cov_report/parse_module_coverage.py build/reports/jacoco $AGGREGATED_REPORT_FILE + cat $AGGREGATED_REPORT_FILE > $GITHUB_STEP_SUMMARY server-tests-mysql: needs: [ server-tests ] diff --git a/.github/workflows/testserver-deployment.yml b/.github/workflows/testserver-deployment.yml new file mode 100644 index 000000000000..5a36abd9f66f --- /dev/null +++ b/.github/workflows/testserver-deployment.yml @@ -0,0 +1,165 @@ +name: Deploy to a test-server + +on: + workflow_dispatch: + inputs: + branch_name: + description: "Which branch to deploy" + required: true + type: string + environment_name: + description: "Which environment to deploy (e.g. artemis-test7.artemis.cit.tum.de, etc.)." + required: true + type: string + triggered_by: + description: "Username that triggered deployment (not required, shown if triggered via GitHub UI, logged if triggered via GitHub app)" + required: false + type: string + + +concurrency: ${{ github.event.inputs.environment_name }} + +env: + CI: true + # Keep filename in sync with the workflow responsible for automatic builds on PRs + PR_AUTO_BUILD_FILE_NAME: "build.yml" + RAW_URL: https://raw.githubusercontent.com/${{ github.repository }}/${{ github.event.inputs.branch_name }} + +jobs: + # Log the inputs for debugging + log-inputs: + name: Log Inputs + runs-on: ubuntu-latest + steps: + - name: Print Inputs + run: | + echo "Branch: ${{ github.event.inputs.branch_name }}" + echo "Environment: ${{ github.event.inputs.environment_name }}" + echo "Triggered by: ${{ github.event.inputs.triggered_by }}" + echo "RAW_URL: ${{ env.RAW_URL }}" + + determine-build-context: + name: Determine Build Context + runs-on: ubuntu-latest + needs: log-inputs + outputs: + pr_number: ${{ steps.get_pr.outputs.pr_number }} + pr_head_sha: ${{ steps.get_pr.outputs.pr_head_sha }} + tag: ${{ steps.get_pr.outputs.tag }} + steps: + - name: Check if a PR exists for the branch + id: get_pr + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + BRANCH_NAME=${{ github.event.inputs.branch_name }} + echo "Checking if PR exists for branch: $BRANCH_NAME targeting 'develop'." + + PR_DETAILS=$(gh api repos/${{ github.repository }}/pulls \ + --paginate \ + --jq ".[] | select(.head.ref == \"$BRANCH_NAME\" and .base.ref == \"develop\") | {number: .number, sha: .head.sha}") + + PR_NUMBER=$(echo "$PR_DETAILS" | jq -r ".number") + PR_HEAD_SHA=$(echo "$PR_DETAILS" | jq -r ".sha") + + if [ -n "$PR_NUMBER" ] && [ "$PR_NUMBER" != "null" ]; then + echo "Found PR: $PR_NUMBER from branch: $BRANCH_NAME targeting 'develop' with Head: $PR_HEAD_SHA." + echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT + echo "pr_head_sha=$PR_HEAD_SHA" >> $GITHUB_OUTPUT + echo "tag=pr-$PR_NUMBER" >> $GITHUB_OUTPUT + else + echo "No PR found for branch: $BRANCH_NAME targeting 'develop'." + echo "pr_number=" >> $GITHUB_OUTPUT + echo "pr_head_sha=" >> $GITHUB_OUTPUT + + # Fetch the latest commit SHA of the branch + LATEST_SHA=$(gh api repos/${{ github.repository }}/git/refs/heads/$BRANCH_NAME --jq '.object.sha') + + if [ -z "$LATEST_SHA" ]; then + echo "::error::Could not find the latest commit SHA for branch $BRANCH_NAME." + exit 1 + fi + + echo "Latest SHA for branch $BRANCH_NAME is $LATEST_SHA." + # Set tag as branch-SHA + echo "tag=branch-$LATEST_SHA" >> $GITHUB_OUTPUT + fi + + + # Build the Docker image (branch without PR) + conditional-build: + if: ${{ needs.determine-build-context.outputs.pr_number == '' }} + needs: determine-build-context + uses: ./.github/workflows/reusable-build.yml + with: + docker: true + docker_ref: ${{ github.event.inputs.branch_name }} + docker_build_tag: ${{ needs.determine-build-context.outputs.tag }} + + # Check if the build has run successfully (PR) + check-existing-build: + name: Check Existing Build + if: ${{ needs.determine-build-context.outputs.pr_number != '' }} + needs: determine-build-context + runs-on: ubuntu-latest + steps: + - name: Get latest successful build for branch + id: check_build + uses: octokit/request-action@v2.x + with: + route: GET /repos/${{ github.repository }}/actions/workflows/build.yml/runs?event=pull_request&status=success&head_sha=${{ needs.determine-build-context.outputs.pr_head_sha }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Fail if no successful build found + if: ${{ steps.check_build.conclusion == 'success' && fromJSON(steps.check_build.outputs.data).total_count == 0 }} + run: | + echo "::error::No successful build found for branch '${{ github.event.inputs.branch_name }}' with SHA '${{ needs.determine-build-context.outputs.pr_head_sha }}'." + exit 1 + + # Deploy to the test-server + deploy: + needs: [ determine-build-context, conditional-build, check-existing-build ] + # Run if either the conditional-build or check-existing-build job was successful + # Use always() since one of the jobs will always skip + if: always() && (needs.conditional-build.result == 'success' || needs.check-existing-build.result == 'success') + name: Deploy to Test-Server + runs-on: ubuntu-latest + environment: + name: ${{ github.event.inputs.environment_name }} + url: ${{ vars.DEPLOYMENT_URL }} + + env: + GATEWAY_USER: "jump" + GATEWAY_HOST: "gateway.artemis.in.tum.de:2010" + GATEWAY_HOST_PUBLIC_KEY: "[gateway.artemis.in.tum.de]:2010 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKtTLiKRILjKZ+Qg4ReWKsG7mLDXkzHfeY5nalSQUNQ4" + + steps: + # Download artemis-server-cli from GH without cloning the Repo + - name: Fetch Artemis CLI + run: | + wget ${{ env.RAW_URL }}/artemis-server-cli + chmod +x artemis-server-cli + + # Configure SSH Key + - name: Setup SSH Keys and known_hosts + env: + SSH_AUTH_SOCK: /tmp/ssh_agent.sock + GATEWAY_SSH_KEY: "${{ secrets.DEPLOYMENT_GATEWAY_SSH_KEY }}" + DEPLOYMENT_SSH_KEY: "${{ secrets.DEPLOYMENT_SSH_KEY }}" + run: | + mkdir -p ~/.ssh + ssh-agent -a $SSH_AUTH_SOCK > /dev/null + ssh-add - <<< $GATEWAY_SSH_KEY + ssh-add - <<< $DEPLOYMENT_SSH_KEY + cat - <<< $GATEWAY_HOST_PUBLIC_KEY >> ~/.ssh/known_hosts + + - name: Deploy Artemis with Docker + env: + SSH_AUTH_SOCK: /tmp/ssh_agent.sock + DEPLOYMENT_USER: ${{ vars.DEPLOYMENT_USER }} + DEPLOYMENT_HOSTS: ${{ vars.DEPLOYMENT_HOSTS }} + TAG: ${{ needs.determine-build-context.outputs.tag }} + BRANCH_NAME: ${{ github.event.inputs.branch_name }} + DEPLOYMENT_FOLDER: ${{ vars.DEPLOYMENT_FOLDER }} + run: | + ./artemis-server-cli docker-deploy "$DEPLOYMENT_USER@$DEPLOYMENT_HOSTS" -g "$GATEWAY_USER@$GATEWAY_HOST" -t $TAG -b $BRANCH_NAME -d $DEPLOYMENT_FOLDER -y diff --git a/.github/workflows/testserver-locks.yml b/.github/workflows/testserver-locks.yml deleted file mode 100644 index bd65d1820b47..000000000000 --- a/.github/workflows/testserver-locks.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: Testserver Locks - -on: - pull_request_target: - types: [labeled] - issues: - types: [labeled] - -jobs: - # Disallow adding testserver locks to PRs manually - noManualSetOfLockLabel: - runs-on: ubuntu-latest - steps: - - uses: actions-ecosystem/action-remove-labels@v1 - if: startsWith(github.event.label.name, 'lock:artemis-test') || startsWith(join(github.event.pull_request.labels.*.name), 'lock:artemis-test') - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - labels: | - lock:artemis-test1 - lock:artemis-test2 - lock:artemis-test3 - lock:artemis-test4 - lock:artemis-test5 - lock:artemis-test6 - lock:artemis-test7 - lock:artemis-test8 - lock:artemis-test9 - lock:artemis-test10 diff --git a/.github/workflows/testserver.yml b/.github/workflows/testserver.yml deleted file mode 100644 index db5b0fce1000..000000000000 --- a/.github/workflows/testserver.yml +++ /dev/null @@ -1,352 +0,0 @@ -name: Deploy to Testserver - -on: - pull_request: - types: [labeled] - -concurrency: test-servers - -env: - RAW_URL: https://raw.githubusercontent.com/${{ github.repository }}/${{ github.sha }} - -jobs: - # Get an up to date version of the label list. github.event.pull_request.labels seems to sometimes be outdated - # if the run was waiting for a while, which can cause duplicate deployments - get-labels: - runs-on: ubuntu-latest - outputs: - labels: ${{ steps.get-labels.outputs.result }} - steps: - - name: Get PR labels - id: get-labels - uses: actions/github-script@v7 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const response = await github.rest.issues.listLabelsOnIssue({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number - }) - const labels = response.data - return labels.map(label => label.name) - - - # Check that the build job has run successfully before deploying - check-build-status: - needs: [ get-labels ] - runs-on: ubuntu-latest - # Only run workflow if the added label is a deploy label - if: contains(needs.get-labels.outputs.labels, 'deploy:artemis-test') - steps: - - name: Get latest successful build for branch - id: check_build - uses: octokit/request-action@v2.x - with: - route: GET /repos/${{ github.repository }}/actions/workflows/build.yml/runs?event=pull_request&status=success&head_sha=${{ github.event.pull_request.head.sha }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - # Remove deployment-error label if new run is started - - uses: actions-ecosystem/action-remove-labels@v1 - if: fromJSON(steps.check_build.outputs.data).total_count > 0 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - labels: | - deployment-error - - # In case of invalid build status, remove deploy labels - - uses: actions-ecosystem/action-remove-labels@v1 - if: fromJSON(steps.check_build.outputs.data).total_count == 0 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - labels: | - deploy:artemis-test1 - deploy:artemis-test2 - deploy:artemis-test3 - deploy:artemis-test4 - deploy:artemis-test5 - deploy:artemis-test6 - deploy:artemis-test7 - deploy:artemis-test8 - deploy:artemis-test9 - deploy:artemis-test10 - - - name: Check if latest push had successful build - if: fromJSON(steps.check_build.outputs.data).total_count == 0 - uses: actions/github-script@v7 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: '### ⚠️ Unable to deploy to test servers ⚠️\nThe docker build needs to run through before deploying.' - }) - core.setFailed('The build needs to run through first. Please wait for the build to finish and then try again.') - - - # Check which test server to deploy to based on the label - filter-matrix: - needs: [ get-labels, check-build-status ] - runs-on: ubuntu-latest - strategy: - matrix: - include: - # Commented out environments are not yet available and will be enabled in the future - - environment: artemis-test1.artemis.cit.tum.de - label-identifier: artemis-test1 - url: https://artemis-test1.artemis.cit.tum.de - user: deployment - hosts: artemis-test1.artemis.cit.tum.de - folder: /opt/artemis - - - environment: artemis-test2.artemis.cit.tum.de - label-identifier: artemis-test2 - url: https://artemis-test2.artemis.cit.tum.de - user: deployment - hosts: artemis-test2.artemis.cit.tum.de - folder: /opt/artemis - - - environment: artemis-test3.artemis.cit.tum.de - label-identifier: artemis-test3 - url: https://artemis-test3.artemis.cit.tum.de - user: deployment - hosts: artemis-test3.artemis.cit.tum.de - folder: /opt/artemis - - - environment: artemis-test4.artemis.cit.tum.de - label-identifier: artemis-test4 - url: https://artemis-test4.artemis.cit.tum.de - user: deployment - hosts: artemis-test4.artemis.cit.tum.de - folder: /opt/artemis - - - environment: artemis-test5.artemis.cit.tum.de - label-identifier: artemis-test5 - url: https://artemis-test5.artemis.cit.tum.de - user: deployment - hosts: artemis-test5.artemis.cit.tum.de - folder: /opt/artemis - - - environment: artemis-test6.artemis.cit.tum.de - label-identifier: artemis-test6 - url: https://artemis-test6.artemis.cit.tum.de - user: deployment - hosts: artemis-test6.artemis.cit.tum.de - folder: /opt/artemis - host_keys: | - - #- environment: artemis-test7.artemis.cit.tum.de - # label-identifier: artemis-test7 - # url: https://artemis-test7.artemis.cit.tum.de - # user: deployment - # hosts: artemis-test7.artemis.cit.tum.de - # folder: /opt/artemis - - #- environment: artemis-test8.artemis.cit.tum.de - # label-identifier: artemis-test8 - # url: https://artemis-test8.artemis.cit.tum.de - # user: deployment - # hosts: artemis-test8.artemis.cit.tum.de - # folder: /opt/artemis - - - environment: artemis-test9.artemis.cit.tum.de - label-identifier: artemis-test9 - url: https://artemis-test9.artemis.cit.tum.de - user: deployment - hosts: artemis-test9.artemis.cit.tum.de - folder: /opt/artemis - host_keys: | - - #- environment: artemis-test10.artemis.cit.tum.de - # label-identifier: artemis-test10 - # url: https://artemis-test10.artemis.cit.tum.de - # user: deployment - # hosts: artemis-test10.artemis.cit.tum.de - # folder: /opt/artemis - outputs: - TS1: ${{ steps.filter.outputs.artemis-test1 || '' }} - TS2: ${{ steps.filter.outputs.artemis-test2 || '' }} - TS3: ${{ steps.filter.outputs.artemis-test3 || '' }} - TS4: ${{ steps.filter.outputs.artemis-test4 || '' }} - TS5: ${{ steps.filter.outputs.artemis-test5 || '' }} - TS6: ${{ steps.filter.outputs.artemis-test6 || '' }} - #TS7: ${{ steps.filter.outputs.artemis-test7 || '' }} - #TS8: ${{ steps.filter.outputs.artemis-test8 || '' }} - TS9: ${{ steps.filter.outputs.artemis-test9 || '' }} - #TS10: ${{ steps.filter.outputs.artemis-test10 || '' }} - steps: - - run: | - echo "$DEPLOY_LABEL" - echo '${{ contains(fromJSON(needs.get-labels.outputs.labels), format('deploy:{0}', matrix.label-identifier)) }}' - - id: filter - env: - MATRIX_JSON: ${{ toJSON(matrix) }} - if: ${{ contains(fromJSON(needs.get-labels.outputs.labels), format('deploy:{0}', matrix.label-identifier)) }} - run: | - MATRIX_JSON=${MATRIX_JSON//$'\n'/} - echo "${{ matrix.label-identifier }}=$MATRIX_JSON" >> $GITHUB_OUTPUT - - - # Process the output of the filter step to create a valid matrix for the deploy step - process-matrix: - needs: [ filter-matrix ] - runs-on: ubuntu-latest - outputs: - matrix: ${{ steps.process.outputs.matrix }} - steps: - - id: process - env: - MATRIX_JSON: ${{ toJSON(needs.filter-matrix.outputs.*) }} - run: | - MATRIX_JSON=${MATRIX_JSON//$'\n'/} - MATRIX_JSON=${MATRIX_JSON//$'"{'/'{'} - MATRIX_JSON=${MATRIX_JSON//$'}"'/'}'} - MATRIX_JSON=${MATRIX_JSON//$'\\"'/'"'} - echo "$MATRIX_JSON" - echo "matrix=$MATRIX_JSON" >> $GITHUB_OUTPUT - - - # Deploy to the test servers - deploy: - needs: [ process-matrix ] - runs-on: ubuntu-latest - concurrency: test-servers-deploy - strategy: - fail-fast: false - matrix: - include: ${{ fromJSON(needs.process-matrix.outputs.matrix) }} - - environment: - name: ${{ matrix.environment }} - url: ${{ matrix.url }} - - env: - DEPLOYMENT_USER: ${{ matrix.user }} - DEPLOYMENT_HOSTS: ${{ matrix.hosts }} - DEPLOYMENT_FOLDER: ${{ matrix.folder }} - GATEWAY_USER: "jump" - GATEWAY_HOST: "gateway.artemis.in.tum.de:2010" - GATEWAY_HOST_PUBLIC_KEY: "[gateway.artemis.in.tum.de]:2010 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKtTLiKRILjKZ+Qg4ReWKsG7mLDXkzHfeY5nalSQUNQ4" - - steps: - - uses: actions-ecosystem/action-remove-labels@v1 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - labels: | - deploy:${{ matrix.label-identifier }} - - - name: Check "lock:${{ matrix.environment }}" label - uses: actions/github-script@v7 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const opts = github.rest.issues.listForRepo.endpoint.merge({ - owner: context.repo.owner, - repo: context.repo.repo, - labels: ['lock:${{ matrix.label-identifier }}'] - }) - const issues = await github.paginate(opts) - if (issues.length == 1 && (!context.issue || issues[0].number != context.issue.number)) { - github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: `#### ⚠️ Unable to deploy to test servers ⚠️\nTestserver "${{ matrix.environment }}" is already in use by PR #${issues[0].number}.` - }) - core.setFailed(`Testserver "${{ matrix.environment }}" is already in use by PR #${issues[0].number}.`); - } else if (issues.length > 1) { - github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: '#### ⚠️ Unable to deploy to test servers ⚠️\nTestserver "${{ matrix.environment }}" is already in use by multiple PRs. Check PRs with label "lock:${{ matrix.label-identifier }}"!' - }) - core.setFailed('Testserver "${{ matrix.environment }}" is already in use by multiple PRs. Check PRs with label "lock:${{ matrix.label-identifier }}"!'); - } - - - name: Compute Tag - uses: actions/github-script@v7 - id: compute-tag - with: - result-encoding: string - script: | - if (context.eventName === "pull_request") { - return "pr-" + context.issue.number; - } - if (context.eventName === "release") { - return "latest"; - } - if (context.eventName === "push") { - if (context.ref.startsWith("refs/tags/")) { - return context.ref.slice(10); - } - if (context.ref === "refs/heads/develop") { - return "develop"; - } - } - return "FALSE"; - - # Download artemis-server-cli from GH without cloning the Repo - - name: Fetch Artemis CLI - run: | - wget ${{ env.RAW_URL }}/artemis-server-cli - chmod +x artemis-server-cli - - # Configure SSH Key - - name: Setup SSH Keys and known_hosts - env: - SSH_AUTH_SOCK: /tmp/ssh_agent.sock - GATEWAY_SSH_KEY: "${{ secrets.DEPLOYMENT_GATEWAY_SSH_KEY }}" - DEPLOYMENT_SSH_KEY: "${{ secrets.DEPLOYMENT_SSH_KEY }}" - run: | - mkdir -p ~/.ssh - ssh-agent -a $SSH_AUTH_SOCK > /dev/null - ssh-add - <<< $GATEWAY_SSH_KEY - ssh-add - <<< $DEPLOYMENT_SSH_KEY - cat - <<< $GATEWAY_HOST_PUBLIC_KEY >> ~/.ssh/known_hosts - - - name: Deploy Artemis with Docker - env: - SSH_AUTH_SOCK: /tmp/ssh_agent.sock - TAG: ${{ steps.compute-tag.outputs.result }} - run: | - for host in $DEPLOYMENT_HOSTS; do - ./artemis-server-cli docker-deploy "$DEPLOYMENT_USER@$host" -g "$GATEWAY_USER@$GATEWAY_HOST" -t $TAG -b $GITHUB_HEAD_REF -d $DEPLOYMENT_FOLDER -y - done - - - name: Add "lock:${{ matrix.environment }}" label - uses: actions/github-script@v7 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - if (context.issue && context.issue.number) { - await github.rest.issues.addLabels({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - labels: ['lock:${{ matrix.label-identifier }}'] - }) - } - - - name: Update badge - uses: RubbaBoy/BYOB@v1.3.0 - with: - NAME: ${{ matrix.label-identifier }} - LABEL: ${{ matrix.environment }} - STATUS: ${{ github.event.pull_request.head.ref }} - COLOR: red - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - # Check that the build job has run successfully before deploying - add-error-label: - needs: [ get-labels, check-build-status, filter-matrix, process-matrix, deploy ] - runs-on: ubuntu-latest - if: ${{ failure() }} - steps: - - name: Add error label - uses: actions-ecosystem/action-add-labels@v1 - with: - labels: deployment-error diff --git a/.gitignore b/.gitignore index 75cb003dda0c..a62b0d8754ac 100644 --- a/.gitignore +++ b/.gitignore @@ -193,6 +193,7 @@ data-exports/ ###################### /src/test/playwright/test-reports/ /src/test/playwright/test-results/* +/src/test/playwright/ssh-keys/known_hosts ################################# # Files generated by prebuild.mjs @@ -219,3 +220,4 @@ data-exports/ # Supporting scripts config ############################## /supporting_scripts/**/*.ini +/legal diff --git a/.idea/runConfigurations/Artemis_JavaScript_Debug.xml b/.idea/runConfigurations/Artemis_JavaScript_Debug.xml index 8bf2211f52a0..262ea91eab32 100644 --- a/.idea/runConfigurations/Artemis_JavaScript_Debug.xml +++ b/.idea/runConfigurations/Artemis_JavaScript_Debug.xml @@ -1,5 +1,5 @@ - - - - \ No newline at end of file + + + + diff --git a/.idea/runConfigurations/Artemis__Server_.xml b/.idea/runConfigurations/Artemis__Server_.xml index 8c5b9c1d48de..f2f0b32d930b 100644 --- a/.idea/runConfigurations/Artemis__Server_.xml +++ b/.idea/runConfigurations/Artemis__Server_.xml @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/.idea/runConfigurations/Artemis__Server__LocalVC___LocalCI_.xml b/.idea/runConfigurations/Artemis__Server__LocalVC___LocalCI_.xml index 9135ec4a2fd2..2718613117ea 100644 --- a/.idea/runConfigurations/Artemis__Server__LocalVC___LocalCI_.xml +++ b/.idea/runConfigurations/Artemis__Server__LocalVC___LocalCI_.xml @@ -1,13 +1,12 @@ - - + \ No newline at end of file diff --git a/.idea/runConfigurations/Artemis__Server__LocalVC___LocalCI__Athena_.xml b/.idea/runConfigurations/Artemis__Server__LocalVC___LocalCI__Athena_.xml index 01a830fc1d7e..b83ca73c7107 100644 --- a/.idea/runConfigurations/Artemis__Server__LocalVC___LocalCI__Athena_.xml +++ b/.idea/runConfigurations/Artemis__Server__LocalVC___LocalCI__Athena_.xml @@ -1,6 +1,6 @@ -