Skip to content

Commit

Permalink
Automatically start podman.socket
Browse files Browse the repository at this point in the history
Always start the system and user `podman.socket` unit on initialization.
There really is no reason to explicitly ask the user about it -- we can
treat it as "extended cockpit" to just access podman.

There also isn't a reason to enable the socket unit -- starting it
on demand is fine from cockpit-podman's perspective.

Now the user will only see the empty state if the service fails, which
should be very rare. So turn the Troubleshoot button into a primary one.

We also don't need the "System/User podman is also available" alerts any
more -- gaining root privileges auto-starts the system service.
  • Loading branch information
martinpitt committed Jan 24, 2025
1 parent 42bcbab commit 8075969
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 260 deletions.
188 changes: 52 additions & 136 deletions src/app.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,8 @@

import React from 'react';

import { Alert, AlertActionCloseButton, AlertActionLink, AlertGroup } from "@patternfly/react-core/dist/esm/components/Alert";
import { Alert, AlertActionCloseButton, AlertGroup } from "@patternfly/react-core/dist/esm/components/Alert";
import { Button } from "@patternfly/react-core/dist/esm/components/Button";
import { Checkbox } from "@patternfly/react-core/dist/esm/components/Checkbox";
import { EmptyState, EmptyStateHeader, EmptyStateFooter, EmptyStateIcon, EmptyStateActions, EmptyStateVariant } from "@patternfly/react-core/dist/esm/components/EmptyState";
import { Page, PageSection, PageSectionVariants } from "@patternfly/react-core/dist/esm/components/Page";
import { Stack } from "@patternfly/react-core/dist/esm/layouts/Stack";
Expand Down Expand Up @@ -61,14 +60,12 @@ class Application extends React.Component {
ownerFilter: "all",
dropDownValue: 'Everything',
notifications: [],
showStartService: true,
version: '1.3.0',
selinuxAvailable: false,
podmanRestartAvailable: false,
userPodmanRestartAvailable: false,
currentUser: _("User"),
userLingeringEnabled: null,
privileged: false,
location: {},
};
this.onAddNotification = this.onAddNotification.bind(this);
Expand All @@ -77,9 +74,7 @@ class Application extends React.Component {
this.onOwnerChanged = this.onOwnerChanged.bind(this);
this.onContainerFilterChanged = this.onContainerFilterChanged.bind(this);
this.updateContainer = this.updateContainer.bind(this);
this.startService = this.startService.bind(this);
this.goToServicePage = this.goToServicePage.bind(this);
this.checkUserService = this.checkUserService.bind(this);
this.onNavigate = this.onNavigate.bind(this);

this.pendingUpdateContainer = {}; // id+system → promise
Expand Down Expand Up @@ -454,47 +449,49 @@ class Application extends React.Component {
});
}

