diff --git a/extensions/podman/src/extension.ts b/extensions/podman/src/extension.ts index c89f1f6effdf3..6fbdf1a4574b5 100644 --- a/extensions/podman/src/extension.ts +++ b/extensions/podman/src/extension.ts @@ -29,6 +29,7 @@ import type { InstalledPodman } from './podman-cli'; import { execPromise, getPodmanCli, getPodmanInstallation } from './podman-cli'; import { PodmanConfiguration } from './podman-configuration'; import { getDetectionChecks } from './detection-checks'; +import { getDisguisedPodmanInformation, getSocketPath, isDisguisedPodman } from './warnings'; type StatusHandler = (name: string, event: extensionApi.ProviderConnectionStatus) => void; @@ -37,11 +38,17 @@ const podmanMachineSocketsDirectoryMac = path.resolve(os.homedir(), '.local/shar let storedExtensionContext; let stopLoop = false; +let disguisedPodmanSocketWatcher: extensionApi.FileSystemWatcher; + // current status of machines const podmanMachinesStatuses = new Map(); const podmanMachinesInfo = new Map(); const currentConnections = new Map(); +// Warning to check to see if the socket is a disguised Podman socket, +// by default we assume it is until proven otherwise when we check +let isDisguisedPodmanSocket = true; + type MachineJSON = { Name: string; CPUs: number; @@ -496,8 +503,11 @@ export async function activate(extensionContext: extensionApi.ExtensionContext): monitorMachines(provider); } + // update the status of the provider if the socket is changed, created or deleted + disguisedPodmanSocketWatcher = setupDisguisedPodmanSocketWatcher(provider, getSocketPath()); + // monitor provider - // like version, checks + // like version, checks, warnings monitorProvider(provider); // register the registries @@ -512,3 +522,47 @@ export function deactivate(): void { stopLoop = true; console.log('stopping podman extension'); } + +function setupDisguisedPodmanSocketWatcher( + provider: extensionApi.Provider, + socketFile: string, +): extensionApi.FileSystemWatcher { + + // Monitor the socket file for any changes, creation or deletion + // and trigger a change if that happens + const disguisedPodmanSocketWatcher = extensionApi.fs.createFileSystemWatcher(socketFile); + + console.log('socket file:', socketFile) + + disguisedPodmanSocketWatcher.onDidChange(() => { + console.log('socket changed') + checkDisguisedPodmanSocket(provider); + }); + + disguisedPodmanSocketWatcher.onDidCreate(() => { + console.log('socket created') + checkDisguisedPodmanSocket(provider); + }); + + disguisedPodmanSocketWatcher.onDidDelete(() => { + console.log('socket deleted') + checkDisguisedPodmanSocket(provider); + }); + + return disguisedPodmanSocketWatcher; +} + +async function checkDisguisedPodmanSocket(provider: extensionApi.Provider) { + // Check to see if the socket is disguised or not. If it is, we'll push a warning up + // to the plugin library to the let the provider know that there is a warning + const disguisedCheck = await isDisguisedPodman(); + if (isDisguisedPodmanSocket != disguisedCheck) { + isDisguisedPodmanSocket = disguisedCheck; + } + + // If isDisguisedPodmanSocket is true, we'll push a warning up to the plugin library with getDisguisedPodmanWarning() + // If isDisguisedPodmanSocket is false, we'll push an empty array up to the plugin library to clear the warning + // as we have no other warnings to display (or implemented) + const retrievedWarnings = isDisguisedPodmanSocket ? [] : [getDisguisedPodmanInformation()]; + provider.updateWarning(retrievedWarnings); +} \ No newline at end of file diff --git a/extensions/podman/src/warnings.ts b/extensions/podman/src/warnings.ts new file mode 100644 index 0000000000000..a0304b768e89a --- /dev/null +++ b/extensions/podman/src/warnings.ts @@ -0,0 +1,101 @@ +/********************************************************************** + * Copyright (C) 2022 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 type * as extensionApi from '@tmpwip/extension-api'; +import * as os from 'node:os'; +import * as http from 'node:http'; + +// Explanations +const detailsExplanation = 'Podman is not emulating the default Docker socket path: '; +const detailsNotWorking = '. Docker-specific tools may not work.'; + +// Default socket paths +const windowsSocketPath = '//./pipe/docker_engine'; +const defaultSocketPath = '/var/run/docker.sock'; + +// Return the warning information to the user if the socket is not a disguised Podman socket +export function getDisguisedPodmanInformation(): extensionApi.ProviderInformation { + let details: string; + + // Set the details message based on the OS + switch (os.platform()) { + case 'win32': + details = detailsExplanation.concat(windowsSocketPath, detailsNotWorking); + break; + case 'darwin': + // Due to how `podman-mac-helper` does not (by default) map the emulator to /var/run/docker.sock, we need to explain + // that the user must go on the Podman Desktop website for more information. This is because the user must manually + // map the socket to /var/run/docker.sock if not done by `podman machine` already (podman machine automatically maps the socket if Docker is not running) + details = detailsExplanation.concat( + defaultSocketPath, + detailsNotWorking, + ' See troubleshooting page on podman-desktop.io for more information.', + ); + break; + default: + details = detailsExplanation.concat(defaultSocketPath, detailsNotWorking); + break; + } + + // Return ProviderInformation with the details message + return { + name: 'Docker Socket Compatibility', + details, + }; +} + +// Async function that checks to see if the current Docker socket is a disguised Podman socket +export async function isDisguisedPodman(): Promise { + const socketPath = getSocketPath(); + + const podmanPingUrl = { + path: '/libpod/_ping', + socketPath, + }; + + return new Promise(resolve => { + const req = http.get(podmanPingUrl, res => { + res.on('data', () => { + // do nothing + }); + + res.on('end', () => { + if (res.statusCode === 200) { + resolve(true); + } else { + resolve(false); + } + }); + }); + + req.once('error', err => { + console.debug('Error while pinging docker as podman', err); + resolve(false); + }); + }); +} + +// Function that checks whether you are running windows, mac or linux and returns back +// the correct Docker socket location +export function getSocketPath(): string { + let socketPath: string = defaultSocketPath; + if (os.platform() === 'win32') { + socketPath = windowsSocketPath; + } + return socketPath; +} diff --git a/packages/extension-api/src/extension-api.d.ts b/packages/extension-api/src/extension-api.d.ts index 1ea769db878e4..fae577004e18c 100644 --- a/packages/extension-api/src/extension-api.d.ts +++ b/packages/extension-api/src/extension-api.d.ts @@ -132,6 +132,13 @@ declare module '@tmpwip/extension-api' { status(): ProviderStatus; } + // For displaying essential information to the user + // "name" of the warning / title and a "details" field for more information + export interface ProviderInformation { + name: string; + details?: string; + } + export interface ProviderDetectionCheck { name: string; details?: string; @@ -146,6 +153,9 @@ declare module '@tmpwip/extension-api' { images?: ProviderImages; links?: ProviderLinks[]; detectionChecks?: ProviderDetectionCheck[]; + + // Provide way to add additional warnings to the provider + warning?: ProviderInformation[]; } export type ProviderConnectionStatus = 'started' | 'stopped' | 'starting' | 'stopping' | 'unknown'; @@ -295,6 +305,10 @@ declare module '@tmpwip/extension-api' { // it may happen after an update or an installation updateDetectionChecks(detectionChecks: ProviderDetectionCheck[]): void; + // update warning information for the provider + readonly warning: ProviderInformation[]; + updateWarning(warning: ProviderInformation[]): void; + // notify that detection checks have changed onDidUpdateDetectionChecks: Event; } diff --git a/packages/main/src/plugin/api/provider-info.ts b/packages/main/src/plugin/api/provider-info.ts index c02fd956b016c..e4b16d11ae950 100644 --- a/packages/main/src/plugin/api/provider-info.ts +++ b/packages/main/src/plugin/api/provider-info.ts @@ -23,6 +23,7 @@ import type { ProviderLinks, ProviderStatus, Link, + ProviderInformation, } from '@tmpwip/extension-api'; export type LifecycleMethod = 'start' | 'stop' | 'delete'; @@ -65,6 +66,9 @@ export interface ProviderInfo { links: ProviderLinks[]; detectionChecks: ProviderDetectionCheck[]; + // warning messages regarding the provider + warning: ProviderInformation[]; + images: ProviderImages; // can install a provider diff --git a/packages/main/src/plugin/provider-impl.ts b/packages/main/src/plugin/provider-impl.ts index ede246f34c32b..94948a58ce49a 100644 --- a/packages/main/src/plugin/provider-impl.ts +++ b/packages/main/src/plugin/provider-impl.ts @@ -37,6 +37,7 @@ import type { ProviderUpdate, ProviderAutostart, KubernetesProviderConnectionFactory, + ProviderInformation, } from '@tmpwip/extension-api'; import type { ProviderRegistry } from './provider-registry'; import { Emitter } from './events/emitter'; @@ -65,6 +66,10 @@ export class ProviderImpl implements Provider, IDisposable { private readonly _onDidUpdateDetectionChecks = new Emitter(); readonly onDidUpdateDetectionChecks: Event = this._onDidUpdateDetectionChecks.event; + private _warning: ProviderInformation[]; + private readonly _onDidUpdateWarning = new Emitter(); + readonly onDidUpdateWarning: Event = this._onDidUpdateWarning.event; + constructor( private _internalId: string, private providerOptions: ProviderOptions, @@ -80,6 +85,7 @@ export class ProviderImpl implements Provider, IDisposable { this._links = providerOptions.links || []; this._detectionChecks = providerOptions.detectionChecks || []; this._images = providerOptions.images || {}; + this._warning = providerOptions.warning || []; // monitor connection statuses setInterval(async () => { @@ -116,6 +122,10 @@ export class ProviderImpl implements Provider, IDisposable { return this._detectionChecks; } + get warning(): ProviderInformation[] { + return this._warning; + } + get images(): ProviderImages { return this._images; } @@ -139,6 +149,12 @@ export class ProviderImpl implements Provider, IDisposable { this._onDidUpdateDetectionChecks.fire(detectionChecks); } + // Update the warnings + updateWarning(warning: ProviderInformation[]): void { + this._warning = warning; + this._onDidUpdateWarning.fire(warning); + } + get status(): ProviderStatus { return this._status; } diff --git a/packages/main/src/plugin/provider-registry.ts b/packages/main/src/plugin/provider-registry.ts index cb7c6645e1101..54a763b215cb9 100644 --- a/packages/main/src/plugin/provider-registry.ts +++ b/packages/main/src/plugin/provider-registry.ts @@ -111,18 +111,32 @@ export class ProviderRegistry { this.lifecycleListeners = []; this.containerConnectionLifecycleListeners = []; + // Every 2 seconds, we will check: + // * The status of the providers + // * Any new warnings or informations for each provider setInterval(async () => { Array.from(this.providers.keys()).forEach(providerKey => { + // Get the provider and its lifecycle const provider = this.providers.get(providerKey); const providerLifecycle = this.providerLifecycles.get(providerKey); + + // If the provider and its lifecycle exist, we will check if (provider && providerLifecycle) { + // Get the status const status = providerLifecycle.status(); + + // If the status does not match the current one, we will send a listener event and update the status if (status !== this.providerStatuses.get(providerKey)) { provider.updateStatus(status); this.listeners.forEach(listener => listener('provider:update-status', this.getProviderInfo(provider))); this.providerStatuses.set(providerKey, status); } } + + // Let the renderer know to check for new warnings and information + if (provider) { + this.apiSender.send('provider:update-warning', provider.id); + } }); }, 2000); } @@ -491,6 +505,7 @@ export class ProviderRegistry { detectionChecks: provider.detectionChecks, images: provider.images, version: provider.version, + warning: provider.warning, installationSupport, }; diff --git a/packages/main/src/tray-menu.ts b/packages/main/src/tray-menu.ts index 313328c4c6eba..7d021dc32af14 100644 --- a/packages/main/src/tray-menu.ts +++ b/packages/main/src/tray-menu.ts @@ -78,6 +78,7 @@ export class TrayMenu { version: '', links: [], images: {}, + warning: [], installationSupport: false, containerProviderConnectionCreation: false, kubernetesProviderConnectionCreation: false, diff --git a/packages/renderer/src/lib/welcome/ProviderReady.svelte b/packages/renderer/src/lib/welcome/ProviderReady.svelte index dfc52148bce99..2f2440d6a63fe 100644 --- a/packages/renderer/src/lib/welcome/ProviderReady.svelte +++ b/packages/renderer/src/lib/welcome/ProviderReady.svelte @@ -1,6 +1,7 @@ + +
+ + {#if providerInfo?.warning?.length > 0} + {#each providerInfo.warning as warn} +
+ ⚠️ + {warn.name}: + {warn.details} +
+ {/each} + {/if} +
diff --git a/packages/renderer/src/stores/providers.ts b/packages/renderer/src/stores/providers.ts index 1110fe317af6e..a2d121dbe63f9 100644 --- a/packages/renderer/src/stores/providers.ts +++ b/packages/renderer/src/stores/providers.ts @@ -64,6 +64,9 @@ window?.events.receive('provider-delete', () => { window?.events.receive('provider:update-status', () => { fetchProviders(); }); +window?.events.receive('provider:update-warning', () => { + fetchProviders(); +}); window.addEventListener('system-ready', () => { fetchProviders(); }); diff --git a/website/docs/troubleshooting.md b/website/docs/troubleshooting.md index b7988e34b1b59..17256bfdd569b 100644 --- a/website/docs/troubleshooting.md +++ b/website/docs/troubleshooting.md @@ -149,6 +149,40 @@ helper_binaries_dir=["/Users/user/example_directory"] **NOTE**: A pre-built binary will be added to the Podman release page so you do not have to build `podman-mac-helper`. An [issue is open for this](https://github.com/containers/podman/issues/16746). +### Warning about Docker compatibility mode + + +#### Issue: + +When running the Podman provider, a warning shows regarding Docker compatibility mode on the dashboard: + +```sh +⚠️ Docker Socket Compatibility: Podman is not emulating the default Docker socket path: '/var/run/docker.sock'. Docker-specific tools may not work. See troubleshooting page on podman-desktop.io for more information. +``` + +This may appear when either: +* The Docker socket is not mounted correctly +* Docker Desktop is also being ran at the same time + +#### Solution: + +**On macOS:** + +1. Stop Docker Desktop (if install) +2. Run the `podman-mac-helper` binary: +```sh +sudo podman-mac-helper install +``` +3. Restart the Podman machine (the default Docker socket path will be recreated and Podman will emulate it) + + +**On Linux / Windows:** + +1. Stop Docker Desktop (if installed) +2. Restart the Podman machine (the default Docker socket path will be recreated and Podman will emulate it) + +*Note:* If Docker Desktop is started again, it will automatically re-alias the default Docker socket location and the Podman compatibilty warning will re-appear. + ## Code Ready Containers