From f1e866c5c61b72cdc7c91afa67fffb10a861e77a Mon Sep 17 00:00:00 2001 From: goodov <5928869+goodov@users.noreply.github.com> Date: Wed, 10 Jul 2024 19:48:27 +0700 Subject: [PATCH] Add test seed upload workflow. (#1117) Add workflows for upload and removal of test seed. Add a single study to test these workflows. --- .github/workflows/delete-test-seed.yml | 25 +++++ .github/workflows/generate-test-seed.yml | 118 ++++++++++++++++++++++ .github/workflows/scripts/comment.js | 32 ++++++ src/seed_tools/commands/create_seed.ts | 1 + src/seed_tools/utils/diff_strings.test.ts | 22 +++- src/seed_tools/utils/diff_strings.ts | 13 ++- 6 files changed, 204 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/delete-test-seed.yml create mode 100644 .github/workflows/generate-test-seed.yml create mode 100644 .github/workflows/scripts/comment.js diff --git a/.github/workflows/delete-test-seed.yml b/.github/workflows/delete-test-seed.yml new file mode 100644 index 00000000..463ba83a --- /dev/null +++ b/.github/workflows/delete-test-seed.yml @@ -0,0 +1,25 @@ +name: Delete Test Seed + +on: + pull_request: + types: [closed] + paths: + - 'seed/seed.json' + - 'studies/**' + +jobs: + build: + runs-on: ubuntu-latest + env: + REMOTE_SEED_PATH: 'pull/${{ github.event.pull_request.number }}/seed' + + steps: + - name: Delete seed + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_PRODUCTION_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_PRODUCTION_SECRET_ACCESS_KEY }} + AWS_REGION: us-west-2 + CLOUDFRONT_DISTRIBUTION_ID: ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }} + run: | + aws s3 rm "s3://brave-production-griffin-origin/$REMOTE_SEED_PATH" + aws cloudfront create-invalidation --distribution-id "$CLOUDFRONT_DISTRIBUTION_ID" --paths "/$REMOTE_SEED_PATH" diff --git a/.github/workflows/generate-test-seed.yml b/.github/workflows/generate-test-seed.yml new file mode 100644 index 00000000..2c431457 --- /dev/null +++ b/.github/workflows/generate-test-seed.yml @@ -0,0 +1,118 @@ +name: Generate Test Seed + +on: + pull_request: + paths: + - 'seed/seed.json' + - 'studies/**' + +jobs: + build: + runs-on: ubuntu-latest + env: + ACTION_RUN_URL: '${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}' + REMOTE_SEED_PATH: 'pull/${{ github.event.pull_request.number }}/seed' + + steps: + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 + with: + fetch-depth: 0 + + - name: Setup Python + uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5 + with: + python-version: '3.11' + + - name: Install python requirements + run: pip install -r seed/requirements.txt + + - name: Comment "Generation In Progress" + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + with: + script: | + const actionRunURL = `${process.env.ACTION_RUN_URL}`; + const commentBody = + `## 🔄 Generating Test Seed + + A new test seed file is currently being generated for this pull request. + + ### What's Next? + + - The generation process typically takes a few minutes. + - Once the generation is complete, this comment will provide further instructions. + - If the generation takes longer than 5 minutes, please review the [workflow logs](${actionRunURL}). + ` + const comment = require('.github/workflows/scripts/comment.js') + await comment(github, context, commentBody) + + - name: Install + run: | + npm ci + + - name: Build & Test + run: | + npm run typecheck:scripts + npm run build:proto + npm run typecheck + npm run test + + - name: Lint + run: | + npm run lint -- --base origin/${{ github.event.pull_request.base.ref }} + + - name: Generate seed + run: | + # Use only python implementation for now. + python seed/serialize.py seed/seed.json + # TODO: enable this when per-file studies will be synced with seed.json. + # npm run seed_tools -- create_seed studies seed.bin + + - name: Upload seed + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_PRODUCTION_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_PRODUCTION_SECRET_ACCESS_KEY }} + AWS_REGION: us-west-2 + CLOUDFRONT_DISTRIBUTION_ID: ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }} + run: | + gzip -c seed.bin | aws s3 cp - "s3://brave-production-griffin-origin/$REMOTE_SEED_PATH" \ + --content-type application/octet-stream \ + --content-encoding gzip + + INVALIDATION_ID=$(aws cloudfront create-invalidation --distribution-id "$CLOUDFRONT_DISTRIBUTION_ID" --paths "/$REMOTE_SEED_PATH" --query 'Invalidation.Id' --output text) + aws cloudfront wait invalidation-completed --distribution-id "$CLOUDFRONT_DISTRIBUTION_ID" --id "$INVALIDATION_ID" + + - name: Comment "Generation Successful" + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + with: + script: | + const fs = require('fs'); + const variationsServerURL = `https://griffin.brave.com/${process.env.REMOTE_SEED_PATH}`; + const serialNumberContent = fs.readFileSync('serialnumber', 'utf8'); + const commentBody = + `## ✅ Test Seed Generated Successfully + + To test the new seed, launch the browser with the following command line: + \`\`\` + --accept-empty-variations-seed-signature --variations-server-url=${variationsServerURL} + \`\`\` + + #### Seed Details + - Serial Number: \`${serialNumberContent}\` + - Uploaded: \`${new Date().toISOString()}\` + ` + const comment = require('.github/workflows/scripts/comment.js') + await comment(github, context, commentBody) + + - name: Comment "Generation Failed" + if: failure() + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + with: + script: | + const actionRunURL = `${process.env.ACTION_RUN_URL}`; + const commentBody = + `## ❌ Test Seed Generation Failed + + [Workflow logs for more information.](${actionRunURL}) + ` + const comment = require('.github/workflows/scripts/comment.js') + await comment(github, context, commentBody) diff --git a/.github/workflows/scripts/comment.js b/.github/workflows/scripts/comment.js new file mode 100644 index 00000000..f1b03c7d --- /dev/null +++ b/.github/workflows/scripts/comment.js @@ -0,0 +1,32 @@ +// Creates or updates a single comment which is looked up by the workflow name. + +module.exports = async (github, context, commentBody) => { + const uniqueCommentTag = ``; + commentBody = `${commentBody}\n${uniqueCommentTag}`; + + const comments = await github.rest.issues.listComments({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + }); + + const existingComment = comments.data.find((comment) => + comment.body.includes(uniqueCommentTag), + ); + + if (existingComment) { + await github.rest.issues.updateComment({ + comment_id: existingComment.id, + owner: context.repo.owner, + repo: context.repo.repo, + body: commentBody, + }); + } else { + await github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: commentBody, + }); + } +}; diff --git a/src/seed_tools/commands/create_seed.ts b/src/seed_tools/commands/create_seed.ts index d00da3fe..0b6c5bfa 100644 --- a/src/seed_tools/commands/create_seed.ts +++ b/src/seed_tools/commands/create_seed.ts @@ -24,6 +24,7 @@ export default new Command('create_seed') .option( '--serial_number_path ', 'file path to write the serial number to', + './serialnumber', ) .option('--mock_serial_number ', 'mock serial number') .action(main); diff --git a/src/seed_tools/utils/diff_strings.test.ts b/src/seed_tools/utils/diff_strings.test.ts index 3406c86f..f19f5f15 100644 --- a/src/seed_tools/utils/diff_strings.test.ts +++ b/src/seed_tools/utils/diff_strings.test.ts @@ -9,8 +9,8 @@ describe('diffStrings', () => { it('should return the diff between two strings', async () => { const string1 = 'Hello, world!'; const string2 = 'Hello, brave!'; - const displayFileName1 = 'file1.txt'; - const displayFileName2 = 'file2.txt'; + const displayFileName1 = 'file1-test.txt'; + const displayFileName2 = 'file2-test.txt'; const result = await diffStrings( string1, @@ -19,7 +19,25 @@ describe('diffStrings', () => { displayFileName2, ); + expect(result).toContain(displayFileName1); + expect(result).toContain(displayFileName2); expect(result).toContain('-Hello, world!'); expect(result).toContain('+Hello, brave!'); }); + + it('should return empty diff between two equal strings', async () => { + const string1 = 'Hello, brave!'; + const string2 = 'Hello, brave!'; + const displayFileName1 = 'file1-test.txt'; + const displayFileName2 = 'file2-test.txt'; + + const result = await diffStrings( + string1, + string2, + displayFileName1, + displayFileName2, + ); + + expect(result).toBe(''); + }); }); diff --git a/src/seed_tools/utils/diff_strings.ts b/src/seed_tools/utils/diff_strings.ts index df425f72..1c9c311b 100644 --- a/src/seed_tools/utils/diff_strings.ts +++ b/src/seed_tools/utils/diff_strings.ts @@ -19,11 +19,12 @@ export default async function diffStrings( ): Promise { const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'diffString-')); + const dateStr = Date.now().toString(); const tmpFile1 = path - .join(tmpDir, `file1-${Date.now()}.txt`) + .join(tmpDir, `file1-${dateStr}.txt`) .replaceAll('\\', '/'); const tmpFile2 = path - .join(tmpDir, `file2-${Date.now()}.txt`) + .join(tmpDir, `file2-${dateStr}.txt`) .replaceAll('\\', '/'); // Write strings to temporary files. @@ -39,11 +40,13 @@ export default async function diffStrings( ); return ''; } catch (error) { + // Handle the case where git diff returns 1 due to differences. if (error.code === 1) { - // Handle the case where git diff returns 1 due to differences. + // Remove root forward slashes from the temporary file paths as git diff + // does not include them. const result = error.stdout - .replaceAll(tmpFile1, displayFileName1) - .replaceAll(tmpFile2, displayFileName2); + .replaceAll(tmpFile1.replace(/^\//, ''), displayFileName1) + .replaceAll(tmpFile2.replace(/^\//, ''), displayFileName2); return result; } else {