Files
shields/services/nexus/nexus.service.js
Caleb Cartwright 6d3798f26f Migrates [Nexus] service to new service model (#2520)
Ports the Nexus service to the new service model. Some related/relevant conversation in #2347 (and closes #2347). Also adds support for authentication which resolves #1699.
2018-12-19 15:45:22 -05:00

227 lines
6.5 KiB
JavaScript

'use strict'
const Joi = require('joi')
const BaseJsonService = require('../base-json')
const { InvalidResponse, NotFound } = require('../errors')
const { isSnapshotVersion } = require('./nexus-version')
const { version: versionColor } = require('../../lib/color-formatters')
const { addv } = require('../../lib/text-formatters')
const serverSecrets = require('../../lib/server-secrets')
const {
optionalDottedVersionNClausesWithOptionalSuffix,
} = require('../validators')
const searchApiSchema = Joi.object({
data: Joi.array()
.items(
Joi.object({
latestRelease: optionalDottedVersionNClausesWithOptionalSuffix,
latestSnapshot: optionalDottedVersionNClausesWithOptionalSuffix,
version: optionalDottedVersionNClausesWithOptionalSuffix,
})
)
.required(),
}).required()
const resolveApiSchema = Joi.object({
data: Joi.object({
baseVersion: optionalDottedVersionNClausesWithOptionalSuffix,
version: optionalDottedVersionNClausesWithOptionalSuffix,
}).required(),
}).required()
module.exports = class Nexus extends BaseJsonService {
static render({ version }) {
return {
message: addv(version),
color: versionColor(version),
}
}
static get category() {
return 'version'
}
static get route() {
return {
base: 'nexus',
// API pattern:
// /nexus/(r|s|<repo-name>)/(http|https)/<nexus.host>[:port][/<entry-path>]/<group>/<artifact>[:k1=v1[:k2=v2[...]]]
format:
'(r|s|[^/]+)/(https?)/((?:[^/]+)(?:/[^/]+)?)/([^/]+)/([^/:]+)(:.+)?',
capture: ['repo', 'scheme', 'host', 'groupId', 'artifactId', 'queryOpt'],
}
}
static get defaultBadgeData() {
return { color: 'blue', label: 'nexus' }
}
static get examples() {
return [
{
title: 'Sonatype Nexus (Releases)',
pattern: 'r/:scheme/:host/:groupId/:artifactId',
namedParams: {
scheme: 'https',
host: 'oss.sonatype.org',
groupId: 'com.google.guava',
artifactId: 'guava',
},
staticExample: this.render({
version: 'v27.0.1-jre',
}),
},
{
title: 'Sonatype Nexus (Snapshots)',
pattern: 's/:scheme/:host/:groupId/:artifactId',
namedParams: {
scheme: 'https',
host: 'oss.sonatype.org',
groupId: 'com.google.guava',
artifactId: 'guava',
},
staticExample: this.render({
version: 'v24.0-SNAPSHOT',
}),
},
{
title: 'Sonatype Nexus (Repository)',
pattern: ':repo/:scheme/:host/:groupId/:artifactId',
namedParams: {
repo: 'developer',
scheme: 'https',
host: 'repository.jboss.org/nexus',
groupId: 'ai.h2o',
artifactId: 'h2o-automl',
},
staticExample: this.render({
version: '3.22.0.2',
}),
},
{
title: 'Sonatype Nexus (Query Options)',
pattern: ':repo/:scheme/:host/:groupId/:artifactId/:queryOpt',
namedParams: {
repo: 'fs-public-snapshots',
scheme: 'https',
host: 'repository.jboss.org/nexus',
groupId: 'com.progress.fuse',
artifactId: 'fusehq',
queryOpt: ':c=agent-apple-osx:p=tar.gz',
},
staticExample: this.render({
version: '7.0.1-SNAPSHOT',
}),
documentation: `
<p>
Note that you can use query options with any Nexus badge type (Releases, Snapshots, or Repository)
</p>
<p>
Query options should be provided as key=value pairs separated by a semicolon
</p>
`,
},
]
}
transform({ repo, json }) {
if (repo === 'r') {
return { version: json.data[0].latestRelease }
} else if (repo === 's') {
// only want to match 1.2.3-SNAPSHOT style versions, which may not always be in
// 'latestSnapshot' so check 'version' as well before continuing to next entry
for (const artifact of json.data) {
if (isSnapshotVersion(artifact.latestSnapshot)) {
return { version: artifact.latestSnapshot }
}
if (isSnapshotVersion(artifact.version)) {
return { version: artifact.version }
}
}
throw new InvalidResponse({ prettyMessage: 'no snapshot versions found' })
} else {
return { version: json.data.baseVersion || json.data.version }
}
}
async handle({ repo, scheme, host, groupId, artifactId, queryOpt }) {
const { json } = await this.fetch({
repo,
scheme,
host,
groupId,
artifactId,
queryOpt,
})
if (json.data.length === 0) {
throw new NotFound({ prettyMessage: 'artifact or version not found' })
}
const { version } = this.transform({ repo, json })
if (!version) {
throw new InvalidResponse({ prettyMessage: 'invalid artifact version' })
}
return this.constructor.render({ version })
}
addQueryParamsToQueryString({ qs, queryOpt }) {
// Users specify query options with 'key=value' pairs, using a
// semicolon delimiter between pairs ([:k1=v1[:k2=v2[...]]]).
// queryOpt will be a string containing those key/value pairs,
// For example: :c=agent-apple-osx:p=tar.gz
const keyValuePairs = queryOpt.split(':')
keyValuePairs.forEach(keyValuePair => {
const paramParts = keyValuePair.split('=')
const paramKey = paramParts[0]
const paramValue = paramParts[1]
qs[paramKey] = paramValue
})
}
async fetch({ repo, scheme, host, groupId, artifactId, queryOpt }) {
const qs = {
g: groupId,
a: artifactId,
}
let schema
let url = `${scheme}://${host}/`
// API pattern:
// for /nexus/[rs]/... pattern, use the search api of the nexus server, and
// for /nexus/<repo-name>/... pattern, use the resolve api of the nexus server.
if (repo === 'r' || repo === 's') {
schema = searchApiSchema
url += 'service/local/lucene/search'
} else {
schema = resolveApiSchema
url += 'service/local/artifact/maven/resolve'
qs.r = repo
qs.v = 'LATEST'
}
if (queryOpt) {
this.addQueryParamsToQueryString({ qs, queryOpt })
}
const options = { qs }
if (serverSecrets && serverSecrets.nexus_user) {
options.auth = {
user: serverSecrets.nexus_user,
pass: serverSecrets.nexus_pass,
}
}
const json = await this._requestJson({
schema,
url,
options,
errorMessages: {
404: 'artifact not found',
},
})
return { json }
}
}