Skip to content

Commit

Permalink
feat: create datasource for artifactory registry (#11602)
Browse files Browse the repository at this point in the history
  • Loading branch information
jjcaballero committed Oct 1, 2021
1 parent a7b34cf commit 9fea985
Show file tree
Hide file tree
Showing 10 changed files with 423 additions and 2 deletions.
2 changes: 2 additions & 0 deletions lib/datasource/api.ts
@@ -1,4 +1,5 @@
import { AdoptiumJavaDatasource } from './adoptium-java';
import { ArtifactoryDatasource } from './artifactory';
import { BitBucketTagsDatasource } from './bitbucket-tags';
import { CdnJsDatasource } from './cdnjs';
import { ClojureDatasource } from './clojure';
Expand Down Expand Up @@ -40,6 +41,7 @@ const api = new Map<string, DatasourceApi>();
export default api;

api.set(AdoptiumJavaDatasource.id, new AdoptiumJavaDatasource());
api.set(ArtifactoryDatasource.id, new ArtifactoryDatasource());
api.set('bitbucket-tags', new BitBucketTagsDatasource());
api.set('cdnjs', new CdnJsDatasource());
api.set('clojure', new ClojureDatasource());
Expand Down
21 changes: 21 additions & 0 deletions lib/datasource/artifactory/__fixtures__/releases-as-files.html
@@ -0,0 +1,21 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<html>
<head>
<meta name="robots" content="noindex" />
<title>Repository Title</title>
</head>

<body>
<h1>Index</h1>
<pre>Name Last modified Size</pre><hr/>
<pre>
<a href="..">..</a>
<a href="1.0.0">1.0.0</a> 21-Jul-2021 20:08 -
<a href="1.0.1">1.0.1</a> 23-Aug-2021 20:03 -
<a href="1.0.2">1.0.2</a> 21-Jul-2021 20:09 -
<a href="1.0.3">1.0.3</a> 06-Feb-2021 09:54 -
</pre>
<hr/>
<address style="font-size:small;">Artifactory Port 8080</address>
</body>
</html>
21 changes: 21 additions & 0 deletions lib/datasource/artifactory/__fixtures__/releases-as-folders.html
@@ -0,0 +1,21 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<html>
<head>
<meta name="robots" content="noindex" />
<title>Repository Title</title>
</head>

<body>
<h1>Index</h1>
<pre>Name Last modified Size</pre><hr/>
<pre>
<a href="../">../</a>
<a href="1.0.0/">1.0.0/</a> 21-Jul-2021 20:08 -
<a href="1.0.1/">1.0.1/</a> 23-Aug-2021 20:03 -
<a href="1.0.2/">1.0.2/</a> 21-Jul-2021 20:09 -
<a href="1.0.3/">1.0.3/</a> 06-Feb-2021 09:54 -
</pre>
<hr/>
<address style="font-size:small;">Artifactory Port 8080</address>
</body>
</html>
80 changes: 80 additions & 0 deletions lib/datasource/artifactory/__snapshots__/index.spec.ts.snap
@@ -0,0 +1,80 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`datasource/artifactory/index getReleases parses real data (files): without slash at the end 1`] = `
Object {
"registryUrl": "https://jfrog.company.com/artifactory",
"releases": Array [
Object {
"releaseTimestamp": "2021-07-21T20:08:00.000Z",
"version": "1.0.0",
},
Object {
"releaseTimestamp": "2021-08-23T20:03:00.000Z",
"version": "1.0.1",
},
Object {
"releaseTimestamp": "2021-07-21T20:09:00.000Z",
"version": "1.0.2",
},
Object {
"releaseTimestamp": "2021-02-06T09:54:00.000Z",
"version": "1.0.3",
},
],
}
`;

exports[`datasource/artifactory/index getReleases parses real data (folders): with slash at the end 1`] = `
Object {
"registryUrl": "https://jfrog.company.com/artifactory",
"releases": Array [
Object {
"releaseTimestamp": "2021-07-21T20:08:00.000Z",
"version": "1.0.0",
},
Object {
"releaseTimestamp": "2021-08-23T20:03:00.000Z",
"version": "1.0.1",
},
Object {
"releaseTimestamp": "2021-07-21T20:09:00.000Z",
"version": "1.0.2",
},
Object {
"releaseTimestamp": "2021-02-06T09:54:00.000Z",
"version": "1.0.3",
},
],
}
`;

