[Drone] Add Drone build badge (#3240)

* Add drone build badge based on travis

* Fix wrong mocked endpoint for done builder

* Refactor service tester using helper method

* Add missing failure status to red statuses

* Remove extraneous invalid svg test from drone

* Test on failure red status in build status spec

* refactor(drone): use json service instead of svg

* refactor(drone): remove status text and extraneous build path in test

* refactor(drone): allow defining self-hosted drone instances

* fix(drone): use proper urls in drone examples

* fix(drone): add drone token authorization for self-hosted instances

* refactor(drone): call render build status badge directly instead of render

* refactor(drone): use server query parameter for self-hosted instances

* fix(drone): separate url and query params in example

* fix(drone): use actual build status message in examples

* fix(drone): add missing message for status code 401

Co-Authored-By: byCedric <me@bycedric.com>

* refactor(drone): remove color from drone tests

* refactor(drone): remove extraneous comments from drone tests

* refactor(drone): remove unused static preview method

* refactor(drone): remove unused static render method

* refactor(drone): reuse render build status badge helper in static previews

* fix(drone): test inaccessible repos on new message
This commit is contained in:
Cedric van Putten
2019-04-16 18:33:15 +02:00
committed by Caleb Cartwright
parent 91d6dd6643
commit 483ecf24de
7 changed files with 206 additions and 1 deletions

View File

@@ -45,6 +45,7 @@ private:
azure_devops_token: 'AZURE_DEVOPS_TOKEN'
bintray_user: 'BINTRAY_USER'
bintray_apikey: 'BINTRAY_API_KEY'
drone_token: 'DRONE_TOKEN'
gh_client_id: 'GH_CLIENT_ID'
gh_client_secret: 'GH_CLIENT_SECRET'
gh_token: 'GH_TOKEN'

View File

@@ -43,6 +43,13 @@ An Azure DevOps Token (PAT) is required for accessing [private Azure DevOps proj
The bintray API [requires authentication](https://bintray.com/docs/api/#_authentication)
Create an account and obtain a token from the user profile page.
## Drone
- `DRONE_TOKEN` (yml: `drone_token`)
The self-hosted Drone API [requires authentication](https://0-8-0.docs.drone.io/api-authentication/)
Login to your Drone instance and obtain a token from the user profile page.
## GitHub
- `GH_TOKEN` (yml: `gh_token`)

View File

@@ -13,7 +13,13 @@ const greenStatuses = [
const orangeStatuses = ['partially succeeded', 'unstable', 'timeout']
const redStatuses = ['error', 'failed', 'failing', 'infrastructure_failure']
const redStatuses = [
'error',
'failed',
'failing',
'failure',
'infrastructure_failure',
]
const otherStatuses = [
'building',

View File

@@ -58,6 +58,7 @@ test(renderBuildStatusBadge, () => {
given({ status: 'error' }),
given({ status: 'failed' }),
given({ status: 'failing' }),
given({ status: 'failure' }),
given({ status: 'infrastructure_failure' }),
]).assert('should be red', b => expect(b).to.include({ color: 'red' }))
})

View File

@@ -0,0 +1,107 @@
'use strict'
const Joi = require('joi')
const serverSecrets = require('../../lib/server-secrets')
const { isBuildStatus, renderBuildStatusBadge } = require('../build-status')
const { optionalUrl } = require('../validators')
const { BaseJsonService } = require('..')
const DroneBuildSchema = Joi.object({
status: Joi.alternatives()
.try(isBuildStatus, Joi.equal('none'))
.required(),
}).required()
const queryParamSchema = Joi.object({
server: optionalUrl,
}).required()
module.exports = class DroneBuild extends BaseJsonService {
static get category() {
return 'build'
}
static get route() {
return {
queryParamSchema,
base: 'drone/build',
pattern: ':user/:repo/:branch*',
}
}
static get defaultBadgeData() {
return {
label: 'build',
}
}
async handle({ user, repo, branch }, { server }) {
const options = {
qs: {
ref: branch ? `refs/heads/${branch}` : undefined,
},
}
if (serverSecrets.drone_token) {
options.headers = {
Authorization: `Bearer ${serverSecrets.drone_token}`,
}
}
if (!server) {
server = 'https://cloud.drone.io'
}
const json = await this._requestJson({
options,
schema: DroneBuildSchema,
url: `${server}/api/repos/${user}/${repo}/builds/latest`,
errorMessages: {
401: 'repo not found or not authorized',
},
})
return renderBuildStatusBadge({ status: json.status })
}
static get examples() {
return [
{
title: 'Drone (cloud)',
pattern: ':user/:repo',
namedParams: {
user: 'drone',
repo: 'drone',
},
staticPreview: renderBuildStatusBadge({ status: 'success' }),
},
{
title: 'Drone (cloud) with branch',
pattern: ':user/:repo/:branch',
namedParams: {
user: 'drone',
repo: 'drone',
branch: 'master',
},
staticPreview: renderBuildStatusBadge({ status: 'success' }),
},
{
title: 'Drone (self-hosted)',
pattern: ':user/:repo',
queryParams: { server: 'https://drone.shields.io' },
namedParams: {
user: 'badges',
repo: 'shields',
},
staticPreview: renderBuildStatusBadge({ status: 'success' }),
},
{
title: 'Drone (self-hosted) with branch',
pattern: ':user/:repo/:branch',
queryParams: { server: 'https://drone.shields.io' },
namedParams: {
user: 'badges',
repo: 'shields',
branch: 'feat/awesome-thing',
},
staticPreview: renderBuildStatusBadge({ status: 'success' }),
},
]
}
}

View File

@@ -0,0 +1,62 @@
'use strict'
const Joi = require('joi')
const { isBuildStatus } = require('../build-status')
const t = (module.exports = require('../tester').createServiceTester())
const { mockDroneCreds, token, restore } = require('./drone-test-helpers')
t.create('cloud-hosted build status on default branch')
.get('/drone/drone.json')
.expectBadge({
label: 'build',
message: Joi.alternatives().try(isBuildStatus, Joi.equal('none')),
})
t.create('cloud-hosted build status on named branch')
.get('/drone/drone/master.json')
.expectBadge({
label: 'build',
message: Joi.alternatives().try(isBuildStatus, Joi.equal('none')),
})
t.create('cloud-hosted build status on unknown repo')
.get('/this-repo/does-not-exist.json')
.expectBadge({
label: 'build',
message: 'repo not found or not authorized',
})
t.create('self-hosted build status on default branch')
.before(mockDroneCreds)
.get('/badges/shields.json?server=https://drone.shields.io')
.intercept(nock =>
nock('https://drone.shields.io/api/repos', {
reqheaders: { authorization: `Bearer ${token}` },
})
.get('/badges/shields/builds/latest')
.reply(200, { status: 'success' })
)
.finally(restore)
.expectBadge({
label: 'build',
message: 'passing',
})
t.create('self-hosted build status on named branch')
.before(mockDroneCreds)
.get(
'/badges/shields/feat/awesome-thing.json?server=https://drone.shields.io'
)
.intercept(nock =>
nock('https://drone.shields.io/api/repos', {
reqheaders: { authorization: `Bearer ${token}` },
})
.get('/badges/shields/builds/latest')
.query({ ref: 'refs/heads/feat/awesome-thing' })
.reply(200, { status: 'success' })
)
.finally(restore)
.expectBadge({
label: 'build',
message: 'passing',
})

View File

@@ -0,0 +1,21 @@
'use strict'
const sinon = require('sinon')
const serverSecrets = require('../../lib/server-secrets')
const token = 'my-token'
function mockDroneCreds() {
serverSecrets['drone_token'] = undefined
sinon.stub(serverSecrets, 'drone_token').value(token)
}
function restore() {
sinon.restore()
}
module.exports = {
token,
mockDroneCreds,
restore,
}