Skip to content

Commit

Permalink
Merge pull request wildfly-extras#701 from spyrkob/JBEAP-27356
Browse files Browse the repository at this point in the history
Handle installation directory being a symbolic link
  • Loading branch information
spyrkob authored Jun 28, 2024
2 parents 633777f + 9a7f987 commit 0129989
Show file tree
Hide file tree
Showing 12 changed files with 168 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,49 @@ public void generateUpdateAndApplyUsingRepositoryArchive() throws Exception {
assertEquals(WfCoreTestBase.UPGRADE_VERSION, wildflyCliStream.get().getVersion());
}

@Test
public void generateUpdateAndApplyIntoSymbolicLink() throws Exception {
final Path manifestPath = temp.newFile().toPath();
final Path provisionConfig = temp.newFile().toPath();
final Path updatePath = tempDir.newFolder("update-candidate").toPath();
MetadataTestUtils.copyManifest("manifests/wfcore-base.yaml", manifestPath);
MetadataTestUtils.prepareChannel(provisionConfig, List.of(manifestPath.toUri().toURL()), defaultRemoteRepositories());

final Path targetLink = Files.createSymbolicLink(temp.newFolder().toPath().resolve("target-link"), targetDir.toPath());
final Path candidateLink = Files.createSymbolicLink(temp.newFolder().toPath().resolve("update-candidate-link"), updatePath);

install(provisionConfig, targetLink);

upgradeStreamInManifest(manifestPath, resolvedUpgradeArtifact);

final URL temporaryRepo = mockTemporaryRepo(true);

// generate update-candidate
ExecutionUtils.prosperoExecution(CliConstants.Commands.UPDATE, CliConstants.Commands.PREPARE,
CliConstants.REPOSITORIES, temporaryRepo.toString(),
CliConstants.CANDIDATE_DIR, candidateLink.toAbsolutePath().toString(),
CliConstants.Y,
CliConstants.DIR, targetDir.getAbsolutePath())
.execute()
.assertReturnCode(ReturnCodes.SUCCESS);

// verify the original server has not been modified
Optional<Stream> wildflyCliStream = getInstalledArtifact(resolvedUpgradeArtifact.getArtifactId(), targetDir.toPath());
assertEquals(WfCoreTestBase.BASE_VERSION, wildflyCliStream.get().getVersion());

// apply update-candidate
ExecutionUtils.prosperoExecution(CliConstants.Commands.UPDATE, CliConstants.Commands.APPLY,
CliConstants.CANDIDATE_DIR, candidateLink.toAbsolutePath().toString(),
CliConstants.Y,
CliConstants.DIR, targetDir.getAbsolutePath())
.execute()
.assertReturnCode(ReturnCodes.SUCCESS);

// verify the original server has been modified
wildflyCliStream = getInstalledArtifact(resolvedUpgradeArtifact.getArtifactId(), targetDir.toPath());
assertEquals(WfCoreTestBase.UPGRADE_VERSION, wildflyCliStream.get().getVersion());
}

