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

devonfw / IDEasy / 27447264803

12 Jun 2026 10:42PM UTC coverage: 70.866% (-0.2%) from 71.052%
27447264803

Pull #2018

github

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

4543 of 7100 branches covered (63.99%)

Branch coverage included in aggregate %.

11752 of 15894 relevant lines covered (73.94%)

3.13 hits per line

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

15.17
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.step.Step;
23
import com.devonfw.tools.ide.tool.GlobalToolCommandlet;
24
import com.devonfw.tools.ide.tool.NativePackageManager;
25
import com.devonfw.tools.ide.tool.PackageManagerCommand;
26
import com.devonfw.tools.ide.tool.ToolEdition;
27
import com.devonfw.tools.ide.tool.ToolInstallRequest;
28
import com.devonfw.tools.ide.tool.ToolInstallation;
29
import com.devonfw.tools.ide.tool.repository.ToolRepository;
30
import com.devonfw.tools.ide.version.VersionIdentifier;
31

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

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

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

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

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

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

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

60
  private ToolInstallation doInstallOnMac(ToolInstallRequest request) {
61

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

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

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

85
  private Path installMacDmg(Path dmg) {
86

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

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

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

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

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

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

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

133
  private static String toSudoCommandLine(String... command) {
134

135
    StringBuilder commandLine = new StringBuilder("sudo");
×
136
    for (String argument : command) {
×
137
      commandLine.append(' ');
×
138
      commandLine.append(quoteShellArgument(argument));
×
139
    }
140
    return commandLine.toString();
×
141
  }
142

143
  private static String quoteShellArgument(String argument) {
144

145
    if (argument.indexOf(' ') < 0 && argument.indexOf('\'') < 0) {
×
146
      return argument;
×
147
    }
148
    return "'" + argument.replace("'", "'\"'\"'") + "'";
×
149
  }
150

151
  @Override
152
  protected List<PackageManagerCommand> getInstallPackageManagerCommands() {
153

154
    String edition = getConfiguredEdition();
×
155
    ToolRepository toolRepository = getToolRepository();
×
156
    VersionIdentifier configuredVersion = getConfiguredVersion();
×
157
    String resolvedVersion = toolRepository.resolveVersion(this.tool, edition, configuredVersion, this).toString();
×
158

159
    PackageManagerCommand packageManagerCommand = new PackageManagerCommand(NativePackageManager.APT, List.of(
×
160
        "curl -fsS https://www.pgadmin.org/static/packages_pgadmin_org.pub | "
161
            + "sudo gpg --yes --dearmor -o /usr/share/keyrings/packages-pgadmin-org.gpg",
162
        "sudo sh -c 'echo \"deb [signed-by=/usr/share/keyrings/packages-pgadmin-org.gpg] "
163
            + "https://ftp.postgresql.org/pub/pgadmin/pgadmin4/apt/$(lsb_release -cs) pgadmin4 main\" "
164
            + "> /etc/apt/sources.list.d/pgadmin4.list && apt update'", String.format(
×
165
            "sudo apt install -y --allow-downgrades pgadmin4=%1$s pgadmin4-server=%1$s pgadmin4-desktop=%1$s pgadmin4-web=%1$s",
166
            resolvedVersion)));
167
    return List.of(packageManagerCommand);
×
168
  }
169

170
  @Override
171
  public void uninstall() {
172

173
    if (this.context.getSystemInfo().isLinux()) {
×
174
      runWithPackageManager(false, getPackageManagerCommandsUninstall());
×
175
    } else {
176
      super.uninstall();
×
177
    }
178
  }
×
179

180
  private List<PackageManagerCommand> getPackageManagerCommandsUninstall() {
181

182
    List<PackageManagerCommand> pmCommands = new ArrayList<>();
×
183

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

187
    return pmCommands;
×
188
  }
189

190
  @Override
191
  protected String getBinaryName() {
192

193
    if (this.context.getSystemInfo().isMac()) {
5✔
194
      return "pgAdmin4";
2✔
195
    }
196
    return "pgadmin4";
2✔
197
  }
198

199
  @Override
200
  protected Path getInstallationPath(String edition, VersionIdentifier resolvedVersion) {
201

202
    Path installationPath = super.getInstallationPath(edition, resolvedVersion);
5✔
203
    if (installationPath != null) {
2✔
204
      return installationPath;
2✔
205
    }
206
    if (this.context.getSystemInfo().isMac()) {
5!
207
      return getMacOsAppPath();
3✔
208
    }
209
    if (this.context.getSystemInfo().isWindows()) {
×
210
      return getExecutableFolderFromWindowsRegistry();
×
211
    }
212
    return null;
×
213
  }
214

215
  protected Path getMacApplicationsPath() {
216

217
    return Path.of("/Applications");
×
218
  }
219

220
  private Path getMacOsAppPath() {
221

222
    Path appPath = getMacApplicationsPath().resolve(PGADMIN_APP);
5✔
223
    Path binary = appPath.resolve("Contents").resolve("MacOS").resolve(getBinaryName());
9✔
224
    if (Files.isExecutable(binary)) {
3!
225
      this.context.getPath().setPath(getName(), binary.getParent());
8✔
226
      return appPath;
2✔
227
    }
228
    return null;
×
229
  }
230

231
  private Path getExecutableFolderFromWindowsRegistry() {
232

233
    WindowsHelper windowsHelper = WindowsHelper.get(this.context);
×
234
    String registryPath = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\pgAdmin 4v9_is1";
×
235
    String displayIcon = windowsHelper.getRegistryValue(registryPath, "DisplayIcon");
×
236
    if (displayIcon != null) {
×
237
      Path executablePath = Paths.get(displayIcon);
×
238
      if (Files.isExecutable(executablePath)) {
×
239
        Path installationDir = executablePath.getParent();
×
240
        this.context.getPath().setPath(getName(), installationDir);
×
241
        return installationDir;
×
242
      }
243
    }
244
    return null;
×
245
  }
246
}
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