diff --git a/.github/release/maven-settings.xml.gpg b/.github/release/maven-settings.xml.gpg new file mode 100644 index 000000000..3bf3100ef Binary files /dev/null and b/.github/release/maven-settings.xml.gpg differ diff --git a/.github/workflows/future.yml b/.github/workflows/future.yml deleted file mode 100644 index 0aaf939f0..000000000 --- a/.github/workflows/future.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: Build - -on: - pull_request: - branches: - - 'future' - paths-ignore: - - '.gitignore' - - 'examples' - - 'README.md' - -jobs: - build: - name: ${{ matrix.os }}-${{ matrix.jdk }} - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: [ ubuntu-latest, windows-latest ] - jdk: [ 11, 17, 21 ] - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - uses: actions/cache@v1 - with: - path: ~/.m2/repository - key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} - restore-keys: | - ${{ runner.os }}-maven- - - name: Set up JDK ${{ matrix.jdk }} - uses: actions/setup-java@v2 - with: - java-version: ${{ matrix.jdk }} - distribution: 'adopt' - cache: maven - - name: Build and test - run: mvn clean install -DallTests -Pdist - - uses: actions/upload-artifact@v2 - if: failure() - with: - name: surefire-reports - path: '**/surefire-reports/*.txt' diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 26785ace6..5fb48c5f3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -3,7 +3,7 @@ name: Build on: push: branches: - - main + - 1.3.x paths-ignore: - '.gitignore' - 'examples' @@ -24,7 +24,7 @@ jobs: fail-fast: false matrix: os: [ ubuntu-latest, windows-latest ] - jdk: [ 11, 17, 21 ] + jdk: [ 17, 21 ] steps: - uses: actions/checkout@v2 with: @@ -40,7 +40,7 @@ jobs: - uses: actions/upload-artifact@v4 if: failure() with: - name: surefire-reports + name: surefire-reports-${{ matrix.os }}-${{ matrix.jdk }} path: '**/surefire-reports/*.txt' pmd: name: ubuntu-latest-pmd @@ -52,7 +52,7 @@ jobs: - name: Set up JDK uses: actions/setup-java@v2 with: - java-version: 11 + java-version: 17 distribution: 'adopt' cache: maven - name: Build and test diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 68cf8c7d2..0727451e3 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -21,15 +21,23 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-java@v1 with: - java-version: 11 + java-version: 17 - name: maven release ${{steps.metadata.outputs.current-version}} run: | java -version + gpg --quiet --batch --yes --decrypt --passphrase="${{ secrets.SECRET_PASSPHRASE }}" --output maven-settings.xml .github/release/maven-settings.xml.gpg git config --global user.name "WildFly Prospero CI" git config --global user.email "wildfly-dev@lists.jboss.org" git checkout -b release - mvn -B release:prepare -Pdist,jboss-release -DreleaseVersion=${{steps.metadata.outputs.current-version}} -DdevelopmentVersion=${{steps.metadata.outputs.next-version}} + mvn -B release:prepare -Pdist,jboss-release -DreleaseVersion=${{steps.metadata.outputs.current-version}} -DdevelopmentVersion=${{steps.metadata.outputs.next-version}} -s maven-settings.xml git checkout ${{github.base_ref}} git rebase release + mvn -B release:perform -Pdist,jboss-release -s maven-settings.xml git push git push --tags + - name: Create GH release + uses: softprops/action-gh-release@v2 + with: + files: dist/build/target/prospero-${{steps.metadata.outputs.current-version}}.zip + tag_name: ${{steps.metadata.outputs.current-version}} + prerelease: contains(${{steps.metadata.outputs.current-version}}, "Beta") diff --git a/integration-tests/src/test/java/org/wildfly/prospero/it/cli/CacheManifestTest.java b/integration-tests/src/test/java/org/wildfly/prospero/it/cli/CacheManifestTest.java index cc89893d8..3d75c6d7a 100644 --- a/integration-tests/src/test/java/org/wildfly/prospero/it/cli/CacheManifestTest.java +++ b/integration-tests/src/test/java/org/wildfly/prospero/it/cli/CacheManifestTest.java @@ -102,8 +102,10 @@ public void setUp() throws Exception { )); // create and deploy second manifest - secondManifest = new ChannelManifest(null, null, null, null, - List.of(new Stream("org.wildfly.core", "wildfly-controller", UPGRADE_VERSION))); + secondManifest = new ChannelManifest.Builder() + .setSchemaVersion("1.0.0") + .addStreams(new Stream("org.wildfly.core", "wildfly-controller", UPGRADE_VERSION)) + .build(); repositoryUtils.deployArtifact(new DefaultArtifact( "org.test.channels", "wf-core-second", @@ -321,7 +323,7 @@ public void updateWithManifestNotInCacheAndNotAvailableFails() throws Exception assertThatThrownBy(()->performUpdate()) .isInstanceOf(UnresolvedChannelMetadataException.class) .hasFieldOrPropertyWithValue("missingArtifacts", - Set.of(new ChannelMetadataCoordinate("org.test.channels", "wf-core-base", "", + Set.of(new ChannelMetadataCoordinate("org.test.channels", "wf-core-base", "1.0.0", ChannelManifest.CLASSIFIER, ChannelManifest.EXTENSION))); } @@ -383,6 +385,7 @@ private ChannelManifest updateWildflyController() throws MalformedURLException { return new ChannelManifest( sourceManifest.getSchemaVersion(), sourceManifest.getId(), + sourceManifest.getLogicalVersion(), sourceManifest.getDescription(), sourceManifest.getManifestRequirements(), streams); diff --git a/pom.xml b/pom.xml index ed0a5f332..ba939cc5f 100644 --- a/pom.xml +++ b/pom.xml @@ -39,24 +39,24 @@ prospero-standalone-galleon-pack ${project.groupId} - 2.3.17.Final + 2.3.18.Final 1.17.1 2.17.0 - 3.0.0 + 3.1.1 1.12.0 4.5.14 4.4.16 3.1.0 3.0.2 - 3.25.0 - 3.8.0 - 1.9.21 + 3.26.0 + 3.8.1 + 1.9.22 3.6.3 2.1.1 1.27 3.5.0 1.5.0.Final - 6.0.3.Final + 6.0.4.Final 6.9.0.202403050737-r 2.17.0 2.1.19.Final @@ -65,18 +65,18 @@ 2.1.5.Final 3.8.16.Final 1.7.0.Final - 7.2.0.Final - 1.0.3.Final - 2.4.1.Final - 1.2.1.Final - 5.14.1 + 7.3.0.Final + 2.0.0.Beta1 + 2.4.2.Final + 1.3.0.Beta2 + 5.14.2 2.0.7 2.2 4.13.2 3.6.0 - 1.1.0.Final + 1.2.1.Final 3.10.1 - 33.0.2.Final + 34.0.0.Final 4.7.6 1.19.0 3.26.3 @@ -518,10 +518,10 @@ org.apache.maven.plugins maven-compiler-plugin - 11 - 11 - 11 - 11 + 17 + 17 + 17 + 17 @@ -565,6 +565,8 @@ @{project.version} clean install + true + false 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 11684f402..daaf29292 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 @@ -716,4 +716,10 @@ default String candidateApplyRollbackSuccess() { default String candidateApplyRollbackFailure(Path backup) { return format(bundle.getString("prospero.candidate.apply.error.rollback_error.desc"), backup); } + + 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..67baaaf89 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 b86b8f95a..3116ccb70 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,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 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 +102,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 +134,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 +160,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 +181,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 d1cdc898e..675181a3a 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 4224f2d39..f0a37700f 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 385c6772a..8e4cd917b 100644 --- a/prospero-cli/src/main/resources/UsageMessages.properties +++ b/prospero-cli/src/main/resources/UsageMessages.properties @@ -155,7 +155,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|@ \ @@ -197,6 +196,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 @@ -281,6 +283,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..c39e202a7 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 dce614283..5dfe1eaad 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 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)); 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 cab8ff124..bba33737a 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; @@ -392,4 +393,14 @@ public interface ProsperoLogger extends BasicLogger { @Message(id = 272, value = "Failed to apply the candidate changes due to: %s") String failedToApplyCandidate(String reason); + + @Message(id = 273, value = "The server [%s] has been modified after the candidate has been created [%s].") + InvalidUpdateCandidateException staleCandidate(Path originalServer, Path candiadate); + + @Message(id = 274, value = "The folder [%s] doesn't contain a server candidate.") + InvalidUpdateCandidateException notCandidate(Path candidateServer); + + @Message(id = 275, 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 925e1b175..6cd3aab1e 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 @@ -753,9 +753,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/actions/InstallationHistoryAction.java b/prospero-common/src/main/java/org/wildfly/prospero/actions/InstallationHistoryAction.java index 727508f19..8f479894f 100644 --- a/prospero-common/src/main/java/org/wildfly/prospero/actions/InstallationHistoryAction.java +++ b/prospero-common/src/main/java/org/wildfly/prospero/actions/InstallationHistoryAction.java @@ -142,7 +142,7 @@ public void prepareRevert(SavedState savedState, MavenOptions mavenOptions, List prepareCandidateAction.buildCandidate(targetDir, galleonEnv, ApplyCandidateAction.Type.REVERT, provisioningConfig, - UpdateSet.EMPTY, (channels) -> revertMetadata.getManifestVersions()); + UpdateSet.EMPTY, revertMetadata::getManifestVersions); } ProsperoLogger.ROOT_LOGGER.revertCandidateCompleted(targetDir); 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 edbfd2199..f75fd9da0 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 @@ -17,6 +17,8 @@ package org.wildfly.prospero.actions; +import org.eclipse.aether.DefaultRepositorySystemSession; +import org.eclipse.aether.RepositorySystem; import org.jboss.galleon.Constants; import org.jboss.galleon.ProvisioningException; import org.jboss.logging.Logger; @@ -36,6 +38,7 @@ import org.wildfly.prospero.galleon.GalleonUtils; import org.wildfly.prospero.licenses.LicenseManager; 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; @@ -51,7 +54,8 @@ import java.util.List; import java.util.Objects; import java.util.Optional; -import java.util.function.Function; +import java.util.function.Supplier; + import org.jboss.galleon.api.Provisioning; import org.jboss.galleon.api.config.GalleonProvisioningConfig; @@ -91,7 +95,7 @@ boolean buildCandidate(Path targetDir, GalleonEnvironment galleonEnv, ApplyCandi */ boolean buildCandidate(Path targetDir, GalleonEnvironment galleonEnv, ApplyCandidateAction.Type operation, GalleonProvisioningConfig config, UpdateSet updateSet) throws ProvisioningException, OperationException { - return this.buildCandidate(targetDir, galleonEnv, operation, config, updateSet, this::getManifestVersionRecord); + return this.buildCandidate(targetDir, galleonEnv, operation, config, updateSet, () -> getManifestVersionRecord(galleonEnv)); } /** @@ -110,7 +114,7 @@ boolean buildCandidate(Path targetDir, GalleonEnvironment galleonEnv, ApplyCandi */ boolean buildCandidate(Path targetDir, GalleonEnvironment galleonEnv, ApplyCandidateAction.Type operation, GalleonProvisioningConfig config, UpdateSet updateSet, - Function, Optional> manifestVersionRecordSupplier) throws ProvisioningException, OperationException { + Supplier> manifestVersionRecordSupplier) throws ProvisioningException, OperationException { Objects.requireNonNull(manifestVersionRecordSupplier); doBuildUpdate(targetDir, galleonEnv, config, manifestVersionRecordSupplier); @@ -127,7 +131,7 @@ boolean buildCandidate(Path targetDir, GalleonEnvironment galleonEnv, ApplyCandi } private void doBuildUpdate(Path targetDir, GalleonEnvironment galleonEnv, GalleonProvisioningConfig provisioningConfig, - Function, Optional> manifestVersionResolver) + Supplier> manifestVersionResolver) throws ProvisioningException, OperationException { final Provisioning provMgr = galleonEnv.getProvisioning(); try { @@ -142,7 +146,7 @@ private void doBuildUpdate(Path targetDir, GalleonEnvironment galleonEnv, Galleo } - final Optional manifestRecord = manifestVersionResolver.apply(galleonEnv.getChannels()); + final Optional manifestRecord = manifestVersionResolver.get(); if (LOG.isTraceEnabled()) { LOG.tracef("Recording manifests: %s", manifestRecord.orElse(new ManifestVersionRecord())); @@ -165,10 +169,9 @@ private void doBuildUpdate(Path targetDir, GalleonEnvironment galleonEnv, Galleo } } - private Optional getManifestVersionRecord(List channels) { - final ProsperoManifestVersionResolver manifestResolver = new ProsperoManifestVersionResolver(mavenSessionManager); + private Optional getManifestVersionRecord(GalleonEnvironment galleonEnv) { try { - return Optional.of(manifestResolver.getCurrentVersions(channels)); + return Optional.of(ManifestVersionResolver.getCurrentVersions(galleonEnv.getChannelSession())); } catch (IOException e) { ProsperoLogger.ROOT_LOGGER.debug("Unable to retrieve current manifest versions", e); return Optional.empty(); @@ -177,7 +180,10 @@ private Optional getManifestVersionRecord(List c private void cacheManifests(ManifestVersionRecord manifestRecord, Path installDir) { try { - ArtifactCache.getInstance(installDir).cache(manifestRecord, mavenSessionManager.getResolvedArtifactVersions()); + final RepositorySystem system = mavenSessionManager.newRepositorySystem(); + final DefaultRepositorySystemSession session = mavenSessionManager.newRepositorySystemSession(system); + + ArtifactCache.getInstance(installDir).cache(manifestRecord, session.getLocalRepositoryManager()); } catch (IOException e) { ProsperoLogger.ROOT_LOGGER.debug("Unable to record manifests in the internal cache", e); } diff --git a/prospero-common/src/main/java/org/wildfly/prospero/actions/ProsperoManifestVersionResolver.java b/prospero-common/src/main/java/org/wildfly/prospero/actions/ProsperoManifestVersionResolver.java deleted file mode 100644 index 190d9f8e5..000000000 --- a/prospero-common/src/main/java/org/wildfly/prospero/actions/ProsperoManifestVersionResolver.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2024 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.actions; - -import org.jboss.logging.Logger; -import org.wildfly.channel.Channel; -import org.wildfly.channel.ChannelManifestMapper; -import org.wildfly.channel.MavenArtifact; -import org.wildfly.channel.MavenCoordinate; -import org.wildfly.prospero.metadata.ManifestVersionRecord; -import org.wildfly.prospero.metadata.ManifestVersionResolver; -import org.wildfly.prospero.wfchannel.MavenSessionManager; -import org.wildfly.prospero.wfchannel.ResolvedArtifactsStore; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.function.Supplier; - -/** - * Identifies manifests used to provision installation. - * It uses {@code ResolvedManifestVersions} to find already resolved manifests and falls back onto - * {@code ManifestVersionResolver} if not possible. - */ -class ProsperoManifestVersionResolver { - - private static final Logger LOG = Logger.getLogger(ProsperoManifestVersionResolver.class.getName()); - - private final ResolvedArtifactsStore manifestVersions; - - private final Supplier manifestVersionResolver; - - ProsperoManifestVersionResolver(MavenSessionManager mavenSessionManager) { - this.manifestVersions = mavenSessionManager.getResolvedArtifactVersions(); - this.manifestVersionResolver = () -> new ManifestVersionResolver( - mavenSessionManager.getProvisioningRepo(), - mavenSessionManager.newRepositorySystem()); - } - - ProsperoManifestVersionResolver(ResolvedArtifactsStore manifestVersions, ManifestVersionResolver manifestVersionResolver) { - this.manifestVersions = manifestVersions; - this.manifestVersionResolver = () -> manifestVersionResolver; - } - - /** - * attempt to resolve the current versions from artifacts recorded during provisioning. - * Fallback on artifacts in the local maven cache if not available. - * - * @param channels - * @return - * @throws IOException - */ - public ManifestVersionRecord getCurrentVersions(List channels) throws IOException { - final ManifestVersionRecord record = new ManifestVersionRecord(); - final ArrayList fallbackChannels = new ArrayList<>(); - for (Channel channel : channels) { - if (channel.getManifestCoordinate().getMaven() != null && channel.getManifestCoordinate().getMaven().getVersion() == null) { - final MavenCoordinate manifestCoord = channel.getManifestCoordinate().getMaven(); - if (LOG.isDebugEnabled()) { - LOG.debugf("Trying to lookup manifest %s", manifestCoord); - } - final MavenArtifact version = manifestVersions.getManifestVersion(manifestCoord.getGroupId(), manifestCoord.getArtifactId()); - if (version == null) { - if (LOG.isDebugEnabled()) { - LOG.debugf("Failed to lookup manifest %s in currently resolved artifacts", manifestCoord); - } - fallbackChannels.add(channel); - } else { - if (LOG.isDebugEnabled()) { - LOG.debugf("Manifest %s resolved in currently resolve artifacts, recording.", manifestCoord); - } - final String description = ChannelManifestMapper.from(version.getFile().toURI().toURL()).getDescription(); - record.addManifest(new ManifestVersionRecord.MavenManifest( - manifestCoord.getGroupId(), - manifestCoord.getArtifactId(), - version.getVersion(), - description)); - } - } else { - if (LOG.isDebugEnabled()) { - LOG.debugf("Manifest for channel %s will be resolved via fallback.", channel.getName()); - } - fallbackChannels.add(channel); - } - } - - if (LOG.isDebugEnabled()) { - LOG.debugf("Resolving channel manifests using fallback mechanisms."); - } - final ManifestVersionRecord currentVersions = manifestVersionResolver.get().getCurrentVersions(fallbackChannels); - currentVersions.getMavenManifests().forEach(record::addManifest); - currentVersions.getOpenManifests().forEach(record::addManifest); - currentVersions.getUrlManifests().forEach(record::addManifest); - - return record; - } -} diff --git a/prospero-common/src/main/java/org/wildfly/prospero/actions/ProvisioningAction.java b/prospero-common/src/main/java/org/wildfly/prospero/actions/ProvisioningAction.java index 212395fcc..7bca82a87 100644 --- a/prospero-common/src/main/java/org/wildfly/prospero/actions/ProvisioningAction.java +++ b/prospero-common/src/main/java/org/wildfly/prospero/actions/ProvisioningAction.java @@ -18,6 +18,8 @@ package org.wildfly.prospero.actions; import org.apache.commons.lang3.StringUtils; +import org.eclipse.aether.DefaultRepositorySystemSession; +import org.eclipse.aether.RepositorySystem; import org.eclipse.aether.resolution.ArtifactResult; import org.jboss.galleon.universe.maven.MavenUniverseException; import org.wildfly.channel.ArtifactCoordinate; @@ -143,8 +145,7 @@ public void provision(GalleonProvisioningConfig provisioningConfig, List version = manifestRecord.getMavenManifests().stream() + .filter(m -> m.getGroupId().equals(groupId) && m.getArtifactId().equals(artifactId)) + .map(ManifestVersionRecord.MavenManifest::getVersion) + .findFirst(); + return version + .map(v -> localRepositoryManager.getPathForLocalArtifact(new DefaultArtifact(groupId, artifactId, ChannelManifest.CLASSIFIER, ChannelManifest.EXTENSION, v))) + .map(p -> localRepositoryManager.getRepository().getBasedir().toPath().resolve(p).toFile()) + .map(f -> new MavenArtifact(groupId, artifactId, ChannelManifest.EXTENSION, ChannelManifest.CLASSIFIER, version.get(), f)) + .orElse(null); + } + private void init() throws IOException { Path artifactLog = cacheDir.resolve(CACHE_FILENAME); diff --git a/prospero-common/src/main/java/org/wildfly/prospero/galleon/CachedVersionResolver.java b/prospero-common/src/main/java/org/wildfly/prospero/galleon/CachedVersionResolver.java index 31de22592..be5871045 100644 --- a/prospero-common/src/main/java/org/wildfly/prospero/galleon/CachedVersionResolver.java +++ b/prospero-common/src/main/java/org/wildfly/prospero/galleon/CachedVersionResolver.java @@ -17,9 +17,6 @@ package org.wildfly.prospero.galleon; -import org.eclipse.aether.AbstractRepositoryListener; -import org.eclipse.aether.RepositoryEvent; -import org.eclipse.aether.RepositoryListener; import org.eclipse.aether.RepositorySystem; import org.eclipse.aether.RepositorySystemSession; import org.eclipse.aether.artifact.DefaultArtifact; @@ -28,6 +25,7 @@ import org.jboss.logging.Logger; import org.wildfly.channel.ArtifactCoordinate; import org.wildfly.channel.ArtifactTransferException; +import org.wildfly.channel.ChannelManifest; import org.wildfly.channel.ChannelMetadataCoordinate; import org.wildfly.channel.UnresolvedMavenArtifactException; import org.wildfly.channel.spi.MavenVersionsResolver; @@ -51,15 +49,12 @@ */ public class CachedVersionResolver implements MavenVersionsResolver { private static final Logger LOG = Logger.getLogger(CachedVersionResolver.class.getName()); - - private static final RepositoryListener NOOP_REPOSITORY_LISTENER = new AbstractRepositoryListener(){}; private final MavenVersionsResolver fallbackResolver; private final RepositorySystem system; private final RepositorySystemSession session; private final ArtifactCache artifactCache; private final Logger log = Logger.getLogger(CachedVersionResolver.class); - private final RepositoryListener listener; private final Function manifestVersionProvider; public CachedVersionResolver(MavenVersionsResolver fallbackResolver, ArtifactCache cache, RepositorySystem system, @@ -70,12 +65,30 @@ public CachedVersionResolver(MavenVersionsResolver fallbackResolver, ArtifactCac this.session = session; this.artifactCache = cache; this.manifestVersionProvider = manifestVersionProvider; - this.listener = session.getRepositoryListener() != null ? session.getRepositoryListener() : NOOP_REPOSITORY_LISTENER; } @Override public Set getAllVersions(String groupId, String artifactId, String extension, String classifier) { - return fallbackResolver.getAllVersions(groupId, artifactId, extension, classifier); + final Set allVersions = fallbackResolver.getAllVersions(groupId, artifactId, extension, classifier); + + if (!allVersions.isEmpty()) { + return allVersions; + } else { + if (ChannelManifest.EXTENSION.equals(extension) && ChannelManifest.CLASSIFIER.equals(classifier)) { + + ArtifactCoordinate a = new ArtifactCoordinate(groupId, artifactId, extension, classifier, ""); + // get version from manifest_versions to verify this is the latest version + final String version = manifestVersionProvider.apply(a); + if (LOG.isDebugEnabled()) { + LOG.debugf("Last used version for manifest %s is %s.", a, version); + } + + if (version != null) { + return Set.of(version); + } + } + return allVersions; + } } @Override @@ -158,17 +171,23 @@ public List resolveChannelMetadata(List history() throws Exception { final List results = new ArrayList<>(); for (SavedState savedState : revisions) { - results.add(new HistoryResult(savedState.getName(), savedState.getTimestamp(), savedState.getType().toString(), savedState.getMsg())); + results.add(new HistoryResult(savedState.getName(), savedState.getTimestamp(), savedState.getType().toString(), + savedState.getMsg(), Collections.emptyList())); } return results; } @@ -117,6 +122,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 +257,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 +415,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 d29d6ac75..a4f4a7c20 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/main/java/org/wildfly/prospero/wfchannel/MavenSessionManager.java b/prospero-common/src/main/java/org/wildfly/prospero/wfchannel/MavenSessionManager.java index 8fc901a17..fe12e7558 100644 --- a/prospero-common/src/main/java/org/wildfly/prospero/wfchannel/MavenSessionManager.java +++ b/prospero-common/src/main/java/org/wildfly/prospero/wfchannel/MavenSessionManager.java @@ -44,7 +44,6 @@ public class MavenSessionManager { private static final String AETHER_OFFLINE_PROTOCOLS_PROPERTY = "aether.offline.protocols"; public static final String AETHER_OFFLINE_PROTOCOLS_VALUE = "file"; private final Path provisioningRepo; - private final ProsperoMavenRepositoryListener repositoryListener = new ProsperoMavenRepositoryListener(); private boolean offline; public MavenSessionManager(MavenOptions mavenOptions) throws ProvisioningException { @@ -100,7 +99,6 @@ public DefaultRepositorySystemSession newRepositorySystemSession(RepositorySyste final DefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession(); final LocalRepository localRepo = new LocalRepository(provisioningRepo.toAbsolutePath().toFile()); - session.setRepositoryListener(repositoryListener); session.setLocalRepositoryManager(system.newLocalRepositoryManager(session, localRepo)); session.setOffline(offline); return session; @@ -117,13 +115,4 @@ public void setOffline(boolean offline) { public boolean isOffline() { return offline; } - - /** - * returns a {@code ResolvedArtifactsStore} containing artifacts resolved during that maven session. - * - * @return - */ - public ResolvedArtifactsStore getResolvedArtifactVersions() { - return repositoryListener; - } } diff --git a/prospero-common/src/main/java/org/wildfly/prospero/wfchannel/ProsperoMavenRepositoryListener.java b/prospero-common/src/main/java/org/wildfly/prospero/wfchannel/ProsperoMavenRepositoryListener.java deleted file mode 100644 index 2ea5f9900..000000000 --- a/prospero-common/src/main/java/org/wildfly/prospero/wfchannel/ProsperoMavenRepositoryListener.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2024 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.wfchannel; - -import org.eclipse.aether.AbstractRepositoryListener; -import org.eclipse.aether.RepositoryEvent; -import org.eclipse.aether.artifact.Artifact; -import org.wildfly.channel.ChannelManifest; -import org.wildfly.channel.MavenArtifact; - -import java.util.HashMap; -import java.util.Map; - -/** - * listener called every time an artifact is resolved by Maven. Keeps track of artifacts resolved by Maven - */ -class ProsperoMavenRepositoryListener extends AbstractRepositoryListener implements ResolvedArtifactsStore { - - private final Map manifestVersions = new HashMap<>(); - - @Override - public MavenArtifact getManifestVersion(String groupId, String artifactId) { - return manifestVersions.get(getKey(groupId, artifactId, ChannelManifest.CLASSIFIER, ChannelManifest.EXTENSION)); - } - - @Override - public void artifactResolved(RepositoryEvent event) { - final Artifact a = event.getArtifact(); - - if (a == null || a.getFile() == null) { - return; - } - - if (a.getClassifier() != null && a.getClassifier().equals(ChannelManifest.CLASSIFIER)) { - manifestVersions.put(getKey(a), - new MavenArtifact(a.getGroupId(), a.getArtifactId(), a.getExtension(), a.getClassifier(), a.getVersion(), a.getFile())); - } - } - - private static String getKey(Artifact a) { - return getKey(a.getGroupId(), a.getArtifactId(), a.getClassifier(), a.getExtension()); - } - - private static String getKey(String groupId, String artifactId, String classifier, String extension) { - return String.format("%s:%s:%s:%s", groupId, artifactId, nullable(classifier), nullable(extension)); - } - - private static String nullable(String txt) { - if (txt == null) { - return ""; - } - return txt; - } -} diff --git a/prospero-common/src/main/java/org/wildfly/prospero/wfchannel/ResolvedArtifactsStore.java b/prospero-common/src/main/java/org/wildfly/prospero/wfchannel/ResolvedArtifactsStore.java deleted file mode 100644 index 298155705..000000000 --- a/prospero-common/src/main/java/org/wildfly/prospero/wfchannel/ResolvedArtifactsStore.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2024 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.wfchannel; - -import org.wildfly.channel.MavenArtifact; - -/** - * a collection of artifacts resolved in the maven session - */ -public interface ResolvedArtifactsStore { - - /** - * queries the store to find a manifest resolved in this maven session matching the {@code GA} - * - * @param groupId - the {@code groupId} of the manifest - * @param artifactId - the {@code artifactId} of the manifest - * @return - {@code MavenArtifact} representing the resolved manifest or {@code null} if the manifest has not been resolved - */ - MavenArtifact getManifestVersion(String groupId, String artifactId); -} diff --git a/prospero-common/src/main/resources/prospero-installation-profiles.yaml b/prospero-common/src/main/resources/prospero-installation-profiles.yaml index 73372dada..454e4668d 100644 --- a/prospero-common/src/main/resources/prospero-installation-profiles.yaml +++ b/prospero-common/src/main/resources/prospero-installation-profiles.yaml @@ -14,4 +14,36 @@ manifest: maven: groupId: "org.wildfly.channels" - artifactId: "wildfly" \ No newline at end of file + artifactId: "wildfly" +- name: "wildfly-ee" + galleonConfiguration: "classpath:wildfly-ee-provisioning.xml" + channels: + - schemaVersion: "2.0.0" + name: "wildfly-ee" + repositories: + - id: "central" + url: "https://repo1.maven.org/maven2/" + - id: "jboss-public" + url: "https://repository.jboss.org/nexus/content/groups/public/" + - id: "mrrc" + url: "https://maven.repository.redhat.com/ga/" + manifest: + maven: + groupId: "org.wildfly.channels" + artifactId: "wildfly-ee" +- name: "wildfly-preview" + galleonConfiguration: "classpath:wildfly-preview-provisioning.xml" + channels: + - schemaVersion: "2.0.0" + name: "wildfly-preview" + repositories: + - id: "central" + url: "https://repo1.maven.org/maven2/" + - id: "jboss-public" + url: "https://repository.jboss.org/nexus/content/groups/public/" + - id: "mrrc" + url: "https://maven.repository.redhat.com/ga/" + manifest: + maven: + groupId: "org.wildfly.channels" + artifactId: "wildfly-preview" \ No newline at end of file diff --git a/prospero-common/src/main/resources/wildfly-ee-provisioning.xml b/prospero-common/src/main/resources/wildfly-ee-provisioning.xml new file mode 100644 index 000000000..602793181 --- /dev/null +++ b/prospero-common/src/main/resources/wildfly-ee-provisioning.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/prospero-common/src/main/resources/wildfly-preview-provisioning.xml b/prospero-common/src/main/resources/wildfly-preview-provisioning.xml new file mode 100644 index 000000000..f6c22b013 --- /dev/null +++ b/prospero-common/src/main/resources/wildfly-preview-provisioning.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/prospero-common/src/test/java/org/wildfly/channel/ChannelSessionTest.java b/prospero-common/src/test/java/org/wildfly/channel/ChannelSessionTest.java index 09631cca0..77b06c590 100644 --- a/prospero-common/src/test/java/org/wildfly/channel/ChannelSessionTest.java +++ b/prospero-common/src/test/java/org/wildfly/channel/ChannelSessionTest.java @@ -53,6 +53,7 @@ public void testExceptionShouldContainMissingUrlOnManifestNotFound() throws Exce Assert.assertTrue(e.getCause() instanceof FileNotFoundException); Assert.assertEquals("file:idontexist.yaml" ,e.getValidationMessages().get(0)); } catch (Exception e) { + e.printStackTrace(); Assert.fail("Expecting " + InvalidChannelMetadataException.class + " but " + e.getClass() + " was thrown"); } } diff --git a/prospero-common/src/test/java/org/wildfly/prospero/actions/ProsperoManifestVersionResolverTest.java b/prospero-common/src/test/java/org/wildfly/prospero/actions/ProsperoManifestVersionResolverTest.java deleted file mode 100644 index 88000f561..000000000 --- a/prospero-common/src/test/java/org/wildfly/prospero/actions/ProsperoManifestVersionResolverTest.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright 2024 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.actions; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; -import org.wildfly.channel.Channel; -import org.wildfly.channel.ChannelManifest; -import org.wildfly.channel.ChannelManifestCoordinate; -import org.wildfly.channel.ChannelManifestMapper; -import org.wildfly.channel.MavenArtifact; -import org.wildfly.prospero.metadata.ManifestVersionRecord; -import org.wildfly.prospero.metadata.ManifestVersionResolver; -import org.wildfly.prospero.wfchannel.ResolvedArtifactsStore; - -import java.io.File; -import java.net.URI; -import java.nio.file.Files; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - - -@RunWith(MockitoJUnitRunner.class) -public class ProsperoManifestVersionResolverTest { - protected static final String A_GROUP_ID = "org.test"; - protected static final String MANIFEST_ONE_ARTIFACT_ID = "manifest-one"; - protected static final String MANIFEST_TWO_ARTIFACT_ID = "manifest-two"; - protected static final String A_VERSION = "1.2.3"; - @Mock - public ResolvedArtifactsStore artifactVersions; - - @Mock - public ManifestVersionResolver manifestVersionResolver; - - @Rule - public TemporaryFolder temp = new TemporaryFolder(); - private File manifestFile; - private ProsperoManifestVersionResolver resolver; - - @Before - public void setUp() throws Exception { - manifestFile = temp.newFile("test"); - Files.writeString(manifestFile.toPath(), ChannelManifestMapper.toYaml( - new ChannelManifest("test", "test", "desc", null))); - - // when the fallback resolver is called return an empty record to make the test pass - when(manifestVersionResolver.getCurrentVersions(any())).thenReturn(new ManifestVersionRecord()); - - resolver = new ProsperoManifestVersionResolver(artifactVersions, manifestVersionResolver); - } - - @Test - public void versionResolvedDuringProvisioning() throws Exception { - whenManifestOneWasResolveDuringProvisioning(); - - final ManifestVersionRecord currentVersions = resolver.getCurrentVersions(List.of(new Channel.Builder() - .setManifestCoordinate(new ChannelManifestCoordinate(A_GROUP_ID, MANIFEST_ONE_ARTIFACT_ID)) - .build())); - - assertThat(currentVersions.getMavenManifests()) - .map(ManifestVersionRecord.MavenManifest::getVersion) - .containsOnly(A_VERSION); - assertThat(currentVersions.getMavenManifests()) - .map(ManifestVersionRecord.MavenManifest::getDescription) - .containsOnly("desc"); - } - - @Test - public void versionNotResolvedDuringProvisioning_FallsbackToMavenCache() throws Exception { - when(artifactVersions.getManifestVersion(A_GROUP_ID, MANIFEST_ONE_ARTIFACT_ID)).thenReturn(null); - - final List channels = List.of(new Channel.Builder() - .setManifestCoordinate(new ChannelManifestCoordinate(A_GROUP_ID, MANIFEST_ONE_ARTIFACT_ID)) - .build()); - resolver.getCurrentVersions(channels); - - verify(manifestVersionResolver).getCurrentVersions(channels); - } - - @Test - public void alreadyResolvedChannelsAreNotPassedThrough() throws Exception { - whenManifestOneWasResolveDuringProvisioning(); - - final List channels = List.of( - new Channel.Builder() - .setManifestCoordinate(new ChannelManifestCoordinate(A_GROUP_ID, MANIFEST_ONE_ARTIFACT_ID)) - .build(), - new Channel.Builder() - .setManifestCoordinate(new ChannelManifestCoordinate(A_GROUP_ID, MANIFEST_TWO_ARTIFACT_ID)) - .build()); - resolver.getCurrentVersions(channels); - - verify(manifestVersionResolver).getCurrentVersions(List.of(channels.get(1))); - } - - @Test - public void nonOpenMavenChannelsAreResolvedWithFallback() throws Exception { - whenManifestOneWasResolveDuringProvisioning(); - - final List channels = List.of( - new Channel.Builder() - .setManifestCoordinate(new ChannelManifestCoordinate(A_GROUP_ID, MANIFEST_ONE_ARTIFACT_ID, A_VERSION)) - .build(), - new Channel.Builder() - .setManifestUrl(new URI("http://test.te/manifest.yaml").toURL()) - .build()); - resolver.getCurrentVersions(channels); - - verify(manifestVersionResolver).getCurrentVersions(channels); - } - - private void whenManifestOneWasResolveDuringProvisioning() { - when(artifactVersions.getManifestVersion(A_GROUP_ID, MANIFEST_ONE_ARTIFACT_ID)).thenReturn( - new MavenArtifact(A_GROUP_ID, MANIFEST_ONE_ARTIFACT_ID, null, null, A_VERSION, manifestFile)); - } -} \ No newline at end of file diff --git a/prospero-common/src/test/java/org/wildfly/prospero/galleon/ArtifactCacheTest.java b/prospero-common/src/test/java/org/wildfly/prospero/galleon/ArtifactCacheTest.java index a77d658b0..999e39176 100644 --- a/prospero-common/src/test/java/org/wildfly/prospero/galleon/ArtifactCacheTest.java +++ b/prospero-common/src/test/java/org/wildfly/prospero/galleon/ArtifactCacheTest.java @@ -21,11 +21,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; -import org.wildfly.channel.ChannelManifest; import org.wildfly.channel.MavenArtifact; -import org.wildfly.prospero.metadata.ManifestVersionRecord; -import org.wildfly.prospero.wfchannel.MavenSessionManager; -import org.wildfly.prospero.wfchannel.ResolvedArtifactsStore; import java.io.File; import java.io.IOException; @@ -38,8 +34,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; public class ArtifactCacheTest { @@ -158,84 +152,6 @@ public void getArtifactDoesntReturnArtifactIfTheHashIsDifferent() throws Excepti assertEquals(Optional.empty(), cachedArtifact); } - @Test - public void cacheMavenManifests_ResolvedInList() throws Exception { - final ManifestVersionRecord record = new ManifestVersionRecord(); - record.addManifest(new ManifestVersionRecord.MavenManifest("foo", "bar", "1.2.3", "")); - final MavenSessionManager msm = mock(MavenSessionManager.class); - final ResolvedArtifactsStore manifestVersions = mock(ResolvedArtifactsStore.class); - when(msm.getResolvedArtifactVersions()).thenReturn(manifestVersions); - final File testFile = temp.newFile("test"); - Files.writeString(testFile.toPath(), "test file"); - when(manifestVersions.getManifestVersion("foo", "bar")).thenReturn( - new MavenArtifact("foo", "bar", null, null, "1.2.3", testFile)); - - cache.cache(record, msm.getResolvedArtifactVersions()); - - // verify the "test" file exists in cache - final Optional cachedArtifact = cache.getArtifact("foo", "bar", ChannelManifest.EXTENSION, ChannelManifest.CLASSIFIER, "1.2.3"); - assertThat(cachedArtifact.get()) - .hasSameBinaryContentAs(testFile); - } - - @Test - public void cacheMavenManifests_NotResolvedLocally() throws Exception { - final ManifestVersionRecord record = new ManifestVersionRecord(); - record.addManifest(new ManifestVersionRecord.MavenManifest("foo", "bar", "1.2.3", "")); - final MavenSessionManager msm = mock(MavenSessionManager.class); - final ResolvedArtifactsStore manifestVersions = mock(ResolvedArtifactsStore.class); - when(msm.getResolvedArtifactVersions()).thenReturn(manifestVersions); - // the artifact is not found in resolved list - when(manifestVersions.getManifestVersion("foo", "bar")).thenReturn(null); - - cache.cache(record, msm.getResolvedArtifactVersions()); - - // verify the "test" file exists in cache - final Optional cachedArtifact = cache.getArtifact("foo", "bar", ChannelManifest.EXTENSION, ChannelManifest.CLASSIFIER, "1.2.3"); - assertThat(cachedArtifact) - .isEmpty(); - } - - @Test - public void cacheMavenManifests_ResolvedFileDoesNotExist() throws Exception { - final ManifestVersionRecord record = new ManifestVersionRecord(); - record.addManifest(new ManifestVersionRecord.MavenManifest("foo", "bar", "1.2.3", "")); - final MavenSessionManager msm = mock(MavenSessionManager.class); - final ResolvedArtifactsStore manifestVersions = mock(ResolvedArtifactsStore.class); - when(msm.getResolvedArtifactVersions()).thenReturn(manifestVersions); - // the artifact is not found in resolved list - when(manifestVersions.getManifestVersion("foo", "bar")).thenReturn(null); - - cache.cache(record, msm.getResolvedArtifactVersions()); - - // verify the "test" file exists in cache - final Optional cachedArtifact = cache.getArtifact("foo", "bar", ChannelManifest.EXTENSION, ChannelManifest.CLASSIFIER, "1.2.3"); - assertThat(cachedArtifact) - .isEmpty(); - } - - @Test - public void cacheMavenManifests_ResolvedInListWithDifferentVersion() throws Exception { - final ManifestVersionRecord record = new ManifestVersionRecord(); - record.addManifest(new ManifestVersionRecord.MavenManifest("foo", "bar", "1.2.3", "")); - final MavenSessionManager msm = mock(MavenSessionManager.class); - final ResolvedArtifactsStore manifestVersions = mock(ResolvedArtifactsStore.class); - when(msm.getResolvedArtifactVersions()).thenReturn(manifestVersions); - final File testFile = temp.newFile("test-1.2.3"); - Files.writeString(testFile.toPath(), "test file 1.2.3"); - final File testFile2 = temp.newFile("test-1.2.4"); - Files.writeString(testFile2.toPath(), "test file 1.2.4"); - when(manifestVersions.getManifestVersion("foo", "bar")).thenReturn( - new MavenArtifact("foo", "bar", null, null, "1.2.4", testFile2)); - - cache.cache(record, msm.getResolvedArtifactVersions()); - - // verify the "test" file exists in cache - final Optional cachedArtifact = cache.getArtifact("foo", "bar", ChannelManifest.EXTENSION, ChannelManifest.CLASSIFIER, "1.2.3"); - assertThat(cachedArtifact) - .isEmpty(); - } - @Test public void cacheRecordsArtifactsInAlphabeticOrder() throws Exception { cache.cache(otherArtifact); diff --git a/prospero-common/src/test/java/org/wildfly/prospero/galleon/CachedVersionResolverTest.java b/prospero-common/src/test/java/org/wildfly/prospero/galleon/CachedVersionResolverTest.java index ac89290c4..50fd49d77 100644 --- a/prospero-common/src/test/java/org/wildfly/prospero/galleon/CachedVersionResolverTest.java +++ b/prospero-common/src/test/java/org/wildfly/prospero/galleon/CachedVersionResolverTest.java @@ -94,7 +94,6 @@ public class CachedVersionResolverTest { @Before public void setUp() throws Exception { - when(session.getRepositoryListener()).thenReturn(repositoryListener); resolver = new CachedVersionResolver(mockResolver, artifactCache, system, session, manifestVersionProvider); } @@ -237,7 +236,6 @@ public void testResolveChannelMetadata_FallbackReturnsCachedFile() throws Except assertThat(resolver.resolveChannelMetadata(List.of(new ChannelMetadataCoordinate("org.test", "manifest-one", ChannelManifest.CLASSIFIER, ChannelManifest.EXTENSION)))) .containsExactly(testFile.toURI().toURL()); - verify(repositoryListener).artifactResolved(any()); } @Test @@ -260,7 +258,6 @@ public void testResolveChannelMetadata_FallbackReturnsCachedFile_WithTwoManifest new ChannelMetadataCoordinate("org.test", "manifest-one", ChannelManifest.CLASSIFIER, ChannelManifest.EXTENSION), new ChannelMetadataCoordinate("org.test", "manifest-two", ChannelManifest.CLASSIFIER, ChannelManifest.EXTENSION)))) .containsExactly(testFileOne.toURI().toURL(), testFileTwo.toURI().toURL()); - verify(repositoryListener, times(2)).artifactResolved(any()); } @Test @@ -288,7 +285,5 @@ public void testResolveChannelMetadata_OneManifestResolvedNormally() throws Exce new ChannelManifestCoordinate("org.test", "manifest-one"), new ChannelManifestCoordinate("org.test", "manifest-two")))) .containsExactly(testFileOne.toURI().toURL(), testFileTwo.toURI().toURL()); - // only called on the cached artifact - verify(repositoryListener).artifactResolved(any()); } } \ No newline at end of file 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 dc08526dc..408e2c618 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 diff --git a/prospero-common/src/test/java/org/wildfly/prospero/wfchannel/ProsperoMavenRepositoryListenerTest.java b/prospero-common/src/test/java/org/wildfly/prospero/wfchannel/ProsperoMavenRepositoryListenerTest.java deleted file mode 100644 index dc9f0d56b..000000000 --- a/prospero-common/src/test/java/org/wildfly/prospero/wfchannel/ProsperoMavenRepositoryListenerTest.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright 2024 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.wfchannel; - -import org.eclipse.aether.RepositoryEvent; -import org.eclipse.aether.RepositorySystemSession; -import org.eclipse.aether.artifact.DefaultArtifact; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; -import org.wildfly.channel.ChannelManifest; -import org.wildfly.channel.MavenArtifact; - -import java.io.File; - -import static org.assertj.core.api.Assertions.assertThat; - -@RunWith(MockitoJUnitRunner.class) -public class ProsperoMavenRepositoryListenerTest { - - protected static final String A_GROUP = "org.test"; - protected static final String AN_ARTIFACT = "artifact-one"; - protected static final String A_VERSION = "1.2.3"; - @Mock - private RepositorySystemSession session; - private ProsperoMavenRepositoryListener listener; - private File testFile; - - @Before - public void setUp() { - - listener = new ProsperoMavenRepositoryListener(); - testFile = new File("test"); - } - - @Test - public void testRecordArtifact() throws Exception { - listener.artifactResolved(new RepositoryEvent.Builder(session, RepositoryEvent.EventType.ARTIFACT_RESOLVED) - .setArtifact(resolvedArtifact(testFile)) - .build()); - - assertThat(listener.getManifestVersion(A_GROUP, AN_ARTIFACT)) - .isEqualTo(new MavenArtifact(A_GROUP, AN_ARTIFACT, - ChannelManifest.EXTENSION, ChannelManifest.CLASSIFIER, A_VERSION, testFile)); - - } - - @Test - public void testDontRecordArtifactWithoutFile() throws Exception { - listener.artifactResolved(new RepositoryEvent.Builder(session, RepositoryEvent.EventType.ARTIFACT_RESOLVED) - .setArtifact(resolvedArtifact(null)) - .build()); - - assertThat(listener.getManifestVersion(A_GROUP, AN_ARTIFACT)) - .isNull(); - } - - @Test - public void testDontRecordArtifactWhenEventDoesntHaveArtifact() throws Exception { - listener.artifactResolved(new RepositoryEvent.Builder(session, RepositoryEvent.EventType.ARTIFACT_RESOLVED) - .build()); - - assertThat(listener.getManifestVersion(A_GROUP, AN_ARTIFACT)) - .isNull(); - } - - @Test - public void testReturnNullIfArtifactHasntBeenResolved() throws Exception { - listener.artifactResolved(new RepositoryEvent.Builder(session, RepositoryEvent.EventType.ARTIFACT_RESOLVED) - .setArtifact(resolvedArtifact(testFile)) - .build()); - - assertThat(listener.getManifestVersion(A_GROUP, "idont-exist")) - .isNull(); - } - - @Test - public void resolvingSameArtifactTwiceReplacesArtifact() throws Exception { - final File testFileTwo = new File("test-two"); - - listener.artifactResolved(new RepositoryEvent.Builder(session, RepositoryEvent.EventType.ARTIFACT_RESOLVED) - .setArtifact(resolvedArtifact(testFile)) - .build()); - listener.artifactResolved(new RepositoryEvent.Builder(session, RepositoryEvent.EventType.ARTIFACT_RESOLVED) - .setArtifact(resolvedArtifact(testFileTwo)) - .build()); - - assertThat(listener.getManifestVersion(A_GROUP, AN_ARTIFACT)) - .isEqualTo(new MavenArtifact(A_GROUP, AN_ARTIFACT, - ChannelManifest.EXTENSION, ChannelManifest.CLASSIFIER, A_VERSION, testFileTwo)); - } - - private DefaultArtifact resolvedArtifact(File testFile) { - if (testFile == null) { - return new DefaultArtifact(A_GROUP, AN_ARTIFACT, - ChannelManifest.CLASSIFIER, ChannelManifest.EXTENSION, A_VERSION); - } else { - return new DefaultArtifact(A_GROUP, AN_ARTIFACT, - ChannelManifest.CLASSIFIER, ChannelManifest.EXTENSION, A_VERSION, - null, testFile); - } - } -} \ No newline at end of file