init(system) {
client.getInfo(system)
.then(reply => {
this.setState({
[system ? "systemServiceAvailable" : "userServiceAvailable"]: true,
version: reply.version.Version,
registries: reply.registries,
cgroupVersion: reply.host.cgroupVersion,
});
this.updateImages(system);
this.initContainers(system);
this.updatePods(system);
client.streamEvents(system,
message => this.handleEvent(message, system))
.then(() => {
this.setState({ [system ? "systemServiceAvailable" : "userServiceAvailable"]: false });
this.cleanupAfterService(system);
})
.catch(e => {
console.log(e);
this.setState({ [system ? "systemServiceAvailable" : "userServiceAvailable"]: false });
this.cleanupAfterService(system);
});

// Listen if podman is still running
const ch = cockpit.channel({ superuser: system ? "require" : null, payload: "stream", unix: client.getAddress(system) });
ch.addEventListener("close", () => {
this.setState({ [system ? "systemServiceAvailable" : "userServiceAvailable"]: false });
this.cleanupAfterService(system);
});
async init(system) {
try {
await cockpit.spawn(["systemctl", ...(system ? [] : ["--user"]), "start", "podman.socket"],
{ superuser: system ? "require" : null, err: "message" });
const reply = await client.getInfo(system);
this.setState({
[system ? "systemServiceAvailable" : "userServiceAvailable"]: true,
version: reply.version.Version,
registries: reply.registries,
cgroupVersion: reply.host.cgroupVersion,
});
this.updateImages(system);
this.initContainers(system);
this.updatePods(system);

try {
await client.streamEvents(system, message => this.handleEvent(message, system));
this.setState({ [system ? "systemServiceAvailable" : "userServiceAvailable"]: false });
this.cleanupAfterService(system);
} catch (e) {
console.error("init", system ? "system" : "user", "streamEvents failed:", e);
this.setState({ [system ? "systemServiceAvailable" : "userServiceAvailable"]: false });
this.cleanupAfterService(system);
}

ch.send("GET " + client.VERSION + "libpod/events HTTP/1.0\r\nContent-Length: 0\r\n\r\n");
})
.catch(() => {
this.setState({
[system ? "systemServiceAvailable" : "userServiceAvailable"]: false,
[system ? "systemContainersLoaded" : "userContainersLoaded"]: true,
[system ? "systemImagesLoaded" : "userImagesLoaded"]: true,
[system ? "systemPodsLoaded" : "userPodsLoaded"]: true
});
});
// Listen if podman is still running
const ch = cockpit.channel({ superuser: system ? "require" : null, payload: "stream", unix: client.getAddress(system) });
ch.addEventListener("close", () => {
this.setState({ [system ? "systemServiceAvailable" : "userServiceAvailable"]: false });
this.cleanupAfterService(system);
});

ch.send("GET " + client.VERSION + "libpod/events HTTP/1.0\r\nContent-Length: 0\r\n\r\n");
} catch (err) {
if (!system || err.problem != 'access-denied')
console.warn("init", system ? "system" : "user", "failed:", err);
this.setState({
[system ? "systemServiceAvailable" : "userServiceAvailable"]: false,
[system ? "systemContainersLoaded" : "userContainersLoaded"]: true,
[system ? "systemImagesLoaded" : "userImagesLoaded"]: true,
[system ? "systemPodsLoaded" : "userPodsLoaded"]: true
});
}
}

componentDidMount() {
Expand All @@ -505,7 +502,7 @@ class Application extends React.Component {
if (!isRoot) {
sessionStorage.setItem('XDG_RUNTIME_DIR', xrd.trim());
this.init(false);
this.checkUserService();
this.checkUserRestartService();
} else {
this.setState({
userImagesLoaded: true,
Expand All @@ -523,8 +520,7 @@ class Application extends React.Component {
cockpit.spawn(["systemctl", "show", "--value", "-p", "LoadState", "podman-restart"], { environ: ["LC_ALL=C"], error: "ignore" })
.then(out => this.setState({ podmanRestartAvailable: out.trim() === "loaded" }));

superuser.addEventListener("changed", () => this.setState({ privileged: !!superuser.allowed }));
this.setState({ privileged: superuser.allowed });
superuser.addEventListener("changed", () => this.init(true));

cockpit.user().then(user => {
this.setState({ currentUser: user.name || _("User") });
Expand Down Expand Up @@ -566,59 +562,11 @@ class Application extends React.Component {
});
}

checkUserService() {
const argv = ["systemctl", "--user", "is-enabled", "podman.socket"];

cockpit.spawn(["systemctl", "--user", "show", "--value", "-p", "LoadState", "podman-restart"], { environ: ["LC_ALL=C"], error: "ignore" })
.then(out => this.setState({ userPodmanRestartAvailable: out.trim() === "loaded" }));

cockpit.spawn(argv, { environ: ["LC_ALL=C"], err: "out" })
.then(() => this.setState({ userServiceExists: true }))
.catch((_, response) => {
if (response.trim() !== "disabled")
this.setState({ userServiceExists: false });
else
this.setState({ userServiceExists: true });
});
}

startService(e) {
if (!e || e.button !== 0)
return;

let argv;
if (this.state.enableService)
argv = ["systemctl", "enable", "--now", "podman.socket"];
else
argv = ["systemctl", "start", "podman.socket"];

cockpit.spawn(argv, { superuser: "require", err: "message" })
.then(() => this.init(true))
.catch(err => {
this.setState({
systemServiceAvailable: false,
systemContainersLoaded: true,
systemImagesLoaded: true
});
console.warn("Failed to start system podman.socket:", JSON.stringify(err));
});

if (this.state.enableService)
argv = ["systemctl", "--user", "enable", "--now", "podman.socket"];
else
argv = ["systemctl", "--user", "start", "podman.socket"];

cockpit.spawn(argv, { err: "message" })
.then(() => this.init(false))
.catch(err => {
this.setState({
userServiceAvailable: false,
userContainersLoaded: true,
userPodsLoaded: true,
userImagesLoaded: true
});
console.warn("Failed to start user podman.socket:", JSON.stringify(err));
});
async checkUserRestartService() {
const out = await cockpit.spawn(
["systemctl", "--user", "show", "--value", "-p", "LoadState", "podman-restart"],
{ environ: ["LC_ALL=C"], error: "ignore" });
this.setState({ userPodmanRestartAvailable: out.trim() === "loaded" });
}

goToServicePage(e) {
Expand All @@ -636,22 +584,13 @@ class Application extends React.Component {
<Page>
<PageSection variant={PageSectionVariants.light}>
<EmptyState variant={EmptyStateVariant.full}>
<EmptyStateHeader titleText={_("Podman service is not active")} icon={<EmptyStateIcon icon={ExclamationCircleIcon} />} headingLevel="h2" />
<EmptyStateHeader titleText={_("Podman service failed")} icon={<EmptyStateIcon icon={ExclamationCircleIcon} />} headingLevel="h2" />
<EmptyStateFooter>
<Checkbox isChecked={this.state.enableService}
id="enable"
label={_("Automatically start podman on boot")}
onChange={ (_event, checked) => this.setState({ enableService: checked }) } />
<Button onClick={this.startService}>
{_("Start podman")}
</Button>
{ cockpit.manifests.system &&
<EmptyStateActions>
<Button variant="link" onClick={this.goToServicePage}>
<Button variant="primary" onClick={this.goToServicePage}>
{_("Troubleshoot")}
</Button>
</EmptyStateActions>
}
</EmptyStateFooter>
</EmptyState>
</PageSection>
Expand Down Expand Up @@ -679,28 +618,6 @@ class Application extends React.Component {
} else
imageContainerList = null;

let startService = "";
const action = (
<>
<AlertActionLink variant='secondary' onClick={this.startService}>{_("Start")}</AlertActionLink>
<AlertActionCloseButton onClose={() => this.setState({ showStartService: false })} />
</>
);
if (!this.state.systemServiceAvailable && this.state.privileged) {
startService = (
<Alert
title={_("System Podman service is also available")}
actionClose={action} />
);
}
if (!this.state.userServiceAvailable && this.state.userServiceExists) {
startService = (
<Alert
title={_("User Podman service is also available")}
actionClose={action} />
);
}

const imageList = (
<Images
key="imageList"
Expand Down Expand Up @@ -778,7 +695,6 @@ class Application extends React.Component {
</PageSection>
<PageSection className='ct-pagesection-mobile'>
<Stack hasGutter>
{ this.state.showStartService ? startService : null }
{imageList}
{containerList}
</Stack>
Expand Down
2 changes: 1 addition & 1 deletion test/browser/browser.sh
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ podman rmi $CONTAINER
# image setup, shared with upstream tests
sh -x test/vm.install

systemctl enable --now cockpit.socket podman.socket
systemctl enable --now cockpit.socket

# HACK: https://issues.redhat.com/browse/RHEL-49567
if rpm -q selinux-policy | grep -q el10; then
Expand Down
Loading

0 comments on commit 8075969

Please sign in to comment.