Skip to content

Commit

Permalink
feat: add warnings and info to provider, add warn re. docker socket
Browse files Browse the repository at this point in the history
### What does this PR do?

* Reworks the plugin and extension API's to provide the ability to pass
  "up" information and warnings about the provider
* Adds a warning check for Podman to see if the docker socket is
  actually Podman in disguise and to warn the user

### Screenshot/screencast of this PR

<!-- Please include a screenshot or a screencast explaining what is doing this PR -->

### What issues does this PR fix or reference?

<!-- Please include any related issue from Podman Desktop repository (or from another issue tracker).
-->

Fixes podman-desktop#905
Fixes podman-desktop#758

### How to test this PR?

If you have podman and docker installed:

1. `yarn watch`
2. Start Podman Desktop
3. Start Docker Desktop (Podman Desktop should now warn you that the
   socket is not being used by Podman)
4. Quit Docker Desktop
5. `podman machine stop && podman machine start` or restart via UI on
   Podman Desktop in order for `podman machine` to re-enable using the
   /var/run/docker.sock
6. Warning should now clear

If only using podman:

1. `yarn watch`
2. Use the podman provider
3. `sudo rm -f /var/run/docker.sock` or mv
4. Warning should appear that cannot find the docker socket

<!-- Please explain steps to reproduce -->

Signed-off-by: Charlie Drage <[email protected]>
  • Loading branch information
cdrage committed Dec 16, 2022
1 parent b605aa6 commit d1ddf71
Show file tree
Hide file tree
Showing 13 changed files with 338 additions and 1 deletion.
36 changes: 35 additions & 1 deletion extensions/podman/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ import type { InstalledPodman } from './podman-cli';
import { execPromise, getPodmanCli, getPodmanInstallation } from './podman-cli';
import { PodmanConfiguration } from './podman-configuration';
import { getDetectionChecks } from './detection-checks';
import { getInformation } from './information';
import { getWarning } from './warning';

type StatusHandler = (name: string, event: extensionApi.ProviderConnectionStatus) => void;

Expand Down Expand Up @@ -215,6 +217,36 @@ async function monitorProvider(provider: extensionApi.Provider) {
provider.updateStatus('installed');
}
}

// Retrieve the newest information + warnings, but only update if there is a change
const retrievedInformation = await getInformation();
const retrievedWarnings = await getWarning();

// Only send an update if the length of the array is different, and if it's not,
// we will check the array to see if any content is different.
if (retrievedInformation.length !== provider.info.length) {
provider.updateInfo(retrievedInformation);
} else {
for (let i = 0; i < retrievedInformation.length; i++) {
if (retrievedInformation[i].details !== provider.info[i].details) {
provider.updateInfo(retrievedInformation);
break;
}
}
}

// Only send an update if the length of the array is different, and if it's not,
// we will check the array to see if any content is different.
if (retrievedWarnings.length !== provider.warning.length) {
provider.updateWarning(retrievedWarnings);
} else {
for (let i = 0; i < retrievedWarnings.length; i++) {
if (retrievedWarnings[i].details !== provider.warning[i].details) {
provider.updateWarning(retrievedWarnings);
break;
}
}
}
} catch (error) {
// ignore the update
}
Expand Down Expand Up @@ -312,6 +344,8 @@ export async function activate(extensionContext: extensionApi.ExtensionContext):
// update detection checks
detectionChecks.push(...getDetectionChecks(installedPodman));

// Pass information here with regards to warnings / info messages with regards to docker socket
// information
const providerOptions: extensionApi.ProviderOptions = {
name: 'Podman',
id: 'podman',
Expand Down Expand Up @@ -497,7 +531,7 @@ export async function activate(extensionContext: extensionApi.ExtensionContext):
}

// monitor provider
// like version, checks
// like version, checks, information and warnings
monitorProvider(provider);

// register the registries
Expand Down
38 changes: 38 additions & 0 deletions extensions/podman/src/information.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**********************************************************************
* 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';

