Rework container shells

This commit is contained in:
crschnick
2026-04-27 09:47:16 +00:00
parent d8138c554d
commit 18846c62b2
4 changed files with 65 additions and 7 deletions
@@ -1,7 +1,11 @@
package io.xpipe.app.ext;
import io.xpipe.app.process.ShellControl;
import io.xpipe.app.process.ShellDialect;
import io.xpipe.app.process.ShellDialects;
import io.xpipe.app.process.ShellStoreState;
import io.xpipe.core.OsType;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.Getter;
@@ -16,10 +20,47 @@ import lombok.extern.jackson.Jacksonized;
@Jacksonized
public class ContainerStoreState extends ShellStoreState {
public static ShellDialect findSuitableDialect(ShellControl sc) throws Exception {
if (!sc.getShellDialect().getDumbMode().supportsAnyPossibleInteraction()) {
return sc.getOsType() == OsType.WINDOWS ? ShellDialects.CMD : ShellDialects.SH;
}
if (sc.getOsType() != OsType.WINDOWS) {
if (sc.view().findProgram("bash").isPresent()) {
return ShellDialects.BASH;
}
if (sc.view().findProgram("zsh").isPresent()) {
return ShellDialects.ZSH;
}
return ShellDialects.SH;
} else {
if (sc.view().findProgram("pwsh").isPresent()) {
return ShellDialects.POWERSHELL_CORE;
}
if (sc.view().findProgram("powershell").isPresent()) {
return ShellDialects.POWERSHELL;
}
return ShellDialects.CMD;
}
}
String imageName;
String containerState;
ShellDialect availableShellDialect;
Boolean shellMissing;
public ShellDialect getEffectiveDialect(ShellControl sc) {
if (availableShellDialect != null) {
return availableShellDialect;
}
return sc.getOsType() != OsType.WINDOWS ? ShellDialects.SH : ShellDialects.CMD;
}
@Override
public DataStoreState mergeCopy(DataStoreState newer) {
var n = (ContainerStoreState) newer;
@@ -32,6 +73,7 @@ public class ContainerStoreState extends ShellStoreState {
super.mergeBuilder(css, b);
b.containerState(useNewer(containerState, css.getContainerState()));
b.imageName(useNewer(imageName, css.getImageName()));
b.availableShellDialect(useNewer(availableShellDialect, css.getAvailableShellDialect()));
b.shellMissing(useNewer(shellMissing, css.getShellMissing()));
}
}
@@ -34,7 +34,8 @@ public class ServiceRefreshHubProvider
@Override
public boolean isApplicable(DataStoreEntryRef<FixedServiceCreatorStore> o) {
return o.getStore().allowManualServicesRefresh();
return o.getStore().allowManualServicesRefresh() && DataStorage.get().getStoreChildren(o.get()).stream()
.noneMatch(e -> e.getStore() instanceof AbstractServiceGroupStore<?>);
}
@Override
@@ -11,6 +11,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
public class PodmanCommandView extends CommandViewBase {
@@ -23,6 +24,12 @@ public class PodmanCommandView extends CommandViewBase {
}
private static <T extends Throwable> T convertException(T s) {
if (s instanceof ProcessOutputException pex) {
if (pex.getOutput().contains("OCI runtime exec failed")) {
ErrorEventFactory.preconfigure(ErrorEventFactory.fromThrowable(s).description("Container does not contain a usable shell"));
}
}
return ErrorEventFactory.expectedIfContains(
s,
"Error: unable to connect to Podman.",
@@ -124,20 +131,20 @@ public class PodmanCommandView extends CommandViewBase {
}
}
public ShellControl exec(String container) {
public ShellControl exec(String container, Function<ShellControl, ShellDialect> dialect) {
var sub = shellControl.subShell();
sub.setDumbOpen(createOpenFunction(container, false));
sub.setTerminalOpen(createOpenFunction(container, true));
sub.setDumbOpen(createOpenFunction(container, dialect, false));
sub.setTerminalOpen(createOpenFunction(container, dialect, true));
return sub.withExceptionConverter(PodmanCommandView::convertException);
}
private ShellOpenFunction createOpenFunction(String containerName, boolean terminal) {
private ShellOpenFunction createOpenFunction(String containerName, Function<ShellControl, ShellDialect> dialect, boolean terminal) {
return new ShellOpenFunction() {
@Override
public CommandBuilder prepareWithoutInitCommand() {
return execCommand(terminal)
.addQuoted(containerName)
.add(ShellDialects.SH.getLaunchCommand().loginCommand());
.add(sc -> dialect.apply(sc).getLaunchCommand().loginCommand());
}
@Override
@@ -136,9 +136,17 @@ public class PodmanContainerStore
@Override
public ShellControl control(ShellControl parent) throws Exception {
refreshContainerState(getCmd().getStore().getHost().getStore().getOrStartSession());
var pc = new PodmanCommandView(parent).container().exec(containerName);
var pc = new PodmanCommandView(parent).container().exec(containerName, getState()::getEffectiveDialect);
pc.withSourceStore(PodmanContainerStore.this);
pc.withShellStateInit(PodmanContainerStore.this);
pc
.onInit(sc -> {
var s = getState();
if (s.getAvailableShellDialect() == null) {
setState(s.toBuilder().availableShellDialect(ContainerStoreState.findSuitableDialect(sc)).build());
}
sc.setOriginalShellDialect(s.getEffectiveDialect(sc));
});
pc.onStartupFail(throwable -> {
if (throwable instanceof LicenseRequiredException) {
return;