Skip to content

Commit

Permalink
feat: required podman update in add node flow
Browse files Browse the repository at this point in the history
  • Loading branch information
jgresham committed Nov 6, 2023
1 parent 23b585e commit d839600
Show file tree
Hide file tree
Showing 12 changed files with 142 additions and 16 deletions.
1 change: 1 addition & 0 deletions assets/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"ChooseYourNetwork": "Choose your network",
"PodmanInstallation": "Podman installation",
"LearnMorePodman": "Learn more about Podman",
"PodmanUpdateRequiredDescription": "An update to Podman is required. Please download and install the update to continue. Note that any running nodes will be temporarily stopped.",
"DownloadAndInstall": "Download and Install",
"StartPodman": "Start Podman",
"StartNode": "Start node",
Expand Down
4 changes: 4 additions & 0 deletions src/main/ipc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ import {
import { checkPorts } from './ports';
import { getAppClientId } from './state/eventReporting';
import { onUserChangedLanguage } from './i18nMain';
import { getPodmanDetails } from './podman/details';
import { updatePodman } from './podman/update';

// eslint-disable-next-line import/prefer-default-export
export const initialize = () => {
Expand Down Expand Up @@ -198,7 +200,9 @@ export const initialize = () => {
ipcMain.handle('getIsPodmanInstalled', isPodmanInstalled);
ipcMain.handle('installPodman', installPodman);
ipcMain.handle('getIsPodmanRunning', isPodmanRunning);
ipcMain.handle('getPodmanDetails', getPodmanDetails);
ipcMain.handle('startPodman', startPodman);
ipcMain.handle('updatePodman', updatePodman);

// Settings
ipcMain.handle('getSetHasSeenSplashscreen', (_event, hasSeen?: boolean) => {
Expand Down
43 changes: 43 additions & 0 deletions src/main/podman/details.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import {
PODMAN_LATEST_VERSION,
PODMAN_MIN_VERSION,
getInstalledPodmanVersion,
} from './install/install';
import { isPodmanInstalled, isPodmanRunning } from './podman';

export type PodmanDetails = {
isInstalled: boolean;
isRunning?: boolean;
installedVersion?: string;
minimumVersionRequired?: string;
latestVersionAvailable?: string;
isOutdated?: boolean; // user must update podman
isOptionalUpdateAvailable?: boolean;
};

export const getPodmanDetails = async (): Promise<PodmanDetails> => {
const isInstalled = await isPodmanInstalled();
const isRunning = await isPodmanRunning();
const installedVersion = await getInstalledPodmanVersion();
let isOutdated;
if (installedVersion) {
isOutdated = installedVersion < PODMAN_MIN_VERSION;
}
let isOptionalUpdateAvailable;
// if outdated, leave as undefined because a required update takes precedent
if (!isOutdated && installedVersion) {
isOptionalUpdateAvailable = installedVersion < PODMAN_LATEST_VERSION;
}

const details: PodmanDetails = {
isInstalled,
isRunning,
installedVersion,
minimumVersionRequired: PODMAN_MIN_VERSION,
latestVersionAvailable: PODMAN_LATEST_VERSION,
isOutdated,
isOptionalUpdateAvailable,
};

return details;
};
2 changes: 1 addition & 1 deletion src/main/podman/install/install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import installOnLinux from './installOnLinux';
import { runCommand } from '../podman';

export const PODMAN_LATEST_VERSION = '4.7.2';
export const PODMAN_MIN_VERSION = '4.6.0';
export const PODMAN_MIN_VERSION = '4.5.0';

// eslint-disable-next-line
const installPodman = async (): Promise<any> => {
Expand Down
1 change: 1 addition & 0 deletions src/main/podman/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { removeNiceNodeMachine } from './machine';
/**
* Stops all nodes, downloads and installs new podman version,
* restarts all the nodes that were previously running prior to the update.
* todo: send messages to the front-end updating the status of the update
*/
export const updatePodman = async () => {
logger.info('updatePodman...');
Expand Down
2 changes: 2 additions & 0 deletions src/main/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,9 @@ contextBridge.exposeInMainWorld('electron', {
getIsPodmanInstalled: () => ipcRenderer.invoke('getIsPodmanInstalled'),
installPodman: () => ipcRenderer.invoke('installPodman'),
getIsPodmanRunning: () => ipcRenderer.invoke('getIsPodmanRunning'),
getPodmanDetails: () => ipcRenderer.invoke('getPodmanDetails'),
startPodman: () => ipcRenderer.invoke('startPodman'),
updatePodman: () => ipcRenderer.invoke('updatePodman'),

// Settings
getSetHasSeenSplashscreen: (hasSeen?: boolean) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export interface AddNodeStepperModalProps {
nodePackageLibrary?: NodePackageLibrary;
step: number;
disableSaveButton: (value: boolean) => void;
setIsPodmanRunning: (value: boolean) => void;
setPodmanInstallDone: (value: boolean) => void;
}

const AddNodeStepperModal = ({
Expand All @@ -37,7 +37,7 @@ const AddNodeStepperModal = ({
nodePackageLibrary,
step,
disableSaveButton,
setIsPodmanRunning,
setPodmanInstallDone,
}: AddNodeStepperModalProps) => {
const [sNodeConfig, setNodeConfig] = useState<AddNodeValues>();
const [sEthereumNodeConfig, setEthereumNodeConfig] =
Expand Down Expand Up @@ -119,7 +119,7 @@ const AddNodeStepperModal = ({
console.log('onChangeDockerInstall newValue ', newValue);
disableSaveButton(false);
if (newValue === 'done') {
setIsPodmanRunning(true);
setPodmanInstallDone(true);
disableSaveButton(false);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
Expand Down
11 changes: 11 additions & 0 deletions src/renderer/Presentational/AddNodeStepper/podmanRequirements.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { PodmanDetails } from '../../../main/podman/details';

export const arePodmanRequirementsMet = (
podmanDetails: PodmanDetails,
): boolean => {
let isPodmanRequirementsMet = false;
if (podmanDetails?.isRunning && !podmanDetails.isOutdated) {
isPodmanRequirementsMet = true;
}
return isPodmanRequirementsMet;
};
24 changes: 16 additions & 8 deletions src/renderer/Presentational/ModalManager/AddNodeModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { updateSelectedNodePackageId } from '../../state/node';
import AddNodeStepperModal from '../AddNodeStepper/AddNodeStepperModal';
import { Modal } from '../../Generics/redesign/Modal/Modal';
import { modalOnChangeConfig, ModalConfig } from './modalUtils';
import { useGetIsPodmanRunningQuery } from '../../state/settingsService';
import { useGetPodmanDetailsQuery } from '../../state/settingsService';
import { reportEvent } from '../../events/reportEvent';
import { NodePackageSpecification } from '../../../common/nodeSpec';
import { AddNodePackageNodeService } from '../../../main/nodePackageManager';
Expand All @@ -15,6 +15,7 @@ import {
NodePackageLibrary,
} from '../../../main/state/nodeLibrary';
import { mergePackageAndClientConfigValues } from '../AddNodeConfiguration/mergePackageAndClientConfigValues';
import { arePodmanRequirementsMet } from '../AddNodeStepper/podmanRequirements';

type Props = {
modalOnClose: () => void;
Expand All @@ -26,7 +27,6 @@ export const AddNodeModal = ({ modalOnClose }: Props) => {
useState<NodePackageSpecification>();
const [isSaveButtonDisabled, setIsSaveButtonDisabled] =
useState<boolean>(false);
const [sIsPodmanRunning, setIsPodmanRunning] = useState<boolean>(false);
const [step, setStep] = useState(0);
const [sNodeLibrary, setNodeLibrary] = useState<NodeLibrary>();
const [sNodePackageLibrary, setNodePackageLibrary] =
Expand All @@ -47,10 +47,10 @@ export const AddNodeModal = ({ modalOnClose }: Props) => {
fetchNodeLibrarys();
}, []);

const qIsPodmanRunning = useGetIsPodmanRunningQuery(null, {
const qPodmanDetails = useGetPodmanDetailsQuery(null, {
pollingInterval: 15000,
});
const isPodmanRunning = qIsPodmanRunning?.data;
const podmanDetails = qPodmanDetails?.data;

const dispatch = useAppDispatch();

Expand Down Expand Up @@ -88,8 +88,8 @@ export const AddNodeModal = ({ modalOnClose }: Props) => {
default:
}

const startNode =
(step === 2 || step === 3) && (isPodmanRunning || sIsPodmanRunning);
const isPodmanRequirementsMet = arePodmanRequirementsMet(podmanDetails);
const startNode = (step === 2 || step === 3) && isPodmanRequirementsMet;
const buttonSaveLabel = startNode ? t('StartNode') : t('Continue');
const buttonCancelLabel = step === 0 ? t('Cancel') : t('Back');
const buttonSaveVariant = startNode ? 'icon-left' : 'text';
Expand Down Expand Up @@ -203,7 +203,8 @@ export const AddNodeModal = ({ modalOnClose }: Props) => {
setStep(2);
// optionally advance to Podman screen (install or start)
} else if (step === 2) {
if (isPodmanRunning || sIsPodmanRunning) {
const isPodmanRequirementsMet = arePodmanRequirementsMet(podmanDetails);
if (isPodmanRequirementsMet) {
modalOnSaveConfig(undefined);
modalOnClose();
} else {
Expand All @@ -216,6 +217,13 @@ export const AddNodeModal = ({ modalOnClose }: Props) => {
}
};

const onSetPodmanInstallDone = () => {
// refetch podman details to verify podman is installed
// which will allow the user to continue.
// This is faster than waiting for the qPodmanDetails polling
qPodmanDetails?.refetch();
};

return (
<Modal
modalTitle={modalTitle}
Expand All @@ -233,7 +241,7 @@ export const AddNodeModal = ({ modalOnClose }: Props) => {
step={step}
nodeLibrary={sNodeLibrary}
nodePackageLibrary={sNodePackageLibrary}
setIsPodmanRunning={setIsPodmanRunning}
setPodmanInstallDone={onSetPodmanInstallDone}
modal
modalConfig={modalConfig}
modalOnChangeConfig={(config, save) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,12 @@ import TimedProgressBar from '../../Generics/redesign/ProgressBar/TimedProgressB
import {
useGetIsPodmanInstalledQuery,
useGetIsPodmanRunningQuery,
useGetPodmanDetailsQuery,
} from '../../state/settingsService';
// import { reportEvent } from '../../events/reportEvent';
import { CHANNELS } from '../../../main/messenger';
import { IpcMessage } from '../../../main/podman/messageFrontEnd';
import { Message } from '../../Generics/redesign/Message/Message';

// 6.5(docker), ? min on 2022 MacbookPro 16inch, baseline
const TOTAL_INSTALL_TIME_SEC = 5 * 60;
Expand All @@ -52,6 +54,10 @@ const PodmanInstallation = ({
pollingInterval: 15000,
});
const isPodmanRunning = qIsPodmanRunning?.data;
const qPodmanDetails = useGetPodmanDetailsQuery(null, {
pollingInterval: 15000,
});
const podmanDetails = qPodmanDetails?.data;
const [sHasStartedDownload, setHasStartedDownload] = useState<boolean>();
const [sDownloadComplete, setDownloadComplete] = useState<boolean>();
const [sDownloadProgress, setDownloadProgress] = useState<number>(0);
Expand All @@ -73,10 +79,10 @@ const PodmanInstallation = ({
console.log('isPodmanRunning: ', isPodmanRunning);

useEffect(() => {
if (isPodmanRunning) {
if (isPodmanRunning && !podmanDetails?.isOutdated) {
onChange('done');
}
}, [isPodmanRunning, onChange]);
}, [isPodmanRunning, podmanDetails, onChange]);

const onClickDownloadAndInstall = async () => {
setHasStartedDownload(true);
Expand All @@ -102,6 +108,22 @@ const PodmanInstallation = ({
console.log('startPodman finished. Start result: ', startResult);
};

const onClickUpdatePodman = async () => {
setHasStartedDownload(true);
if (navigator.userAgent.indexOf('Linux') !== -1) {
setDownloadComplete(true);
}
const updateResult = await electron.updatePodman();
// todo: consolodate these to just use qPodmanDetails
qIsPodmanInstalled.refetch();
qIsPodmanRunning.refetch();
qPodmanDetails.refetch();
if (updateResult && !updateResult.error) {
setInstallComplete(true);
}
console.log('updatePodman finished. Start result: ', updateResult);
};

const podmanMessageListener = (message: FileDownloadProgress[]) => {
// set totalSize & progress
if (message[0]) {
Expand Down Expand Up @@ -167,6 +189,7 @@ const PodmanInstallation = ({
// }, []);

console.log('isPodmanInstalled', isPodmanInstalled);
console.log('podmanDetails', podmanDetails);

// listen to podman install messages
return (
Expand All @@ -182,7 +205,16 @@ const PodmanInstallation = ({
</div>
{/* Podman is not installed */}
<div className={installContentContainer}>
{!isPodmanInstalled && (
{podmanDetails?.isOutdated && (
<div style={{ marginBottom: 32 }}>
<Message
description={t('PodmanUpdateRequiredDescription')}
title={t('Podman update required')}
type="info"
/>
</div>
)}
{(!isPodmanInstalled || podmanDetails?.isOutdated) && (
<>
{!sDownloadComplete && !sInstallComplete && (
<>
Expand All @@ -191,7 +223,13 @@ const PodmanInstallation = ({
<Button
type="primary"
label={t('DownloadAndInstall')}
onClick={onClickDownloadAndInstall}
onClick={() => {
if (isPodmanInstalled && podmanDetails?.isOutdated) {
onClickUpdatePodman();
} else {
onClickDownloadAndInstall();
}
}}
/>
<div className={captionText}>~100MB {t('download')}</div>
</div>
Expand Down
3 changes: 3 additions & 0 deletions src/renderer/preload.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { CheckStorageDetails } from '../main/files';
import { FailSystemRequirementsData } from '../main/minSystemRequirement';
import { SystemData } from '../main/systemInfo';
import { ConfigValuesMap } from '../common/nodeConfig';
import { PodmanDetails } from '../main/podman/details';

// Since we are using Chrome only in Electron and this is not a web standard yet,
// we extend window.performance to include Chrome's memory stats
Expand Down Expand Up @@ -101,7 +102,9 @@ declare global {
getIsPodmanInstalled(): boolean;
installPodman(): any;
getIsPodmanRunning(): true;
getPodmanDetails(): PodmanDetails;
startPodman(): any;
updatePodman(): any;

// Settings
getSetHasSeenSplashscreen(hasSeen?: boolean): boolean;
Expand Down
15 changes: 15 additions & 0 deletions src/renderer/state/settingsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { createApi, fakeBaseQuery } from '@reduxjs/toolkit/query/react';
import { Settings } from '../../main/state/settings';
import electron from '../electronGlobal';
import { PodmanDetails } from '../../main/podman/details';

type CustomerErrorType = {
message: string;
Expand Down Expand Up @@ -55,11 +56,25 @@ export const RtkqSettingsService: any = createApi({
return { data };
},
}),
getPodmanDetails: builder.query<PodmanDetails, null>({
queryFn: async () => {
let data;
try {
data = await electron.getPodmanDetails();
} catch (e) {
const error = { message: 'Unable to getPodmanDetails' };
console.log(e);
return { error };
}
return { data };
},
}),
}),
});

export const {
useGetSettingsQuery,
useGetIsPodmanInstalledQuery,
useGetIsPodmanRunningQuery,
useGetPodmanDetailsQuery,
} = RtkqSettingsService;

0 comments on commit d839600

Please sign in to comment.