Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor, feat: patch authors and rebase when pushing #797

Merged
merged 6 commits into from
Apr 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions components/git/security.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import CLI from '../../lib/cli.js';
import SecurityReleaseSteward from '../../lib/prepare_security.js';
import PrepareSecurityRelease from '../../lib/prepare_security.js';
import UpdateSecurityRelease from '../../lib/update_security_release.js';
import SecurityBlog from '../../lib/security_blog.js';
import SecurityAnnouncement from '../../lib/security-announcement.js';
Expand Down Expand Up @@ -138,7 +138,7 @@ async function requestCVEs() {
async function startSecurityRelease(argv) {
const logStream = process.stdout.isTTY ? process.stdout : process.stderr;
const cli = new CLI(logStream);
const release = new SecurityReleaseSteward(cli);
const release = new PrepareSecurityRelease(cli);
return release.start();
}

Expand Down
202 changes: 74 additions & 128 deletions lib/prepare_security.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import nv from '@pkgjs/nv';
import fs from 'node:fs';
import path from 'node:path';
import auth from './auth.js';
Expand All @@ -10,100 +9,89 @@ import {
PLACEHOLDERS,
checkoutOnSecurityReleaseBranch,
commitAndPushVulnerabilitiesJSON,
getSummary,
validateDate,
promptDependencies,
getSupportedVersions
getSupportedVersions,
pickReport
} from './security-release/security-release.js';
import _ from 'lodash';

export default class SecurityReleaseSteward {
export default class PrepareSecurityRelease {
repository = NEXT_SECURITY_RELEASE_REPOSITORY;
title = 'Next Security Release';
constructor(cli) {
this.cli = cli;
}

async start() {
const { cli } = this;
const credentials = await auth({
github: true,
h1: true
});

const req = new Request(credentials);
const release = new PrepareSecurityRelease(req);
const releaseDate = await release.promptReleaseDate(cli);
this.req = new Request(credentials);
const releaseDate = await this.promptReleaseDate();
if (releaseDate !== 'TBD') {
validateDate(releaseDate);
}

const createVulnerabilitiesJSON = await release.promptVulnerabilitiesJSON(cli);
const createVulnerabilitiesJSON = await this.promptVulnerabilitiesJSON();

let securityReleasePRUrl;
if (createVulnerabilitiesJSON) {
securityReleasePRUrl = await this.createVulnerabilitiesJSON(
req, release, releaseDate, { cli });
securityReleasePRUrl = await this.startVulnerabilitiesJSONCreation(releaseDate);
}

const createIssue = await release.promptCreateRelaseIssue(cli);
const createIssue = await this.promptCreateRelaseIssue();

if (createIssue) {
const content = await release.buildIssue(releaseDate, securityReleasePRUrl);
await release.createIssue(content, { cli });
const content = await this.buildIssue(releaseDate, securityReleasePRUrl);
await createIssue(
this.title, content, this.repository, { cli: this.cli, repository: this.repository });
};

cli.ok('Done!');
this.cli.ok('Done!');
}

async createVulnerabilitiesJSON(req, release, releaseDate, { cli }) {
async startVulnerabilitiesJSONCreation(releaseDate) {
// checkout on the next-security-release branch
checkoutOnSecurityReleaseBranch(cli, this.repository);
checkoutOnSecurityReleaseBranch(this.cli, this.repository);

// choose the reports to include in the security release
const reports = await release.chooseReports(cli);
const depUpdates = await release.getDependencyUpdates({ cli });
const reports = await this.chooseReports();
const depUpdates = await this.getDependencyUpdates();
const deps = _.groupBy(depUpdates, 'name');

// create the vulnerabilities.json file in the security-release repo
const filePath = await release.createVulnerabilitiesJSON(reports, deps, releaseDate, { cli });
const filePath = await this.createVulnerabilitiesJSON(reports, deps, releaseDate);

// review the vulnerabilities.json file
const review = await release.promptReviewVulnerabilitiesJSON(cli);
const review = await this.promptReviewVulnerabilitiesJSON();

if (!review) {
cli.info(`To push the vulnerabilities.json file run:
this.cli.info(`To push the vulnerabilities.json file run:
- git add ${filePath}
- git commit -m "chore: create vulnerabilities.json for next security release"
- git push -u origin ${NEXT_SECURITY_RELEASE_BRANCH}
- open a PR on ${release.repository.owner}/${release.repository.repo}`);
- open a PR on ${this.repository.owner}/${this.repository.repo}`);
return;
};

// commit and push the vulnerabilities.json file
const commitMessage = 'chore: create vulnerabilities.json for next security release';
commitAndPushVulnerabilitiesJSON(filePath, commitMessage, { cli, repository: this.repository });
commitAndPushVulnerabilitiesJSON(filePath,
commitMessage,
{ cli: this.cli, repository: this.repository });

const createPr = await release.promptCreatePR(cli);
const createPr = await this.promptCreatePR();

if (!createPr) return;

// create pr on the security-release repo
return release.createPullRequest(req, { cli });
return this.createPullRequest();
}
}

class PrepareSecurityRelease {
repository = NEXT_SECURITY_RELEASE_REPOSITORY;
title = 'Next Security Release';

constructor(req, repository) {
this.req = req;
if (repository) {
this.repository = repository;
}
}

promptCreatePR(cli) {
return cli.prompt(
promptCreatePR() {
return this.cli.prompt(
'Create the Next Security Release PR?',
{ defaultAnswer: true });
}
Expand All @@ -125,31 +113,32 @@ class PrepareSecurityRelease {
}
}

async promptReleaseDate(cli) {
async promptReleaseDate() {
const nextWeekDate = new Date();
nextWeekDate.setDate(nextWeekDate.getDate() + 7);
// Format the date as YYYY/MM/DD
const formattedDate = nextWeekDate.toISOString().slice(0, 10).replace(/-/g, '/');
return cli.prompt('Enter target release date in YYYY/MM/DD format (TBD if not defined yet):', {
questionType: 'input',
defaultAnswer: formattedDate
});
return this.cli.prompt(
'Enter target release date in YYYY/MM/DD format (TBD if not defined yet):', {
questionType: 'input',
defaultAnswer: formattedDate
});
}

async promptVulnerabilitiesJSON(cli) {
return cli.prompt(
async promptVulnerabilitiesJSON() {
return this.cli.prompt(
'Create the vulnerabilities.json?',
{ defaultAnswer: true });
}

async promptCreateRelaseIssue(cli) {
return cli.prompt(
async promptCreateRelaseIssue() {
return this.cli.prompt(
'Create the Next Security Release issue?',
{ defaultAnswer: true });
}

async promptReviewVulnerabilitiesJSON(cli) {
return cli.prompt(
async promptReviewVulnerabilitiesJSON() {
return this.cli.prompt(
'Please review vulnerabilities.json and press enter to proceed.',
{ defaultAnswer: true });
}
Expand All @@ -161,67 +150,21 @@ class PrepareSecurityRelease {
return content;
}

async createIssue(content, { cli }) {
const data = await this.req.createIssue(this.title, content, this.repository);
if (data.html_url) {
cli.ok(`Created: ${data.html_url}`);
} else {
cli.error(data);
process.exit(1);
}
}

async chooseReports(cli) {
cli.info('Getting triaged H1 reports...');
async chooseReports() {
this.cli.info('Getting triaged H1 reports...');
const reports = await this.req.getTriagedReports();
const supportedVersions = (await nv('supported'))
.map((v) => `${v.versionName}.x`)
.join(',');
const selectedReports = [];

for (const report of reports.data) {
const {
id, attributes: { title, cve_ids },
relationships: { severity, weakness, reporter }
} = report;
const link = `https://hackerone.com/reports/${id}`;
const reportSeverity = {
rating: severity?.data?.attributes?.rating || '',
cvss_vector_string: severity?.data?.attributes?.cvss_vector_string || '',
weakness_id: weakness?.data?.id || ''
};

cli.separator();
cli.info(`Report: ${link} - ${title} (${reportSeverity?.rating})`);
const include = await cli.prompt(
'Would you like to include this report to the next security release?',
{ defaultAnswer: true });
if (!include) {
continue;
}

const versions = await cli.prompt('Which active release lines this report affects?', {
questionType: 'input',
defaultAnswer: supportedVersions
});
const summaryContent = await getSummary(id, this.req);

selectedReports.push({
id,
title,
cveIds: cve_ids,
severity: reportSeverity,
summary: summaryContent ?? '',
affectedVersions: versions.split(',').map((v) => v.replace('v', '').trim()),
link,
reporter: reporter.data.attributes.username
});
const rep = await pickReport(report, { cli: this.cli, req: this.req });
if (!rep) continue;
selectedReports.push(rep);
}
return selectedReports;
}

async createVulnerabilitiesJSON(reports, dependencies, releaseDate, { cli }) {
cli.separator('Creating vulnerabilities.json...');
async createVulnerabilitiesJSON(reports, dependencies, releaseDate) {
this.cli.separator('Creating vulnerabilities.json...');
const file = JSON.stringify({
releaseDate,
reports,
Expand All @@ -237,14 +180,14 @@ class PrepareSecurityRelease {

const fullPath = path.join(folderPath, 'vulnerabilities.json');
fs.writeFileSync(fullPath, file);
cli.ok(`Created ${fullPath} `);
this.cli.ok(`Created ${fullPath} `);

return fullPath;
}

async createPullRequest(req, { cli }) {
async createPullRequest() {
const { owner, repo } = this.repository;
const response = await req.createPullRequest(
const response = await this.req.createPullRequest(
this.title,
'List of vulnerabilities to be included in the next security release',
{
Expand All @@ -257,49 +200,52 @@ class PrepareSecurityRelease {
);
const url = response?.html_url;
if (url) {
cli.ok(`Created: ${url}`);
this.cli.ok(`Created: ${url}`);
return url;
}
if (response?.errors) {
for (const error of response.errors) {
cli.error(error.message);
this.cli.error(error.message);
}
} else {
cli.error(response);
this.cli.error(response);
}
process.exit(1);
}

async getDependencyUpdates({ cli }) {
async getDependencyUpdates() {
const deps = [];
cli.log('\n');
cli.separator('Dependency Updates');
const updates = await cli.prompt('Are there dependency updates in this security release?', {
defaultAnswer: true,
questionType: 'confirm'
});
this.cli.log('\n');
this.cli.separator('Dependency Updates');
const updates = await this.cli.prompt('Are there dependency updates in this security release?',
{
defaultAnswer: true,
questionType: 'confirm'
});

if (!updates) return deps;

const supportedVersions = await getSupportedVersions();

let asking = true;
while (asking) {
const dep = await promptDependencies(cli);
const dep = await promptDependencies(this.cli);
if (!dep) {
asking = false;
break;
}

const name = await cli.prompt('What is the name of the dependency that has been updated?', {
defaultAnswer: '',
questionType: 'input'
});
const name = await this.cli.prompt(
'What is the name of the dependency that has been updated?', {
defaultAnswer: '',
questionType: 'input'
});

const versions = await cli.prompt('Which release line does this dependency update affect?', {
defaultAnswer: supportedVersions,
questionType: 'input'
});
const versions = await this.cli.prompt(
'Which release line does this dependency update affect?', {
defaultAnswer: supportedVersions,
questionType: 'input'
});

try {
const prUrl = dep.replace('https://github.com/', 'https://api.github.com/repos/').replace('pull', 'pulls');
Expand All @@ -311,7 +257,7 @@ class PrepareSecurityRelease {
title,
affectedVersions: versions.split(',').map((v) => v.replace('v', '').trim())
});
cli.separator();
this.cli.separator();
} catch (error) {
this.cli.error('Invalid PR url. Please provide a valid PR url.');
this.cli.error(error);
Expand Down
Loading
Loading