From 2727c6dce4d1d6dcd1e76adadce6755cf86af753 Mon Sep 17 00:00:00 2001 From: Jean-Francois Denise <jdenise@redhat.com> Date: Tue, 21 Nov 2023 17:28:24 +0100 Subject: [PATCH] Add support for WildFly Channel in arquillian plugin --- arquillian-plugin/pom.xml | 8 ++ .../arquillian/ChannelConfiguration.java | 112 ++++++++++++++++++ .../plugin/arquillian/ConfiguredChannels.java | 78 ++++++++++++ .../glow/plugin/arquillian/FeaturePack.java | 4 +- .../glow/plugin/arquillian/ScanMojo.java | 64 +++++++++- .../java/org/wildfly/glow/GlowSession.java | 33 ++++-- .../src/main/java/org/wildfly/glow/Utils.java | 7 +- pom.xml | 16 ++- tests/run-cli-tests.sh | 9 ++ 9 files changed, 312 insertions(+), 19 deletions(-) create mode 100644 arquillian-plugin/src/main/java/org/wildfly/glow/plugin/arquillian/ChannelConfiguration.java create mode 100644 arquillian-plugin/src/main/java/org/wildfly/glow/plugin/arquillian/ConfiguredChannels.java diff --git a/arquillian-plugin/pom.xml b/arquillian-plugin/pom.xml index 90561409..77cb40db 100644 --- a/arquillian-plugin/pom.xml +++ b/arquillian-plugin/pom.xml @@ -61,6 +61,14 @@ <groupId>org.jboss.shrinkwrap</groupId> <artifactId>shrinkwrap-impl-base</artifactId> </dependency> + <dependency> + <groupId>org.wildfly.channel</groupId> + <artifactId>channel-core</artifactId> + </dependency> + <dependency> + <groupId>org.wildfly.channel</groupId> + <artifactId>maven-resolver</artifactId> + </dependency> </dependencies> <build> <plugins> diff --git a/arquillian-plugin/src/main/java/org/wildfly/glow/plugin/arquillian/ChannelConfiguration.java b/arquillian-plugin/src/main/java/org/wildfly/glow/plugin/arquillian/ChannelConfiguration.java new file mode 100644 index 00000000..a60d606c --- /dev/null +++ b/arquillian-plugin/src/main/java/org/wildfly/glow/plugin/arquillian/ChannelConfiguration.java @@ -0,0 +1,112 @@ +/* + * Copyright 2023 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.wildfly.glow.plugin.arquillian; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +import org.apache.maven.plugin.MojoExecutionException; +import org.eclipse.aether.repository.RemoteRepository; +import org.wildfly.channel.Channel; +import org.wildfly.channel.ChannelManifestCoordinate; +import org.wildfly.channel.Repository; + +/** + * A channel configuration. Contains a {@code manifest} composed of a {@code groupId}, an {@code artifactId} + * an optional {@code version} or a {@code url}. + * + * @author jdenise + */ +public class ChannelConfiguration { + private static final Pattern FILE_MATCHER = Pattern.compile("^(file|http|https)://.*"); + + private ChannelManifestCoordinate manifest; + + /** + * @return the manifest + */ + public ChannelManifestCoordinate getManifest() { + return manifest; + } + + public void set(final String channel) { + // Is this a URL? + if (FILE_MATCHER.matcher(channel).matches()) { + try { + this.manifest = new ChannelManifestCoordinate(new URL(channel)); + } catch (MalformedURLException e) { + throw new IllegalArgumentException("Failed to parse URL for " + channel, e); + } + } else { + // Treat as a Maven GAV + final String[] coords = channel.split(":"); + if (coords.length > 2) { + this.manifest = new ChannelManifestCoordinate(coords[0], coords[1], coords[2]); + } else if (coords.length == 2) { + this.manifest = new ChannelManifestCoordinate(coords[0], coords[1]); + } else { + throw new IllegalArgumentException( + "A channel must be a Maven GAV in the format groupId:artifactId:version. The groupId and artifactId are both required."); + } + } + } + + void setManifest(ChannelManifestCoordinate manifest) { + this.manifest = manifest; + } + + private void validate() throws MojoExecutionException { + if (getManifest() == null) { + throw new MojoExecutionException("Invalid Channel. No manifest specified."); + } + ChannelManifestCoordinate coordinates = getManifest(); + if (coordinates.getUrl() == null && coordinates.getGroupId() == null && coordinates.getArtifactId() == null) { + throw new MojoExecutionException( + "Invalid Channel. Manifest must contain a groupId, artifactId and (optional) version or an url."); + } + if (coordinates.getUrl() == null) { + if (coordinates.getGroupId() == null) { + throw new MojoExecutionException("Invalid Channel. Manifest groupId is null."); + } + if (coordinates.getArtifactId() == null) { + throw new MojoExecutionException("Invalid Channel. Manifest artifactId is null."); + } + } else { + if (coordinates.getGroupId() != null) { + throw new MojoExecutionException("Invalid Channel. Manifest groupId is set although an URL is provided."); + } + if (coordinates.getArtifactId() != null) { + throw new MojoExecutionException("Invalid Channel. Manifest artifactId is set although an URL is provided."); + } + if (coordinates.getVersion() != null) { + throw new MojoExecutionException("Invalid Channel. Manifest version is set although an URL is provided."); + } + } + } + + public Channel toChannel(List<RemoteRepository> repositories) throws MojoExecutionException { + validate(); + List<Repository> repos = new ArrayList<>(); + for (RemoteRepository r : repositories) { + repos.add(new Repository(r.getId(), r.getUrl())); + } + return new Channel(null, null, null, repos, getManifest(), null, null); + } +} diff --git a/arquillian-plugin/src/main/java/org/wildfly/glow/plugin/arquillian/ConfiguredChannels.java b/arquillian-plugin/src/main/java/org/wildfly/glow/plugin/arquillian/ConfiguredChannels.java new file mode 100644 index 00000000..96a0d274 --- /dev/null +++ b/arquillian-plugin/src/main/java/org/wildfly/glow/plugin/arquillian/ConfiguredChannels.java @@ -0,0 +1,78 @@ +/* + * Copyright 2022 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.wildfly.glow.plugin.arquillian; + +import static org.wildfly.channel.maven.VersionResolverFactory.DEFAULT_REPOSITORY_MAPPER; + +import java.net.MalformedURLException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.logging.Log; +import org.apache.maven.repository.internal.MavenRepositorySystemUtils; +import org.eclipse.aether.DefaultRepositorySystemSession; +import org.eclipse.aether.RepositorySystem; +import org.eclipse.aether.RepositorySystemSession; +import org.eclipse.aether.repository.RemoteRepository; +import org.wildfly.channel.Channel; +import org.wildfly.channel.ChannelSession; +import org.wildfly.channel.Repository; +import org.wildfly.channel.UnresolvedMavenArtifactException; +import org.wildfly.channel.maven.VersionResolverFactory; + +public class ConfiguredChannels { + + private final ChannelSession channelSession; + public ConfiguredChannels(List<ChannelConfiguration> channels, + RepositorySystem system, + RepositorySystemSession contextSession, + List<RemoteRepository> repositories, Log log, boolean offline) + throws MalformedURLException, UnresolvedMavenArtifactException, MojoExecutionException { + if (channels.isEmpty()) { + throw new MojoExecutionException("No channel specified."); + } + DefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession(); + session.setLocalRepositoryManager(contextSession.getLocalRepositoryManager()); + session.setOffline(offline); + Map<String, RemoteRepository> mapping = new HashMap<>(); + for (RemoteRepository r : repositories) { + mapping.put(r.getId(), r); + } + List<Channel> channelDefinitions = new ArrayList<>(); + for (ChannelConfiguration channelConfiguration : channels) { + channelDefinitions.add(channelConfiguration.toChannel(repositories)); + } + Function<Repository, RemoteRepository> mapper = r -> { + RemoteRepository rep = mapping.get(r.getId()); + if (rep == null) { + rep = DEFAULT_REPOSITORY_MAPPER.apply(r); + } + return rep; + }; + VersionResolverFactory factory = new VersionResolverFactory(system, session, mapper); + channelSession = new ChannelSession(channelDefinitions, factory); + } + + ChannelSession getChannelSession() { + return channelSession; + } + +} diff --git a/arquillian-plugin/src/main/java/org/wildfly/glow/plugin/arquillian/FeaturePack.java b/arquillian-plugin/src/main/java/org/wildfly/glow/plugin/arquillian/FeaturePack.java index ec5b1b40..ac4b14c2 100644 --- a/arquillian-plugin/src/main/java/org/wildfly/glow/plugin/arquillian/FeaturePack.java +++ b/arquillian-plugin/src/main/java/org/wildfly/glow/plugin/arquillian/FeaturePack.java @@ -85,8 +85,10 @@ public String getMavenCoords() { builder.append(":").append(getClassifier() == null ? "" : getClassifier()).append(":") .append(type == null ? "" : type); } + // For WildFly Channel we need the Maven GA to be ended by a ':' if no version. + builder.append(":"); if (getVersion() != null) { - builder.append(":").append(getVersion()); + builder.append(getVersion()); } return builder.toString(); } diff --git a/arquillian-plugin/src/main/java/org/wildfly/glow/plugin/arquillian/ScanMojo.java b/arquillian-plugin/src/main/java/org/wildfly/glow/plugin/arquillian/ScanMojo.java index 00cdd672..d39271b7 100644 --- a/arquillian-plugin/src/main/java/org/wildfly/glow/plugin/arquillian/ScanMojo.java +++ b/arquillian-plugin/src/main/java/org/wildfly/glow/plugin/arquillian/ScanMojo.java @@ -52,6 +52,7 @@ import java.io.File; import java.io.FileWriter; import java.io.IOException; +import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; import java.net.URLClassLoader; @@ -66,6 +67,9 @@ import java.util.Map; import java.util.Set; import org.apache.maven.plugin.logging.Log; +import org.jboss.galleon.universe.maven.repo.MavenRepoManager; +import org.wildfly.channel.UnresolvedMavenArtifactException; +import org.wildfly.channel.VersionResult; import org.wildfly.glow.error.IdentifiedError; import static org.wildfly.glow.plugin.arquillian.GlowArquillianDeploymentExporter.TEST_CLASSPATH; import static org.wildfly.glow.plugin.arquillian.GlowArquillianDeploymentExporter.TEST_PATHS; @@ -203,6 +207,43 @@ public void trace(Object s) { @Parameter(property = "org.wildfly.glow.verbose") private boolean verbose = false; + /** + * A list of channels used for resolving artifacts while provisioning. + * <p> + * Defining a channel: + * + * <pre> + * <channels> + * <channel> + * <manifest> + * <groupId>org.wildfly.channels</groupId> + * <artifactId>wildfly-30.0</artifactId> + * </manifest> + * </channel> + * <channel> + * <manifest> + * <url>https://example.example.org/channel/30</url> + * </manifest> + * </channel> + * </channels> + * </pre> + * </p> + * <p> + * The {@code wildfly.channels} property can be used pass a comma delimited string for the channels. The channel + * can be a URL or a Maven GAV. If a Maven GAV is used, the groupId and artifactId are required. + * <br> + * Examples: + * + * <pre> + * -Dorg.wildfly.glow.channels="https://channels.example.org/30" + * -Dorg.wildfly.glow.channels="https://channels.example.org/30,org.example.channel:updates-30" + * -Dorg.wildfly.glow.channels="https://channels.example.org/30,org.example.channel:updates-30:1.0.2" + * </pre> + * </p> + */ + @Parameter(alias = "channels", property = "org.wildfly.glow.channels") + List<ChannelConfiguration> channels; + @Override public void execute() throws MojoExecutionException, MojoFailureException { // Make sure that the 'hidden' properties used by the Arguments class come from the Maven configuration @@ -232,6 +273,27 @@ public void execute() throws MojoExecutionException, MojoFailureException { for (String s : project.getTestClasspathElements()) { paths.add(new File(s).getAbsolutePath()); } + MavenRepoManager artifactResolver = new MavenArtifactRepositoryManager(repoSystem, repoSession, repositories); + if (channels != null && !channels.isEmpty()) { + getLog().debug("WildFly channel enabled, feature-pack versions are retrieved from channels (if stream known)."); + try { + ConfiguredChannels cr = new ConfiguredChannels(channels, + repoSystem, repoSession, repositories, + getLog(), true); + for (FeaturePack fp : featurePacks) { + try { + VersionResult res = cr.getChannelSession().findLatestMavenArtifactVersion(fp.getGroupId(), fp.getArtifactId(), + fp.getExtension(), fp.getClassifier(), null); + getLog().debug(fp.getGroupId() +":"+fp.getArtifactId() + ", Channel resolved version " + res.getVersion()); + fp.setVersion(res.getVersion()); + } catch (Exception ex) { + getLog().debug("Got exception trying to resolve " + fp.getGroupId() +":"+fp.getArtifactId(), ex); + } + } + } catch (MalformedURLException | UnresolvedMavenArtifactException ex) { + throw new MojoExecutionException(ex.getLocalizedMessage(), ex); + } + } Arguments arguments = Arguments.scanBuilder(). setExecutionProfiles(profiles). setBinaries(retrieveDeployments(paths, classesRootFolder, outputFolder)). @@ -242,8 +304,6 @@ public void execute() throws MojoExecutionException, MojoFailureException { setJndiLayers(layersForJndi). setVerbose(verbose || getLog().isDebugEnabled()). setOutput(OutputFormat.PROVISIONING_XML).build(); - MavenArtifactRepositoryManager artifactResolver - = new MavenArtifactRepositoryManager(repoSystem, repoSession, repositories); ScanResults results = GlowSession.scan(artifactResolver, arguments, writer); if (expectedDiscovery != null) { diff --git a/core/src/main/java/org/wildfly/glow/GlowSession.java b/core/src/main/java/org/wildfly/glow/GlowSession.java index d54495b7..8f8c7249 100644 --- a/core/src/main/java/org/wildfly/glow/GlowSession.java +++ b/core/src/main/java/org/wildfly/glow/GlowSession.java @@ -57,6 +57,7 @@ import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; +import org.jboss.galleon.universe.FeaturePackLocation.ProducerSpec; import static org.wildfly.glow.OutputFormat.BOOTABLE_JAR; import static org.wildfly.glow.OutputFormat.DOCKER_IMAGE; @@ -809,16 +810,34 @@ private static ProvisioningConfig buildProvisioningConfig(ProvisioningConfig inp Map<FPID, FeaturePackConfig> map = new HashMap<>(); Map<FPID, FPID> universeToGav = new HashMap<>(); for (FeaturePackConfig cfg : input.getFeaturePackDeps()) { - FeaturePackLocation.FPID fpid = Utils.toMavenCoordinates(cfg.getLocation().getFPID(), universeResolver); - map.put(fpid, cfg); - universeToGav.put(cfg.getLocation().getFPID(), fpid); + FeaturePackLocation.FPID loc = null; + for (FeaturePackLocation.FPID f : fpDependencies.keySet()) { + if (cfg.getLocation().getProducer().equals(f.getProducer())) { + loc = f; + break; + } + } + if(loc == null) { + throw new ProvisioningException("Input fp "+ cfg.getLocation() + " not found in resolved feature-packs " + fpDependencies.keySet()); + } + map.put(loc, cfg); + universeToGav.put(cfg.getLocation().getFPID(), loc); } - Set<FeaturePackLocation.FPID> activeFeaturePacks = new LinkedHashSet<>(); - // Add WildFly first. + Map<ProducerSpec, FeaturePackLocation.FPID> tmpFps = new HashMap<>(); FeaturePackLocation.FPID baseFPID = universeToGav.get(input.getFeaturePackDeps().iterator().next().getLocation().getFPID()); - activeFeaturePacks.add(baseFPID); + tmpFps.put(baseFPID.getProducer(), baseFPID); for (Layer l : allBaseLayers) { - activeFeaturePacks.addAll(l.getFeaturePacks()); + for(FPID fpid : l.getFeaturePacks()) { + tmpFps.put(fpid.getProducer(), fpid); + } + } + Set<FeaturePackLocation.FPID> activeFeaturePacks = new LinkedHashSet<>(); + // Order follow the one from the input + for(FeaturePackConfig cfg : input.getFeaturePackDeps()) { + FeaturePackLocation.FPID fpid = tmpFps.get(cfg.getLocation().getProducer()); + if (fpid != null) { + activeFeaturePacks.add(fpid); + } } // Remove dependencies that are not Main FP... //System.out.println("Active FP " + activeFeaturePacks); diff --git a/core/src/main/java/org/wildfly/glow/Utils.java b/core/src/main/java/org/wildfly/glow/Utils.java index e418e63f..4b7eb9a0 100644 --- a/core/src/main/java/org/wildfly/glow/Utils.java +++ b/core/src/main/java/org/wildfly/glow/Utils.java @@ -222,13 +222,8 @@ public static Map<String, Layer> getAllLayers(UniverseResolver universeResolver, } l.getFeaturePacks().add(fpid); } - + Set<ProducerSpec> producers = fpDependencies.computeIfAbsent(fpid, (value) -> new HashSet<>()); for (FeaturePackConfig cfg : fp.getSpec().getFeaturePackDeps()) { - Set<ProducerSpec> producers = fpDependencies.get(fpid); - if (producers == null) { - producers = new HashSet<>(); - fpDependencies.put(fpid, producers); - } FPID fpidDep = toMavenCoordinates(cfg.getLocation().getFPID(), universeResolver); producers.add(fpidDep.getProducer()); } diff --git a/pom.xml b/pom.xml index d5c3471d..b0ca9cf7 100644 --- a/pom.xml +++ b/pom.xml @@ -28,9 +28,10 @@ <version.org.apache.maven>3.8.6</version.org.apache.maven> <version.org.apache.maven.checkstyle>3.0.0</version.org.apache.maven.checkstyle> <version.org.apache.maven.resolver>1.6.3</version.org.apache.maven.resolver> - <version.org.jboss.galleon>5.2.0.Final</version.org.jboss.galleon> + <version.org.jboss.galleon>5.2.2.Final</version.org.jboss.galleon> + <version.org.wildfly.channel>1.0.5.Final</version.org.wildfly.channel> <version.org.jboss.logging.slf4j-jboss-logging>1.2.1.Final</version.org.jboss.logging.slf4j-jboss-logging> - <version.org.wildfly.galleon-plugins>6.4.2.Final</version.org.wildfly.galleon-plugins> + <version.org.wildfly.galleon-plugins>6.5.3.Final</version.org.wildfly.galleon-plugins> <version.org.yaml.snakeyaml>2.0</version.org.yaml.snakeyaml> <version.org.apache.commons>3.12.0</version.org.apache.commons> <version.org.apache.maven.maven-core>3.6.2</version.org.apache.maven.maven-core> @@ -389,7 +390,16 @@ <artifactId>slf4j-jboss-logging</artifactId> <version>${version.org.jboss.logging.slf4j-jboss-logging}</version> </dependency> - + <dependency> + <groupId>org.wildfly.channel</groupId> + <artifactId>channel-core</artifactId> + <version>${version.org.wildfly.channel}</version> + </dependency> + <dependency> + <groupId>org.wildfly.channel</groupId> + <artifactId>maven-resolver</artifactId> + <version>${version.org.wildfly.channel}</version> + </dependency> <!-- Test dependencies --> <dependency> <groupId>jakarta.annotation</groupId> diff --git a/tests/run-cli-tests.sh b/tests/run-cli-tests.sh index 13e72164..2674c2fe 100644 --- a/tests/run-cli-tests.sh +++ b/tests/run-cli-tests.sh @@ -80,6 +80,15 @@ if [ $? -ne 0 ]; then exit 1 fi +echo "* Show configuration cloud" + +java -jar $jar show-configuration --cloud + +if [ $? -ne 0 ]; then + echo "Error, check log" + exit 1 +fi + echo kitchensink test \ "[bean-validation, cdi, ee-integration, ejb-lite, h2-driver, jaxrs, jpa, jsf]==>ee-core-profile-server,ejb-lite,h2-driver,jaxrs,jpa,jsf" \