• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

devonfw / IDEasy / 27450388724

13 Jun 2026 12:11AM UTC coverage: 70.811% (-0.2%) from 71.052%
27450388724

Pull #2018

github

web-flow
Merge e98a6e89f into 8b8989cb3
Pull Request #2018: fixed pgAdmin macOS dmg installation

4538 of 7102 branches covered (63.9%)

Branch coverage included in aggregate %.

11750 of 15900 relevant lines covered (73.9%)

3.13 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

14.38
cli/src/main/java/com/devonfw/tools/ide/tool/pgadmin/PgAdmin.java
1
package com.devonfw.tools.ide.tool.pgadmin;
2

3
import java.nio.file.Files;
4
import java.nio.file.Path;
5
import java.nio.file.Paths;
6
import java.util.ArrayList;
7
import java.util.Arrays;
8
import java.util.List;
9
import java.util.Set;
10

11
import org.slf4j.Logger;
12
import org.slf4j.LoggerFactory;
13

14
import com.devonfw.tools.ide.cli.CliException;
15
import com.devonfw.tools.ide.common.Tag;
16
import com.devonfw.tools.ide.context.IdeContext;
17
import com.devonfw.tools.ide.io.FileAccess;
18
import com.devonfw.tools.ide.log.IdeLogLevel;
19
import com.devonfw.tools.ide.os.WindowsHelper;
20
import com.devonfw.tools.ide.process.ProcessErrorHandling;
21
import com.devonfw.tools.ide.process.ProcessMode;
22
import com.devonfw.tools.ide.process.ProcessResult;
23
import com.devonfw.tools.ide.step.Step;
24
import com.devonfw.tools.ide.tool.GlobalToolCommandlet;
25
import com.devonfw.tools.ide.tool.NativePackageManager;
26
import com.devonfw.tools.ide.tool.PackageManagerCommand;
27
import com.devonfw.tools.ide.tool.ToolEdition;
28
import com.devonfw.tools.ide.tool.ToolInstallRequest;
29
import com.devonfw.tools.ide.tool.ToolInstallation;
30
import com.devonfw.tools.ide.tool.repository.ToolRepository;
31
import com.devonfw.tools.ide.version.VersionIdentifier;
32

33
/**
34
 * {@link GlobalToolCommandlet} for <a href="https://www.pgadmin.org/">pgadmin</a>
35
 */
