diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index f50d1a8ca5..c293a606ae 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -6,6 +6,7 @@ This file documents all notable changes to https://github.com/devonfw/IDEasy[IDE Release with new features and bugfixes: +* https://github.com/devonfw/IDEasy/issues/2345[#2345]: DOCKER_EDITION ignored on Linux * https://github.com/devonfw/IDEasy/issues/2381[#2381]: Make node and npm pristine versioned tools with per-project npm global prefix The full list of changes for this release can be found in https://github.com/devonfw/IDEasy/milestone/51?closed=1[milestone 2026.10.001]. diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/NativePackage.java b/cli/src/main/java/com/devonfw/tools/ide/tool/NativePackage.java index 8156b97618..d8509191fd 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/NativePackage.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/NativePackage.java @@ -1,5 +1,6 @@ package com.devonfw.tools.ide.tool; +import java.nio.file.Path; import java.util.List; import java.util.Objects; @@ -13,6 +14,7 @@ public class NativePackage { private final List extraInstallOptions; private final List setupCommands; private final List cleanupCommands; + private final List optionalNativePackageArtifactPaths; /** * Creates a new {@link NativePackage} with optional fields defaulting to empty lists. @@ -25,11 +27,27 @@ public class NativePackage { */ public NativePackage(NativePackageManager pm, List packages, List extraInstallOptions, List setupCommands, List cleanupCommands) { + this(pm, packages, extraInstallOptions, setupCommands, cleanupCommands, null); + } + + /** + * Creates a new {@link NativePackage} with optional fields defaulting to empty lists. + * + * @param pm the specific {@link NativePackageManager} + * @param packages the packages that need to be handled. + * @param extraInstallOptions extra install options (optional) + * @param setupCommands commands to run before install (optional) + * @param cleanupCommands commands to run after uninstall (optional) + * @param optionalNativePackageArtifactPaths paths to local package artifacts (optional) + */ + public NativePackage(NativePackageManager pm, List packages, + List extraInstallOptions, List setupCommands, List cleanupCommands, List optionalNativePackageArtifactPaths) { this.packageManager = Objects.requireNonNull(pm, "package manager must not be null"); this.packages = List.copyOf(Objects.requireNonNull(packages, "packages must not be null")); this.extraInstallOptions = extraInstallOptions != null ? List.copyOf(extraInstallOptions) : List.of(); this.setupCommands = setupCommands != null ? List.copyOf(setupCommands) : List.of(); this.cleanupCommands = cleanupCommands != null ? List.copyOf(cleanupCommands) : List.of(); + this.optionalNativePackageArtifactPaths = optionalNativePackageArtifactPaths != null ? List.copyOf(optionalNativePackageArtifactPaths) : List.of(); } /** @@ -39,7 +57,7 @@ public NativePackage(NativePackageManager pm, List packages, * @param packages the packages that need to be handled */ public NativePackage(NativePackageManager pm, List packages) { - this(pm, packages, null, null, null); + this(pm, packages, null, null, null, null); } /** @@ -88,6 +106,13 @@ public List getCleanupCommands() { return cleanupCommands; } + /** + * @return set {@link optionalNativePackageArtifactPaths}. + */ + public List getOptionalNativePackageArtifactPaths() { + return optionalNativePackageArtifactPaths; + } + /** * @param version the version to pin the {@link #getPackages()} to or {@code null} to install the latest available version. * @return {@link PackageManagerCommand} for installation. diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/NativePackageManager.java b/cli/src/main/java/com/devonfw/tools/ide/tool/NativePackageManager.java index 614cef4c67..869a6385f5 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/NativePackageManager.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/NativePackageManager.java @@ -1,5 +1,6 @@ package com.devonfw.tools.ide.tool; +import java.nio.file.Path; import java.util.ArrayList; import java.util.List; @@ -183,8 +184,14 @@ public PackageManagerCommand install(NativePackage nativePackage, String version command.append(' ').append(option); } command.append(' ').append(this.installCommand); - for (String pkg : nativePackage.getPackages()) { - command.append(' ').append(getPackageSpec(pkg, version)); + if (nativePackage.getOptionalNativePackageArtifactPaths().isEmpty()) { + for (String pkg : nativePackage.getPackages()) { + command.append(' ').append(getPackageSpec(pkg, version)); + } + } else { + for (Path nativePackageArtifactPath : nativePackage.getOptionalNativePackageArtifactPaths()) { + command.append(' ').append(nativePackageArtifactPath); + } } commands.add(command.toString()); return new PackageManagerCommand(this, commands); diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/docker/Docker.java b/cli/src/main/java/com/devonfw/tools/ide/tool/docker/Docker.java index 6cfefde1e6..53a0704be2 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/docker/Docker.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/docker/Docker.java @@ -1,5 +1,6 @@ package com.devonfw.tools.ide.tool.docker; +import java.nio.file.Path; import java.util.List; import java.util.Map; import java.util.Set; @@ -16,8 +17,12 @@ import com.devonfw.tools.ide.tool.GlobalToolCommandlet; import com.devonfw.tools.ide.tool.NativePackage; import com.devonfw.tools.ide.tool.NativePackageManager; +import com.devonfw.tools.ide.tool.PackageManagerCommand; import com.devonfw.tools.ide.tool.ToolEdition; import com.devonfw.tools.ide.tool.ToolEditionAndVersion; +import com.devonfw.tools.ide.tool.ToolInstallRequest; +import com.devonfw.tools.ide.tool.ToolInstallation; +import com.devonfw.tools.ide.tool.repository.ToolRepository; import com.devonfw.tools.ide.version.VersionIdentifier; /** @@ -35,6 +40,10 @@ public class Docker extends GlobalToolCommandlet { private static final Pattern DOCKER_DESKTOP_VERSION_PATTERN = Pattern.compile("^([0-9]+(?:\\.[0-9]+){1,2})"); + private static final String EDITION_DOCKER = "docker"; + + private Path downloadedDebPackageForDocker; + /** * The constructor. * @@ -70,6 +79,34 @@ private String detectContainerRuntime() { @Override protected List getNativePackages() { + + if (isDockerDesktopEditionConfigured()) { + + List artifactPaths = (this.downloadedDebPackageForDocker == null) ? List.of() : List.of(this.downloadedDebPackageForDocker); + + return List.of( + new NativePackage( + NativePackageManager.APT, + List.of("docker-desktop"), + List.of("--allow-downgrades"), + List.of( + "sudo install -m 0755 -d /etc/apt/keyrings", + "sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc", + "sudo chmod a+r /etc/apt/keyrings/docker.asc", + "echo \"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] " + + "https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo \\\"$VERSION_CODENAME\\\") stable\" | " + + "sudo tee /etc/apt/sources.list.d/docker.list > /dev/null", + "sudo apt update" + ), + List.of( + "sudo rm -f /etc/apt/sources.list.d/docker.list", + "sudo rm -f /etc/apt/keyrings/docker.asc" + ), + artifactPaths + ) + ); + } + return List.of( new NativePackage( NativePackageManager.ZYPPER, @@ -119,6 +156,28 @@ protected ToolEditionAndVersion adjustRequestedEdition(ToolEditionAndVersion req return requested; } + @Override + protected ToolInstallation doInstall(ToolInstallRequest request) { + if (isDockerDesktopEditionConfigured()) { + downloadDebPackageStepAndSetPackagePath(request.getRequested().getResolvedVersion()); + } + return super.doInstall(request); + } + + private void downloadDebPackageStepAndSetPackagePath(VersionIdentifier resolvedVersion) { + ToolRepository toolRepository = this.context.getDefaultToolRepository(); + this.downloadedDebPackageForDocker = toolRepository.download(this.tool, EDITION_DOCKER, resolvedVersion, this); + } + + @Override + protected List getInstallPackageManagerCommands(VersionIdentifier resolvedVersion) { + if (!isDockerDesktopEditionConfigured()) { + return super.getInstallPackageManagerCommands(resolvedVersion); + } + + return getNativePackages().stream().map(nativePackage -> nativePackage.install(null)).toList(); + } + @Override public boolean isExtract() { @@ -200,6 +259,10 @@ private VersionIdentifier getRancherDesktopClientVersion() { } } + private boolean isDockerDesktopEditionConfigured() { + return EDITION_DOCKER.equals(getConfiguredEdition()); + } + @Override public String getToolHelpArguments() { diff --git a/cli/src/test/java/com/devonfw/tools/ide/tool/NativePackageManagerTest.java b/cli/src/test/java/com/devonfw/tools/ide/tool/NativePackageManagerTest.java index 69e7d0f545..76916f7885 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/tool/NativePackageManagerTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/tool/NativePackageManagerTest.java @@ -2,6 +2,7 @@ import static org.assertj.core.api.Assertions.assertThat; +import java.nio.file.Path; import java.util.List; import org.junit.jupiter.api.Test; @@ -213,6 +214,53 @@ void testParseVersionQueryOutputForRpmBasedPackageManager() { assertThat(NativePackageManager.DNF.parseVersionQueryOutput("1.0.0")).isEqualTo("1.0.0"); } + @Test + void installUsesPackageNameAndVersionWhenNoArtifactPathConfigured() { + NativePackage nativePackage = new NativePackage(NativePackageManager.APT, List.of("docker-desktop")); + PackageManagerCommand result = NativePackageManager.APT.install(nativePackage, "1.2.3"); + + List commands = result.commands(); + String installCommand = commands.getLast(); + assertThat(installCommand).contains("apt", "install -y", "docker-desktop=1.2.3*"); + assertThat(installCommand).doesNotContain(".deb"); + } + + @Test + void installUsesArtifactPathWhenConfiguredInsteadOfPackageName() { + Path debPath = Path.of("/tmp/downloads/docker-desktop-4.34.0-amd64"); + + NativePackage nativePackage = new NativePackage(NativePackageManager.APT, List.of("docker-desktop"), null, null, null, List.of(debPath)); + + PackageManagerCommand result = NativePackageManager.APT.install(nativePackage, null); + + List commands = result.commands(); + String installCommand = commands.getLast(); + assertThat(installCommand).contains(debPath.toString()); + assertThat(installCommand).doesNotContain("docker-desktop="); + } + + @Test + void installFallsBackToPackagesWhenArtifactPathListIsEmpty() { + NativePackage nativePackage = new NativePackage(NativePackageManager.APT, List.of("docker-desktop"), null, null, null); + + PackageManagerCommand result = NativePackageManager.APT.install(nativePackage, null); + + List commands = result.commands(); + String installCommand = commands.getLast(); + assertThat(installCommand).contains("docker-desktop"); + } + + @Test + void installRejectsMismatchingPackageManager() { + NativePackage nativePackage = new NativePackage(NativePackageManager.APT, List.of("docker-desktop"), null, null, null); + + PackageManagerCommand result = NativePackageManager.APT.install(nativePackage, null); + + List commands = result.commands(); + String installCommand = commands.getLast(); + assertThat(installCommand).contains("docker-desktop"); + } + @Test void testBrewInstallAndUninstallCommandsDoNotUseSudo() { NativePackage np = NativePackage.of(NativePackageManager.BREW, "pkg1"); diff --git a/url-updater/src/main/java/com/devonfw/tools/ide/url/tool/docker/DockerDesktopUrlUpdater.java b/url-updater/src/main/java/com/devonfw/tools/ide/url/tool/docker/DockerDesktopUrlUpdater.java index 01417d03e3..39a9ff871f 100644 --- a/url-updater/src/main/java/com/devonfw/tools/ide/url/tool/docker/DockerDesktopUrlUpdater.java +++ b/url-updater/src/main/java/com/devonfw/tools/ide/url/tool/docker/DockerDesktopUrlUpdater.java @@ -1,5 +1,6 @@ package com.devonfw.tools.ide.url.tool.docker; + import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -39,17 +40,14 @@ public String getTool() { protected void addVersion(UrlVersion urlVersion) { VersionIdentifier vid = VersionIdentifier.of(urlVersion.getName()); - String version = urlVersion.getName().replaceAll("\\.", ""); - // get Code for version String body = doGetResponseBodyAsString(getVersionUrl()); - String regex = "href=#" + version - // .......1.........................................................2................. - + ".{8,12}(\r\n|\r|\n).{0,350}href=https://desktop\\.docker\\.com.*?(\\d{5,6}).*\\.exe"; + String regex = "## " + Pattern.quote(urlVersion.getName()) + + ".*?\\[Windows]\\(https://desktop\\.docker\\.com/win/main/amd64/(\\d{5,6})/"; Pattern pattern = Pattern.compile(regex, Pattern.DOTALL); Matcher matcher = pattern.matcher(body); - String code; + if (matcher.find()) { - code = matcher.group(2); + String code = matcher.group(1); boolean success = doAddVersion(urlVersion, getDownloadBaseUrl() + "/win/main/amd64/" + code + "/Docker%20Desktop%20Installer.exe", WINDOWS); if (!success) { @@ -58,6 +56,7 @@ protected void addVersion(UrlVersion urlVersion) { if (WINDOWS_ONLY_VERSIONS.stream().noneMatch(i -> vid.compareVersion(i).isEqual())) { doAddVersion(urlVersion, getDownloadBaseUrl() + "/mac/main/amd64/" + code + "/Docker.dmg", MAC, X64); doAddVersion(urlVersion, getDownloadBaseUrl() + "/mac/main/arm64/" + code + "/Docker.dmg", MAC, ARM64); + doAddVersion(urlVersion, getDownloadBaseUrl() + "/linux/main/amd64/" + code + "/docker-desktop-amd64.deb", LINUX); } } } @@ -65,7 +64,7 @@ protected void addVersion(UrlVersion urlVersion) { @Override protected String getVersionUrl() { - return getVersionBaseUrl() + "/desktop/release-notes/"; + return getVersionBaseUrl() + "/desktop/release-notes.md"; } @Override