diff --git a/.github/workflows/precommit.yml b/.github/workflows/precommit.yml new file mode 100644 index 00000000000..115ed732d90 --- /dev/null +++ b/.github/workflows/precommit.yml @@ -0,0 +1,118 @@ +name: precommit + +on: + pull_request: + # we want to run the CI on every PR targetting those branches + branches: [dev] + +concurrency: + group: precommit-deploy + cancel-in-progress: true + +jobs: + build: + runs-on: buildjet-8vcpu-ubuntu-2204 + + outputs: + unit_tests_report: ${{ env.UNIT_TEST_REPORT_FILE }} + build_artifact: ${{ env.BUILD_ARTIFACT }} + total_additions: ${{ steps.check_additions.outputs.total_additions }} + + env: + BUILD_DIR: server/dist/s3/ + BUILD_ARTIFACT: ebs.zip + COMMIT_URL: ${{github.event.head_commit.url}} + COMMITTER: ${{github.event.head_commit.committer.name}} + CHANGELOG_FILE: './changelog.md' + UNIT_TEST_REPORT_FILE: './unit-tests.log' + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ github.event.pull_request.head.sha }} + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 18.16.x + cache: 'yarn' + + - name: Install JS dependencies + run: yarn --immutable + + - name: Update configuration + run: yarn configure + + - name: Build + run: yarn build:prod + + - uses: actions/upload-artifact@v4 + with: + name: 'build-artifact' + path: '${{env.BUILD_DIR}}${{env.BUILD_ARTIFACT}}' + + - name: Fetch PR details + id: pr_details + uses: octokit/request-action@v2.x + with: + route: GET /repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Check total PR additions + id: check_additions + run: | + # Store response data in variable + response='${{ steps.pr_details.outputs.data }}' + # Parse additions from response + total_additions=$(echo "$response" | jq -r '.additions') + echo "Found total additions: $total_additions" + # Output for subsequent steps + echo "total_additions=$total_additions" >> $GITHUB_OUTPUT + + deploy_to_aws: + name: 'Deploy to live environments' + runs-on: ubuntu-latest + needs: [build] + + steps: + - uses: actions/download-artifact@v4 + with: + name: 'build-artifact' + + - name: Deploy to precommit environment + id: deploy + if: ${{ needs.build.outputs.total_additions > 100 && github.actor != 'dependabot[bot]' }} + uses: einaregilsson/beanstalk-deploy@v22 + with: + application_name: Webapp + aws_access_key: ${{ secrets.WEBTEAM_AWS_ACCESS_KEY_ID }} + aws_secret_key: ${{ secrets.WEBTEAM_AWS_SECRET_ACCESS_KEY }} + deployment_package: ${{needs.build.outputs.build_artifact}} + environment_name: wire-webapp-precommit + region: eu-central-1 + use_existing_version_if_available: true + version_description: ${{ github.sha }} + version_label: ${{ github.run_id }} + wait_for_deployment: false + wait_for_environment_recovery: 150 + + - name: Deployment Status + if: ${{ always() }} + run: | + if [[ "${{ steps.deploy.outcome }}" == "success" ]]; then + echo "✅ Deployment completed successfully" + elif [[ "${{ steps.deploy.outcome }}" == "skipped" ]]; then + if [[ "${{ needs.build.outputs.total_additions }}" -le 100 ]]; then + echo "⏭️ Deployment was skipped: PR has ${{ needs.build.outputs.total_additions }} additions (threshold: 100)" + elif [[ "${{ github.actor }}" == "dependabot[bot]" ]]; then + echo "⏭️ Deployment was skipped: PR is from dependabot" + else + echo "⏭️ Deployment was skipped" + fi + else + echo "❌ Deployment failed" + exit 1 + fi diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 00000000000..4746d4d307d --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,166 @@ +@NonCPS +def checkWorkflowRun(Map run, String commitHash) { + final String headSha = run['head_sha'] + if (headSha == commitHash) { + echo("Found hash ${headSha}") + final String conclusion = run['conclusion'] + echo("conclusion: ${conclusion}") + + switch (conclusion) { + case 'success': + return true + case 'failure': + final String url = run['url'] + error("❌ **Build failed for branch '${GIT_BRANCH_WEBAPP}'** See [Github Actions](${url})") + break + case 'cancelled': + final String url = run['url'] + error("⚠️ **Build aborted for branch '${GIT_BRANCH_WEBAPP}'** See [Github Actions](${url})") + break + } + } + return false +} + +pipeline { + agent { + node { + label 'built-in' + } + } + + options { disableConcurrentBuilds(abortPrevious: true) } + + environment { + CREDENTIALS = credentials('GITHUB_TOKEN_WEB') + WIRE_BOT_SECRET = credentials('JENKINSBOT_WEBAPP_DEV') + webappApplicationPath = 'https://wire-webapp-precommit.zinfra.io/' + } + + stages { + stage('Wait for GitHub action to finish') { + when { + expression { BRANCH_NAME ==~ /PR-[0-9]+/ } + } + steps { + script { + def commit_hash = sh(script: 'git rev-parse HEAD', returnStdout: true).trim() + def pr_number = BRANCH_NAME.replaceAll(/\D/, '') + def changeTargetBranch = env.CHANGE_TARGET + + def targetWorkflowUrl + switch (changeTargetBranch) { + case ['dev']: + targetWorkflowUrl = 'https://api.github.com/repos/wireapp/wire-webapp/actions/workflows/128602012/runs' + break + default: + targetWorkflowUrl = 'https://api.github.com/repos/wireapp/wire-webapp/actions/workflows/128602012/runs' + break + } + + echo("Wait for github actions to start for ${BRANCH_NAME} against ${changeTargetBranch}") + final def VALID_STATUSES = ['queued', 'in_progress', 'completed'] + timeout(time: 45, unit: 'MINUTES') { + waitUntil { + def output = sh label: 'Get runs', returnStdout: true, script: "curl -s -L -H 'Accept: application/vnd.github+json' -H 'Authorization: Bearer ${CREDENTIALS}' -H 'X-GitHub-Api-Version: 2022-11-28' ${targetWorkflowUrl}" + def json = readJSON text: output + if (json['message']) { + echo('Output: ' + output) + error('**Trigger script failed:** ' + json['message']) + } + def runs = json['workflow_runs'] + echo('Looking for PR-' + pr_number + ' with hash' + commit_hash) + def matchingRun = runs.find { it['head_sha'] == commit_hash } + if (matchingRun) { + echo('Found ' + commit_hash) + def status = matchingRun['status'] + echo('status: ' + status) + env.GITHUB_ACTION_URL = matchingRun['url'].replace('api.github.com/repos', 'github.com/') + return VALID_STATUSES.contains(status) + } + false + } + sleep(20) + } + } + } + } + + stage('Check GitHub Action Status') { + when { expression { BRANCH_NAME ==~ /PR-[0-9]+/ } } + steps { + timeout(time: 15, unit: 'MINUTES') { + script { + def commit_hash = sh(script: 'git rev-parse HEAD', returnStdout: true).trim() + final String apiUrl = 'https://api.github.com/repos/wireapp/wire-webapp/actions/workflows/128602012/runs' + final String curlCmd = "curl -u \${CREDENTIALS} ${apiUrl}" + waitUntil { + final String output = sh(label: 'Check workflow', returnStdout: true, script: curlCmd) + final Object jsonData = readJSON(text: output) + final List workflowRuns = jsonData['workflow_runs'] + echo("Looking for hash ${commit_hash}") + + return workflowRuns.any { run -> checkWorkflowRun(run, commit_hash) } + } + } + } + } + } + + stage('Check deployment') { + steps { + script { + def commit_hash = sh(script: 'git rev-parse HEAD', returnStdout: true).trim() + String commitMsg = sh(returnStdout: true, script: 'git log -1 --pretty=%B').trim() + try { + // Wait until deployment has finished (20 retries * 30 seconds == 10 minutes) + timeout(time: 10, unit: 'MINUTES') { + waitUntil { + def randomid = sh returnStdout: true, script: 'uuidgen' + randomid = randomid.trim() + def current_hash = sh returnStdout: true, script: "curl '${webappApplicationPath}commit?v=${randomid}'" + current_hash = current_hash.trim() + echo('Current version is: ' + current_hash) + if (current_hash == commit_hash) { + echo('Deployment finished.') + return true + } + env.MESSAGE = 'Current hash still is ' + current_hash + ' and not ' + commit_hash + sh "echo '${MESSAGE}' > deployment.log" + sleep(30) + return false + } + } + } catch (e) { + def reason = sh returnStdout: true, script: 'cat deployment.log || echo ""' + String errorMessage = """❌ **Deployment failed on** ${webappApplicationPath} + ${commitMsg} + **Reason:** ${e} + ${reason}""" + wireSend secret: env.WIRE_BOT_SECRET, message: errorMessage + } + def successMessage = """✅ **Deployment successful on** ${webappApplicationPath} + ${commitMsg}""" + wireSend secret: env.WIRE_BOT_SECRET, message: successMessage + } + } + } + + stage('Trigger smoke test') { + steps { + build job: 'Webapp_Smoke_Chrome', parameters: [string(name: 'TAGS', value: '@smoke'), string(name: 'GIT_BRANCH', value: 'web-dev'), string(name: 'webappApplicationPath', value: "$webappApplicationPath")], wait: false + } + } + } + + post { + success { + wireSend secret: env.WIRE_BOT_SECRET, message: "✅ **Build finished for branch '$env.webappApplicationPath'**" + } + failure { + script { + wireSend(secret: env.WIRE_BOT_SECRET, message: "❌ **$BRANCH_NAME**\n[$CHANGE_TITLE](${CHANGE_URL})\nBuild aborted or failed! See [Github Actions](${env.GITHUB_ACTION_URL})") + } + } + } +} \ No newline at end of file