// This function gets any "essential" information with regards to the Podman plugin that the user should know.
// For example: the Podman version, the Podman API version, how many containers, etc.
// With this information, you can help the user to understand what is going on with the plugin.
export async function getInformation(): Promise<extensionApi.ProviderInformation[]> {
const infos: extensionApi.ProviderInformation[] = [];

/* Test information
// TO REMOVE:
// Purposely pass in test information to the provider.
const testInfo: extensionApi.ProviderInformation = {
name: 'Status',
details: 'Current status of the provider',
};
infos.push(testInfo);
*/

return infos;
}
111 changes: 111 additions & 0 deletions extensions/podman/src/warning.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/**********************************************************************
* 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';

// Passes any warnings with regards to the plugin to the user.
export async function getWarning(): Promise<extensionApi.ProviderInformation[]> {
console.log('getWarning() called');
const warnings: extensionApi.ProviderInformation[] = [];

// FIRST CHECK
// Check to see if the user is running the disguised Podman socket and warn if not
const isPodman = await isDisguisedPodman(getSocketPath());
if (!isPodman) {
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;
}

// Create the message and push it to the array
const podmanWarn: extensionApi.ProviderInformation = {
name: 'Docker Socket Compatibility',
details: details,
};
warnings.push(podmanWarn);
}

return warnings;
}

// Async function that checks to see if the current Docker socket is a disguised Podman socket
export async function isDisguisedPodman(socketPath: string): Promise<boolean> {
const podmanPingUrl = {
path: '/libpod/_ping',
socketPath,
};
return new Promise<boolean>(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;
}
20 changes: 20 additions & 0 deletions packages/extension-api/src/extension-api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -146,6 +153,11 @@ declare module '@tmpwip/extension-api' {
images?: ProviderImages;
links?: ProviderLinks[];
detectionChecks?: ProviderDetectionCheck[];

// Provide way to add additional information to the provider
// in the form of information and warnings
info?: ProviderInformation[];
warning?: ProviderInformation[];
}

export type ProviderConnectionStatus = 'started' | 'stopped' | 'starting' | 'stopping' | 'unknown';
Expand Down Expand Up @@ -295,6 +307,14 @@ declare module '@tmpwip/extension-api' {
// it may happen after an update or an installation
updateDetectionChecks(detectionChecks: ProviderDetectionCheck[]): void;

// update need-to-know information about the provider
readonly info: ProviderInformation[];
updateInfo(info: ProviderInformation[]): void;

// update warning information for the provider
readonly warning: ProviderInformation[];
updateWarning(warning: ProviderInformation[]): void;

// notify that detection checks have changed
onDidUpdateDetectionChecks: Event<ProviderDetectionCheck[]>;
}
Expand Down
5 changes: 5 additions & 0 deletions packages/main/src/plugin/api/provider-info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import type {
ProviderLinks,
ProviderStatus,
Link,
ProviderInformation,
} from '@tmpwip/extension-api';

export type LifecycleMethod = 'start' | 'stop' | 'delete';
Expand Down Expand Up @@ -65,6 +66,10 @@ export interface ProviderInfo {
links: ProviderLinks[];
detectionChecks: ProviderDetectionCheck[];

// Need-to-know information and warning messages regarding the provider
info: ProviderInformation[];
warning: ProviderInformation[];

images: ProviderImages;

// can install a provider
Expand Down
31 changes: 31 additions & 0 deletions packages/main/src/plugin/provider-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -65,6 +66,14 @@ export class ProviderImpl implements Provider, IDisposable {
private readonly _onDidUpdateDetectionChecks = new Emitter<ProviderDetectionCheck[]>();
readonly onDidUpdateDetectionChecks: Event<ProviderDetectionCheck[]> = this._onDidUpdateDetectionChecks.event;

private _info: ProviderInformation[];
private readonly _onDidUpdateInfo = new Emitter<ProviderInformation[]>();
readonly onDidUpdateInfo: Event<ProviderInformation[]> = this._onDidUpdateInfo.event;

private _warning: ProviderInformation[];
private readonly _onDidUpdateWarning = new Emitter<ProviderInformation[]>();
readonly onDidUpdateWarning: Event<ProviderInformation[]> = this._onDidUpdateWarning.event;

constructor(
private _internalId: string,
private providerOptions: ProviderOptions,
Expand All @@ -80,6 +89,8 @@ export class ProviderImpl implements Provider, IDisposable {
this._links = providerOptions.links || [];
this._detectionChecks = providerOptions.detectionChecks || [];
this._images = providerOptions.images || {};
this._info = providerOptions.info || [];
this._warning = providerOptions.warning || [];

// monitor connection statuses
setInterval(async () => {
Expand Down Expand Up @@ -116,6 +127,14 @@ export class ProviderImpl implements Provider, IDisposable {
return this._detectionChecks;
}

get info(): ProviderInformation[] {
return this._info;
}

get warning(): ProviderInformation[] {
return this._warning;
}

get images(): ProviderImages {
return this._images;
}
Expand All @@ -139,6 +158,18 @@ export class ProviderImpl implements Provider, IDisposable {
this._onDidUpdateDetectionChecks.fire(detectionChecks);
}

// Update the information
updateInfo(info: ProviderInformation[]): void {
this._info = info;
this._onDidUpdateInfo.fire(info);
}

// Update the warnings
updateWarning(warning: ProviderInformation[]): void {
this._warning = warning;
this._onDidUpdateWarning.fire(warning);
}

get status(): ProviderStatus {
return this._status;
}
Expand Down
18 changes: 18 additions & 0 deletions packages/main/src/plugin/provider-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,18 +106,34 @@ 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);
this.apiSender.send('provider:update-info', provider.id);
}

});
}, 2000);
}
Expand Down Expand Up @@ -476,6 +492,8 @@ export class ProviderRegistry {
detectionChecks: provider.detectionChecks,
images: provider.images,
version: provider.version,
info: provider.info,
warning: provider.warning,
installationSupport,
};

Expand Down
2 changes: 2 additions & 0 deletions packages/main/src/tray-menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ export class TrayMenu {
version: '',
links: [],
images: {},
info: [],
warning: [],
installationSupport: false,
containerProviderConnectionCreation: false,
kubernetesProviderConnectionCreation: false,
Expand Down
Loading

0 comments on commit d1ddf71

Please sign in to comment.