forked from containers/podman-desktop
-
Notifications
You must be signed in to change notification settings - Fork 0
/
compatibility-mode.ts
245 lines (216 loc) · 9.17 KB
/
compatibility-mode.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
/**********************************************************************
* Copyright (C) 2023 Red Hat, Inc.
*
* 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.
*
* SPDX-License-Identifier: Apache-2.0
***********************************************************************/
import * as extensionApi from '@podman-desktop/api';
import * as sudo from 'sudo-prompt';
import * as fs from 'node:fs';
import * as os from 'node:os';
import { execPromise, getPodmanCli } from './podman-cli';
import { findRunningMachine } from './extension';
const podmanSystemdSocket = 'podman.socket';
// Create an abstract class for compatibility mode (macOS only)
// TODO: Windows, Linux
abstract class SocketCompatibility {
abstract isEnabled(): boolean;
abstract enable(): Promise<void>;
abstract disable(): Promise<void>;
abstract details: string;
abstract tooltipText(): string;
}
export class DarwinSocketCompatibility extends SocketCompatibility {
// Shows the details of the compatibility mode on what we do.
details =
'The podman-mac-helper binary will be run, linking the Docker socket to Podman. This requires administrative privileges.';
// This will show the "opposite" of what the current state is
// "Enable" if it's currently disabled, "Disable" if it's currently enabled
// for tooltip text
tooltipText(): string {
const text = 'macOS Docker socket compatibility for Podman.';
return this.isEnabled() ? `Disable ${text}` : `Enable ${text}`;
}
// Find the podman-mac-helper binary which should only be located in either
// brew or podman's install location
findPodmanHelper(): string {
const homebrewPath = '/opt/homebrew/bin/podman-mac-helper';
const podmanPath = '/opt/podman/bin/podman-mac-helper';
const userBinaryPath = '/usr/local/bin/podman-mac-helper';
if (fs.existsSync(homebrewPath)) {
return homebrewPath;
} else if (fs.existsSync(podmanPath)) {
return podmanPath;
} else if (fs.existsSync(userBinaryPath)) {
return userBinaryPath;
} else {
return '';
}
}
// Check to see if com.github.containers.podman.helper-<username>.plist exists
isEnabled(): boolean {
const username = os.userInfo().username;
const filename = `/Library/LaunchDaemons/com.github.containers.podman.helper-${username}.plist`;
return fs.existsSync(filename);
}
// Run sudo command for podman mac helper
async runSudoMacHelperCommand(command: string): Promise<void> {
const sudoOptions = {
name: 'Podman Desktop Compatibility Mode',
};
return new Promise((resolve, reject) => {
sudo.exec(command, sudoOptions, (error, stdout, stderr) => {
// podman-mac-helper does not error out on failure for some reason, so we need to check the output for
// 'Error:' to determine if the command failed despite the exit code being 0
// Issue: https://github.com/containers/podman/issues/17785
// we'll most likely need to keep this check for old releases of podman-mac-helper.
if (stderr?.includes('Error:')) {
reject(stderr);
}
if (error) {
reject(error);
} else {
resolve();
}
});
});
}
async runCommand(command: string, description: string): Promise<void> {
// Find the podman-mac-helper binary
const podmanHelperBinary = this.findPodmanHelper();
if (podmanHelperBinary === '') {
await extensionApi.window.showErrorMessage('podman-mac-helper binary not found.', 'OK');
return;
}
const fullCommand = `${podmanHelperBinary} ${command}`;
try {
await this.runSudoMacHelperCommand(fullCommand);
await extensionApi.window.showInformationMessage(
`Docker socket compatibility mode for Podman has been ${description}.`,
);
} catch (error) {
console.error(`Error running podman-mac-helper: ${error}`);
await extensionApi.window.showErrorMessage(`Error running podman-mac-helper: ${error}`, 'OK');
}
}
// Prompt the user that you need to restart the current podman machine for the changes to take effect
async promptRestart(machine: string): Promise<void> {
const result = await extensionApi.window.showInformationMessage(
`Restarting your Podman machine is required to apply the changes. Would you like to restart the Podman machine '${machine}' now?`,
'Yes',
'Cancel',
);
if (result === 'Yes') {
// Await since we must wait for the machine to stop before starting it again
await execPromise(getPodmanCli(), ['machine', 'stop', machine]);
await execPromise(getPodmanCli(), ['machine', 'start', machine]);
}
}
// Enable the compatibility mode by running podman-mac-helper install
// Run the command with sudo privileges and output the result to the user
async enable(): Promise<void> {
await this.runCommand('install', 'enabled');
// Prompt the user to restart the podman machine if it's running
const isRunning = await findRunningMachine();
if (isRunning !== '') {
await this.promptRestart(isRunning);
}
return Promise.resolve();
}
async disable(): Promise<void> {
await this.runCommand('uninstall', 'disabled');
// Prompt the user to restart the podman machine if it's running
const isRunning = await findRunningMachine();
if (isRunning !== '') {
await this.promptRestart(isRunning);
}
return Promise.resolve();
}
}
export class LinuxSocketCompatibility extends SocketCompatibility {
details =
'Administrative privileges are required to enable or disable the systemd Podman socket for Docker compatibility.';
// This will show the "opposite" of what the current state is
// "Enable" if it's currently disabled, "Disable" if it's currently enabled
// for tooltip text
tooltipText(): string {
const text = 'Linux Docker socket compatibility for Podman.';
return this.isEnabled() ? `Disable ${text}` : `Enable ${text}`;
}
// isEnabled() checks to see if /etc/systemd/system/socket.target.wants/podman.socket exists
isEnabled(): boolean {
const filename = '/etc/systemd/system/socket.target.wants/podman.socket';
return fs.existsSync(filename);
}
// Runs the systemd command either 'enable' or 'disable'
async runSystemdCommand(command: string, description: string): Promise<void> {
// Only allow enable or disable, throw error if anything else is inputted
if (command !== 'enable' && command !== 'disable') {
throw new Error('runSystemdCommand only accepts enable or disable as the command');
}
// Create the full command to run with --now as well as the podman socket name
const fullCommand = [command, '--now', podmanSystemdSocket];
try {
// Have to run via sudo
await execPromise('systemctl', fullCommand);
} catch (error) {
console.error(`Error running systemctl command: ${error}`);
await extensionApi.window.showErrorMessage(`Error running systemctl command: ${error}`, 'OK');
return;
}
// Show information message to the user that they may need to run
// ln -s /run/podman/podman.sock /var/run/docker.sock to enable Docker compatibility
if (command === 'enable') {
// Show information and give the user an option of Yes or Cancel
const result = await extensionApi.window.showInformationMessage(
'Do you want to create a symlink from /run/podman/podman.sock to /var/run/docker.sock to enable Docker compatibility without having to set the DOCKER_HOST environment variable?',
'Yes',
'Cancel',
);
// If the user clicked Yes, run the ln command
if (result === 'Yes') {
try {
await execPromise('pkexec', ['ln', '-s', '/run/podman/podman.sock', '/var/run/docker.sock']);
await extensionApi.window.showInformationMessage(
'Symlink created successfully. The Podman socket is now available at /var/run/docker.sock.',
);
} catch (error) {
console.error(`Error creating symlink: ${error}`);
await extensionApi.window.showErrorMessage(`Error creating symlink: ${error}`, 'OK');
return;
}
}
}
await extensionApi.window.showInformationMessage(
`Podman systemd socket has been ${description} for Docker compatibility.`,
);
}
async enable(): Promise<void> {
return this.runSystemdCommand('enable', 'enabled');
}
async disable(): Promise<void> {
return this.runSystemdCommand('disable', 'disabled');
}
}
// TODO: Windows
export function getSocketCompatibility(): SocketCompatibility {
switch (process.platform) {
case 'darwin':
return new DarwinSocketCompatibility();
case 'linux':
return new LinuxSocketCompatibility();
default:
throw new Error(`Unsupported platform ${process.platform}`);
}
}