Docker: Use gocmdbridge instead of DeviceShell

Change-Id: Ic5ba7e203d7c5210dc2c3fe21c2c92039877e9bd
Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
Marcus Tillmanns
2024-06-20 15:31:39 +02:00
parent 1ae0757f5d
commit 431c598a34
3 changed files with 73 additions and 114 deletions

View File

@@ -34,6 +34,8 @@ public:
Utils::expected_str<void> signalProcess(int pid, Utils::ControlSignal signal) const;
Utils::Environment deviceEnvironment() const override;
protected:
Utils::expected_str<void> reinit();
@@ -78,8 +80,6 @@ protected:
bool renameFile(const Utils::FilePath &filePath, const Utils::FilePath &target) const override;
Utils::Environment deviceEnvironment() const override;
Utils::expected_str<Utils::FilePath> createTempFile(const Utils::FilePath &filePath) override;
Utils::expected_str<std::unique_ptr<Utils::FilePathWatcher>> watch(

View File

@@ -1,5 +1,6 @@
add_qtc_plugin(Docker
DEPENDS Utils
CONDITION TARGET CmdBridgeClient
DEPENDS Utils CmdBridgeClient
PLUGIN_DEPENDS Core ProjectExplorer QtSupport
SOURCES
docker_global.h

View File

@@ -31,6 +31,9 @@
#include <qtsupport/qtversionfactory.h>
#include <qtsupport/qtversionmanager.h>
#include <client/bridgedfileaccess.h>
#include <client/cmdbridgeclient.h>
#include <utils/algorithm.h>
#include <utils/async.h>
#include <utils/basetreeview.h>
@@ -101,41 +104,13 @@ const char DockerDeviceEnableLldbFlags[] = "DockerDeviceEnableLldbFlags";
const char DockerDeviceClangDExecutable[] = "DockerDeviceClangDExecutable";
const char DockerDeviceExtraArgs[] = "DockerDeviceExtraCreateArguments";
class ContainerShell : public DeviceShell
{
public:
ContainerShell(const QString &containerId, const FilePath &devicePath)
: m_containerId(containerId)
, m_devicePath(devicePath)
{}
private:
void setupShellProcess(Process *shellProcess) final
{
shellProcess->setCommand(
{settings().dockerBinaryPath(), {"container", "start", "-i", "-a", m_containerId}});
}
CommandLine createFallbackCommand(const CommandLine &cmdLine) override
{
CommandLine result = cmdLine;
result.setExecutable(m_devicePath.withNewPath(cmdLine.executable().path()));
return result;
}
private:
QString m_containerId;
FilePath m_devicePath;
};
class DockerDeviceFileAccess : public UnixDeviceFileAccess
class DockerDeviceFileAccess : public CmdBridge::FileAccess
{
public:
DockerDeviceFileAccess(DockerDevicePrivate *dev)
: m_dev(dev)
{}
RunResult runInShell(const CommandLine &cmdLine, const QByteArray &stdInData) const override;
QString mapToDevicePath(const QString &hostPath) const override;
DockerDevicePrivate *m_dev = nullptr;
@@ -301,8 +276,6 @@ public:
CommandLine createCommandLine();
RunResult runInShell(const CommandLine &cmd, const QByteArray &stdInData = {});
expected_str<void> updateContainerAccess();
void changeMounts(QStringList newMounts);
bool ensureReachable(const FilePath &other);
@@ -315,6 +288,8 @@ public:
QString repoAndTagEncoded() const { return deviceSettings->repoAndTagEncoded(); }
QString dockerImageId() const { return deviceSettings->imageId(); }
QPair<Utils::OsType, Utils::OsArch> osTypeAndArch() const;
expected_str<Environment> environment();
CommandLine withDockerExecCmd(const CommandLine &cmd,
@@ -354,14 +329,11 @@ public:
FilePath containerPath;
};
QMutex m_shellMutex;
std::unique_ptr<ContainerShell> m_shell;
QString m_container;
std::optional<Environment> m_cachedEnviroment;
bool m_isShutdown = false;
DockerDeviceFileAccess m_fileAccess{this};
std::unique_ptr<DockerDeviceFileAccess> m_fileAccess;
};
class DockerProcessImpl : public ProcessInterface
@@ -507,9 +479,8 @@ void DockerProcessImpl::sendControlSignal(ControlSignal controlSignal)
m_process.closeWriteChannel();
return;
}
const int signal = controlSignalToInt(controlSignal);
m_devicePrivate->runInShell(
{"kill", {QString("-%1").arg(signal), QString("%2").arg(m_remotePID)}});
static_cast<DockerDeviceFileAccess *>(m_device->fileAccess())
->signalProcess(m_remotePID, controlSignal);
} else {
// clang-format off
switch (controlSignal) {
@@ -553,13 +524,6 @@ Tasks DockerDevicePrivate::validateMounts() const
return result;
}
RunResult DockerDeviceFileAccess::runInShell(const CommandLine &cmdLine,
const QByteArray &stdInData) const
{
QTC_ASSERT(m_dev, return {});
return m_dev->runInShell(cmdLine, stdInData);
}
QString DockerDeviceFileAccess::mapToDevicePath(const QString &hostPath) const
{
// make sure to convert windows style paths to unix style paths with the file system case:
@@ -577,7 +541,15 @@ DockerDevice::DockerDevice(std::unique_ptr<DockerDeviceSettings> deviceSettings)
: ProjectExplorer::IDevice(std::move(deviceSettings))
, d(new DockerDevicePrivate(this))
{
setFileAccess(&d->m_fileAccess);
setFileAccess([this]() -> DeviceFileAccess * {
if (!d->m_fileAccess) {
auto fileAccess = std::make_unique<DockerDeviceFileAccess>(d);
QTC_ASSERT_EXPECTED(
fileAccess->init(rootPath().withNewPath("/tmp/_qtc_cmdbridge")), return nullptr);
d->m_fileAccess = std::move(fileAccess);
}
return d->m_fileAccess.get();
});
setDisplayType(Tr::tr("Docker"));
setOsType(OsTypeLinux);
setupId(IDevice::ManuallyAdded);
@@ -697,16 +669,6 @@ void DockerDevicePrivate::stopCurrentContainer()
if (!DockerApi::isDockerDaemonAvailable(false).value_or(false))
return;
QMutexLocker lk(&m_shellMutex);
if (m_shell) {
// We have to disconnect the shell from the device, otherwise it will try to
// tell us about the container being stopped. Since that signal is emitted in a different
// thread, it would be delayed received by us when we might already have started
// a new shell.
m_shell->disconnect(this);
m_shell.reset();
}
Process proc;
proc.setCommand({settings().dockerBinaryPath(), {"container", "stop", m_container}});
@@ -796,11 +758,19 @@ expected_str<void> isValidMountInfo(const DockerDevicePrivate::MountPair &mi)
QStringList DockerDevicePrivate::createMountArgs() const
{
auto osAndArch = osTypeAndArch();
const Utils::expected_str<Utils::FilePath> cmdBridgePath = CmdBridge::Client::getCmdBridgePath(
osAndArch.first, osAndArch.second, Core::ICore::libexecPath());
QStringList cmds;
QList<MountPair> mounts;
for (const FilePath &m : deviceSettings->mounts())
mounts.append({m, m});
if (cmdBridgePath)
mounts.append({cmdBridgePath.value(), FilePath("/tmp/_qtc_cmdbridge")});
for (const MountPair &mi : mounts) {
if (isValidMountInfo(mi))
cmds += toMountArg(mi);
@@ -897,41 +867,21 @@ expected_str<void> DockerDevicePrivate::startContainer()
if (!createResult)
return make_unexpected(createResult.error());
QMutexLocker lk(&m_shellMutex);
m_shell = std::make_unique<ContainerShell>(m_container, q->rootPath());
connect(m_shell.get(), &DeviceShell::done, this, [this](const ProcessResultData &resultData) {
if (m_shell)
m_shell.release()->deleteLater();
if (resultData.m_error != QProcess::UnknownError
|| resultData.m_exitStatus == QProcess::NormalExit)
return;
qCWarning(dockerDeviceLog) << "Container shell encountered error:" << resultData.m_error;
DockerApi::recheckDockerDaemon();
//: %1 is the application name (Qt Creator)
MessageManager::writeFlashing(Tr::tr("Docker daemon appears to be not running. "
"Verify daemon is up and running and reset the "
"Docker daemon in Docker device preferences "
"or restart %1.")
.arg(QGuiApplication::applicationDisplayName()));
});
QTC_ASSERT(m_shell,
return make_unexpected(Tr::tr("Failed to create container shell (Out of memory).")));
return m_shell->start();
Process startProcess;
startProcess.setCommand({settings().dockerBinaryPath(), {"container", "start", m_container}});
startProcess.runBlocking();
if (startProcess.result() != ProcessResult::FinishedWithSuccess) {
return make_unexpected(Tr::tr("Failed starting Docker container. Exit code: %1, output: %2")
.arg(startProcess.exitCode())
.arg(startProcess.allOutput()));
}
return {};
}
expected_str<void> DockerDevicePrivate::updateContainerAccess()
{
{
QMutexLocker lk(&m_shellMutex);
if (m_shell && m_shell->state() == DeviceShell::State::Succeeded)
return {};
}
if (!m_container.isEmpty() && DockerApi::instance()->isContainerRunning(m_container))
return {};
if (m_isShutdown)
return make_unexpected(Tr::tr("Device is shut down"));
@@ -1050,27 +1000,26 @@ void DockerDevice::aboutToBeRemoved() const
expected_str<void> DockerDevicePrivate::fetchSystemEnviroment()
{
if (m_cachedEnviroment)
return {};
if (m_fileAccess) {
m_cachedEnviroment = m_fileAccess->deviceEnvironment();
return {};
}
expected_str<void> result = updateContainerAccess();
if (!result)
return result;
QString stdErr;
Process proc;
proc.setCommand(withDockerExecCmd(CommandLine{"env"}));
proc.runBlocking();
const QString remoteOutput = proc.cleanedStdOut();
if (m_shell && m_shell->state() == DeviceShell::State::Succeeded) {
const RunResult result = runInShell(CommandLine{"env"});
const QString out = QString::fromUtf8(result.stdOut);
m_cachedEnviroment = Environment(out.split('\n', Qt::SkipEmptyParts), q->osType());
stdErr = QString::fromUtf8(result.stdErr);
} else {
Process proc;
proc.setCommand(withDockerExecCmd(CommandLine{"env"}));
proc.start();
proc.waitForFinished();
const QString remoteOutput = proc.cleanedStdOut();
m_cachedEnviroment = Environment(remoteOutput.split('\n', Qt::SkipEmptyParts), q->osType());
stdErr = proc.cleanedStdErr();
}
m_cachedEnviroment = Environment(remoteOutput.split('\n', Qt::SkipEmptyParts), q->osType());
QString stdErr = proc.cleanedStdErr();
if (stdErr.isEmpty())
return {};
@@ -1078,14 +1027,6 @@ expected_str<void> DockerDevicePrivate::fetchSystemEnviroment()
return make_unexpected("Could not read container environment: " + stdErr);
}
RunResult DockerDevicePrivate::runInShell(const CommandLine &cmd, const QByteArray &stdInData)
{
if (!updateContainerAccess())
return {};
QTC_ASSERT(m_shell, return {});
return m_shell->runInShell(cmd, stdInData);
}
// Factory
class DockerImageItem final : public TreeItem
@@ -1303,6 +1244,23 @@ void DockerDeviceFactory::shutdownExistingDevices()
}
}
QPair<Utils::OsType, Utils::OsArch> DockerDevicePrivate::osTypeAndArch() const
{
Process proc;
proc.setCommand(
{settings().dockerBinaryPath(),
{"image", "inspect", repoAndTag(), "--format", "{{.Os}}\t{{.Architecture}}"}});
proc.runBlocking();
if (proc.result() != ProcessResult::FinishedWithSuccess)
return {Utils::OsType::OsTypeOther, Utils::OsArch::OsArchUnknown};
const QString out = proc.cleanedStdOut().trimmed();
const QStringList parts = out.split('\t');
if (parts.size() != 2)
return {Utils::OsType::OsTypeOther, Utils::OsArch::OsArchUnknown};
return {osTypeFromString(parts.at(0)), osArchFromString(parts.at(1))};
}
expected_str<Environment> DockerDevicePrivate::environment()
{
if (!m_cachedEnviroment) {