exports[`datasource/artifactory/index getReleases parses real data (merge strategy with 2 registries) 1`] = `
Object {
"releases": Array [
Object {
"registryUrl": "https://jfrog.company.com/artifactory",
"releaseTimestamp": "2021-07-21T20:08:00.000Z",
"version": "1.0.0",
},
Object {
"registryUrl": "https://jfrog.company.com/artifactory",
"releaseTimestamp": "2021-08-23T20:03:00.000Z",
"version": "1.0.1",
},
Object {
"registryUrl": "https://jfrog.company.com/artifactory",
"releaseTimestamp": "2021-07-21T20:09:00.000Z",
"version": "1.0.2",
},
Object {
"registryUrl": "https://jfrog.company.com/artifactory",
"releaseTimestamp": "2021-02-06T09:54:00.000Z",
"version": "1.0.3",
},
Object {
"registryUrl": "https://jfrog.company.com/artifactory/production",
"version": "1.3.0",
},
],
}
`;
1 change: 1 addition & 0 deletions lib/datasource/artifactory/common.ts
@@ -0,0 +1 @@
export const datasource = 'artifactory';
156 changes: 156 additions & 0 deletions lib/datasource/artifactory/index.spec.ts
@@ -0,0 +1,156 @@
import { getPkgReleases } from '..';
import * as httpMock from '../../../test/http-mock';
import { loadFixture } from '../../../test/util';
import { EXTERNAL_HOST_ERROR } from '../../constants/error-messages';
import { logger } from '../../logger';
import { joinUrlParts } from '../../util/url';
import { ArtifactoryDatasource } from '.';

const datasource = ArtifactoryDatasource.id;

const testRegistryUrl = 'https://jfrog.company.com/artifactory';
const testLookupName = 'project';
const testConfig = {
registryUrls: [testRegistryUrl],
depName: testLookupName,
};
const fixtureReleasesAsFolders = loadFixture('releases-as-folders.html');
const fixtureReleasesAsFiles = loadFixture('releases-as-files.html');

function getPath(folder: string): string {
return `/${folder}`;
}

describe('datasource/artifactory/index', () => {
beforeEach(() => {
jest.resetAllMocks();
});

describe('getReleases', () => {
it('parses real data (folders): with slash at the end', async () => {
httpMock
.scope(testRegistryUrl)
.get(getPath(testLookupName))
.reply(200, fixtureReleasesAsFolders);
const res = await getPkgReleases({
...testConfig,
datasource,
lookupName: testLookupName,
});
expect(res.releases).toHaveLength(4);
expect(res).toMatchSnapshot({
registryUrl: 'https://jfrog.company.com/artifactory',
});
});

it('parses real data (files): without slash at the end', async () => {
httpMock
.scope(testRegistryUrl)
.get(getPath(testLookupName))
.reply(200, fixtureReleasesAsFiles);
const res = await getPkgReleases({
...testConfig,
datasource,
lookupName: testLookupName,
});
expect(res.releases).toHaveLength(4);
expect(res).toMatchSnapshot({
registryUrl: 'https://jfrog.company.com/artifactory',
});
});

it('parses real data (merge strategy with 2 registries)', async () => {
const secondRegistryUrl: string = joinUrlParts(
testRegistryUrl,
'production'
);
httpMock
.scope(testRegistryUrl)
.get(getPath(testLookupName))
.reply(200, fixtureReleasesAsFiles);
httpMock
.scope(secondRegistryUrl)
.get(getPath(testLookupName))
.reply(200, '<html>\n<h1>Header</h1>\n<a>1.3.0</a>\n<hmtl/>');
const res = await getPkgReleases({
registryUrls: [testRegistryUrl, secondRegistryUrl],
depName: testLookupName,
datasource,
lookupName: testLookupName,
});
expect(res.releases).toHaveLength(5);
expect(res).toMatchSnapshot();
});

it('returns null without registryUrl + warning', async () => {
const res = await getPkgReleases({
datasource,
depName: testLookupName,
lookupName: testLookupName,
});
expect(logger.warn).toHaveBeenCalledTimes(1);
expect(logger.warn).toHaveBeenCalledWith(
{ lookupName: 'project' },
'artifactory datasource requires custom registryUrl. Skipping datasource'
);
expect(res).toBeNull();
});

it('returns null for empty 200 OK', async () => {
httpMock
.scope(testRegistryUrl)
.get(getPath(testLookupName))
.reply(200, '<html>\n<h1>Header wo. nodes</h1>\n<hmtl/>');
expect(
await getPkgReleases({
...testConfig,
datasource,
lookupName: testLookupName,
})
).toBeNull();
});

it('404 returns null', async () => {
httpMock.scope(testRegistryUrl).get(getPath(testLookupName)).reply(404);
expect(
await getPkgReleases({
...testConfig,
datasource,
lookupName: testLookupName,
})
).toBeNull();
expect(logger.warn).toHaveBeenCalledTimes(1);
expect(logger.warn).toHaveBeenCalledWith(
{
lookupName: 'project',
registryUrl: 'https://jfrog.company.com/artifactory',
},
'artifactory: `Not Found` error'
);
});

it('throws for error diff than 404', async () => {
httpMock.scope(testRegistryUrl).get(getPath(testLookupName)).reply(502);
await expect(
getPkgReleases({
...testConfig,
datasource,
lookupName: testLookupName,
})
).rejects.toThrow(EXTERNAL_HOST_ERROR);
});

it('throws no Http error', async () => {
httpMock
.scope(testRegistryUrl)
.get(getPath(testLookupName))
.replyWithError('unknown error');
const res = await getPkgReleases({
...testConfig,
datasource,
lookupName: testLookupName,
});
expect(res).toBeNull();
});
});
});

0 comments on commit 9fea985

Please sign in to comment.