diff --git a/pom.xml b/pom.xml
index bd7497a0..1eccc721 100644
--- a/pom.xml
+++ b/pom.xml
@@ -66,7 +66,7 @@
3.8.16.Final
1.7.0.Final
7.1.0.Final
- 1.0.2.Final
+ 2.0.0.Beta1-SNAPSHOT
2.4.1.Final
1.2.1.Final
5.12.0
diff --git a/prospero-cli/src/main/java/org/wildfly/prospero/cli/CliMessages.java b/prospero-cli/src/main/java/org/wildfly/prospero/cli/CliMessages.java
index 2610ba4e..0f7231ad 100644
--- a/prospero-cli/src/main/java/org/wildfly/prospero/cli/CliMessages.java
+++ b/prospero-cli/src/main/java/org/wildfly/prospero/cli/CliMessages.java
@@ -695,4 +695,10 @@ default String getProfile() {
default OperationException sizeOfChannel(Path channelFile) {
return new OperationException(format(bundle.getString("prospero.channels.error.morethanonechannel.found"), channelFile));
}
+
+ default OperationException cancelledByConfilcts() {
+ return new OperationException(format(
+ bundle.getString("prospero.updates.apply.candidate.cancel_conflicts"),
+ CliConstants.NO_CONFLICTS_ONLY));
+ }
}
diff --git a/prospero-cli/src/main/java/org/wildfly/prospero/cli/commands/CliConstants.java b/prospero-cli/src/main/java/org/wildfly/prospero/cli/commands/CliConstants.java
index 6fa3b30e..67baaaf8 100644
--- a/prospero-cli/src/main/java/org/wildfly/prospero/cli/commands/CliConstants.java
+++ b/prospero-cli/src/main/java/org/wildfly/prospero/cli/commands/CliConstants.java
@@ -101,4 +101,7 @@ private Commands() {
public static final String VV = "-vv";
public static final String Y = "-y";
public static final String YES = "--yes";
+ public static final String NO_CONFLICTS_ONLY = "--no-conflicts-only";
+ public static final String DRY_RUN = "--dry-run";
+
}
diff --git a/prospero-cli/src/main/java/org/wildfly/prospero/cli/commands/RevertCommand.java b/prospero-cli/src/main/java/org/wildfly/prospero/cli/commands/RevertCommand.java
index b86b8f95..7317f3c5 100644
--- a/prospero-cli/src/main/java/org/wildfly/prospero/cli/commands/RevertCommand.java
+++ b/prospero-cli/src/main/java/org/wildfly/prospero/cli/commands/RevertCommand.java
@@ -42,6 +42,7 @@
import org.wildfly.prospero.cli.FileConflictPrinter;
import org.wildfly.prospero.cli.RepositoryDefinition;
import org.wildfly.prospero.api.TemporaryFilesManager;
+import org.wildfly.prospero.cli.ReturnCodes;
import picocli.CommandLine;
import static org.wildfly.prospero.cli.ReturnCodes.SUCCESS;
@@ -52,12 +53,22 @@
)
public class RevertCommand extends AbstractParentCommand {
- private static int applyCandidate(CliConsole console, ApplyCandidateAction applyCandidateAction, boolean yes) throws OperationException, ProvisioningException {
+ private static int applyCandidate(CliConsole console, ApplyCandidateAction applyCandidateAction,
+ boolean yes, boolean noConflictsOnly, boolean dryRun)
+ throws OperationException, ProvisioningException {
List artifactUpdates = applyCandidateAction.findUpdates().getArtifactUpdates();
console.printArtifactChanges(artifactUpdates);
final List conflicts = applyCandidateAction.getConflicts();
FileConflictPrinter.print(conflicts, console);
+ if (dryRun) {
+ return ReturnCodes.SUCCESS;
+ }
+
+ if (noConflictsOnly && !conflicts.isEmpty()) {
+ throw CliMessages.MESSAGES.cancelledByConfilcts();
+ }
+
if (!yes && !artifactUpdates.isEmpty() && !console.confirm(CliMessages.MESSAGES.continueWithRevert(),
CliMessages.MESSAGES.applyingChanges(), CliMessages.MESSAGES.revertCancelled())) {
return SUCCESS;
@@ -92,6 +103,9 @@ public static class PerformCommand extends AbstractMavenCommand {
@CommandLine.Option(names = {CliConstants.Y, CliConstants.YES})
boolean yes;
+ @CommandLine.Option(names = {CliConstants.NO_CONFLICTS_ONLY})
+ boolean noConflictsOnly;
+
public PerformCommand(CliConsole console, ActionFactory actionFactory) {
super(console, actionFactory);
}
@@ -121,7 +135,7 @@ public Integer call() throws Exception {
validateRevertCandidate(installationDirectory, tempDirectory, applyCandidateAction);
- applyCandidate(console, applyCandidateAction, yes);
+ applyCandidate(console, applyCandidateAction, yes, noConflictsOnly, false);
} catch (IOException e) {
throw ProsperoLogger.ROOT_LOGGER.unableToCreateTemporaryDirectory(e);
}
@@ -147,6 +161,12 @@ public static class ApplyCommand extends AbstractCommand {
@CommandLine.Option(names = {CliConstants.Y, CliConstants.YES})
boolean yes;
+ @CommandLine.Option(names = {CliConstants.NO_CONFLICTS_ONLY})
+ boolean noConflictsOnly;
+
+ @CommandLine.Option(names = {CliConstants.DRY_RUN})
+ boolean dryRun;
+
public ApplyCommand(CliConsole console, ActionFactory actionFactory) {
super(console, actionFactory);
}
@@ -162,7 +182,7 @@ public Integer call() throws Exception {
console.println(CliMessages.MESSAGES.revertStart(installationDirectory, applyCandidateAction.getCandidateRevision().getName()));
console.println("");
- applyCandidate(console, applyCandidateAction, yes);
+ applyCandidate(console, applyCandidateAction, yes, noConflictsOnly, dryRun);
if(remove) {
applyCandidateAction.removeCandidate(candidateDirectory.toFile());
}
diff --git a/prospero-cli/src/main/java/org/wildfly/prospero/cli/commands/UpdateCommand.java b/prospero-cli/src/main/java/org/wildfly/prospero/cli/commands/UpdateCommand.java
index d1cdc898..675181a3 100644
--- a/prospero-cli/src/main/java/org/wildfly/prospero/cli/commands/UpdateCommand.java
+++ b/prospero-cli/src/main/java/org/wildfly/prospero/cli/commands/UpdateCommand.java
@@ -89,6 +89,9 @@ public static class PerformCommand extends AbstractMavenCommand {
@CommandLine.Option(names = {CliConstants.Y, CliConstants.YES})
boolean yes;
+ @CommandLine.Option(names = {CliConstants.NO_CONFLICTS_ONLY})
+ boolean noConflictsOnly;
+
public PerformCommand(CliConsole console, ActionFactory actionFactory) {
super(console, actionFactory);
}
@@ -119,7 +122,7 @@ public Integer call() throws Exception {
console.println(CliMessages.MESSAGES.updateHeader(installationDir));
try (UpdateAction updateAction = actionFactory.update(installationDir, mavenOptions, console, repositories)) {
- performUpdate(updateAction, yes, console, installationDir);
+ performUpdate(updateAction, yes, console, installationDir, noConflictsOnly);
}
}
@@ -129,7 +132,7 @@ public Integer call() throws Exception {
return ReturnCodes.SUCCESS;
}
- private boolean performUpdate(UpdateAction updateAction, boolean yes, CliConsole console, Path installDir) throws OperationException, ProvisioningException {
+ private boolean performUpdate(UpdateAction updateAction, boolean yes, CliConsole console, Path installDir, boolean noConflictsOnly) throws OperationException, ProvisioningException {
Path targetDir = null;
try {
targetDir = Files.createTempDirectory("update-candidate");
@@ -141,6 +144,11 @@ private boolean performUpdate(UpdateAction updateAction, boolean yes, CliConsole
final List conflicts = applyCandidateAction.getConflicts();
if (!conflicts.isEmpty()) {
FileConflictPrinter.print(conflicts, console);
+
+ if (noConflictsOnly) {
+ throw CliMessages.MESSAGES.cancelledByConfilcts();
+ }
+
if (!yes && !console.confirm(CliMessages.MESSAGES.continueWithUpdate(), "", CliMessages.MESSAGES.updateCancelled())) {
return false;
}
@@ -226,6 +234,12 @@ public static class ApplyCommand extends AbstractCommand {
@CommandLine.Option(names = {CliConstants.Y, CliConstants.YES})
boolean yes;
+ @CommandLine.Option(names = {CliConstants.NO_CONFLICTS_ONLY})
+ boolean noConflictsOnly;
+
+ @CommandLine.Option(names = {CliConstants.DRY_RUN})
+ boolean dryRun;
+
public ApplyCommand(CliConsole console, ActionFactory actionFactory) {
super(console, actionFactory);
}
@@ -257,6 +271,14 @@ public Integer call() throws Exception {
final List conflicts = applyCandidateAction.getConflicts();
FileConflictPrinter.print(conflicts, console);
+ if (dryRun) {
+ return ReturnCodes.SUCCESS;
+ }
+
+ if (noConflictsOnly && !conflicts.isEmpty()) {
+ throw CliMessages.MESSAGES.cancelledByConfilcts();
+ }
+
// there always should be updates, so confirm update
if (!yes && !console.confirm(CliMessages.MESSAGES.continueWithUpdate(), CliMessages.MESSAGES.applyingUpdates(), CliMessages.MESSAGES.updateCancelled())) {
return ReturnCodes.SUCCESS;
diff --git a/prospero-cli/src/main/java/org/wildfly/prospero/cli/spi/CliProviderImpl.java b/prospero-cli/src/main/java/org/wildfly/prospero/cli/spi/CliProviderImpl.java
index 4224f2d3..f0a37700 100644
--- a/prospero-cli/src/main/java/org/wildfly/prospero/cli/spi/CliProviderImpl.java
+++ b/prospero-cli/src/main/java/org/wildfly/prospero/cli/spi/CliProviderImpl.java
@@ -49,20 +49,22 @@ public String getScriptName(OsShell shell) {
}
@Override
- public String getApplyUpdateCommand(Path installationPath, Path candidatePath) {
+ public String getApplyUpdateCommand(Path installationPath, Path candidatePath, boolean noConflictsOnly) {
return CliConstants.Commands.UPDATE + " " + CliConstants.Commands.APPLY + " "
+ CliConstants.DIR + " " + escape(installationPath.toAbsolutePath()) + " "
+ CliConstants.CANDIDATE_DIR + " " + escape(candidatePath.toAbsolutePath()) + " "
+ CliConstants.YES + " "
+ + (noConflictsOnly ? CliConstants.NO_CONFLICTS_ONLY + " " : "")
+ CliConstants.REMOVE;
}
@Override
- public String getApplyRevertCommand(Path installationPath, Path candidatePath) {
+ public String getApplyRevertCommand(Path installationPath, Path candidatePath, boolean noConflictsOnly) {
return CliConstants.Commands.REVERT + " " + CliConstants.Commands.APPLY + " "
+ CliConstants.DIR + " " + escape(installationPath.toAbsolutePath()) + " "
+ CliConstants.CANDIDATE_DIR + " " + escape(candidatePath.toAbsolutePath()) + " "
+ CliConstants.YES + " "
+ + (noConflictsOnly ? CliConstants.NO_CONFLICTS_ONLY + " " : "")
+ CliConstants.REMOVE;
}
diff --git a/prospero-cli/src/main/resources/UsageMessages.properties b/prospero-cli/src/main/resources/UsageMessages.properties
index acfd47fe..ecfef6ce 100644
--- a/prospero-cli/src/main/resources/UsageMessages.properties
+++ b/prospero-cli/src/main/resources/UsageMessages.properties
@@ -152,7 +152,6 @@ dir = Location of the existing application server. If not specified, current wor
${prospero.dist.name}.install.dir = Target directory where the application server will be provisioned.
${prospero.dist.name}.clone.recreate.dir = Target directory where the application server will be provisioned.
-dry-run = Print components that can be upgraded, but do not perform the upgrades.
fpl.0 = Maven coordinates of a Galleon feature pack. The specified feature pack is installed \
with default layers and packages.
fpl.1 = When you use this option, you should also specify the @|bold --channels|@ or a combination of @|bold --manifest|@ \
@@ -194,6 +193,9 @@ package-stability-level.1 = Valid options are ${COMPLETION-CANDIDATES}.
${prospero.dist.name}.update.prepare.candidate-dir = Target directory where the candidate server will be provisioned. The existing server is not updated.
${prospero.dist.name}.update.subscribe.product = Specify the product name. This must be a known feature pack supported by ${prospero.dist.name}.
${prospero.dist.name}.update.subscribe.version = Specify the version of the product.
+no-conflicts-only = Rejects the operation if any file conflicts are detected. If not used, the user will be asked to \
+ confirm automatic conflict resolution, unless @|bold --yes|@ option is used.
+dry-run = Prints the changes that would be performed by executing the command, but does not perform any changes on the filesystem.
#
# Exit Codes
@@ -278,6 +280,8 @@ prospero.updates.apply.validation.candidate.wrong_type=Unable to apply candidate
prospero.updates.apply.validation.candidate.not_candidate=Unable to apply candidate.%n Installation at [%s] doesn't have a candidate marker file.
prospero.updates.apply.candidate.remove=Remove the candidate directory after applying update.
+prospero.updates.apply.candidate.cancel_conflicts = Potential conflicts exist in the installation. Resolve the conflicts in the listed files, or \
+ use [%s=false] to preserve user changes where possible.
prospero.updates.build.candidate.header=Building update candidate for %s%n
prospero.updates.build.candidate.complete=Update candidate generated in %s
diff --git a/prospero-cli/src/test/java/org/wildfly/prospero/cli/commands/ApplyUpdateCommandTest.java b/prospero-cli/src/test/java/org/wildfly/prospero/cli/commands/ApplyUpdateCommandTest.java
index 6bd1c71e..c39e202a 100644
--- a/prospero-cli/src/test/java/org/wildfly/prospero/cli/commands/ApplyUpdateCommandTest.java
+++ b/prospero-cli/src/test/java/org/wildfly/prospero/cli/commands/ApplyUpdateCommandTest.java
@@ -24,6 +24,7 @@
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.wildfly.prospero.actions.ApplyCandidateAction;
import org.wildfly.prospero.api.FileConflict;
@@ -43,6 +44,7 @@
import java.util.Collections;
import java.util.List;
+import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -188,6 +190,54 @@ public void testAskForConfirmationIfConflictsPresent() throws Exception {
assertEquals(1, askedConfirmation);
}
+ @Test
+ public void noConflictArgumentFailsCommand_WhenConflictsAreFound() throws Exception {
+ final Path updatePath = mockInstallation("update");
+ final Path targetPath = mockInstallation("target");
+ when(applyCandidateAction.getConflicts()).thenReturn(List.of(mock(FileConflict.class)));
+
+ int exitCode = commandLine.execute(CliConstants.Commands.UPDATE, CliConstants.Commands.APPLY,
+ CliConstants.CANDIDATE_DIR, updatePath.toString(),
+ CliConstants.DIR, targetPath.toString(),
+ CliConstants.NO_CONFLICTS_ONLY);
+
+ assertEquals(ReturnCodes.PROCESSING_ERROR, exitCode);
+ assertThat(getErrorOutput())
+ .contains(CliMessages.MESSAGES.cancelledByConfilcts().getMessage());
+
+ verify(applyCandidateAction, Mockito.never()).applyUpdate(any());
+ }
+
+ @Test
+ public void noConflictArgumentHasNoEffect_WhenNoConflictsAreFound() throws Exception {
+ final Path updatePath = mockInstallation("update");
+ final Path targetPath = mockInstallation("target");
+ when(applyCandidateAction.getConflicts()).thenReturn(Collections.emptyList());
+
+ int exitCode = commandLine.execute(CliConstants.Commands.UPDATE, CliConstants.Commands.APPLY,
+ CliConstants.CANDIDATE_DIR, updatePath.toString(),
+ CliConstants.DIR, targetPath.toString(),
+ CliConstants.NO_CONFLICTS_ONLY);
+
+ assertEquals(ReturnCodes.SUCCESS, exitCode);
+ verify(applyCandidateAction).applyUpdate(ApplyCandidateAction.Type.UPDATE);
+ }
+
+ @Test
+ public void dryRun_DoesntCallApplyAction() throws Exception {
+ final Path updatePath = mockInstallation("update");
+ final Path targetPath = mockInstallation("target");
+ when(applyCandidateAction.getConflicts()).thenReturn(Collections.emptyList());
+
+ int exitCode = commandLine.execute(CliConstants.Commands.UPDATE, CliConstants.Commands.APPLY,
+ CliConstants.CANDIDATE_DIR, updatePath.toString(),
+ CliConstants.DIR, targetPath.toString(),
+ CliConstants.DRY_RUN);
+
+ assertEquals(ReturnCodes.SUCCESS, exitCode);
+ verify(applyCandidateAction, Mockito.never()).applyUpdate(any());
+ }
+
private Path mockInstallation(String target) throws IOException, MetadataException, XMLStreamException {
final Path targetPath = temp.newFolder(target).toPath();
MetadataTestUtils.createInstallationMetadata(targetPath).close();
diff --git a/prospero-cli/src/test/java/org/wildfly/prospero/cli/commands/RevertApplyCommandTest.java b/prospero-cli/src/test/java/org/wildfly/prospero/cli/commands/RevertApplyCommandTest.java
index dce61428..5dfe1eaa 100644
--- a/prospero-cli/src/test/java/org/wildfly/prospero/cli/commands/RevertApplyCommandTest.java
+++ b/prospero-cli/src/test/java/org/wildfly/prospero/cli/commands/RevertApplyCommandTest.java
@@ -25,10 +25,12 @@
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.wildfly.prospero.actions.ApplyCandidateAction;
import org.wildfly.prospero.api.Console;
import org.wildfly.prospero.actions.InstallationHistoryAction;
+import org.wildfly.prospero.api.FileConflict;
import org.wildfly.prospero.api.SavedState;
import org.wildfly.prospero.api.exceptions.OperationException;
import org.wildfly.prospero.cli.AbstractConsoleTest;
@@ -40,9 +42,13 @@
import java.nio.file.Path;
import java.util.Collections;
+import java.util.List;
+import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -119,4 +125,46 @@ public void callApplyOperation() throws Exception {
assertEquals(ReturnCodes.SUCCESS, exitCode);
verify(applyCandidateAction).applyUpdate(ApplyCandidateAction.Type.REVERT);
}
+
+ @Test
+ public void noConflictArgumentFailsCommand_WhenConflictsAreFound() throws Exception {
+ when(applyCandidateAction.getConflicts()).thenReturn(List.of(mock(FileConflict.class)));
+
+ int exitCode = commandLine.execute(CliConstants.Commands.REVERT, CliConstants.Commands.APPLY,
+ CliConstants.DIR, installationDir.toString(),
+ CliConstants.CANDIDATE_DIR, updateDir.toString(),
+ CliConstants.NO_CONFLICTS_ONLY);
+
+ assertEquals(ReturnCodes.PROCESSING_ERROR, exitCode);
+ assertThat(getErrorOutput())
+ .contains(CliMessages.MESSAGES.cancelledByConfilcts().getMessage());
+
+ verify(applyCandidateAction, Mockito.never()).applyUpdate(any());
+ }
+
+ @Test
+ public void noConflictArgumentHasNoEffect_WhenNoConflictsAreFound() throws Exception {
+ when(applyCandidateAction.getConflicts()).thenReturn(Collections.emptyList());
+
+ int exitCode = commandLine.execute(CliConstants.Commands.REVERT, CliConstants.Commands.APPLY,
+ CliConstants.DIR, installationDir.toString(),
+ CliConstants.CANDIDATE_DIR, updateDir.toString(),
+ CliConstants.NO_CONFLICTS_ONLY);
+
+ assertEquals(ReturnCodes.SUCCESS, exitCode);
+ verify(applyCandidateAction).applyUpdate(ApplyCandidateAction.Type.REVERT);
+ }
+
+ @Test
+ public void dryRun_DoesntCallApplyAction() throws Exception {
+ when(applyCandidateAction.getConflicts()).thenReturn(Collections.emptyList());
+
+ int exitCode = commandLine.execute(CliConstants.Commands.REVERT, CliConstants.Commands.APPLY,
+ CliConstants.CANDIDATE_DIR, updateDir.toString(),
+ CliConstants.DIR, installationDir.toString(),
+ CliConstants.DRY_RUN);
+
+ assertEquals(ReturnCodes.SUCCESS, exitCode);
+ verify(applyCandidateAction, Mockito.never()).applyUpdate(any());
+ }
}
diff --git a/prospero-cli/src/test/java/org/wildfly/prospero/cli/commands/RevertPerformCommandTest.java b/prospero-cli/src/test/java/org/wildfly/prospero/cli/commands/RevertPerformCommandTest.java
index 843f9f8d..652e285f 100644
--- a/prospero-cli/src/test/java/org/wildfly/prospero/cli/commands/RevertPerformCommandTest.java
+++ b/prospero-cli/src/test/java/org/wildfly/prospero/cli/commands/RevertPerformCommandTest.java
@@ -31,11 +31,13 @@
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.wildfly.channel.Repository;
import org.wildfly.prospero.actions.ApplyCandidateAction;
import org.wildfly.prospero.api.Console;
import org.wildfly.prospero.actions.InstallationHistoryAction;
+import org.wildfly.prospero.api.FileConflict;
import org.wildfly.prospero.api.MavenOptions;
import org.wildfly.prospero.api.SavedState;
import org.wildfly.prospero.api.exceptions.OperationException;
@@ -50,6 +52,7 @@
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -154,6 +157,35 @@ public void passRemoteRepositories() throws Exception {
.containsExactly("http://temp.repo.te");
}
+ @Test
+ public void noConflictArgumentFailsCommand_WhenConflictsAreFound() throws Exception {
+ when(applyCandidateAction.getConflicts()).thenReturn(List.of(mock(FileConflict.class)));
+
+ int exitCode = commandLine.execute(CliConstants.Commands.REVERT, CliConstants.Commands.PERFORM,
+ CliConstants.DIR, installationDir.toString(),
+ CliConstants.REVISION, "abcd",
+ CliConstants.NO_CONFLICTS_ONLY);
+
+ assertEquals(ReturnCodes.PROCESSING_ERROR, exitCode);
+ assertThat(getErrorOutput())
+ .contains(CliMessages.MESSAGES.cancelledByConfilcts().getMessage());
+
+ verify(applyCandidateAction, Mockito.never()).applyUpdate(any());
+ }
+
+ @Test
+ public void noConflictArgumentHasNoEffect_WhenNoConflictsAreFound() throws Exception {
+ when(applyCandidateAction.getConflicts()).thenReturn(Collections.emptyList());
+
+ int exitCode = commandLine.execute(CliConstants.Commands.REVERT, CliConstants.Commands.PERFORM,
+ CliConstants.DIR, installationDir.toString(),
+ CliConstants.REVISION, "abcd",
+ CliConstants.NO_CONFLICTS_ONLY);
+
+ assertEquals(ReturnCodes.SUCCESS, exitCode);
+ verify(applyCandidateAction).applyUpdate(ApplyCandidateAction.Type.REVERT);
+ }
+
@Override
protected MavenOptions getCapturedMavenOptions() throws Exception {
verify(historyAction).prepareRevert(eq(new SavedState("abcd")), mavenOptions.capture(), any(), any());
diff --git a/prospero-cli/src/test/java/org/wildfly/prospero/cli/commands/UpdateCommandTest.java b/prospero-cli/src/test/java/org/wildfly/prospero/cli/commands/UpdateCommandTest.java
index 7b76e405..855a40f5 100644
--- a/prospero-cli/src/test/java/org/wildfly/prospero/cli/commands/UpdateCommandTest.java
+++ b/prospero-cli/src/test/java/org/wildfly/prospero/cli/commands/UpdateCommandTest.java
@@ -39,6 +39,7 @@
import org.wildfly.prospero.actions.ApplyCandidateAction;
import org.wildfly.prospero.actions.UpdateAction;
import org.wildfly.prospero.api.ArtifactChange;
+import org.wildfly.prospero.api.FileConflict;
import org.wildfly.prospero.api.MavenOptions;
import org.wildfly.prospero.cli.ActionFactory;
import org.wildfly.prospero.cli.CliMessages;
@@ -51,7 +52,9 @@
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
@@ -303,6 +306,37 @@ public void spliRepositoriesFromArgument() throws Exception {
}
+ @Test
+ public void noConflictArgumentFailsCommand_WhenConflictsAreFound() throws Exception {
+ when(updateAction.findUpdates()).thenReturn(new UpdateSet(List.of(change("1.0.0", "1.0.1"))));
+ when(updateAction.buildUpdate(any())).thenReturn(true);
+ when(applyCandidateAction.getConflicts()).thenReturn(List.of(mock(FileConflict.class)));
+
+ int exitCode = commandLine.execute(CliConstants.Commands.UPDATE, CliConstants.Commands.PERFORM,
+ CliConstants.DIR, installationDir.toString(),
+ CliConstants.NO_CONFLICTS_ONLY);
+
+ assertEquals(ReturnCodes.PROCESSING_ERROR, exitCode);
+ assertThat(getErrorOutput())
+ .contains(CliMessages.MESSAGES.cancelledByConfilcts().getMessage());
+
+ verify(applyCandidateAction, Mockito.never()).applyUpdate(any());
+ }
+
+ @Test
+ public void noConflictArgumentHasNoEffect_WhenNoConflictsAreFound() throws Exception {
+ when(updateAction.findUpdates()).thenReturn(new UpdateSet(List.of(change("1.0.0", "1.0.1"))));
+ when(updateAction.buildUpdate(any())).thenReturn(true);
+ when(applyCandidateAction.getConflicts()).thenReturn(Collections.emptyList());
+
+ int exitCode = commandLine.execute(CliConstants.Commands.UPDATE, CliConstants.Commands.PERFORM,
+ CliConstants.DIR, installationDir.toString(),
+ CliConstants.NO_CONFLICTS_ONLY);
+
+ assertEquals(ReturnCodes.SUCCESS, exitCode);
+ verify(applyCandidateAction).applyUpdate(ApplyCandidateAction.Type.UPDATE);
+ }
+
private ArtifactChange change(String oldVersion, String newVersion) {
return ArtifactChange.updated(new DefaultArtifact("org.foo", "bar", null, oldVersion),
new DefaultArtifact("org.foo", "bar", null, newVersion));
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 da92cd14..8517672e 100644
--- a/prospero-common/src/main/java/org/wildfly/prospero/ProsperoLogger.java
+++ b/prospero-common/src/main/java/org/wildfly/prospero/ProsperoLogger.java
@@ -26,6 +26,7 @@
import org.jboss.logging.annotations.Message;
import org.jboss.logging.annotations.MessageLogger;
import org.wildfly.channel.InvalidChannelMetadataException;
+import org.wildfly.prospero.actions.ApplyCandidateAction;
import org.wildfly.prospero.actions.FeaturesAddAction;
import org.wildfly.prospero.api.exceptions.ArtifactPromoteException;
import org.wildfly.prospero.api.exceptions.ChannelDefinitionException;
@@ -389,4 +390,15 @@ public interface ProsperoLogger extends BasicLogger {
@Message(id = 271, value = "Unable to evaluate symbolic link %s.")
RuntimeException unableToEvaluateSymbolicLink(Path symlink, @Cause IOException e);
+
+ @Message(id = 272, value = "The server [%s] has been modified after the candidate has been created [%s].")
+ InvalidUpdateCandidateException staleCandidate(Path originalServer, Path candiadate);
+
+ @Message(id = 273, value = "The folder [%s] doesn't contain a server candidate.")
+ InvalidUpdateCandidateException notCandidate(Path candidateServer);
+
+ @Message(id = 274, value = "The candidate at [%s] was not prepared for %s operation.")
+ InvalidUpdateCandidateException wrongCandidateOperation(Path candidateServer, ApplyCandidateAction.Type operationType);
+
+
}
diff --git a/prospero-common/src/main/java/org/wildfly/prospero/actions/FeaturesAddAction.java b/prospero-common/src/main/java/org/wildfly/prospero/actions/FeaturesAddAction.java
index fc6b49c8..1b9a9b5f 100644
--- a/prospero-common/src/main/java/org/wildfly/prospero/actions/FeaturesAddAction.java
+++ b/prospero-common/src/main/java/org/wildfly/prospero/actions/FeaturesAddAction.java
@@ -744,9 +744,9 @@ public PrepareCandidateAction newPrepareCandidateActionInstance(
}
@Override
- public ApplyCandidateAction newApplyCandidateActionInstance(Path candidateDir)
+ public ApplyCandidateAction newApplyCandidateActionInstance(Path candidatePath)
throws ProvisioningException, OperationException {
- return new ApplyCandidateAction(installDir, candidateDir);
+ return new ApplyCandidateAction(installDir, candidatePath);
}
}
}
diff --git a/prospero-common/src/main/java/org/wildfly/prospero/spi/ProsperoInstallationManager.java b/prospero-common/src/main/java/org/wildfly/prospero/spi/ProsperoInstallationManager.java
index 1568b69a..4e543abf 100644
--- a/prospero-common/src/main/java/org/wildfly/prospero/spi/ProsperoInstallationManager.java
+++ b/prospero-common/src/main/java/org/wildfly/prospero/spi/ProsperoInstallationManager.java
@@ -5,8 +5,10 @@
import org.wildfly.channel.ChannelManifestCoordinate;
import org.wildfly.channel.MavenCoordinate;
import org.wildfly.installationmanager.ArtifactChange;
+import org.wildfly.installationmanager.CandidateType;
import org.wildfly.installationmanager.Channel;
import org.wildfly.installationmanager.ChannelChange;
+import org.wildfly.installationmanager.FileConflict;
import org.wildfly.installationmanager.HistoryResult;
import org.wildfly.installationmanager.InstallationChanges;
import org.wildfly.installationmanager.ManifestVersion;
@@ -16,11 +18,13 @@
import org.wildfly.installationmanager.spi.InstallationManager;
import org.wildfly.installationmanager.spi.OsShell;
import org.wildfly.prospero.ProsperoLogger;
+import org.wildfly.prospero.actions.ApplyCandidateAction;
import org.wildfly.prospero.actions.InstallationExportAction;
import org.wildfly.prospero.actions.InstallationHistoryAction;
import org.wildfly.prospero.actions.MetadataAction;
import org.wildfly.prospero.actions.UpdateAction;
import org.wildfly.prospero.api.MavenOptions.Builder;
+import org.wildfly.prospero.api.exceptions.InvalidUpdateCandidateException;
import org.wildfly.prospero.galleon.GalleonCallbackAdapter;
import org.wildfly.prospero.metadata.ManifestVersionRecord;
import org.wildfly.prospero.spi.internal.CliProvider;
@@ -117,6 +121,61 @@ public boolean prepareUpdate(Path targetDir, List repositories) thro
}
}
+ @Override
+ public Collection verifyCandidate(Path candidatePath, CandidateType candidateType) throws Exception {
+ final ApplyCandidateAction applyCandidateAction = actionFactory.getApplyCandidateAction(candidatePath);
+ final ApplyCandidateAction.Type operation;
+ switch (candidateType) {
+ case UPDATE:
+ operation = ApplyCandidateAction.Type.UPDATE;
+ break;
+ case REVERT:
+ operation = ApplyCandidateAction.Type.REVERT;
+ break;
+ default:
+ throw new IllegalArgumentException("Unsupported candidate type: " + candidateType);
+ }
+
+ final ApplyCandidateAction.ValidationResult validationResult = applyCandidateAction.verifyCandidate(operation);
+ switch (validationResult) {
+ case OK:
+ // we're good, continue
+ break;
+ case STALE:
+ throw ProsperoLogger.ROOT_LOGGER.staleCandidate(installationDir, candidatePath);
+ case NO_CHANGES:
+ throw ProsperoLogger.ROOT_LOGGER.noChangesAvailable(installationDir, candidatePath);
+ case NOT_CANDIDATE:
+ throw ProsperoLogger.ROOT_LOGGER.notCandidate(candidatePath);
+ case WRONG_TYPE:
+ throw ProsperoLogger.ROOT_LOGGER.wrongCandidateOperation(candidatePath, operation);
+ default:
+ // unexpected validation type - include the error in the description
+ throw new InvalidUpdateCandidateException(String.format("The candidate server %s is invalid - %s.", candidatePath, validationResult));
+ }
+
+ return map(applyCandidateAction.getConflicts(), ProsperoInstallationManager::mapFileConflict);
+ }
+
+ private static FileConflict mapFileConflict(org.wildfly.prospero.api.FileConflict fileConflict) {
+ return new FileConflict(Path.of(fileConflict.getRelativePath()), map(fileConflict.getUserChange()), map(fileConflict.getUpdateChange()), fileConflict.getResolution() == org.wildfly.prospero.api.FileConflict.Resolution.UPDATE);
+ }
+
+ private static FileConflict.Status map(org.wildfly.prospero.api.FileConflict.Change change) {
+ switch (change) {
+ case MODIFIED:
+ return FileConflict.Status.MODIFIED;
+ case ADDED:
+ return FileConflict.Status.ADDED;
+ case REMOVED:
+ return FileConflict.Status.REMOVED;
+ case NONE:
+ return FileConflict.Status.NONE;
+ default:
+ throw new IllegalArgumentException("Unknown file conflict change: " + change);
+ }
+ }
+
@Override
public List findUpdates(List repositories) throws Exception {
try (UpdateAction updateAction = actionFactory.getUpdateAction(map(repositories, ProsperoInstallationManager::mapRepository))) {
@@ -197,24 +256,34 @@ public String generateApplyRevertCommand(Path scriptHome, Path candidatePath) th
@Override
public String generateApplyUpdateCommand(Path scriptHome, Path candidatePath, OsShell shell) throws OperationNotAvailableException {
+ return generateApplyUpdateCommand(scriptHome, candidatePath, shell, false);
+ }
+
+ @Override
+ public String generateApplyRevertCommand(Path scriptHome, Path candidatePath, OsShell shell) throws OperationNotAvailableException {
+ return generateApplyUpdateCommand(scriptHome, candidatePath, shell, false);
+ }
+
+ @Override
+ public String generateApplyUpdateCommand(Path scriptHome, Path candidatePath, OsShell shell, boolean noConflictsOnly) throws OperationNotAvailableException {
final Optional cliProviderLoader = ServiceLoader.load(CliProvider.class).findFirst();
if (cliProviderLoader.isEmpty()) {
throw new OperationNotAvailableException("Installation manager does not support CLI operations.");
}
final CliProvider cliProvider = cliProviderLoader.get();
- return escape(scriptHome.resolve(cliProvider.getScriptName(shell))) + " " + cliProvider.getApplyUpdateCommand(installationDir, candidatePath);
+ return escape(scriptHome.resolve(cliProvider.getScriptName(shell))) + " " + cliProvider.getApplyUpdateCommand(installationDir, candidatePath, false);
}
@Override
- public String generateApplyRevertCommand(Path scriptHome, Path candidatePath, OsShell shell) throws OperationNotAvailableException {
+ public String generateApplyRevertCommand(Path scriptHome, Path candidatePath, OsShell shell, boolean noConflictsOnly) throws OperationNotAvailableException {
final Optional cliProviderLoader = ServiceLoader.load(CliProvider.class).findFirst();
if (cliProviderLoader.isEmpty()) {
throw new OperationNotAvailableException("Installation manager does not support CLI operations.");
}
final CliProvider cliProvider = cliProviderLoader.get();
- return escape(scriptHome.resolve(cliProvider.getScriptName(shell))) + " " + cliProvider.getApplyRevertCommand(installationDir, candidatePath);
+ return escape(scriptHome.resolve(cliProvider.getScriptName(shell))) + " " + cliProvider.getApplyRevertCommand(installationDir, candidatePath, noConflictsOnly);
}
@Override
@@ -345,6 +414,10 @@ protected InstallationExportAction getInstallationExportAction() {
return new InstallationExportAction(server);
}
+ protected ApplyCandidateAction getApplyCandidateAction(Path candidateDir) throws ProvisioningException, OperationException {
+ return new ApplyCandidateAction(server, candidateDir);
+ }
+
org.wildfly.prospero.api.MavenOptions getMavenOptions() {
return mavenOptions;
}
diff --git a/prospero-common/src/main/java/org/wildfly/prospero/spi/internal/CliProvider.java b/prospero-common/src/main/java/org/wildfly/prospero/spi/internal/CliProvider.java
index d29d6ac7..a4f4a7c2 100644
--- a/prospero-common/src/main/java/org/wildfly/prospero/spi/internal/CliProvider.java
+++ b/prospero-common/src/main/java/org/wildfly/prospero/spi/internal/CliProvider.java
@@ -37,16 +37,18 @@ public interface CliProvider {
*
* @param installationPath
* @param candidatePath
+ * @param noConflictsOnly - whether to append the no-conflicts-only flag
* @return
*/
- String getApplyUpdateCommand(Path installationPath, Path candidatePath);
+ String getApplyUpdateCommand(Path installationPath, Path candidatePath, boolean noConflictsOnly);
/**
* generates command used to apply a revert candidate in {@code candidatePath} into {@code installationPath}
*
* @param installationPath
* @param candidatePath
+ * @param noConflictsOnly - whether to append the no-conflicts-only flag
* @return
*/
- String getApplyRevertCommand(Path installationPath, Path candidatePath);
+ String getApplyRevertCommand(Path installationPath, Path candidatePath, boolean noConflictsOnly);
}
diff --git a/prospero-common/src/test/java/org/wildfly/prospero/spi/ProsperoInstallationManagerTest.java b/prospero-common/src/test/java/org/wildfly/prospero/spi/ProsperoInstallationManagerTest.java
index dc08526d..408e2c61 100644
--- a/prospero-common/src/test/java/org/wildfly/prospero/spi/ProsperoInstallationManagerTest.java
+++ b/prospero-common/src/test/java/org/wildfly/prospero/spi/ProsperoInstallationManagerTest.java
@@ -26,8 +26,11 @@
import org.wildfly.channel.Channel;
import org.wildfly.channel.ChannelManifestCoordinate;
import org.wildfly.channel.Repository;
+import org.wildfly.installationmanager.CandidateType;
+import org.wildfly.installationmanager.FileConflict;
import org.wildfly.installationmanager.InstallationChanges;
import org.wildfly.installationmanager.MavenOptions;
+import org.wildfly.prospero.actions.ApplyCandidateAction;
import org.wildfly.prospero.actions.InstallationHistoryAction;
import org.wildfly.prospero.actions.UpdateAction;
import org.wildfly.prospero.api.ChannelChange;
@@ -35,9 +38,12 @@
import org.wildfly.prospero.updates.UpdateSet;
import java.nio.file.Path;
+import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
@@ -66,6 +72,9 @@ public class ProsperoInstallationManagerTest {
@Mock
private InstallationHistoryAction historyAction;
+ @Mock
+ private ApplyCandidateAction applyCandidateAction;
+
@Rule
public TemporaryFolder temp = new TemporaryFolder();
@@ -245,4 +254,37 @@ public void mapMavenOptions() throws Exception {
assertTrue(mavenOptions.isNoLocalCache());
assertNull(mavenOptions.getLocalCache());
}
+
+ @Test
+ public void testCheckUpdatesMapsConflicts() throws Exception {
+ final ProsperoInstallationManager mgr = new ProsperoInstallationManager(actionFactory);
+
+ when(actionFactory.getApplyCandidateAction(any())).thenReturn(applyCandidateAction);
+ when(applyCandidateAction.verifyCandidate(any())).thenReturn(ApplyCandidateAction.ValidationResult.OK);
+ when(applyCandidateAction.getConflicts()).thenReturn(List.of(
+ org.wildfly.prospero.api.FileConflict.userModified("foo/bar").updateModified().userPreserved(),
+ org.wildfly.prospero.api.FileConflict.userModified("system/file_a").updateModified().overwritten(),
+ org.wildfly.prospero.api.FileConflict.userAdded("system/file_b").updateAdded().overwritten()
+ ));
+
+ final Collection conflicts = mgr.verifyCandidate(Path.of("candidate"), CandidateType.UPDATE);
+ assertThat(conflicts)
+ .contains(
+ new FileConflict(Path.of("foo/bar"), FileConflict.Status.MODIFIED, FileConflict.Status.MODIFIED, false),
+ new FileConflict(Path.of("system/file_a"), FileConflict.Status.MODIFIED, FileConflict.Status.MODIFIED, true),
+ new FileConflict(Path.of("system/file_b"), FileConflict.Status.ADDED, FileConflict.Status.ADDED, true)
+ );
+ }
+
+ @Test
+ public void testCheckUpdatesThrowsVerificationExceptions() throws Exception {
+ final ProsperoInstallationManager mgr = new ProsperoInstallationManager(actionFactory);
+
+ when(actionFactory.getApplyCandidateAction(any())).thenReturn(applyCandidateAction);
+ when(applyCandidateAction.verifyCandidate(any())).thenReturn(ApplyCandidateAction.ValidationResult.STALE);
+
+ assertThatThrownBy(() -> mgr.verifyCandidate(Path.of("candidate"), CandidateType.UPDATE))
+ .hasMessageContaining("has been modified after the candidate has been created");
+
+ }
}
\ No newline at end of file