diff --git a/integration-tests/src/test/java/org/wildfly/prospero/it/commonapi/UpdateTest.java b/integration-tests/src/test/java/org/wildfly/prospero/it/commonapi/UpdateTest.java index 1bdcc6dc5..ef524051c 100644 --- a/integration-tests/src/test/java/org/wildfly/prospero/it/commonapi/UpdateTest.java +++ b/integration-tests/src/test/java/org/wildfly/prospero/it/commonapi/UpdateTest.java @@ -34,6 +34,7 @@ import org.wildfly.channel.ChannelMapper; import org.wildfly.channel.Repository; import org.wildfly.channel.Stream; +import org.wildfly.prospero.actions.ApplyCandidateAction; import org.wildfly.prospero.api.exceptions.MetadataException; import org.wildfly.prospero.metadata.ManifestVersionRecord; import org.wildfly.prospero.actions.UpdateAction; @@ -44,6 +45,8 @@ import org.wildfly.prospero.metadata.ProsperoMetadataUtils; import org.wildfly.prospero.model.ManifestYamlSupport; import org.wildfly.prospero.test.MetadataTestUtils; +import org.wildfly.prospero.updates.CandidateProperties; +import org.wildfly.prospero.updates.CandidatePropertiesParser; import org.wildfly.prospero.wfchannel.MavenSessionManager; import java.io.File; @@ -176,6 +179,12 @@ public void prepareUpdateCreatesMarkerFile() throws Exception { assertThat(record.get().getMavenManifests()) .map(ManifestVersionRecord.MavenManifest::getVersion) .containsExactly("1.0.1"); + + final Path channelNamesFile = preparedUpdatePath.resolve(ProsperoMetadataUtils.METADATA_DIR).resolve(ApplyCandidateAction.CANDIDATE_CHANNEL_NAME_LIST); + assertTrue(Files.exists(channelNamesFile)); + + final CandidateProperties candidateProperties = CandidatePropertiesParser.read(channelNamesFile); + assertEquals("test", candidateProperties.getUpdateChannel("org.wildfly.core:wildfly-cli")); } @Test diff --git a/prospero-cli/src/main/java/org/wildfly/prospero/cli/CliConsole.java b/prospero-cli/src/main/java/org/wildfly/prospero/cli/CliConsole.java index 202457b19..b82659fe6 100644 --- a/prospero-cli/src/main/java/org/wildfly/prospero/cli/CliConsole.java +++ b/prospero-cli/src/main/java/org/wildfly/prospero/cli/CliConsole.java @@ -160,9 +160,11 @@ public void updatesFound(List artifactUpdates) { final Optional newVersion = artifactUpdate.getNewVersion(); final Optional oldVersion = artifactUpdate.getOldVersion(); final String artifactName = artifactUpdate.getArtifactName(); + final String channelName = artifactUpdate.getChannelName().map(name -> "[" + name + "]") + .orElse(""); - getStdOut().printf(" %s%-50s %-20s ==> %-20s%n", artifactUpdate.isDowngrade()?"[*]":"", artifactName, oldVersion.orElse("[]"), - newVersion.orElse("[]")); + getStdOut().printf(" %s%-50s %-20s ==> %-20s %-20s%n", artifactUpdate.isDowngrade()?"[*]":"", artifactName, oldVersion.orElse("[]"), + newVersion.orElse("[]"), channelName); } if (artifactUpdates.stream().anyMatch(ArtifactChange::isDowngrade)) { @@ -180,9 +182,11 @@ public void changesFound(List artifactUpdates) { final Optional newVersion = artifactUpdate.getNewVersion(); final Optional oldVersion = artifactUpdate.getOldVersion(); final String artifactName = artifactUpdate.getArtifactName(); + final String channelName = artifactUpdate.getChannelName().map(name -> "[" + name + "]") + .orElse(""); - getStdOut().printf(" %-50s %-20s ==> %-20s%n", artifactName, oldVersion.orElse("[]"), - newVersion.orElse("[]")); + getStdOut().printf(" %-50s %-20s ==> %-20s %-20s%n", artifactName, oldVersion.orElse("[]"), + newVersion.orElse("[]"),channelName); } } } diff --git a/prospero-cli/src/test/java/org/wildfly/prospero/cli/CliConsoleTest.java b/prospero-cli/src/test/java/org/wildfly/prospero/cli/CliConsoleTest.java new file mode 100644 index 000000000..b25896a50 --- /dev/null +++ b/prospero-cli/src/test/java/org/wildfly/prospero/cli/CliConsoleTest.java @@ -0,0 +1,81 @@ +/* + * 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.prospero.cli; + +import org.eclipse.aether.artifact.DefaultArtifact; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.wildfly.prospero.api.ArtifactChange; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class CliConsoleTest extends AbstractConsoleTest { + private CliConsole cliConsole; + + private ByteArrayOutputStream outputStream; + private PrintStream originalOut; + + @Before + public void setUp() { + cliConsole = new CliConsole(); + outputStream = new ByteArrayOutputStream(); + originalOut = System.out; + System.setOut(new PrintStream(outputStream)); + } + + @Test + public void testUpdatesFoundWithUpdates_ArtifactChange_update() { + final List artifactChanges = new ArrayList<>(); + final ArtifactChange artifactChange = ArtifactChange.updated(new DefaultArtifact("test.group", "test-artifact2", "jar", "2.0.0"), new DefaultArtifact("test.group", "test-artifact2", "jar", "2.1.0"), "channel-1"); + + artifactChanges.add(artifactChange); + cliConsole.updatesFound(artifactChanges); + final String capturedOutput = outputStream.toString(); + + assertThat(capturedOutput) + .contains("test.group:test-artifact2") + .contains("[channel-1]"); + } + @Test + public void testUpdatesFoundWithUpdates_ArtifactChange_add() { + final List artifactChanges = new ArrayList<>(); + final ArtifactChange artifactChange = ArtifactChange.added(new DefaultArtifact("test.group", "test-artifact2", "jar", "2.0.0"), "channel-1"); + + artifactChanges.add(artifactChange); + cliConsole.updatesFound(artifactChanges); + final String capturedOutput = outputStream.toString(); + + assertThat(capturedOutput) + .contains("test.group:test-artifact2") + .contains("[channel-1]"); + } + + @After + public void destory() throws IOException { + outputStream.close(); + cliConsole = null; + System.setOut(originalOut); + } +} diff --git a/prospero-common/src/main/java/org/wildfly/prospero/ProsperoLogger.java b/prospero-common/src/main/java/org/wildfly/prospero/ProsperoLogger.java index 119ddcf61..65d620c82 100644 --- a/prospero-common/src/main/java/org/wildfly/prospero/ProsperoLogger.java +++ b/prospero-common/src/main/java/org/wildfly/prospero/ProsperoLogger.java @@ -350,4 +350,16 @@ public interface ProsperoLogger extends BasicLogger { @Message(id = 260, value = "The selected folder %s cannot be created.") IllegalArgumentException dirMustBeWritable(Path directory); + + @Message(id = 261, value = "Channel map data has been written to %s.") + @LogMessage(level = Logger.Level.DEBUG) + void channelNamesWrittenToFile(String fileName); + + @Message(id = 262, value = "Unable to create a candidate properties file %s.") + @LogMessage(level = Logger.Level.ERROR) + void unableToWriteChannelNamesToFile(String fileName, @Cause Exception e); + + @Message(id = 263, value = "Unable to read the candidate properties file %s.") + @LogMessage(level = Logger.Level.ERROR) + void unableToReadChannelNames(String fileName, @Cause Exception e); } diff --git a/prospero-common/src/main/java/org/wildfly/prospero/actions/ApplyCandidateAction.java b/prospero-common/src/main/java/org/wildfly/prospero/actions/ApplyCandidateAction.java index 30954df82..3b03aa91b 100644 --- a/prospero-common/src/main/java/org/wildfly/prospero/actions/ApplyCandidateAction.java +++ b/prospero-common/src/main/java/org/wildfly/prospero/actions/ApplyCandidateAction.java @@ -39,6 +39,7 @@ import org.jboss.galleon.Errors; import org.jboss.galleon.ProvisioningManager; +import org.jboss.logging.Logger; import org.wildfly.prospero.ProsperoLogger; import org.wildfly.prospero.api.ArtifactChange; import org.wildfly.prospero.api.FileConflict; @@ -70,6 +71,8 @@ import org.wildfly.prospero.galleon.GalleonEnvironment; import org.wildfly.prospero.installation.git.GitStorage; import org.wildfly.prospero.metadata.ProsperoMetadataUtils; +import org.wildfly.prospero.updates.CandidateProperties; +import org.wildfly.prospero.updates.CandidatePropertiesParser; import org.wildfly.prospero.updates.MarkerFile; import org.wildfly.prospero.updates.UpdateSet; import org.wildfly.prospero.wfchannel.MavenSessionManager; @@ -81,10 +84,13 @@ public class ApplyCandidateAction { public static final Path STANDALONE_STARTUP_MARKER = Path.of("standalone", "tmp", "startup-marker"); public static final Path DOMAIN_STARTUP_MARKER = Path.of("domain", "tmp", "startup-marker"); + public static final String CANDIDATE_CHANNEL_NAME_LIST = "candidate_properties.yaml"; private final Path updateDir; private final Path installationDir; private final SystemPaths systemPaths; + private static final Logger log = Logger.getLogger(ApplyCandidateAction.class); + public enum Type { UPDATE("UPDATE"), REVERT("REVERT"), FEATURE_ADD("FEATURE_ADD"); @@ -280,10 +286,14 @@ public UpdateSet findUpdates() throws OperationException { candidateMap.put(artifact.getGroupId() + ":" + artifact.getArtifactId(), artifact); } List changes = new ArrayList<>(); + + final CandidateProperties candidateProperties = readCandidateProperties(); + for (String key : baseMap.keySet()) { if (candidateMap.containsKey(key)) { if (!baseMap.get(key).getVersion().equals(candidateMap.get(key).getVersion())) { - changes.add(ArtifactChange.updated(baseMap.get(key), candidateMap.get(key))); + final String updateChannelName = candidateProperties.getUpdateChannel(key); + changes.add(ArtifactChange.updated(baseMap.get(key), candidateMap.get(key), updateChannelName)); } } else { changes.add(ArtifactChange.removed(baseMap.get(key))); @@ -299,6 +309,21 @@ public UpdateSet findUpdates() throws OperationException { return new UpdateSet(changes); } + private CandidateProperties readCandidateProperties() { + final Path candidatePropertiesPath = updateDir + .resolve(ProsperoMetadataUtils.METADATA_DIR).resolve(CANDIDATE_CHANNEL_NAME_LIST); + if (Files.exists(candidatePropertiesPath)) { + try { + return CandidatePropertiesParser.read(candidatePropertiesPath); + } catch (IOException | MetadataException e) { + ProsperoLogger.ROOT_LOGGER.unableToReadChannelNames(candidatePropertiesPath.toString(), e); + } + } + + // return default properties if not able to read the file + return new CandidateProperties(Collections.emptyList()); + } + /** * returns the revision of the candidate server * @return {@code SavedState} diff --git a/prospero-common/src/main/java/org/wildfly/prospero/actions/PrepareCandidateAction.java b/prospero-common/src/main/java/org/wildfly/prospero/actions/PrepareCandidateAction.java index af646471a..bcbb65565 100644 --- a/prospero-common/src/main/java/org/wildfly/prospero/actions/PrepareCandidateAction.java +++ b/prospero-common/src/main/java/org/wildfly/prospero/actions/PrepareCandidateAction.java @@ -25,6 +25,7 @@ import org.wildfly.channel.ChannelManifest; import org.wildfly.channel.UnresolvedMavenArtifactException; import org.wildfly.prospero.ProsperoLogger; +import org.wildfly.prospero.api.ArtifactChange; import org.wildfly.prospero.api.InstallationMetadata; import org.wildfly.prospero.api.SavedState; import org.wildfly.prospero.api.exceptions.ArtifactResolutionException; @@ -35,12 +36,18 @@ import org.wildfly.prospero.galleon.GalleonUtils; import org.wildfly.prospero.metadata.ManifestVersionRecord; import org.wildfly.prospero.metadata.ManifestVersionResolver; +import org.wildfly.prospero.metadata.ProsperoMetadataUtils; import org.wildfly.prospero.model.ProsperoConfig; +import org.wildfly.prospero.updates.CandidateProperties; +import org.wildfly.prospero.updates.CandidatePropertiesParser; import org.wildfly.prospero.updates.MarkerFile; +import org.wildfly.prospero.updates.UpdateSet; import org.wildfly.prospero.wfchannel.MavenSessionManager; import java.io.IOException; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Optional; @@ -59,11 +66,29 @@ class PrepareCandidateAction implements AutoCloseable{ boolean buildCandidate(Path targetDir, GalleonEnvironment galleonEnv, ApplyCandidateAction.Type operation, ProvisioningConfig config) throws ProvisioningException, OperationException { + return this.buildCandidate(targetDir, galleonEnv, operation, config, new UpdateSet(Collections.emptyList())); + } + + /** + * Builds an update/revert candidate server in {@code targetDir}. + * + * @param targetDir + * @param galleonEnv + * @param operation + * @param config + * @param updateSet + * @return + * @throws ProvisioningException + * @throws OperationException + */ + boolean buildCandidate(Path targetDir, GalleonEnvironment galleonEnv, ApplyCandidateAction.Type operation, + ProvisioningConfig config, UpdateSet updateSet) throws ProvisioningException, OperationException { doBuildUpdate(targetDir, galleonEnv, config); try { final SavedState savedState = metadata.getRevisions().get(0); new MarkerFile(savedState.getName(), operation).write(targetDir); + writeCandidateProperties(updateSet, targetDir); } catch (IOException e) { throw new RuntimeException(e); } @@ -115,4 +140,24 @@ private void writeProsperoMetadata(Path home, ChannelManifest manifest, List updates = new ArrayList<>(); + + for (ArtifactChange artifactChange : updateSet.getArtifactUpdates()) { + final String[] gaSplit = artifactChange.getArtifactName().split(":"); + if (artifactChange.getChannelName().isPresent()) { + updates.add(new CandidateProperties.ComponentUpdate(gaSplit[0], gaSplit[1], artifactChange.getChannelName().get())); + } + } + + final Path candidateFile = installationDir.resolve(ProsperoMetadataUtils.METADATA_DIR).resolve(ApplyCandidateAction.CANDIDATE_CHANNEL_NAME_LIST); + try { + CandidatePropertiesParser.write(new CandidateProperties(updates), candidateFile); + ProsperoLogger.ROOT_LOGGER.channelNamesWrittenToFile(candidateFile.toFile().getAbsolutePath()); + } catch (IOException e) { + ProsperoLogger.ROOT_LOGGER.unableToWriteChannelNamesToFile(candidateFile.toFile().getAbsolutePath(),e); + } + + } } diff --git a/prospero-common/src/main/java/org/wildfly/prospero/actions/UpdateAction.java b/prospero-common/src/main/java/org/wildfly/prospero/actions/UpdateAction.java index d081a2a12..290f8dd77 100644 --- a/prospero-common/src/main/java/org/wildfly/prospero/actions/UpdateAction.java +++ b/prospero-common/src/main/java/org/wildfly/prospero/actions/UpdateAction.java @@ -110,7 +110,8 @@ public boolean buildUpdate(Path targetDir) throws ProvisioningException, Operati InstallFolderUtils.verifyIsWritable(targetDir); } - if (findUpdates().isEmpty()) { + final UpdateSet updateSet = findUpdates(); + if (updateSet.isEmpty()) { ProsperoLogger.ROOT_LOGGER.noUpdatesFound(installDir); return false; } @@ -122,7 +123,7 @@ public boolean buildUpdate(Path targetDir) throws ProvisioningException, Operati final ProvisioningConfig provisioningConfig = ProvisioningXmlParser.parse(PathsUtils.getProvisioningXml(installDir)); final boolean result = prepareCandidateAction.buildCandidate(targetDir, galleonEnv, - ApplyCandidateAction.Type.UPDATE, provisioningConfig); + ApplyCandidateAction.Type.UPDATE, provisioningConfig, updateSet); ProsperoLogger.ROOT_LOGGER.updateCandidateCompleted(targetDir); return result; } diff --git a/prospero-common/src/main/java/org/wildfly/prospero/api/ArtifactChange.java b/prospero-common/src/main/java/org/wildfly/prospero/api/ArtifactChange.java index 61c70ceb1..826c6dfaa 100644 --- a/prospero-common/src/main/java/org/wildfly/prospero/api/ArtifactChange.java +++ b/prospero-common/src/main/java/org/wildfly/prospero/api/ArtifactChange.java @@ -24,10 +24,16 @@ import java.util.Optional; public class ArtifactChange extends Diff { + private final String channelName; + public static ArtifactChange added(Artifact newVersion) { Objects.requireNonNull(newVersion); return new ArtifactChange(toGav(newVersion), null, newVersion.getVersion()); } + public static ArtifactChange added(Artifact newVersion, String channelName) { + Objects.requireNonNull(newVersion); + return new ArtifactChange(toGav(newVersion), null, newVersion.getVersion(), channelName); + } public static ArtifactChange removed(Artifact oldVersion) { Objects.requireNonNull(oldVersion); @@ -39,9 +45,19 @@ public static ArtifactChange updated(Artifact oldVersion, Artifact newVersion) { Objects.requireNonNull(newVersion); return new ArtifactChange(toGav(oldVersion), oldVersion.getVersion(), newVersion.getVersion()); } + public static ArtifactChange updated(Artifact oldVersion, Artifact newVersion, String channelName) { + Objects.requireNonNull(oldVersion); + Objects.requireNonNull(newVersion); + return new ArtifactChange(toGav(oldVersion), oldVersion.getVersion(), newVersion.getVersion(), channelName); + } private ArtifactChange(String gav, String oldVersion, String newVersion) { + this(gav, oldVersion, newVersion, null); + } + + private ArtifactChange(String gav, String oldVersion, String newVersion, String channelName) { super(gav, oldVersion, newVersion); + this.channelName = channelName; } @SuppressWarnings("OptionalGetWithoutIsPresent") @@ -54,6 +70,10 @@ public Optional getOldVersion() { return getOldValue(); } + public Optional getChannelName() { + return Optional.ofNullable(channelName); + } + public Optional getNewVersion() { return getNewValue(); } @@ -89,6 +109,7 @@ public boolean isUpdated() { } public String prettyPrint() { - return String.format("[%s] %s %s ==> %s", getStatus(), getName().orElse(""), getOldValue().orElse("[]"), getNewValue().orElse("[]")); + return String.format("[%s] %s %s ==> %s @ %s", getStatus(), getName().orElse(""), getOldValue().orElse("[]"), + getNewValue().orElse("[]"), getChannelName().orElse("Unknown")); } } diff --git a/prospero-common/src/main/java/org/wildfly/prospero/galleon/ChannelMavenArtifactRepositoryManager.java b/prospero-common/src/main/java/org/wildfly/prospero/galleon/ChannelMavenArtifactRepositoryManager.java index 8793f56e3..555947acc 100644 --- a/prospero-common/src/main/java/org/wildfly/prospero/galleon/ChannelMavenArtifactRepositoryManager.java +++ b/prospero-common/src/main/java/org/wildfly/prospero/galleon/ChannelMavenArtifactRepositoryManager.java @@ -20,6 +20,7 @@ import org.jboss.galleon.ProvisioningException; import org.jboss.galleon.layout.FeaturePackDescriber; import org.jboss.galleon.util.ZipUtils; +import org.jboss.logging.Logger; import org.wildfly.channel.ArtifactTransferException; import org.wildfly.channel.ChannelManifest; import org.wildfly.channel.NoStreamFoundException; @@ -48,6 +49,7 @@ import java.util.stream.Collectors; public class ChannelMavenArtifactRepositoryManager implements MavenRepoManager, ChannelResolvable { + private static final Logger LOG = Logger.getLogger(ChannelMavenArtifactRepositoryManager.class); private static final String REQUIRE_CHANNEL_FOR_ALL_ARTIFACT = "org.wildfly.plugins.galleon.all.artifact.requires.channel.resolution"; private final ChannelSession channelSession; private final ChannelManifest manifest; @@ -214,6 +216,9 @@ private void resolveArtifactsWithFallbackVersions(MavenArtifactMapper mapperNotR List channelArtifacts; try { channelArtifacts = channelSession.resolveMavenArtifacts(coordinates); + if (LOG.isDebugEnabled()) { + channelArtifacts.forEach(a->LOG.debugf("Installing artifact [%s:%s:%s] from channel [%s]", a.getGroupId(), a.getArtifactId(), a.getVersion(), a.getChannelName().orElse("Unknown"))); + } mapperNotRequiringChannels.applyResolution(channelArtifacts); } catch (ArtifactTransferException e) { throw new MavenUniverseException(e.getLocalizedMessage(), e); diff --git a/prospero-common/src/main/java/org/wildfly/prospero/updates/CandidateProperties.java b/prospero-common/src/main/java/org/wildfly/prospero/updates/CandidateProperties.java new file mode 100644 index 000000000..7fcda1cd1 --- /dev/null +++ b/prospero-common/src/main/java/org/wildfly/prospero/updates/CandidateProperties.java @@ -0,0 +1,117 @@ +package org.wildfly.prospero.updates; + + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +public class CandidateProperties { + + protected static final String DEFAULT_SCHEMA = "1.0.0"; + private final String schemaVersion; + private final List updates; + + @JsonIgnore + private final Map updatesMap = new HashMap<>(); + + public CandidateProperties(List updates) { + this(DEFAULT_SCHEMA, updates); + } + + @JsonCreator + public CandidateProperties(@JsonProperty(required = true, value = "schemaVersion") String schemaVersion, + @JsonProperty(value = "updates") List updates) { + this.schemaVersion = schemaVersion; + this.updates = updates==null ? Collections.emptyList() : updates; + this.updates.forEach(u->updatesMap.put(u.getGroupId() + ":" + u.getArtifactId(), u.getChannelName())); + } + + public String getSchemaVersion() { + return schemaVersion; + } + + public List getUpdates() { + return updates; + } + + @Override + public String toString() { + return "CandidateProperties{" + + "schemaVersion='" + schemaVersion + '\'' + + ", updates=" + updates + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CandidateProperties that = (CandidateProperties) o; + return Objects.equals(schemaVersion, that.schemaVersion) && Objects.equals(updates, that.updates); + } + + @Override + public int hashCode() { + return Objects.hash(schemaVersion, updates); + } + + @JsonIgnore + public String getUpdateChannel(String key) { + return updatesMap.get(key); + } + + public static class ComponentUpdate { + private String groupId; + private String artifactId; + private String channelName; + + @JsonCreator + public ComponentUpdate(@JsonProperty(value = "groupId") String groupId, + @JsonProperty(value = "artifactId") String artifactId, + @JsonProperty(value = "channelName") String channelName) { + this.groupId = groupId; + this.artifactId = artifactId; + this.channelName = channelName; + } + + public String getGroupId() { + return groupId; + } + + public String getArtifactId() { + return artifactId; + } + + public String getChannelName() { + return channelName; + } + + @Override + public String toString() { + return "ComponentUpdate{" + + "groupId='" + groupId + '\'' + + ", artifactId='" + artifactId + '\'' + + ", channelName='" + channelName + '\'' + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ComponentUpdate that = (ComponentUpdate) o; + return Objects.equals(groupId, that.groupId) && Objects.equals(artifactId, that.artifactId) && Objects.equals(channelName, that.channelName); + } + + @Override + public int hashCode() { + return Objects.hash(groupId, artifactId, channelName); + } + } +} diff --git a/prospero-common/src/main/java/org/wildfly/prospero/updates/CandidatePropertiesParser.java b/prospero-common/src/main/java/org/wildfly/prospero/updates/CandidatePropertiesParser.java new file mode 100644 index 000000000..04c91c9b0 --- /dev/null +++ b/prospero-common/src/main/java/org/wildfly/prospero/updates/CandidatePropertiesParser.java @@ -0,0 +1,38 @@ +package org.wildfly.prospero.updates; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import org.wildfly.prospero.api.exceptions.MetadataException; + +import java.io.IOException; +import java.nio.file.Path; + + +public class CandidatePropertiesParser { + + protected static final YAMLFactory YAML_FACTORY = new YAMLFactory(); + protected static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(YAML_FACTORY); + + public static CandidateProperties read(Path file) throws IOException, MetadataException { + final JsonNode node = OBJECT_MAPPER.readTree(file.toFile()); + checkSchemaVersion(node); + + return OBJECT_MAPPER.readValue(file.toFile(), CandidateProperties.class); + } + + private static void checkSchemaVersion(JsonNode node) throws MetadataException { + JsonNode schemaVersion = node.path("schemaVersion"); + String version = schemaVersion.asText(); + if (version == null || version.isEmpty()) { + throw new MetadataException("The candidate properties file does not have schemaVersion field."); + } + if (!version.equals("1.0.0")) { + throw new MetadataException("Unknown schemaVersion for the candidate properties file."); + } + } + + public static void write(CandidateProperties properties, Path file) throws IOException { + OBJECT_MAPPER.writeValue(file.toFile(), properties); + } +} diff --git a/prospero-common/src/main/java/org/wildfly/prospero/updates/UpdateFinder.java b/prospero-common/src/main/java/org/wildfly/prospero/updates/UpdateFinder.java index f868bd9e7..b2d984b1d 100644 --- a/prospero-common/src/main/java/org/wildfly/prospero/updates/UpdateFinder.java +++ b/prospero-common/src/main/java/org/wildfly/prospero/updates/UpdateFinder.java @@ -21,6 +21,7 @@ import org.eclipse.aether.artifact.DefaultArtifact; import org.wildfly.channel.ChannelSession; import org.wildfly.channel.UnresolvedMavenArtifactException; +import org.wildfly.channel.VersionResult; import org.wildfly.prospero.api.ArtifactChange; import org.wildfly.prospero.api.exceptions.ArtifactResolutionException; @@ -82,10 +83,13 @@ public UpdateSet findUpdates(List artifacts) throws ArtifactResolution private Optional findUpdates(Artifact artifact) throws ArtifactResolutionException { final String latestVersion; + final Optional channelName; try { - latestVersion = channelSession.findLatestMavenArtifactVersion(artifact.getGroupId(), - artifact.getArtifactId(), artifact.getExtension(), artifact.getClassifier(), null) - .getVersion(); + final VersionResult versionResult = channelSession.findLatestMavenArtifactVersion(artifact.getGroupId(), + artifact.getArtifactId(), artifact.getExtension(), artifact.getClassifier(), null); + latestVersion = versionResult.getVersion(); + channelName = versionResult.getChannelName(); + } catch (UnresolvedMavenArtifactException e) { return Optional.of(ArtifactChange.removed(artifact)); } @@ -94,7 +98,7 @@ private Optional findUpdates(Artifact artifact) throws ArtifactR if (latestVersion == null || latest.getVersion().equals(artifact.getVersion())) { return Optional.empty(); } else { - return Optional.of(ArtifactChange.updated(artifact, latest)); + return Optional.of(ArtifactChange.updated(artifact, latest, channelName.orElse(null))); } } diff --git a/prospero-common/src/test/java/org/wildfly/prospero/actions/ApplyCandidateActionTest.java b/prospero-common/src/test/java/org/wildfly/prospero/actions/ApplyCandidateActionTest.java index 9e6c5319b..5d535638e 100644 --- a/prospero-common/src/test/java/org/wildfly/prospero/actions/ApplyCandidateActionTest.java +++ b/prospero-common/src/test/java/org/wildfly/prospero/actions/ApplyCandidateActionTest.java @@ -45,6 +45,8 @@ import org.wildfly.prospero.installation.git.GitStorage; import org.wildfly.prospero.metadata.ProsperoMetadataUtils; import org.wildfly.prospero.metadata.ManifestVersionRecord; +import org.wildfly.prospero.updates.CandidateProperties; +import org.wildfly.prospero.updates.CandidatePropertiesParser; import org.wildfly.prospero.updates.MarkerFile; import org.wildfly.prospero.utils.filestate.DirState; @@ -479,6 +481,47 @@ public void verifyRemoveCandidate() throws Exception { Assert.assertFalse(Files.exists(updatePath)); } + @Test + public void findUpdatesContainsChannelNameIfProvided() throws Exception { + createSimpleFeaturePacks(); + + install(installationPath, FPL_100); + Files.writeString(installationPath.resolve(METADATA_DIR).resolve(ProsperoMetadataUtils.MANIFEST_FILE_NAME), manifest("manifest 01", + List.of(new Stream("org.test", "foo", "1.0.0"), + new Stream("org.test", "bar", "1.0.0")))); + prepareUpdate(updatePath, installationPath, FPL_101); + Files.writeString(updatePath.resolve(METADATA_DIR).resolve(ProsperoMetadataUtils.MANIFEST_FILE_NAME), manifest("manifest 01", + List.of(new Stream("org.test", "foo", "1.0.1"), + new Stream("org.test", "bar", "1.0.0")))); + CandidatePropertiesParser.write( + new CandidateProperties(List.of(new CandidateProperties.ComponentUpdate("org.test", "foo", "test-channel"))), + updatePath.resolve(METADATA_DIR).resolve(ApplyCandidateAction.CANDIDATE_CHANNEL_NAME_LIST)); + + assertThat(new ApplyCandidateAction(installationPath, updatePath).findUpdates().getArtifactUpdates()) + .containsOnly(ArtifactChange.updated(new DefaultArtifact("org.test", "foo", null, "1.0.0"), + new DefaultArtifact("org.test", "foo", null, "1.0.1"), + "test-channel"), + ArtifactChange.updated(new DefaultArtifact("org.test", "foo", null, "1.0.0"), + new DefaultArtifact("org.test", "bar", null, "1.0.1"))); + } + + @Test + public void findUpdatesIgnoresInvalidCandidatePropertiesFile() throws Exception { + createSimpleFeaturePacks(); + + install(installationPath, FPL_100); + Files.writeString(installationPath.resolve(METADATA_DIR).resolve(ProsperoMetadataUtils.MANIFEST_FILE_NAME), manifest("manifest 01", + List.of(new Stream("org.test", "foo", "1.0.0")))); + prepareUpdate(updatePath, installationPath, FPL_101); + Files.writeString(updatePath.resolve(METADATA_DIR).resolve(ProsperoMetadataUtils.MANIFEST_FILE_NAME), manifest("manifest 01", + List.of(new Stream("org.test", "foo", "1.0.1")))); + Files.writeString(updatePath.resolve(METADATA_DIR).resolve(ApplyCandidateAction.CANDIDATE_CHANNEL_NAME_LIST), "I'm invalid"); + + assertThat(new ApplyCandidateAction(installationPath, updatePath).findUpdates().getArtifactUpdates()) + .containsOnly(ArtifactChange.updated(new DefaultArtifact("org.test", "foo", null, "1.0.0"), + new DefaultArtifact("org.test", "foo", null, "1.0.1"))); + } + private void createSimpleFeaturePacks() throws ProvisioningException { creator.newFeaturePack(FeaturePackLocation.fromString(FPL_100).getFPID()) .newPackage("p1", true) diff --git a/prospero-common/src/test/java/org/wildfly/prospero/updates/CandidatePropertiesTest.java b/prospero-common/src/test/java/org/wildfly/prospero/updates/CandidatePropertiesTest.java new file mode 100644 index 000000000..48e74615d --- /dev/null +++ b/prospero-common/src/test/java/org/wildfly/prospero/updates/CandidatePropertiesTest.java @@ -0,0 +1,53 @@ +package org.wildfly.prospero.updates; + +import org.assertj.core.api.Assertions; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.wildfly.prospero.api.exceptions.MetadataException; + +import java.io.File; +import java.nio.file.Files; +import java.util.Collections; +import java.util.List; + +import static org.junit.Assert.*; + +public class CandidatePropertiesTest { + + @Rule + public final TemporaryFolder temp = new TemporaryFolder(); + + @Test + public void serializeProperties() throws Exception { + final CandidateProperties candidateProperties = new CandidateProperties( + List.of(new CandidateProperties.ComponentUpdate("foo", "bar", "test"))); + + final File resultFile = temp.newFile(); + CandidatePropertiesParser.write(candidateProperties, resultFile.toPath()); + + final CandidateProperties readProperties = CandidatePropertiesParser.read(resultFile.toPath()); + + assertEquals(candidateProperties, readProperties); + } + + @Test + public void readPropertiesWithEmptyUpdateList() throws Exception { + final File resultFile = temp.newFile(); + Files.writeString(resultFile.toPath(), "schemaVersion: \"1.0.0\"\n" + + "updates:"); + + final CandidateProperties readProperties = CandidatePropertiesParser.read(resultFile.toPath()); + + assertEquals(Collections.emptyList(), readProperties.getUpdates()); + } + + @Test + public void readPropertiesWithoutVersion() throws Exception { + final File resultFile = temp.newFile(); + Files.writeString(resultFile.toPath(), "updates:"); + + Assertions.assertThatThrownBy(()->CandidatePropertiesParser.read(resultFile.toPath())) + .isInstanceOf(MetadataException.class); + } +} \ No newline at end of file diff --git a/prospero-common/src/test/java/org/wildfly/prospero/updates/UpdateFinderTest.java b/prospero-common/src/test/java/org/wildfly/prospero/updates/UpdateFinderTest.java index bf47b4242..7b9136c60 100644 --- a/prospero-common/src/test/java/org/wildfly/prospero/updates/UpdateFinderTest.java +++ b/prospero-common/src/test/java/org/wildfly/prospero/updates/UpdateFinderTest.java @@ -27,6 +27,7 @@ import org.wildfly.channel.ArtifactTransferException; import org.wildfly.channel.ChannelSession; import org.wildfly.channel.VersionResult; +import org.wildfly.prospero.api.ArtifactChange; import java.util.Arrays; import java.util.Collections; @@ -57,9 +58,11 @@ public void testDowngradeIsPossible() throws Exception { final UpdateSet updates = finder.findUpdates(artifacts); assertEquals(1, updates.getArtifactUpdates().size()); - assertEquals("org.foo:bar", updates.getArtifactUpdates().get(0).getArtifactName()); - assertEquals("1.0.0", updates.getArtifactUpdates().get(0).getNewVersion().get()); - assertEquals("1.0.1", updates.getArtifactUpdates().get(0).getOldVersion().get()); + final ArtifactChange actualChange = updates.getArtifactUpdates().get(0); + assertEquals("org.foo:bar", actualChange.getArtifactName()); + assertEquals("1.0.0", actualChange.getNewVersion().get()); + assertEquals("1.0.1", actualChange.getOldVersion().get()); + assertEquals(Optional.empty(), actualChange.getChannelName()); } @Test @@ -109,4 +112,23 @@ public void testRemoval() throws Exception { assertEquals(Optional.empty(), updates.getArtifactUpdates().get(0).getNewVersion()); assertEquals("1.0.0", updates.getArtifactUpdates().get(0).getOldVersion().get()); } + + @Test + public void findUpdatesIncludesChannelNames() throws Exception { + when(channelSession.findLatestMavenArtifactVersion("org.foo", "bar", "jar", "", null)) + .thenReturn(new VersionResult("1.0.0", "test-channel")); + + UpdateFinder finder = new UpdateFinder(channelSession); + final List artifacts = Arrays.asList( + new DefaultArtifact("org.foo", "bar", "jar", "1.0.1") + ); + final UpdateSet updates = finder.findUpdates(artifacts); + + assertEquals(1, updates.getArtifactUpdates().size()); + final ArtifactChange actualUpdate = updates.getArtifactUpdates().get(0); + assertEquals("org.foo:bar", actualUpdate.getArtifactName()); + assertEquals("1.0.0", actualUpdate.getNewVersion().get()); + assertEquals("1.0.1", actualUpdate.getOldVersion().get()); + assertEquals("test-channel", actualUpdate.getChannelName().orElse(null)); + } } \ No newline at end of file