mirror of
https://github.com/dependabot/fetch-metadata.git
synced 2026-03-13 18:17:13 -04:00
Add a class to verify dependabot prs/commits
- Add ci config
This commit is contained in:
30
.github/workflows/ci.yml
vendored
Normal file
30
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
name: CI
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: CI
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup nodejs
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '14'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Run linter
|
||||
run: npm run lint
|
||||
|
||||
- name: Run tests
|
||||
run: npm test
|
||||
160
src/dependabot/verified_commits.test.ts
Normal file
160
src/dependabot/verified_commits.test.ts
Normal file
@@ -0,0 +1,160 @@
|
||||
import * as github from '@actions/github'
|
||||
import * as core from '@actions/core'
|
||||
import nock from 'nock'
|
||||
import { Context } from '@actions/github/lib/context'
|
||||
import { getMessage } from './verified_commits'
|
||||
|
||||
beforeAll(() => {
|
||||
nock.disableNetConnect()
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
jest.restoreAllMocks()
|
||||
|
||||
jest.spyOn(core, 'debug').mockImplementation(jest.fn())
|
||||
jest.spyOn(core, 'warning').mockImplementation(jest.fn())
|
||||
|
||||
process.env.GITHUB_REPOSITORY = 'dependabot/dependabot'
|
||||
})
|
||||
|
||||
test('it returns false if the action is not invoked on a PullRequest', async () => {
|
||||
expect(await getMessage(mockGitHubClient, mockGitHubOtherContext())).toBe(false)
|
||||
|
||||
expect(core.warning).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Event payload missing `pull_request` key.')
|
||||
)
|
||||
})
|
||||
|
||||
test('it returns false for an event triggered by someone other than Dependabot', async () => {
|
||||
expect(await getMessage(mockGitHubClient, mockGitHubPullContext('jane-doe'))).toBe(false)
|
||||
|
||||
expect(core.debug).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Event actor 'jane-doe' is not Dependabot.")
|
||||
)
|
||||
})
|
||||
|
||||
test('it returns false if there is more than 1 commit', async () => {
|
||||
nock('https://api.github.com').get('/repos/dependabot/dependabot/pulls/101/commits')
|
||||
.reply(200, [
|
||||
{
|
||||
commit: {
|
||||
message: 'Bump lodash from 1.0.0 to 2.0.0'
|
||||
}
|
||||
},
|
||||
{
|
||||
commit: {
|
||||
message: 'Add some more things.'
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
expect(await getMessage(mockGitHubClient, mockGitHubPullContext())).toBe(false)
|
||||
|
||||
expect(core.warning).toHaveBeenCalledWith(
|
||||
expect.stringContaining("It looks like this PR has contains commits that aren't part of a Dependabot update.")
|
||||
)
|
||||
})
|
||||
|
||||
test('it returns false if the commit was authored by someone other than Dependabot', async () => {
|
||||
nock('https://api.github.com').get('/repos/dependabot/dependabot/pulls/101/commits')
|
||||
.reply(200, [
|
||||
{
|
||||
author: {
|
||||
login: 'dependanot'
|
||||
},
|
||||
commit: {
|
||||
message: 'Bump lodash from 1.0.0 to 2.0.0'
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
expect(await getMessage(mockGitHubClient, mockGitHubPullContext())).toBe(false)
|
||||
|
||||
expect(core.warning).toHaveBeenCalledWith(
|
||||
expect.stringContaining("It looks like this PR has contains commits that aren't part of a Dependabot update.")
|
||||
)
|
||||
})
|
||||
|
||||
test('it returns false if the commit is has no verification payload', async () => {
|
||||
nock('https://api.github.com').get('/repos/dependabot/dependabot/pulls/101/commits')
|
||||
.reply(200, [
|
||||
{
|
||||
author: {
|
||||
login: 'dependabot[bot]'
|
||||
},
|
||||
commit: {
|
||||
message: 'Bump lodash from 1.0.0 to 2.0.0',
|
||||
verification: null
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
expect(await getMessage(mockGitHubClient, mockGitHubPullContext())).toBe(false)
|
||||
})
|
||||
|
||||
test('it returns false if the commit is not verified', async () => {
|
||||
nock('https://api.github.com').get('/repos/dependabot/dependabot/pulls/101/commits')
|
||||
.reply(200, [
|
||||
{
|
||||
author: {
|
||||
login: 'dependabot[bot]'
|
||||
},
|
||||
commit: {
|
||||
message: 'Bump lodash from 1.0.0 to 2.0.0',
|
||||
verification: {
|
||||
verified: false
|
||||
}
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
expect(await getMessage(mockGitHubClient, mockGitHubPullContext())).toBe(false)
|
||||
})
|
||||
|
||||
test('it returns the commit message for a PR authored exclusively by Dependabot with verified commits', async () => {
|
||||
nock('https://api.github.com').get('/repos/dependabot/dependabot/pulls/101/commits')
|
||||
.reply(200, [
|
||||
{
|
||||
author: {
|
||||
login: 'dependabot[bot]'
|
||||
},
|
||||
commit: {
|
||||
message: 'Bump lodash from 1.0.0 to 2.0.0',
|
||||
verification: {
|
||||
verified: true
|
||||
}
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
expect(await getMessage(mockGitHubClient, mockGitHubPullContext())).toEqual('Bump lodash from 1.0.0 to 2.0.0')
|
||||
})
|
||||
|
||||
const mockGitHubClient = github.getOctokit('mock-token')
|
||||
|
||||
function mockGitHubOtherContext (): Context {
|
||||
const ctx = new Context()
|
||||
ctx.payload = {
|
||||
issue: {
|
||||
number: 100
|
||||
}
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
function mockGitHubPullContext (actor = 'dependabot[bot]'): Context {
|
||||
const ctx = new Context()
|
||||
ctx.payload = {
|
||||
pull_request: {
|
||||
number: 101
|
||||
},
|
||||
repository: {
|
||||
name: 'dependabot',
|
||||
owner: {
|
||||
login: 'dependabot'
|
||||
}
|
||||
}
|
||||
}
|
||||
ctx.actor = actor
|
||||
return ctx
|
||||
}
|
||||
63
src/dependabot/verified_commits.ts
Normal file
63
src/dependabot/verified_commits.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import * as core from '@actions/core'
|
||||
import { GitHub } from '@actions/github/lib/utils'
|
||||
import { Context } from '@actions/github/lib/context'
|
||||
|
||||
const DEPENDABOT_LOGIN = 'dependabot[bot]'
|
||||
|
||||
export async function getMessage (client: InstanceType<typeof GitHub>, context: Context): Promise<string | false> {
|
||||
core.debug('Verifying the job is for an authentic Dependabot Pull Request')
|
||||
|
||||
const { pull_request: pr } = context.payload
|
||||
|
||||
if (!pr) {
|
||||
core.warning(
|
||||
"Event payload missing `pull_request` key. Make sure you're " +
|
||||
'triggering this action on the `pull_request` or `pull_request_target` events.'
|
||||
)
|
||||
return false
|
||||
}
|
||||
|
||||
// Don't bother hitting the API if the event actor isn't Dependabot
|
||||
if (context.actor !== DEPENDABOT_LOGIN) {
|
||||
core.debug(`Event actor '${context.actor}' is not Dependabot.`)
|
||||
return false
|
||||
}
|
||||
|
||||
core.debug('Verifying the Pull Request contents are from Dependabot')
|
||||
|
||||
const { data: commits } = await client.rest.pulls.listCommits({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: pr.number
|
||||
})
|
||||
|
||||
if (commits.length > 1) {
|
||||
warnOtherCommits()
|
||||
return false
|
||||
}
|
||||
|
||||
const { commit, author } = commits[0]
|
||||
|
||||
if (author?.login !== DEPENDABOT_LOGIN) {
|
||||
warnOtherCommits()
|
||||
return false
|
||||
}
|
||||
|
||||
if (!commit.verification?.verified) {
|
||||
// TODO: Promote to setFailed
|
||||
core.warning(
|
||||
"Dependabot's commit signature is not verified, refusing to proceed."
|
||||
)
|
||||
return false
|
||||
}
|
||||
|
||||
return commit.message
|
||||
}
|
||||
|
||||
function warnOtherCommits (): void {
|
||||
core.warning(
|
||||
"It looks like this PR has contains commits that aren't part of a Dependabot update. " +
|
||||
"Try using '@dependabot rebase' to remove merge commits or '@dependabot recreate' to remove " +
|
||||
'any non-Dependabot changes.'
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user