Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(datasource/maven): look for maven snapshot pom #11327

Merged
merged 16 commits into from Oct 10, 2021
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?><metadata>
<groupId>org.example</groupId>
<artifactId>package</artifactId>
<version>1.0.1-SNAPSHOT</version>
<versioning>
<snapshot>
<buildNumber>5</buildNumber>
</snapshot>
<snapshotVersions>
<snapshotVersion>
<extension>jar</extension>
<value>1.0.1-20200101.123456-5</value>
<updated>20200101123456</updated>
</snapshotVersion>
<snapshotVersion>
<extension>pom</extension>
<value>1.0.1-20200101.123456-5</value>
<updated>20200101123456</updated>
</snapshotVersion>
<snapshotVersion>
<classifier>javadoc</classifier>
<extension>jar</extension>
<value>1.0.1-20200101.123456-5</value>
<updated>20200101123456</updated>
</snapshotVersion>
</snapshotVersions>
<lastUpdated>20130301200000</lastUpdated>
</versioning>
</metadata>
30 changes: 30 additions & 0 deletions lib/datasource/maven/__fixtures__/metadata-snapshot-version.xml
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?><metadata>
<groupId>org.example</groupId>
<artifactId>package</artifactId>
<version>1.0.1-SNAPSHOT</version>
<versioning>
<snapshot>
<timestamp>20200101.123456</timestamp>
<buildNumber>5</buildNumber>
</snapshot>
<snapshotVersions>
<snapshotVersion>
<extension>jar</extension>
<value>1.0.1-20200101.123456-5</value>
<updated>20200101123456</updated>
</snapshotVersion>
<snapshotVersion>
<extension>pom</extension>
<value>1.0.1-20200101.123456-5</value>
<updated>20200101123456</updated>
</snapshotVersion>
<snapshotVersion>
<classifier>javadoc</classifier>
<extension>jar</extension>
<value>1.0.1-20200101.123456-5</value>
<updated>20200101123456</updated>
</snapshotVersion>
</snapshotVersions>
<lastUpdated>20130301200000</lastUpdated>
</versioning>
</metadata>
14 changes: 14 additions & 0 deletions lib/datasource/maven/__fixtures__/metadata-snapshot.xml
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<groupId>org.example</groupId>
<artifactId>package</artifactId>
<versioning>
<latest>1.0.1-SNAPSHOT</latest>
<release>1.0.0</release>
<versions>
<version>1.0.0</version>
<version>1.0.1-SNAPSHOT</version>
</versions>
<lastUpdated>20210101000000</lastUpdated>
</versioning>
</metadata>
98 changes: 98 additions & 0 deletions lib/datasource/maven/index.spec.ts
Expand Up @@ -9,13 +9,20 @@ import { id as datasource } from '.';
const baseUrl = 'https://repo.maven.apache.org/maven2';
const baseUrlCustom = 'https://custom.registry.renovatebot.com';

interface SnapshotOpts {
version: string;
concreteVersions?: string[];
meta?: string;
}

interface MockOpts {
dep?: string;
base?: string;
meta?: string | null;
pom?: string | null;
latest?: string;
jars?: Record<string, number> | null;
snapshots?: SnapshotOpts[] | null;
}

function mockGenericPackage(opts: MockOpts = {}) {
Expand Down Expand Up @@ -64,6 +71,31 @@ function mockGenericPackage(opts: MockOpts = {}) {
.reply(status, '', { 'Last-Modified': timestamp });
});
}

if (opts.snapshots !== undefined) {
opts.snapshots.forEach((snapshot) => {
if (snapshot.meta) {
scope
.get(`/${packagePath}/${snapshot.version}/maven-metadata.xml`)
.reply(200, snapshot.meta);
}

if (snapshot.concreteVersions) {
snapshot.concreteVersions.forEach((concreteVersion) => {
const [major, minor, patch] = snapshot.version
.split('.')
.map((x) => parseInt(x, 10))
.map((x) => (x < 10 ? `0${x}` : `${x}`));
const timestamp = `2020-01-01T${major}:${minor}:${patch}.000Z`;
scope
.head(
`/${packagePath}/${snapshot.version}/${artifact}-${concreteVersion}.pom`
)
.reply(200, '', { 'Last-Modified': timestamp });
});
}
});
}
}

