Docker: Use generic DeviceShell class

Change-Id: Ic2afc6931f7cdb791d81344df6edbdb117cc090b
Reviewed-by: Jarek Kobus <jaroslaw.kobus@qt.io>
This commit is contained in:
Marcus Tillmanns
2022-05-05 15:08:46 +02:00
parent 88eb4c03f6
commit c6b75fc615
4 changed files with 43 additions and 123 deletions

View File

@@ -209,7 +209,7 @@ void DeviceShell::close()
* Override this function to setup the shell process. * Override this function to setup the shell process.
* The default implementation just sets the command line to "bash" * The default implementation just sets the command line to "bash"
*/ */
void DeviceShell::setupShellProcess(QtcProcess* shellProcess) void DeviceShell::setupShellProcess(QtcProcess *shellProcess)
{ {
shellProcess->setCommand(CommandLine{"bash"}); shellProcess->setCommand(CommandLine{"bash"});
} }

View File

@@ -68,7 +68,7 @@ protected:
void close(); void close();
private: private:
virtual void setupShellProcess(QtcProcess* shellProcess); virtual void setupShellProcess(QtcProcess *shellProcess);
virtual void startupFailed(const CommandLine &cmdLine); virtual void startupFailed(const CommandLine &cmdLine);
void closeShellProcess(); void closeShellProcess();

View File

