diff --git a/README.md b/README.md index ea6e91e..42ee61f 100644 --- a/README.md +++ b/README.md @@ -21,9 +21,10 @@ Then add the extension to the playbook: antora: extensions: - require: "@kiwigrid/antora-maven-content" + mavenSettings: true # defaults to false, true resolves to '$HOME/.m2/settings.xml' a string is taken as is repositories: - - baseUrl: maven-central # required - fetchOptions: # optional + - baseUrl: https://www.example.com # required + fetchOptions: # optional headers: "Authorization": "Basic " sources: @@ -65,6 +66,12 @@ For each picked version a corresponding playbook content source entry is created * points to a local transient cached on-demand git repository the artifact has been extracted to * is configured with the same [start path(s)](https://docs.antora.org/antora/3.0/playbook/content-source-start-paths/) +### Maven `settings.xml` + +If `mavenSettings` is given a maven settings.xml is parsed for repositories and authentication data. +The value of the option can be `true` to use `$HOME/.m2/settings.xml` or a string pointing to a settings file. +Only active profiles are extracted. + ## Contributions [Are welcome!](CONTRIBUTING.md) diff --git a/lib/extension.js b/lib/extension.js index c31ff90..6da6023 100644 --- a/lib/extension.js +++ b/lib/extension.js @@ -7,6 +7,7 @@ const { const MavenClient = require('./maven-client') const MavenContentSource = require('./maven-content-source') const ContentSourceFactory = require('./content-source-factory') +const Process = require("process"); /** * This antora extension allows to add content from maven coordinates. @@ -44,10 +45,14 @@ class MavenContentSourceExtension { this.contentSourceFactory = new ContentSourceFactory(this.mavenClient, this.git, this.logger); } - async onPlaybookBuilt({ playbook }) { + async onPlaybookBuilt({playbook}) { this.logger.info("Add Maven Content Sources to playbook..."); - const repositories = this.config.repositories?.map(entry => new MavenRepository(entry)); + const repositories = this.config.repositories?.map(entry => new MavenRepository(entry)) || []; const coordinates = this.config.sources?.map(entry => new MavenContentCoordinate(entry)) || []; + if (this.config.mavenSettings) { + const settingsFile = this.config.mavenSettings === true ? Process.env.HOME + '/.m2/settings.xml' : this.config.mavenSettings; + repositories.push(...(await this.mavenClient.extractRepositoriesFromSettingsFile(settingsFile))); + } // copy playbook as it is frozen deeply... const mutablePlaybook = MavenContentSourceExtension.#unfreezePlaybookSources(playbook); await this.contentSourceFactory.produceContentSourcesIntoPlaybook(repositories, coordinates, mutablePlaybook); diff --git a/lib/maven-client.js b/lib/maven-client.js index 2dc740e..5d8c6d1 100644 --- a/lib/maven-client.js +++ b/lib/maven-client.js @@ -1,4 +1,5 @@ const fetch = require("node-fetch"); +const fs = require("fs") const libxml = require("libxmljs2"); const unzip = require("unzip-stream"); const tar = require("tar"); @@ -12,6 +13,8 @@ const semver = require('semver') const META_DATA_FILE_NAME = 'maven-metadata.xml'; +const SETTINGS_XML_NS = 'http://maven.apache.org/SETTINGS/1.0.0' + class MavenClient { logger; @@ -140,11 +143,11 @@ class MavenClient { .join('/'); const snapshotMetaData = await MavenClient.#downloadXml(snaphotMetadataUrl, repository.fetchOptions); const latestVersion = snapshotMetaData.get( - '//snapshotVersion[extension=' + '//snapshotVersion[extension="' + mavenArtifact.extension - + '][classifier=' + + '"][classifier="' + mavenArtifact.classifier - + ']/value/text()')?.text() + + '"]/value/text()')?.text() if (!latestVersion) { throw new MavenClientError('Cannot find latest snapshot version info for ' + mavenArtifact.extension + ' in maven metadata: ' + snapshotMetaData) } @@ -213,6 +216,41 @@ class MavenClient { return groupId.split('\.') } + async extractRepositoriesFromSettingsFile(settingsFilePath) { + this.logger.debug('Loading repositories from ' + settingsFilePath + '...'); + if (!fs.existsSync(settingsFilePath)) { + this.logger.warn('Skip loading repos from settings, ' + settingsFilePath + ' does not exist.'); + return []; + } + const mavenSettingsXml = await fs.promises.readFile(settingsFilePath, "utf-8") + const mavenSettingsDocument = libxml.parseXml(mavenSettingsXml); + this.logger.debug('Settings file parsed ...'); + const activeProfileNamePredicate = mavenSettingsDocument + .find('/xmlns:settings/xmlns:activeProfiles/xmlns:activeProfile/text()', SETTINGS_XML_NS) + .map(node => 'xmlns:id="' + node.text() + '"') + .join(","); + this.logger.debug('Found active profiles: ' + activeProfileNamePredicate); + const activeProfileRepoNodes = mavenSettingsDocument.find('/xmlns:settings/xmlns:profiles/xmlns:profile[' + activeProfileNamePredicate + ']/xmlns:repositories/xmlns:repository', SETTINGS_XML_NS) + return activeProfileRepoNodes.map(node => { + this.logger.debug('Found active profile repo: ' + node); + const repoId = node.get('xmlns:id/text()', SETTINGS_XML_NS)?.text() + const baseUrl = node.get('xmlns:url/text()', SETTINGS_XML_NS)?.text() + const serverNode = mavenSettingsDocument.get('/xmlns:settings/xmlns:servers/xmlns:server[xmlns:id="'+ repoId +'"]', SETTINGS_XML_NS); + const fetchOptions = {}; + if (serverNode) { + this.logger.debug('Found active profile repo server: ' + serverNode); + const userName = serverNode.get('xmlns:username/text()', SETTINGS_XML_NS)?.text() + const password = serverNode.get('xmlns:password/text()', SETTINGS_XML_NS)?.text() + const buff = Buffer.from(userName + ':' + password, 'utf-8'); + fetchOptions.headers = { + 'Authorization': 'Basic ' + buff.toString('base64') + } + } + const mavenRepository = new MavenRepository({baseUrl, fetchOptions}); + this.logger.info('Found repo in maven settings: ' + mavenRepository); + return mavenRepository; + }) + } } class MavenClientError extends Error {