36
public class PgAdmin extends GlobalToolCommandlet {
37

38
  private static final Logger LOG = LoggerFactory.getLogger(PgAdmin.class);
4✔
39

40
  private static final String PGADMIN_APP = "pgAdmin 4.app";
41

42
  /**
43
   * The constructor.
44
   *
45
   * @param context the {@link IdeContext}.
46
   */
47
  public PgAdmin(IdeContext context) {
48

49
    super(context, "pgadmin", Set.of(Tag.DB, Tag.ADMIN));
7✔
50
  }
1✔
51

52
  @Override
53
  protected ToolInstallation doInstall(ToolInstallRequest request) {
54

55
    if (this.context.getSystemInfo().isMac()) {
×
56
      return doInstallOnMac(request);
×
57
    }
58
    return super.doInstall(request);
×
59
  }
60

61
  private ToolInstallation doInstallOnMac(ToolInstallRequest request) {
62

63
    VersionIdentifier resolvedVersion = request.getRequested().getResolvedVersion();
×
64
    ToolEdition toolEdition = getToolWithConfiguredEdition();
×
65
    Path installationPath = getInstallationPath(toolEdition.edition(), resolvedVersion);
×
66
    if ((installationPath != null) && !this.context.isForceMode()) {
×
67
      return toolAlreadyInstalled(request);
×
68
    }
69

70
    resolvedVersion = cveCheck(request);
×
71
    Path dmg = getToolRepository().download(this.tool, toolEdition.edition(), resolvedVersion, this);
×
72
    Path appPath = installMacDmg(dmg);
×
73

74
    installationPath = getInstallationPath(toolEdition.edition(), resolvedVersion);
×
75
    if (installationPath == null) {
×
76
      throw new CliException("The tool " + this.tool + " was installed but the pgAdmin app could not be found in " + getMacApplicationsPath() + ".");
×
77
    }
78
    IdeLogLevel.SUCCESS.log(LOG, "Successfully installed {} in version {} at {}", this.tool, resolvedVersion, appPath);
×
79
    Step step = request.getStep();
×
80
    if (step != null) {
×
81
      step.success(true);
×
82
    }
83
    return createToolInstallation(installationPath, resolvedVersion, true, request.getProcessContext(), request.isAdditionalInstallation());
×
84
  }
85

86
  private Path installMacDmg(Path dmg) {
87

88
    FileAccess fileAccess = this.context.getFileAccess();
×
89
    Path mountPath = fileAccess.createTempDir("pgadmin-dmg");
×
90
    boolean mounted = false;
×
91
    try {
92
      this.context.newProcess().executable("hdiutil").addArgs("attach", "-quiet", "-nobrowse", "-mountpoint", mountPath, dmg).run();
×
93
      mounted = true;
×
94

95
      Path sourceApp = getMacOsHelper().findAppDir(mountPath);
×
96
      if (sourceApp == null) {
×
97
        throw new CliException("No pgAdmin .app bundle was found in " + dmg + ".");
×
98
      }
99

100
      Path targetApp = getMacApplicationsPath().resolve(PGADMIN_APP);
×
101
      if (Files.exists(targetApp) && this.context.isForceMode()) {
×
102
        runWithPrivilegeFallback("/bin/rm", "-rf", targetApp.toString());
×
103
      }
104
      runWithPrivilegeFallback("/usr/bin/ditto", sourceApp.toString(), targetApp.toString());
×
105
      return targetApp;
×
106
    } finally {
107
      if (mounted) {
×
108
        this.context.newProcess().errorHandling(ProcessErrorHandling.LOG_WARNING).executable("hdiutil")
×
109
            .addArgs("detach", "-force", mountPath).run(ProcessMode.DEFAULT_SILENT);
×
110
      }
111
      deleteMountPath(mountPath, fileAccess);
×
112
    }
113
  }
114

115
  private void deleteMountPath(Path mountPath, FileAccess fileAccess) {
116

117
    try {
118
      if (Files.isDirectory(mountPath) && (fileAccess.findFirst(mountPath, p -> true, false) != null)) {
×
119
        LOG.warn("Skipping deletion of temporary pgAdmin DMG mount path {} because it still contains files.", mountPath);
×
120
        return;
×
121
      }
122
      fileAccess.delete(mountPath);
×
123
    } catch (RuntimeException e) {
×
124
      LOG.warn("Failed to delete temporary pgAdmin DMG mount path {}.", mountPath, e);
×
125
    }
×
126
  }
×
127

128
  private void runPrivileged(String... command) {
129

130
    logPrivilegedCommands(List.of(toSudoCommandLine(command)));
×
131
    this.context.newProcess().executable("sudo").addArgs(command).run();
×
132
  }
×
133

134
  private void runWithPrivilegeFallback(String... command) {
135

136
    ProcessResult result = this.context.newProcess().errorHandling(ProcessErrorHandling.NONE).executable(command[0])
×
137
        .addArgs(Arrays.copyOfRange(command, 1, command.length)).run(ProcessMode.DEFAULT);
×
138
    if (!result.isSuccessful()) {
×
139
      LOG.debug("Command {} failed without elevated privileges. Retrying with sudo.", List.of(command));
×
140
      runPrivileged(command);
×
141
    }
142
  }
×
143

144
  private static String toSudoCommandLine(String... command) {
145

146
    StringBuilder commandLine = new StringBuilder("sudo");
×
147
    for (String argument : command) {
×
148
      commandLine.append(' ');
×
149
      commandLine.append(quoteShellArgument(argument));
×
150
    }
151
    return commandLine.toString();
×
152
  }
153

154
  private static String quoteShellArgument(String argument) {
155

156
    if (argument.indexOf(' ') < 0 && argument.indexOf('\'') < 0) {
×
157
      return argument;
×
158
    }
159
    return "'" + argument.replace("'", "'\"'\"'") + "'";
×
160
  }
161

162
  @Override
163
  protected List<PackageManagerCommand> getInstallPackageManagerCommands() {
164

165
    String edition = getConfiguredEdition();
×
166
    ToolRepository toolRepository = getToolRepository();
×
167
    VersionIdentifier configuredVersion = getConfiguredVersion();
×
168
    String resolvedVersion = toolRepository.resolveVersion(this.tool, edition, configuredVersion, this).toString();
×
169

170
    PackageManagerCommand packageManagerCommand = new PackageManagerCommand(NativePackageManager.APT, List.of(
×
171
        "curl -fsS https://www.pgadmin.org/static/packages_pgadmin_org.pub | "
172
            + "sudo gpg --yes --dearmor -o /usr/share/keyrings/packages-pgadmin-org.gpg",
173
        "sudo sh -c 'echo \"deb [signed-by=/usr/share/keyrings/packages-pgadmin-org.gpg] "
174
            + "https://ftp.postgresql.org/pub/pgadmin/pgadmin4/apt/$(lsb_release -cs) pgadmin4 main\" "
175
            + "> /etc/apt/sources.list.d/pgadmin4.list && apt update'", String.format(
×
176
            "sudo apt install -y --allow-downgrades pgadmin4=%1$s pgadmin4-server=%1$s pgadmin4-desktop=%1$s pgadmin4-web=%1$s",
177
            resolvedVersion)));
178
    return List.of(packageManagerCommand);
×
179
  }
180

181
  @Override
182
  public void uninstall() {
183

184
    if (this.context.getSystemInfo().isLinux()) {
×
185
      runWithPackageManager(false, getPackageManagerCommandsUninstall());
×
186
    } else {
187
      super.uninstall();
×
188
    }
189
  }
×
190

191
  private List<PackageManagerCommand> getPackageManagerCommandsUninstall() {
192

193
    List<PackageManagerCommand> pmCommands = new ArrayList<>();
×
194

195
    pmCommands.add(new PackageManagerCommand(NativePackageManager.APT,
×
196
        Arrays.asList("sudo apt -y autoremove pgadmin4 pgadmin4-server pgadmin4-desktop pgadmin4-web")));
×
197

198
    return pmCommands;
×
199
  }
200

201
  @Override
202
  protected String getBinaryName() {
203

204
    if (this.context.getSystemInfo().isMac()) {
5✔
205
      return "pgAdmin 4";
2✔
206
    }
207
    return "pgadmin4";
2✔
208
  }
209

210
  @Override
211
  protected Path getInstallationPath(String edition, VersionIdentifier resolvedVersion) {
212

213
    Path installationPath = super.getInstallationPath(edition, resolvedVersion);
5✔
214
    if (installationPath != null) {
2✔
215
      return installationPath;
2✔
216
    }
217
    if (this.context.getSystemInfo().isMac()) {
5!
218
      return getMacOsAppPath();
3✔
219
    }
220
    if (this.context.getSystemInfo().isWindows()) {
×
221
      return getExecutableFolderFromWindowsRegistry();
×
222
    }
223
    return null;
×
224
  }
225

226
  protected Path getMacApplicationsPath() {
227

228
    return Path.of("/Applications");
×
229
  }
230

231
  private Path getMacOsAppPath() {
232

233
    Path appPath = getMacApplicationsPath().resolve(PGADMIN_APP);
5✔
234
    Path binary = appPath.resolve("Contents").resolve("MacOS").resolve(getBinaryName());
9✔
235
    if (Files.isExecutable(binary)) {
3!
236
      this.context.getPath().setPath(getName(), binary.getParent());
8✔
237
      return appPath;
2✔
238
    }
239
    return null;
×
240
  }
241

242
  private Path getExecutableFolderFromWindowsRegistry() {
243

244
    WindowsHelper windowsHelper = WindowsHelper.get(this.context);
×
245
    String registryPath = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\pgAdmin 4v9_is1";
×
246
    String displayIcon = windowsHelper.getRegistryValue(registryPath, "DisplayIcon");
×
247
    if (displayIcon != null) {
×
248
      Path executablePath = Paths.get(displayIcon);
×
249
      if (Files.isExecutable(executablePath)) {
×
250
        Path installationDir = executablePath.getParent();
×
251
        this.context.getPath().setPath(getName(), installationDir);
×
252
        return installationDir;
×
253
      }
254
    }
255
    return null;
×
256
  }
257
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc