Skip to content

Commit

Permalink
feat: onboarding podman fresh install (containers#3172)
Browse files Browse the repository at this point in the history
Signed-off-by: lstocchi <lstocchi@redhat.com>
  • Loading branch information
lstocchi committed Jul 19, 2023
1 parent c2236d6 commit 5692e1c
Show file tree
Hide file tree
Showing 15 changed files with 1,438 additions and 57 deletions.
160 changes: 160 additions & 0 deletions extensions/podman/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,18 @@
{
"command": "podman.info",
"title": "podman: Specific info about podman"
},
{
"command": "podman.onboarding.checkPodmanInstalled",
"title": "podman: Check podman installation"
},
{
"command": "podman.onboarding.checkPodmanRequirements",
"title": "podman: Check system requirements to install podman"
},
{
"command": "podman.onboarding.installPodman",
"title": "podman: Install podman"
}
],
"configuration": {
Expand Down Expand Up @@ -95,6 +107,154 @@
"description": "Machine with root privileges"
}
}
},
"onboarding": {
"title": "Get started with Podman Desktop",
"steps": [
{
"id": "podmanSetup",
"title": "Podman Setup",
"commands": [
{
"id": "checkPodmanInstalled",
"command": "podman.onboarding.checkPodmanInstalled",
"response": {
"status": "string",
"installed": "string"
}
},
{
"id": "checkPodmanRequirements",
"command": "podman.onboarding.checkPodmanRequirements",
"response": {
"status": "string",
"warningsMarkdown": "string"
}
},
{
"id": "installPodman",
"command": "podman.onboarding.installPodman",
"response": {
"status": "string"
}
}
],
"views": [
{
"id": "checkPodmanInstalledView",
"title": "Checking for Podman installation",
"media": {
"path": "icon.png",
"altText": "podman logo"
},
"commandAtActivation": [
{
"command": "checkPodmanInstalled"
}
],
"completionEvents": [
"onCommandResult:checkPodmanInstalled"
]
},
{
"id": "startPodmanInstallation",
"title": "We could not find Podman. Let's install it!",
"media": {
"path": "icon.png",
"altText": "podman logo"
},
"when": "onCommandResult:checkPodmanInstalled.installed == failed"
},
{
"id": "checkPodmanRequirements",
"title": "Checking for system requirements to install Podman",
"media": {
"path": "icon.png",
"altText": "podman logo"
},
"when": "onCommandResult:checkPodmanInstalled.installed == failed",
"commandAtActivation": [
{
"command": "checkPodmanRequirements"
}
],
"completionEvents": [
"onCommandResult:checkPodmanRequirements"
]
},
{
"id": "missingRequirementView",
"title": "Some system requirements are missing",
"media": {
"path": "icon.png",
"altText": "podman logo"
},
"when": "onCommandResult:checkPodmanRequirements.status == failed && onCommandResult:checkPodmanInstalled.installed == failed",
"completionEvents": [
"onCommandResult:checkPodmanRequirements.status == ok"
],
"content": [
[
{
"value": "${checkPodmanRequirements.warningsMarkdown}",
"template": "dark_bg"
}
]
,
[
{
"value": "When possible, we've provided information on how to address these requirements."
}
]
,
[
{
"label": "Check requirements again",
"command": "checkPodmanRequirements",
"component": "button"
}
]
]
},
{
"id": "installPodman",
"title": "Installing Podman",
"description": "Once installed, we will enable and configure the extension",
"media": {
"path": "icon.png",
"altText": "podman logo"
},
"when": "onCommandResult:checkPodmanInstalled.installed == failed",
"commandAtActivation": [
{
"command": "installPodman"
}
],
"completionEvents": [
"onCommandResult:installPodman"
]
},
{
"id": "podmanFailedInstallation",
"title": "Failed installing Podman",
"media": {
"path": "icon.png",
"altText": "podman logo"
},
"when": "onCommandResult:installPodman.status == failed && onCommandResult:checkPodmanInstalled.installed == failed"
},
{
"id": "podmanInstalled",
"title": "Podman successfully installed",
"media": {
"path": "icon.png",
"altText": "podman logo"
},
"when": "onCommandResult:installPodman.status == ok || onCommandResult:checkPodmanInstalled.installed == ok"
}
]
}
]
}
},
"scripts": {
Expand Down
76 changes: 76 additions & 0 deletions extensions/podman/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -805,6 +805,7 @@ export async function activate(extensionContext: extensionApi.ExtensionContext):
const disposable = extensionApi.Disposable.create(() => {
podmanProcess.kill();
});

extensionContext.subscriptions.push(disposable);
initDefaultLinux(provider).catch((error: unknown) => {
console.error('Error while initializing default linux', error);
Expand All @@ -821,6 +822,81 @@ export async function activate(extensionContext: extensionApi.ExtensionContext):
console.error('Error while monitoring provider', error);
});

const onboardingCheckInstalltionCommand = extensionApi.commands.registerCommand('podman.onboarding.checkPodmanInstalled', async () => {
const installation = await getPodmanInstallation();
return {
status: 'completed',
installed: installation ? 'ok' : 'failed',
};
});

const onboardingCheckReqsCommand = extensionApi.commands.registerCommand('podman.onboarding.checkPodmanRequirements', async () => {
const checks = podmanInstall.getInstallChecks();
const result = [];
let successful = true;
for (const check of checks) {
try {
const checkResult = await check.execute();

result.push({
name: check.title,
successful: checkResult.successful,
description: checkResult.description,
docLinks: checkResult.docLinks,
});

if (!checkResult.successful) {
successful = false;
}
} catch (err) {
result.push({
name: check.title,
successful: false,
description: err instanceof Error ? err.message : typeof err === 'object' ? err?.toString() : 'unknown error',
});
successful = false;
}
}

let warningsMarkdown = '';

for (const res of result) {
warningsMarkdown += `* ${res.successful ? '✅' : '❌'} ${res.name} \n`;
if (res.description) {
warningsMarkdown += res.description
if (res.docLinks) {
warningsMarkdown += ' See: '
for (const link of res.docLinks) {
warningsMarkdown += `[${link.title}](${link.url}) `
}
warningsMarkdown += '\n';
}
}
}

return {
status: successful ? 'ok' : 'failed',
warningsMarkdown: warningsMarkdown
};

});

const onboardingInstallPodmanCommand = extensionApi.commands.registerCommand('podman.onboarding.installPodman', async () => {
try {
await podmanInstall.doInstallPodman(provider);
return {
status: 'ok'
};
} catch (e) {
console.error(e);
return {
status: 'failed',
};
}
});

extensionContext.subscriptions.push(onboardingCheckInstalltionCommand, onboardingCheckReqsCommand, onboardingInstallPodmanCommand);

// register the registries
const registrySetup = new RegistrySetup();
await registrySetup.setup();
Expand Down
37 changes: 20 additions & 17 deletions packages/main/src/plugin/api/onboarding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,62 +24,65 @@ export interface OnboardingCommandResponse {
export interface OnboardingCommand {
id: string;
command: string;
response: OnboardingCommandResponse;
response?: { [key: string]: any };
args: string[];
}

export interface OnboardingCommandAtActivation {
command: string;
order: number;
}

export interface OnboardingViewButtonItem {
export interface OnboardingBaseItem {
id: string;
template?: 'dark_bg';
}

export interface OnboardingViewButtonItem extends OnboardingBaseItem {
component: "button";
label: string;
value: string;
id: string;
command: string;
}

export interface OnboardingViewTextItem {
export interface OnboardingViewTextItem extends OnboardingBaseItem {
component: "text";
value: string;
style: string;
id: string;
}

export type OnboardingViewItem = OnboardingViewTextItem | OnboardingViewButtonItem;

export interface OnboardingViewRow {
row: number;
items: OnboardingViewItem[];
}
export type OnboardingStepStatus = 'completed' | 'failed' | 'skipped';

export interface OnboardingStepView {
id: string;
title: string;
description: string;
media: { path: string; altText: string };
isSkippable: boolean;
commandAtActivation: OnboardingCommandAtActivation[];
enableCompletionEvents: string[];
completionEvents: string[];
content: OnboardingViewRow[];
content: OnboardingViewItem[][];
when: string;
status: OnboardingStepStatus;
showNext?: boolean;
}

export interface OnboardingStep {
id: string;
title: string;
description: string;
isSkippable: boolean;
commands: OnboardingCommand[];
views: OnboardingStepView[];
media: { path: string; altText: string };
status: OnboardingStepStatus;
}

export interface Onboarding {
title: string;
description: string;
media: { path: string; altText: string };
description?: string;
media?: { path: string; altText: string };
steps: OnboardingStep[];
}

export interface OnboardingInfo extends Onboarding {
extension: string;
}
7 changes: 6 additions & 1 deletion packages/main/src/plugin/extension-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,11 @@ export class ExtensionLoader {
this.iconRegistry.registerIconContribution(extension, icons);
}

const onboarding = extension.manifest?.contributes?.onboarding;
if (onboarding) {
this.onboardingRegistry.registerOnboarding(extension, onboarding);
}

this.analyzedExtensions.set(extension.id, extension);
this.extensionState.delete(extension.id);
this.extensionStateErrors.delete(extension.id);
Expand Down Expand Up @@ -1092,7 +1097,7 @@ export class ExtensionLoader {
}
const onboarding = analyzedExtension.manifest?.contributes?.onboarding;
if (onboarding) {
this.onboardingRegistry.registerOnboarding(analyzedExtension, onboarding);
this.onboardingRegistry.unregisterOnboarding(analyzedExtension.id);
}
}
this.activatedExtensions.delete(extensionId);
Expand Down
8 changes: 6 additions & 2 deletions packages/main/src/plugin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ import type { IconInfo } from './api/icon-info.js';
import { Directories } from './directories.js';
import { CustomPickRegistry } from './custompick/custompick-registry.js';
import { OnboardingRegistry } from './onboarding-registry.js';
import { Onboarding } from './api/onboarding.js';
import { Onboarding, OnboardingCommandResponse, OnboardingInfo } from './api/onboarding.js';
import { ContextRegistry } from './context-registry.js';
import { ContextInfo } from './api/context-info.js';

Expand Down Expand Up @@ -1705,10 +1705,14 @@ export class PluginSystem {
window.close();
});

this.ipcHandle('onboardingRegistry:listOnboarding', async (): Promise<Onboarding[]> => {
this.ipcHandle('onboardingRegistry:listOnboarding', async (): Promise<OnboardingInfo[]> => {
return onboardingRegistry.listOnboarding();
});

this.ipcHandle('onboardingRegistry:executeOnboardingCommand', async (_listener, extension: string, stepId: string, commandId: string, args?: any[]): Promise<void> => {
return onboardingRegistry.executeOnboardingCommand(extension, stepId, commandId, args);
});

const dockerDesktopInstallation = new DockerDesktopInstallation(
apiSender,
containerProviderRegistry,
Expand Down

0 comments on commit 5692e1c

Please sign in to comment.