private Path createRepositoryArchive(URL temporaryRepo) throws URISyntaxException, IOException {
final Path repoPath = Path.of(temporaryRepo.toURI());
final Path root = tempDir.newFolder("repo-root").toPath();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -386,4 +386,7 @@ public interface ProsperoLogger extends BasicLogger {

@Message(id = 270, value = "Unable to compare the hash content between the installation %s and candidate installation %s.")
MetadataException unableToCompareHashDirs(Path installationDir, Path updateDir, @Cause Exception e);

@Message(id = 271, value = "Unable to evaluate symbolic link %s.")
RuntimeException unableToEvaluateSymbolicLink(Path symlink, @Cause IOException e);
}
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,8 @@ public static Type from (final String text) {
public ApplyCandidateAction(Path installationDir, Path updateDir)
throws ProvisioningException, OperationException {
this.updateDir = updateDir;
this.installationDir = installationDir;
this.installationDir = InstallFolderUtils.toRealPath(installationDir);
updateDir = InstallFolderUtils.toRealPath(updateDir);

try {
this.systemPaths = SystemPaths.load(updateDir);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,10 @@ public FeaturesAddAction(MavenOptions mavenOptions, Path installDir, List<Reposi
List<Repository> repositories, Console console, CandidateActionsFactory candidateActionsFactory,
FeaturePackTemplateManager featurePackTemplateManager)
throws MetadataException, ProvisioningException {
this.installDir = installDir;
this.installDir = InstallFolderUtils.toRealPath(installDir);

this.console = console;
this.metadata = InstallationMetadata.loadInstallation(installDir);
this.metadata = InstallationMetadata.loadInstallation(this.installDir);
this.prosperoConfig = addTemporaryRepositories(repositories);

final MavenOptions mergedOptions = prosperoConfig.getMavenOptions().merge(mavenOptions);
Expand Down Expand Up @@ -134,6 +135,8 @@ public void addFeaturePack(String featurePackCoord, Set<ConfigId> defaultConfigN
verifyFeaturePackCoord(featurePackCoord);
Objects.requireNonNull(defaultConfigNames);

candidatePath = InstallFolderUtils.toRealPath(candidatePath);

FeaturePackLocation fpl = FeaturePackLocationParser.resolveFpl(featurePackCoord);

if (ProsperoLogger.ROOT_LOGGER.isTraceEnabled()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import org.wildfly.prospero.ProsperoLogger;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

Expand All @@ -40,6 +41,42 @@ static void verifyIsEmptyDir(Path directory) {
}
}

/**
* convert the {@code symlink} to a real path. If any of the parent folders are a symlink, they will be
* converted to real path as well.
*
* @param symlink
* @return
*/
static Path toRealPath(Path symlink) {
/*
* There's an issue when trying to copy artifacts to a folder that is symlink
* This only happens when the last segment of path is symlink itself, but to be consistent, we replace the
* symlink anywhere in the path. This way we know we're always operating on a real path from this point on.
*/
Path path = symlink;

// find a symlink (if any) in the path and its parents
while (path != null && !(Files.exists(path) && Files.isSymbolicLink(path))) {
path = path.getParent();
}

// if no symlinks were found we got to the root of the path (null) and we don't need to anything
if (path == null) {
return symlink;
} else {
// get the subfolder path between the symlink and the actual path
final Path relativized = path.relativize(symlink);
try {
// evaluate the symlink and append the relative path to get back to the starting folder
return path.toRealPath().resolve(relativized);
} catch (IOException e) {
// we know the file at path does exist, so if we got an exception here, that's an I/O error
throw ProsperoLogger.ROOT_LOGGER.unableToEvaluateSymbolicLink(symlink, e);
}
}
}

private static boolean isWritable(final Path path) {
Path absPath = path.toAbsolutePath();
if (Files.exists(absPath)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public class InstallationExportAction {
private final Path installationDir;

public InstallationExportAction(Path installationDir) {
this.installationDir = installationDir;
this.installationDir = InstallFolderUtils.toRealPath(installationDir);
}

public static void main(String[] args) throws Exception {
Expand All @@ -45,6 +45,8 @@ public void export(Path exportPath) throws IOException, MetadataException {
throw ProsperoLogger.ROOT_LOGGER.installationDirDoesNotExist(installationDir);
}

exportPath = InstallFolderUtils.toRealPath(exportPath);

try (InstallationMetadata metadataBundle = InstallationMetadata.loadInstallation(installationDir)) {

metadataBundle.exportMetadataBundle(exportPath);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public class InstallationHistoryAction {
private final Console console;

public InstallationHistoryAction(Path installation, Console console) {
this.installation = installation;
this.installation = InstallFolderUtils.toRealPath(installation);
this.console = console;
}

Expand Down Expand Up @@ -111,6 +111,8 @@ public void prepareRevert(SavedState savedState, MavenOptions mavenOptions, List
InstallFolderUtils.verifyIsWritable(targetDir);
}

targetDir = InstallFolderUtils.toRealPath(targetDir);

try (InstallationMetadata metadata = InstallationMetadata.loadInstallation(installation)) {
ProsperoLogger.ROOT_LOGGER.revertCandidateStarted(installation);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public class InstallationRestoreAction {
private final MavenSessionManager mavenSessionManager;

public InstallationRestoreAction(Path installDir, MavenOptions mavenOptions, Console console) throws ProvisioningException {
this.installDir = installDir;
this.installDir = InstallFolderUtils.toRealPath(installDir);
this.mavenSessionManager = new MavenSessionManager(mavenOptions);
this.console = console;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public class MetadataAction implements AutoCloseable {
private final InstallationMetadata installationMetadata;

public MetadataAction(Path installation) throws MetadataException {
installation = InstallFolderUtils.toRealPath(installation);
this.installationMetadata = InstallationMetadata.loadInstallation(installation);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public class ProvisioningAction {
private final MavenOptions mvnOptions;

public ProvisioningAction(Path installDir, MavenOptions mvnOptions, Console console) throws ProvisioningException {
this.installDir = installDir;
this.installDir = InstallFolderUtils.toRealPath(installDir);
this.console = console;
this.mvnOptions = mvnOptions;
this.mavenSessionManager = new MavenSessionManager(mvnOptions);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ public class UpdateAction implements AutoCloseable {

public UpdateAction(Path installDir, MavenOptions mavenOptions, Console console, List<Repository> overrideRepositories)
throws OperationException, ProvisioningException {
this.metadata = InstallationMetadata.loadInstallation(installDir);
this.installDir = installDir;
this.installDir = InstallFolderUtils.toRealPath(installDir);
this.metadata = InstallationMetadata.loadInstallation(this.installDir);
this.console = console;
this.prosperoConfig = addTemporaryRepositories(overrideRepositories);
this.mavenOptions = prosperoConfig.getMavenOptions().merge(mavenOptions);
Expand Down Expand Up @@ -111,6 +111,8 @@ public boolean buildUpdate(Path targetDir) throws ProvisioningException, Operati
InstallFolderUtils.verifyIsWritable(targetDir);
}

targetDir = InstallFolderUtils.toRealPath(targetDir);

final UpdateSet updateSet = findUpdates();
if (updateSet.isEmpty()) {
ProsperoLogger.ROOT_LOGGER.noUpdatesFound(installDir);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package org.wildfly.prospero.actions;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

import static org.assertj.core.api.Assertions.assertThat;

public class InstallFolderUtilsTest {

@Rule
public TemporaryFolder temp = new TemporaryFolder();

@Test
public void testSymlinkIsConvertedToRealPath() throws Exception {
final Path realPath = createFolder();
final Path link = Files.createSymbolicLink(temp.newFolder().toPath().resolve("link"), realPath);

assertThat(InstallFolderUtils.toRealPath(link))
.isEqualTo(realPath);
}

@Test
public void testSymlinkNonExistingSubfolderIsConvertedToRealPath() throws Exception {
final Path realPath = createFolder();
final Path link = Files.createSymbolicLink(temp.newFolder().toPath().resolve("link"), realPath);

assertThat(InstallFolderUtils.toRealPath(link.resolve("test/foo")))
.isEqualTo(realPath.resolve("test/foo"));
}

@Test
public void testSymlinkSubfolderIsConvertedToRealPath() throws Exception {
final Path realPath = createFolder();
Files.createDirectories(realPath.resolve("test/foo"));
final Path link = Files.createSymbolicLink(temp.newFolder().toPath().resolve("link"), realPath);

assertThat(InstallFolderUtils.toRealPath(link.resolve("test/foo")))
.isEqualTo(realPath.resolve("test/foo"));
}

@Test
public void testNonExistingFolderIsNotConverted() throws Exception {
final Path folder = temp.newFolder().toPath().toRealPath().resolve("link");

assertThat(InstallFolderUtils.toRealPath(folder))
.isEqualTo(folder);
}

@Test
public void testExistingFolderIsNotConverted() throws Exception {
final Path folder = temp.newFolder().toPath().toRealPath();

assertThat(InstallFolderUtils.toRealPath(folder))
.isEqualTo(folder);
}

private Path createFolder() throws IOException {
return temp.newFolder("real").toPath().toRealPath();
}
}

0 comments on commit 0129989

Please sign in to comment.