function get(
Expand Down Expand Up @@ -306,6 +338,72 @@ describe('datasource/maven/index', () => {
expect(httpMock.getTrace()).toMatchSnapshot();
});

it('includes snapshot releases', async () => {
mockGenericPackage({
meta: loadFixture('metadata-snapshot.xml'),
latest: '1.0.0',
jars: { '1.0.0': 200 },
snapshots: [
{
version: '1.0.1-SNAPSHOT',
concreteVersions: ['1.0.1-20200101.123456-5'],
meta: loadFixture('metadata-snapshot-version.xml'),
},
],
});

const { releases } = await get('org.example:package', baseUrl);

expect(releases).toMatchObject([
{ version: '1.0.0' },
{ version: '1.0.1-SNAPSHOT' },
]);
});

it('skips missing snapshot releases', async () => {
mockGenericPackage({
meta: loadFixture('metadata-snapshot.xml'),
latest: '1.0.0',
jars: { '1.0.0': 200 },
});

httpMock
.scope(baseUrl)
.get('/org/example/package/1.0.1-SNAPSHOT/maven-metadata.xml')
.reply(404, '');
httpMock
.scope(baseUrl)
.head('/org/example/package/1.0.1-SNAPSHOT/package-1.0.1-SNAPSHOT.pom')
.reply(404, '');
bpfoster marked this conversation as resolved.
Show resolved Hide resolved

const { releases } = await get('org.example:package', baseUrl);

expect(releases).toMatchObject([{ version: '1.0.0' }]);
});

it('skips invalid snapshot releases', async () => {
mockGenericPackage({
meta: loadFixture('metadata-snapshot.xml'),
latest: '1.0.0',
jars: { '1.0.0': 200 },
snapshots: [
{
version: '1.0.1-SNAPSHOT',
meta: loadFixture('metadata-snapshot-version-invalid.xml'),
},
],
});

httpMock
.scope(baseUrl)
.head('/org/example/package/1.0.1-SNAPSHOT/package-1.0.1-SNAPSHOT.pom')
.reply(404, '');
bpfoster marked this conversation as resolved.
Show resolved Hide resolved

const { releases } = await get('org.example:package', baseUrl);

expect(releases).toMatchObject([{ version: '1.0.0' }]);
});

describe('fetching parent info', () => {
const parentPackage = {
dep: 'org.example:parent',
Expand Down
70 changes: 66 additions & 4 deletions lib/datasource/maven/index.ts
Expand Up @@ -95,6 +95,66 @@ function isValidArtifactsInfo(
return versions.every((v) => info[v] !== undefined);
}

function isSnapshotVersion(version: string): boolean {
if (version.endsWith('-SNAPSHOT')) {
return true;
}
return false;
}

function extractSnapshotVersion(metadata: XmlDocument): string {
const version = metadata
.descendantWithPath('version')
?.val?.replace('-SNAPSHOT', '');

const snapshot = metadata.descendantWithPath('versioning.snapshot');
const timestamp = snapshot?.childNamed('timestamp')?.val;
const build = snapshot?.childNamed('buildNumber')?.val;

if (!version || !timestamp || !build) {
return null;
}
return `${version}-${timestamp}-${build}`;
}

async function getSnapshotFullVersion(
version: string,
dependency: MavenDependency,
repoUrl: string
): Promise<string | null> {
const metadataUrl = getMavenUrl(
dependency,
repoUrl,
`${version}/maven-metadata.xml`
);

const { xml: mavenMetadata } = await downloadMavenXml(metadataUrl);
if (!mavenMetadata) {
return null;
}

return extractSnapshotVersion(mavenMetadata);
}

async function createUrlForDependencyPom(
version: string,
dependency: MavenDependency,
repoUrl: string
): Promise<string> {
if (isSnapshotVersion(version)) {
const fullVersion = await getSnapshotFullVersion(
version,
dependency,
repoUrl
);
if (fullVersion !== null) {
return `${version}/${dependency.name}-${fullVersion}.pom`;
}
}

return `${version}/${dependency.name}-${version}.pom`;
}

async function filterMissingArtifacts(
dependency: MavenDependency,
repoUrl: string,
Expand All @@ -106,15 +166,17 @@ async function filterMissingArtifacts(
await packageCache.get<ArtifactsInfo>(cacheNamespace, cacheKey);

if (!isValidArtifactsInfo(artifactsInfo, versions)) {
const queue = versions
.map((version): [string, url.URL | null] => {
const versionUrls = versions.map(
async (version): Promise<[string, url.URL | null]> => {
const artifactUrl = getMavenUrl(
dependency,
repoUrl,
`${version}/${dependency.name}-${version}.pom`
await createUrlForDependencyPom(version, dependency, repoUrl)
);
return [version, artifactUrl];
})
}
);
const queue = (await Promise.all(versionUrls))
bpfoster marked this conversation as resolved.
Show resolved Hide resolved
.filter(([_, artifactUrl]) => Boolean(artifactUrl))
.map(
([version, artifactUrl]) =>
Expand Down