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

feat(maven): settings.xml registry extraction #13592

Merged
merged 16 commits into from
Jan 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/usage/java.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,14 @@ Renovate can update dependency versions found in Maven `pom.xml` files.

Renovate will search repositories for all `pom.xml` files and processes them independently.

Renovate will also parse `settings.xml` files in the following locations:

- `.mvn/settings.xml`
- `.m2/settings.xml`
- `settings.xml`

Any repository URLs found within will be added as `registryUrls` to extracted dependencies.

## Custom registry support, and authentication

Unless using `deepExtract`, Renovate does not make use of authentication credentials available to Gradle.
Expand Down
74 changes: 74 additions & 0 deletions lib/manager/maven/__fixtures__/complex.settings.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 https://maven.apache.org/xsd/settings-1.0.0.xsd">
<mirrors>
<mirror>
<id>my-maven-repo</id>
<url>https://artifactory.company.com/artifactory/my-maven-repo</url>
<mirrorOf>*</mirrorOf>
</mirror>
<mirror>
<id>my-maven-repo-v2</id>
<url>https://repo.adobe.com/nexus/content/groups/public</url>
<mirrorOf>custom-repo</mirrorOf>
</mirror>
</mirrors>
<profiles>
<profile>
<id>adobe-public</id>
<repositories>
<repository>
<id>adobe-public-releases</id>
<name>Adobe Public Repository</name>
<url>https://repo.adobe.com/nexus/content/groups/public</url>
<releases>
<enabled>true</enabled>
<updatePolicy>never</updatePolicy>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>adobe-public-releases-v2</id>
<name>Adobe Public Repository v2</name>
<url>https://repo.adobe.com/v2/nexus/content/groups/public</url>
<releases>
<enabled>true</enabled>
<updatePolicy>never</updatePolicy>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</profile>
<profile>
<id>adobe-public-v2</id>
<repositories>
<repository>
<id>adobe-public-releases-v3</id>
<name>Adobe Public Repository</name>
<url>https://repo.adobe.com/v3/nexus/content/groups/public</url>
<releases>
<enabled>true</enabled>
<updatePolicy>never</updatePolicy>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>adobe-public-releases-v4</id>
<name>Adobe Public Repository v2</name>
<url>https://repo.adobe.com/v4/nexus/content/groups/public</url>
<releases>
<enabled>true</enabled>
<updatePolicy>never</updatePolicy>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</profile>
</profiles>
</settings>
9 changes: 9 additions & 0 deletions lib/manager/maven/__fixtures__/mirror.settings.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 https://maven.apache.org/xsd/settings-1.0.0.xsd">
<mirrors>
<mirror>
<id>my-maven-repo</id>
<url>https://artifactory.company.com/artifactory/my-maven-repo</url>
<mirrorOf>*</mirrorOf>
</mirror>
</mirrors>
</settings>
21 changes: 21 additions & 0 deletions lib/manager/maven/__fixtures__/profile.settings.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 https://maven.apache.org/xsd/settings-1.0.0.xsd">
<profiles>
<profile>
<id>adobe-public</id>
<repositories>
<repository>
<id>adobe-public-releases</id>
<name>Adobe Public Repository</name>
<url>https://repo.adobe.com/nexus/content/groups/public</url>
<releases>
<enabled>true</enabled>
<updatePolicy>never</updatePolicy>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</profile>
</profiles>
</settings>
39 changes: 38 additions & 1 deletion lib/manager/maven/extract.spec.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { loadFixture } from '../../../test/util';
import { extractPackage } from './extract';
import { extractPackage, extractRegistries } from './extract';

const minimumContent = loadFixture(`minimum.pom.xml`);
const simpleContent = loadFixture(`simple.pom.xml`);

const mirrorSettingsContent = loadFixture(`mirror.settings.xml`);
const profileSettingsContent = loadFixture(`profile.settings.xml`);
const complexSettingsContent = loadFixture(`complex.settings.xml`);

describe('manager/maven/extract', () => {
describe('extractDependencies', () => {
it('returns null for invalid XML', () => {
Expand Down Expand Up @@ -81,4 +85,37 @@ describe('manager/maven/extract', () => {
});
});
});
describe('extractRegistries', () => {
it('returns null for invalid XML', () => {
expect(extractRegistries(undefined)).toBeEmptyArray();
expect(extractRegistries('invalid xml content')).toBeEmptyArray();
expect(extractRegistries('<foobar></foobar>')).toBeEmptyArray();
expect(extractRegistries('<settings></settings>')).toBeEmptyArray();
});

it('extract registries from a simple mirror settings file', () => {
const res = extractRegistries(mirrorSettingsContent);
expect(res).toStrictEqual([
'https://artifactory.company.com/artifactory/my-maven-repo',
]);
});

it('extract registries from a simple profile settings file', () => {
const res = extractRegistries(profileSettingsContent);
expect(res).toStrictEqual([
'https://repo.adobe.com/nexus/content/groups/public',
]);
});

it('extract registries from a complex profile settings file', () => {
const res = extractRegistries(complexSettingsContent);
expect(res).toStrictEqual([
'https://artifactory.company.com/artifactory/my-maven-repo',
'https://repo.adobe.com/nexus/content/groups/public',
'https://repo.adobe.com/v2/nexus/content/groups/public',
'https://repo.adobe.com/v3/nexus/content/groups/public',
'https://repo.adobe.com/v4/nexus/content/groups/public',
]);
});
});
});
88 changes: 84 additions & 4 deletions lib/manager/maven/extract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,61 @@ export function extractPackage(
return result;
}

