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 2610ba4e2..0f7231ad7 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 6fa3b30e4..95bdf7da4 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,5 @@ 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"; } 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 b86b8f95a..9d1bde853 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 @@ -52,12 +52,16 @@ ) 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) throws OperationException, ProvisioningException { List artifactUpdates = applyCandidateAction.findUpdates().getArtifactUpdates(); console.printArtifactChanges(artifactUpdates); final List conflicts = applyCandidateAction.getConflicts(); FileConflictPrinter.print(conflicts, console); + 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 +96,9 @@ public static class PerformCommand extends AbstractMavenCommand { @CommandLine.Option(names = {CliConstants.Y, CliConstants.YES}) boolean yes; + @CommandLine.Option(names = {"--no-conflicts-only"}) + boolean noConflictsOnly; + public PerformCommand(CliConsole console, ActionFactory actionFactory) { super(console, actionFactory); } @@ -121,7 +128,7 @@ public Integer call() throws Exception { validateRevertCandidate(installationDirectory, tempDirectory, applyCandidateAction); - applyCandidate(console, applyCandidateAction, yes); + applyCandidate(console, applyCandidateAction, yes, noConflictsOnly); } catch (IOException e) { throw ProsperoLogger.ROOT_LOGGER.unableToCreateTemporaryDirectory(e); } @@ -147,6 +154,9 @@ public static class ApplyCommand extends AbstractCommand { @CommandLine.Option(names = {CliConstants.Y, CliConstants.YES}) boolean yes; + @CommandLine.Option(names = {CliConstants.NO_CONFLICTS_ONLY}) + boolean noConflictsOnly; + public ApplyCommand(CliConsole console, ActionFactory actionFactory) { super(console, actionFactory); } @@ -162,7 +172,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); 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 d1cdc898e..3c64c9b40 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,9 @@ public static class ApplyCommand extends AbstractCommand { @CommandLine.Option(names = {CliConstants.Y, CliConstants.YES}) boolean yes; + @CommandLine.Option(names = {"--no-conflicts-only"}) + boolean noConflictsOnly; + public ApplyCommand(CliConsole console, ActionFactory actionFactory) { super(console, actionFactory); } @@ -257,6 +268,10 @@ public Integer call() throws Exception { final List conflicts = applyCandidateAction.getConflicts(); FileConflictPrinter.print(conflicts, console); + 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/resources/UsageMessages.properties b/prospero-cli/src/main/resources/UsageMessages.properties index acfd47fef..5575fef15 100644 --- a/prospero-cli/src/main/resources/UsageMessages.properties +++ b/prospero-cli/src/main/resources/UsageMessages.properties @@ -194,6 +194,8 @@ 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. # # 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 6bd1c71e9..1f2f7c837 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,39 @@ 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); + } + 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 dce614283..80a26fe19 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,33 @@ 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); + } } 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 843f9f8d5..652e285f5 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 7b76e4052..855a40f56 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));