Skip to content

Commit

Permalink
chore(ci): authenticate release issue by approval comment (#316)
Browse files Browse the repository at this point in the history
* chore(ci): authenticate release issue by approval comment

* chore: fix lint error

* chore: clean up

* chore: validate comment body strictly

* chore: early return if body does not exist

* chore: extract octokit generation

* chore: remove unused import
  • Loading branch information
eunjae-lee authored Apr 1, 2022
1 parent 0011fcc commit 832d12f
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 33 deletions.
1 change: 1 addition & 0 deletions config/release.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"mainBranch": "main",
"owner": "algolia",
"repo": "api-clients-automation",
"teamSlug": "api-clients-automation",
"targetBranch": {
"javascript": "next",
"php": "next",
Expand Down
8 changes: 2 additions & 6 deletions scripts/ci/codegen/upsertGenerationComment.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
/* eslint-disable no-console */
import { Octokit } from '@octokit/rest';

import { run } from '../../common';
import { OWNER, REPO } from '../../release/common';
import { getOctokit, OWNER, REPO } from '../../release/common';

import commentText from './text';

const BOT_NAME = 'algolia-bot';
const PR_NUMBER = parseInt(process.env.PR_NUMBER || '0', 10);
const octokit = new Octokit({
auth: `token ${process.env.GITHUB_TOKEN}`,
});
const octokit = getOctokit(process.env.GITHUB_TOKEN!);

const args = process.argv.slice(2);
const allowedTriggers = ['notification', 'codegen', 'noGen', 'cleanup'];
Expand Down
9 changes: 9 additions & 0 deletions scripts/release/common.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import path from 'path';

import { Octokit } from '@octokit/rest';

import clientsConfig from '../../config/clients.config.json';
import config from '../../config/release.config.json';
import { getGitHubUrl, run } from '../common';
Expand All @@ -8,6 +10,7 @@ export const RELEASED_TAG = config.releasedTag;
export const MAIN_BRANCH = config.mainBranch;
export const OWNER = config.owner;
export const REPO = config.repo;
export const TEAM_SLUG = config.teamSlug;
export const MAIN_PACKAGE = Object.keys(clientsConfig).reduce(
(mainPackage: { [lang: string]: string }, lang: string) => {
return {
Expand All @@ -18,6 +21,12 @@ export const MAIN_PACKAGE = Object.keys(clientsConfig).reduce(
{}
);

export function getOctokit(githubToken: string): Octokit {
return new Octokit({
auth: `token ${githubToken}`,
});
}

export function getTargetBranch(language: string): string {
return config.targetBranch[language] || config.defaultTargetBranch;
}
Expand Down
14 changes: 9 additions & 5 deletions scripts/release/create-release-issue.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
/* eslint-disable no-console */
import { Octokit } from '@octokit/rest';
import dotenv from 'dotenv';
import semver from 'semver';

import { LANGUAGES, ROOT_ENV_PATH, run, getPackageVersion } from '../common';
import type { Language } from '../types';

import { RELEASED_TAG, MAIN_BRANCH, OWNER, REPO, MAIN_PACKAGE } from './common';
import {
RELEASED_TAG,
MAIN_BRANCH,
OWNER,
REPO,
MAIN_PACKAGE,
getOctokit,
} from './common';
import TEXT from './text';
import type {
Versions,
Expand Down Expand Up @@ -246,9 +252,7 @@ async function createReleaseIssue(): Promise<void> {
TEXT.approval,
].join('\n\n');

const octokit = new Octokit({
auth: `token ${process.env.GITHUB_TOKEN}`,
});
const octokit = getOctokit(process.env.GITHUB_TOKEN!);

octokit.rest.issues
.create({
Expand Down
59 changes: 43 additions & 16 deletions scripts/release/process-release.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ import {
RELEASED_TAG,
OWNER,
REPO,
TEAM_SLUG,
getMarkdownSection,
configureGitHubAuthor,
cloneRepository,
getOctokit,
} from './common';
import TEXT from './text';
import type {
Expand All @@ -50,14 +52,22 @@ const BEFORE_CLIENT_COMMIT: { [lang: string]: BeforeClientCommitCommand } = {
},
};

function getIssueBody(): string {
return JSON.parse(
execa.sync('curl', [
'-H',
`Authorization: token ${process.env.GITHUB_TOKEN}`,
`https://api.github.com/repos/${OWNER}/${REPO}/issues/${process.env.EVENT_NUMBER}`,
]).stdout
).body;
async function getIssueBody(): Promise<string> {
const octokit = getOctokit(process.env.GITHUB_TOKEN!);
const {
data: { body },
} = await octokit.rest.issues.get({
owner: OWNER,
repo: REPO,
issue_number: Number(process.env.EVENT_NUMBER),
});

if (!body) {
throw new Error(
`Unexpected \`body\` of the release issue: ${JSON.stringify(body)}`
);
}
return body;
}

function getDateStamp(): string {
Expand Down Expand Up @@ -154,6 +164,26 @@ async function updateChangelog({
);
}

async function isAuthorizedRelease(): Promise<boolean> {
const octokit = getOctokit(process.env.GITHUB_TOKEN!);
const { data: members } = await octokit.rest.teams.listMembersInOrg({
org: OWNER,
team_slug: TEAM_SLUG,
});

const { data: comments } = await octokit.rest.issues.listComments({
owner: OWNER,
repo: REPO,
issue_number: Number(process.env.EVENT_NUMBER),
});

return comments.some(
(comment) =>
comment.body?.toLowerCase().trim() === 'approved' &&
members.find((member) => member.login === comment.user?.login)
);
}

async function processRelease(): Promise<void> {
if (!process.env.GITHUB_TOKEN) {
throw new Error('Environment variable `GITHUB_TOKEN` does not exist.');
Expand All @@ -163,16 +193,13 @@ async function processRelease(): Promise<void> {
throw new Error('Environment variable `EVENT_NUMBER` does not exist.');
}

const issueBody = getIssueBody();

if (
!getMarkdownSection(issueBody, TEXT.approvalHeader)
.split('\n')
.find((line) => line.startsWith(`- [x] ${TEXT.approved}`))
) {
throw new Error('The issue was not approved.');
if (!(await isAuthorizedRelease())) {
throw new Error(
'The issue was not approved.\nA team member must leave a comment "approved" in the release issue.'
);
}

const issueBody = await getIssueBody();
const versionsToRelease = getVersionsToRelease(issueBody);

await updateOpenApiTools(versionsToRelease);
Expand Down
8 changes: 2 additions & 6 deletions scripts/release/text.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
const APPROVED = `Approved`;

export default {
header: `## Summary`,

Expand Down Expand Up @@ -33,10 +31,8 @@ export default {
changelogDescription: `Update the following lines. Once merged, it will be reflected to \`changelogs/*.\``,

approvalHeader: `## Approval`,
approved: APPROVED,
approval: [
`To proceed this release, check the box below and close the issue.`,
`To skip this release, just close the issue.`,
`- [ ] ${APPROVED}`,
`To proceed this release, a team member must leave a comment "approved" in this issue.`,
`To skip this release, just close it.`,
].join('\n'),
};

0 comments on commit 832d12f

Please sign in to comment.