@@ -1,4 +1,5 @@
add_qtc_plugin(Docker add_qtc_plugin(Docker
DEPENDS Utils
PLUGIN_DEPENDS Core ProjectExplorer QtSupport PLUGIN_DEPENDS Core ProjectExplorer QtSupport
SOURCES SOURCES
docker_global.h docker_global.h

View File

@@ -50,6 +50,7 @@
#include <utils/algorithm.h> #include <utils/algorithm.h>
#include <utils/basetreeview.h> #include <utils/basetreeview.h>
#include <utils/deviceshell.h>
#include <utils/environment.h> #include <utils/environment.h>
#include <utils/fileutils.h> #include <utils/fileutils.h>
#include <utils/hostosinfo.h> #include <utils/hostosinfo.h>
@@ -144,14 +145,32 @@ class DockerPortsGatheringMethod : public PortsGatheringMethod
} }
}; };
class ContainerShell : public Utils::DeviceShell
{
public:
ContainerShell(const QString &containerId)
: m_containerId(containerId)
{
start();
}
private:
void setupShellProcess(QtcProcess *shellProcess) final
{
shellProcess->setCommand({"docker", {"container", "start", "-i", "-a", m_containerId}});
}
private:
QString m_containerId;
};
class DockerDevicePrivate : public QObject class DockerDevicePrivate : public QObject
{ {
Q_DECLARE_TR_FUNCTIONS(Docker::Internal::DockerDevice) Q_DECLARE_TR_FUNCTIONS(Docker::Internal::DockerDevice)
public: public:
DockerDevicePrivate(DockerDevice *parent) : q(parent) DockerDevicePrivate(DockerDevice *parent)
: q(parent)
{} {}
~DockerDevicePrivate() { stopCurrentContainer(); } ~DockerDevicePrivate() { stopCurrentContainer(); }
@@ -170,8 +189,9 @@ public:
DockerDeviceData m_data; DockerDeviceData m_data;
// For local file access // For local file access
QPointer<QtcProcess> m_shell;
mutable QMutex m_shellMutex; std::unique_ptr<ContainerShell> m_shell;
QString m_container; QString m_container;
Environment m_cachedEnviroment; Environment m_cachedEnviroment;
@@ -428,20 +448,7 @@ void DockerDevicePrivate::stopCurrentContainer()
if (m_container.isEmpty() || !DockerApi::isDockerDaemonAvailable(false).value_or(false)) if (m_container.isEmpty() || !DockerApi::isDockerDaemonAvailable(false).value_or(false))
return; return;
if (m_shell) { m_shell.reset();
QMutexLocker l(&m_shellMutex);
m_shell->write("exit\n");
m_shell->waitForFinished(2000);
if (m_shell->state() == QProcess::NotRunning) {
LOG("Clean exit via shell");
m_container.clear();
delete m_shell;
m_shell = nullptr;
return;
}
m_shell->terminate();
}
QtcProcess proc; QtcProcess proc;
proc.setCommand({"docker", {"container", "stop", m_container}}); proc.setCommand({"docker", {"container", "stop", m_container}});
@@ -507,40 +514,21 @@ void DockerDevicePrivate::startContainer()
return; return;
LOG("Container via process: " << m_container); LOG("Container via process: " << m_container);
CommandLine dockerRun{"docker", {"container" , "start", "-i", "-a", m_container}}; m_shell = std::make_unique<ContainerShell>(m_container);
LOG("RUNNING: " << dockerRun.toUserOutput()); connect(m_shell.get(), &DeviceShell::errorOccurred, this, [this] (QProcess::ProcessError error) {
QPointer<QtcProcess> shell = new QtcProcess; qCWarning(dockerDeviceLog) << "Container shell encountered error:" << error;
shell->setProcessMode(ProcessMode::Writer); m_shell.reset();
connect(shell, &QtcProcess::finished, this, [this, shell] {
LOG("\nSHELL FINISHED\n"); DockerApi::recheckDockerDaemon();
QTC_ASSERT(shell, return); MessageManager::writeFlashing(tr("Docker daemon appears to be not running. "
const int exitCode = shell->exitCode(); "Verify daemon is up and running and reset the "
LOG("RES: " << int(shell->result()) "docker daemon on the docker device settings page "
<< " EXIT CODE: " << exitCode "or restart Qt Creator."));
<< " STDOUT: " << shell->readAllStandardOutput()
<< " STDERR: " << shell->readAllStandardError());
// negative exit codes indicate problems like no docker daemon, missing permissions,
// no shell and seem to result in exit codes 125+
if (exitCode > 120) {
DockerApi::recheckDockerDaemon();
LOG("DOCKER DAEMON NOT RUNNING?");
MessageManager::writeFlashing(tr("Docker daemon appears to be not running. "
"Verify daemon is up and running and reset the "
"docker daemon on the docker device settings page "
"or restart Qt Creator."));
}
}); });
QTC_ASSERT(!m_shell, delete m_shell); if (!m_shell->waitForStarted()) {
m_shell = shell;
m_shell->setCommand(dockerRun);
m_shell->start();
m_shell->waitForStarted();
if (!m_shell->isRunning()) {
DockerApi::recheckDockerDaemon(); DockerApi::recheckDockerDaemon();
LOG("DOCKER SHELL FAILED"); qCWarning(dockerDeviceLog) << "Container shell failed to start";
return;
} }
} }
@@ -1094,83 +1082,14 @@ bool DockerDevicePrivate::runInContainer(const CommandLine &cmd) const
bool DockerDevicePrivate::runInShell(const CommandLine &cmd) const bool DockerDevicePrivate::runInShell(const CommandLine &cmd) const
{ {
if (QThread::currentThread() != qApp->thread()) { QTC_ASSERT(m_shell, return false);
bool result = false; return m_shell->runInShell(cmd);
QMetaObject::invokeMethod(const_cast<DockerDevicePrivate*>(this), [this, &cmd, &result] {
result = this->runInShell(cmd);
}, Qt::BlockingQueuedConnection);
return result;
}
if (!QTC_GUARD(DockerApi::isDockerDaemonAvailable(false).value_or(false))) {
LOG("No daemon. Could not run " << cmd.toUserOutput());
return false;
}
QTC_ASSERT(m_shell, LOG("No shell. Could not run " << cmd.toUserOutput()); return false);
QMutexLocker l(&m_shellMutex);
m_shell->readAllStandardOutput(); // clean possible left-overs
m_shell->write(cmd.toUserOutput() + "\necho $?\n");
QTC_ASSERT(m_shell->waitForReadyRead(), return false);
QByteArray output = m_shell->readAllStandardOutput();
bool ok;
int result = output.toInt(&ok);
LOG("Run command in shell:" << cmd.toUserOutput() << "result: " << output << " ==>" << result);
QTC_ASSERT(ok, return false);
return result == 0;
}
// generate hex value
static QByteArray randomHex()
{
quint32 val = QRandomGenerator::global()->generate();
return QString::number(val, 16).toUtf8();
} }
QByteArray DockerDevicePrivate::outputForRunInShell(const CommandLine &cmd) const QByteArray DockerDevicePrivate::outputForRunInShell(const CommandLine &cmd) const
{ {
if (QThread::currentThread() != qApp->thread()) { QTC_ASSERT(m_shell.get(), return {});
QByteArray result; return m_shell->outputForRunInShell(cmd).stdOutput;
QMetaObject::invokeMethod(const_cast<DockerDevicePrivate*>(this), [this, &cmd, &result] {
result = this->outputForRunInShell(cmd);
}, Qt::BlockingQueuedConnection);
return result;
}
if (!DockerApi::isDockerDaemonAvailable(false).value_or(false))
return {};
QTC_ASSERT(m_shell && m_shell->isRunning(), return {});
QMutexLocker l(&m_shellMutex);
m_shell->readAllStandardOutput(); // clean possible left-overs
const QByteArray oldError = m_shell->readAllStandardError(); // clean possible left-overs
if (!oldError.isEmpty()) {
LOG("Unexpected old stderr: " << oldError);
QTC_CHECK(false);
}
const QByteArray markerWithNewLine("___QC_DOCKER_" + randomHex() + "_OUTPUT_MARKER___\n");
m_shell->write(cmd.toUserOutput() + "\necho -n \"" + markerWithNewLine + "\"\n");
QByteArray output;
while (!output.endsWith(markerWithNewLine)) {
QTC_ASSERT(m_shell->isRunning(), return {});
m_shell->waitForReadyRead();
output.append(m_shell->readAllStandardOutput());
}
LOG("Run command in shell:" << cmd.toUserOutput() << "output size:" << output.size());
if (QTC_GUARD(output.endsWith(markerWithNewLine)))
output.chop(markerWithNewLine.size());
const QByteArray currentError = m_shell->readAllStandardError();
if (!currentError.isEmpty()) {
LOG("Unexpected current stderr: " << currentError);
QTC_CHECK(false);
}
return output;
} }
// Factory // Factory