diff --git a/services/nexus/nexus-redirect.service.js b/services/nexus/nexus-redirect.service.js new file mode 100644 index 0000000000..fb194284ae --- /dev/null +++ b/services/nexus/nexus-redirect.service.js @@ -0,0 +1,21 @@ +'use strict' + +const { redirector } = require('..') + +module.exports = [ + redirector({ + category: 'version', + route: { + base: 'nexus', + pattern: + ':repo(r|s|[^/]+)/:scheme(http|https)/:hostAndPath+/:groupId/:artifactId([^/:]+?):queryOpt(:.+?)?', + }, + transformPath: ({ repo, groupId, artifactId }) => + `/nexus/${repo}/${groupId}/${artifactId}`, + transformQueryParams: ({ scheme, hostAndPath, queryOpt }) => ({ + server: `${scheme}://${hostAndPath}`, + queryOpt, + }), + dateAdded: new Date('2019-07-26'), + }), +] diff --git a/services/nexus/nexus-redirect.tester.js b/services/nexus/nexus-redirect.tester.js new file mode 100644 index 0000000000..7dad69de73 --- /dev/null +++ b/services/nexus/nexus-redirect.tester.js @@ -0,0 +1,48 @@ +'use strict' + +const { ServiceTester } = require('../tester') + +const t = (module.exports = new ServiceTester({ + id: 'NexusRedirect', + title: 'NexusRedirect', + pathPrefix: '/nexus', +})) + +t.create('Nexus release') + .get('/r/https/oss.sonatype.org/com.google.guava/guava.json', { + followRedirect: false, + }) + .expectStatus(301) + .expectHeader( + 'Location', + `/nexus/r/com.google.guava/guava.json?server=${encodeURIComponent( + 'https://oss.sonatype.org' + )}` + ) + +t.create('Nexus snapshot') + .get('/s/https/oss.sonatype.org/com.google.guava/guava.json', { + followRedirect: false, + }) + .expectStatus(301) + .expectHeader( + 'Location', + `/nexus/s/com.google.guava/guava.json?server=${encodeURIComponent( + 'https://oss.sonatype.org' + )}` + ) + +t.create('Nexus repository with query opts') + .get( + '/fs-public-snapshots/https/repository.jboss.org/nexus/com.progress.fuse/fusehq:p=tar.gz:c=agent-apple-osx.json', + { + followRedirect: false, + } + ) + .expectStatus(301) + .expectHeader( + 'Location', + `/nexus/fs-public-snapshots/com.progress.fuse/fusehq.json?queryOpt=${encodeURIComponent( + ':p=tar.gz:c=agent-apple-osx' + )}&server=${encodeURIComponent('https://repository.jboss.org/nexus')}` + ) diff --git a/services/nexus/nexus.service.js b/services/nexus/nexus.service.js index f98ca9f937..53d403f080 100644 --- a/services/nexus/nexus.service.js +++ b/services/nexus/nexus.service.js @@ -3,6 +3,7 @@ const Joi = require('@hapi/joi') const { version: versionColor } = require('../color-formatters') const { addv } = require('../text-formatters') +const { optionalUrl } = require('../validators') const { optionalDottedVersionNClausesWithOptionalSuffix, } = require('../validators') @@ -33,6 +34,15 @@ const resolveApiSchema = Joi.object({ }).required(), }).required() +// https://repository.sonatype.org/nexus-restlet1x-plugin/default/docs/path__artifact_maven_resolve.html +// https://repository.sonatype.org/nexus-indexer-lucene-plugin/default/docs/path__lucene_search.html +const queryParamSchema = Joi.object({ + server: optionalUrl.required(), + queryOpt: Joi.string() + .regex(/(:(?:q|g|a|v|p|c|cn|sha1|from|count|repositoryId|e|r)=[\w-. ]+)+/i) + .optional(), +}).required() + module.exports = class Nexus extends BaseJsonService { static get category() { return 'version' @@ -41,12 +51,8 @@ module.exports = class Nexus extends BaseJsonService { static get route() { return { base: 'nexus', - // API pattern: - // /nexus/(r|s|)/(http|https)/[:port][/]//[:k1=v1[:k2=v2[...]]] - pattern: - // Do not base new services on this route pattern. - // See https://github.com/badges/shields/issues/3714 - ':repo(r|s|[^/]+)/:scheme(http|https)/:hostAndPath+/:groupId/:artifactId([^/:]+?):queryOpt(:.+?)?', + pattern: ':repo(r|s|[^/]+)/:groupId/:artifactId', + queryParamSchema, } } @@ -58,54 +64,57 @@ module.exports = class Nexus extends BaseJsonService { return [ { title: 'Sonatype Nexus (Releases)', - pattern: 'r/:scheme(http|https)/:hostAndPath/:groupId/:artifactId', + pattern: 'r/:groupId/:artifactId', namedParams: { - scheme: 'https', - hostAndPath: 'oss.sonatype.org', groupId: 'com.google.guava', artifactId: 'guava', }, + queryParams: { + server: 'https://oss.sonatype.org', + }, staticPreview: this.render({ version: 'v27.0.1-jre', }), }, { title: 'Sonatype Nexus (Snapshots)', - pattern: 's/:scheme(http|https)/:hostAndPath/:groupId/:artifactId', + pattern: 's/:groupId/:artifactId', namedParams: { - scheme: 'https', - hostAndPath: 'oss.sonatype.org', groupId: 'com.google.guava', artifactId: 'guava', }, + queryParams: { + server: 'https://oss.sonatype.org', + }, staticPreview: this.render({ version: 'v24.0-SNAPSHOT', }), }, { title: 'Sonatype Nexus (Repository)', - pattern: ':repo/:scheme(http|https)/:hostAndPath/:groupId/:artifactId', + pattern: ':repo/:groupId/:artifactId', namedParams: { repo: 'developer', - scheme: 'https', - hostAndPath: 'repository.jboss.org/nexus', groupId: 'ai.h2o', artifactId: 'h2o-automl', }, + queryParams: { + server: 'https://repository.jboss.org/nexus', + }, staticPreview: this.render({ version: '3.22.0.2', }), }, { title: 'Sonatype Nexus (Query Options)', - pattern: - ':repo/:scheme(http|https)/:hostAndPath/:groupId/:artifactId/:queryOpt', + pattern: ':repo/:groupId/:artifactId', namedParams: { repo: 'fs-public-snapshots', - scheme: 'https', - hostAndPath: 'repository.jboss.org/nexus', groupId: 'com.progress.fuse', artifactId: 'fusehq', + }, + queryParams: { + server: 'https://repository.jboss.org/nexus', queryOpt: ':c=agent-apple-osx:p=tar.gz', }, staticPreview: this.render({ @@ -148,13 +157,13 @@ module.exports = class Nexus extends BaseJsonService { }) } - async fetch({ repo, scheme, hostAndPath, groupId, artifactId, queryOpt }) { + async fetch({ server, repo, groupId, artifactId, queryOpt }) { const qs = { g: groupId, a: artifactId, } let schema - let url = `${scheme}://${hostAndPath}/` + let url = `${server}${server.slice(-1) === '/' ? '' : '/'}` // API pattern: // for /nexus/[rs]/... pattern, use the search api of the nexus server, and // for /nexus//... pattern, use the resolve api of the nexus server. @@ -215,11 +224,10 @@ module.exports = class Nexus extends BaseJsonService { } } - async handle({ repo, scheme, hostAndPath, groupId, artifactId, queryOpt }) { + async handle({ repo, groupId, artifactId }, { server, queryOpt }) { const { json } = await this.fetch({ repo, - scheme, - hostAndPath, + server, groupId, artifactId, queryOpt, diff --git a/services/nexus/nexus.spec.js b/services/nexus/nexus.spec.js index 1cca34fed0..59a38c8a88 100644 --- a/services/nexus/nexus.spec.js +++ b/services/nexus/nexus.spec.js @@ -88,13 +88,18 @@ describe('Nexus', function() { .reply(200, { data: [{ latestRelease: '2.3.4' }] }) expect( - await Nexus.invoke(defaultContext, config, { - repo: 'r', - scheme: 'https', - hostAndPath: 'repository.jboss.org/nexus', - groupId: 'jboss', - artifactId: 'jboss-client', - }) + await Nexus.invoke( + defaultContext, + config, + { + repo: 'r', + groupId: 'jboss', + artifactId: 'jboss-client', + }, + { + server: 'https://repository.jboss.org/nexus', + } + ) ).to.deep.equal({ message: 'v2.3.4', color: 'blue', diff --git a/services/nexus/nexus.tester.js b/services/nexus/nexus.tester.js index 5d9df348ad..ecc1f32380 100644 --- a/services/nexus/nexus.tester.js +++ b/services/nexus/nexus.tester.js @@ -7,16 +7,16 @@ const t = (module.exports = require('../tester').createServiceTester()) t.create('search release version valid artifact') .timeout(15000) - .get('/r/https/oss.sonatype.org/com.google.guava/guava.json') + .get('/r/com.google/bitcoinj.json?server=https://oss.sonatype.org') .expectBadge({ label: 'nexus', message: isVersion, }) t.create('search release version of an nonexistent artifact') - .timeout(10000) + .timeout(15000) .get( - '/r/https/oss.sonatype.org/com.google.guava/nonexistent-artifact-id.json' + '/r/com.google.guava/nonexistent-artifact-id.json?server=https://oss.sonatype.org' ) .expectBadge({ label: 'nexus', @@ -24,17 +24,17 @@ t.create('search release version of an nonexistent artifact') }) t.create('search snapshot version valid snapshot artifact') - .timeout(10000) - .get('/s/https/oss.sonatype.org/com.google.guava/guava.json') + .timeout(15000) + .get('/s/com.google.guava/guava.json?server=https://oss.sonatype.org') .expectBadge({ label: 'nexus', message: isVersion, }) t.create('search snapshot version of an nonexistent artifact') - .timeout(10000) + .timeout(15000) .get( - '/s/https/oss.sonatype.org/com.google.guava/nonexistent-artifact-id.json' + '/s/com.google.guava/nonexistent-artifact-id.json?server=https://oss.sonatype.org' ) .expectBadge({ label: 'nexus', @@ -43,15 +43,21 @@ t.create('search snapshot version of an nonexistent artifact') }) t.create('repository version') - .get('/developer/https/repository.jboss.org/nexus/ai.h2o/h2o-automl.json') + .timeout(15000) + .get( + '/developer/ai.h2o/h2o-automl.json?server=https://repository.jboss.org/nexus' + ) .expectBadge({ label: 'nexus', message: isVersion, }) t.create('repository version with query') + .timeout(15000) .get( - '/fs-public-snapshots/https/repository.jboss.org/nexus/com.progress.fuse/fusehq:c=agent-apple-osx:p=tar.gz.json' + `/fs-public-snapshots/com.progress.fuse/fusehq.json?server=https://repository.jboss.org/nexus&queryOpt=${encodeURIComponent( + ':p=tar.gz:c=agent-apple-osx' + )}` ) .expectBadge({ label: 'nexus', @@ -59,8 +65,9 @@ t.create('repository version with query') }) t.create('repository version of an nonexistent artifact') + .timeout(15000) .get( - '/developer/https/repository.jboss.org/nexus/jboss/nonexistent-artifact-id.json' + '/developer/jboss/nonexistent-artifact-id.json?server=https://repository.jboss.org/nexus' ) .expectBadge({ label: 'nexus',