export function extractRegistries(rawContent: string): string[] {
if (!rawContent) {
return [];
}

const settings = parseSettings(rawContent);
if (!settings) {
return [];
}

const urls = [];

const mirrorUrls = parseUrls(settings, 'mirrors');
urls.push(...mirrorUrls);

settings.childNamed('profiles')?.eachChild((profile) => {
const repositoryUrls = parseUrls(profile, 'repositories');
urls.push(...repositoryUrls);
});

// filter out duplicates
return [...new Set(urls)];
}

function parseUrls(xmlNode: XmlElement, path: string): string[] {
const children = xmlNode.descendantWithPath(path);
const urls = [];
if (children?.children) {
children.eachChild((child) => {
rarkins marked this conversation as resolved.
Show resolved Hide resolved
const url = child.valueWithPath('url');
if (url) {
urls.push(url);
}
});
}
return urls;
}

export function parseSettings(raw: string): XmlDocument | null {
let settings: XmlDocument;
try {
settings = new XmlDocument(raw);
} catch (e) {
return null;
}
const { name, attr } = settings;
if (name !== 'settings') {
return null;
}
if (attr.xmlns === 'http://maven.apache.org/SETTINGS/1.0.0') {
return settings;
}
return null;
}

export function resolveParents(packages: PackageFile[]): PackageFile[] {
const packageFileNames: string[] = [];
const extractedPackages: Record<string, PackageFile> = {};
Expand Down Expand Up @@ -310,17 +365,42 @@ export async function extractAllPackageFiles(
packageFiles: string[]
): Promise<PackageFile[]> {
const packages: PackageFile[] = [];
const additionalRegistryUrls = [];

for (const packageFile of packageFiles) {
const content = await readLocalFile(packageFile, 'utf8');
if (content) {
if (!content) {
logger.trace({ packageFile }, 'packageFile has no content');
continue;
}
if (packageFile.endsWith('settings.xml')) {
const registries = extractRegistries(content);
if (registries) {
logger.debug(
{ registries, packageFile },
'Found registryUrls in settings.xml'
);
additionalRegistryUrls.push(...registries);
}
} else {
const pkg = extractPackage(content, packageFile);
if (pkg) {
packages.push(pkg);
} else {
logger.debug({ packageFile }, 'can not read dependencies');
logger.trace({ packageFile }, 'can not read dependencies');
}
}
}
if (additionalRegistryUrls) {
for (const pkgFile of packages) {
for (const dep of pkgFile.deps) {
/* istanbul ignore else */
viceice marked this conversation as resolved.
Show resolved Hide resolved
if (dep.registryUrls) {
dep.registryUrls.push(...additionalRegistryUrls);
} else {
dep.registryUrls = [...additionalRegistryUrls];
}
}
} else {
logger.debug({ packageFile }, 'packageFile has no content');
}
}
return cleanResult(resolveParents(packages));
Expand Down
22 changes: 22 additions & 0 deletions lib/manager/maven/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const pomContent = loadFixture('simple.pom.xml');
const pomParent = loadFixture('parent.pom.xml');
const pomChild = loadFixture('child.pom.xml');
const origContent = loadFixture('grouping.pom.xml');
const settingsContent = loadFixture('mirror.settings.xml');

function selectDep(deps: PackageDependency[], name = 'org.example:quuz') {
return deps.find((dep) => dep.depName === name);
Expand All @@ -28,6 +29,27 @@ describe('manager/maven/index', () => {
expect(res).toBeEmptyArray();
});

it('should return packages with urls from a settings file', async () => {
fs.readLocalFile
.mockResolvedValueOnce(settingsContent)
.mockResolvedValueOnce(pomContent);
const packages = await extractAllPackageFiles({}, [
'settings.xml',
'simple.pom.xml',
]);
const urls = [
'https://repo.maven.apache.org/maven2',
'https://maven.atlassian.com/content/repositories/atlassian-public/',
'https://artifactory.company.com/artifactory/my-maven-repo',
];
for (const pkg of packages) {
for (const dep of pkg.deps) {
const depUrls = [...dep.registryUrls];
expect(depUrls).toEqual(urls);
}
}
});

it('should return package files info', async () => {
fs.readLocalFile.mockResolvedValueOnce(pomContent);
const packages = await extractAllPackageFiles({}, ['random.pom.xml']);
Expand Down
2 changes: 1 addition & 1 deletion lib/manager/maven/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export { updateDependency } from './update';
export const language = ProgrammingLanguage.Java;

export const defaultConfig = {
fileMatch: ['\\.pom\\.xml$', '(^|/)pom\\.xml$'],
fileMatch: ['(^|/|\\.)pom\\.xml$', '^(((\\.mvn)|(\\.m2))/)?settings\\.xml$'],
versioning: mavenVersioning.id,
};

Expand Down