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.
* The default implementation just sets the command line to "bash"
*/
void DeviceShell::setupShellProcess(QtcProcess* shellProcess)
void DeviceShell::setupShellProcess(QtcProcess *shellProcess)
{
shellProcess->setCommand(CommandLine{"bash"});
}

View File

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

View File

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

View File

@@ -50,6 +50,7 @@
#include <utils/algorithm.h>
#include <utils/basetreeview.h>
#include <utils/deviceshell.h>
#include <utils/environment.h>
#include <utils/fileutils.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
{
Q_DECLARE_TR_FUNCTIONS(Docker::Internal::DockerDevice)
public:
DockerDevicePrivate(DockerDevice *parent) : q(parent)
DockerDevicePrivate(DockerDevice *parent)
: q(parent)
{}
~DockerDevicePrivate() { stopCurrentContainer(); }
@@ -170,8 +189,9 @@ public:
DockerDeviceData m_data;
// For local file access
QPointer<QtcProcess> m_shell;
mutable QMutex m_shellMutex;
std::unique_ptr<ContainerShell> m_shell;
QString m_container;
Environment m_cachedEnviroment;
@@ -428,20 +448,7 @@ void DockerDevicePrivate::stopCurrentContainer()
if (m_container.isEmpty() || !DockerApi::isDockerDaemonAvailable(false).value_or(false))
return;
if (m_shell) {
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();
}
m_shell.reset();
QtcProcess proc;
proc.setCommand({"docker", {"container", "stop", m_container}});
@@ -507,40 +514,21 @@ void DockerDevicePrivate::startContainer()
return;
LOG("Container via process: " << m_container);
CommandLine dockerRun{"docker", {"container" , "start", "-i", "-a", m_container}};
LOG("RUNNING: " << dockerRun.toUserOutput());
QPointer<QtcProcess> shell = new QtcProcess;
shell->setProcessMode(ProcessMode::Writer);
connect(shell, &QtcProcess::finished, this, [this, shell] {
LOG("\nSHELL FINISHED\n");
QTC_ASSERT(shell, return);
const int exitCode = shell->exitCode();
LOG("RES: " << int(shell->result())
<< " EXIT CODE: " << exitCode
<< " 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."));
}
m_shell = std::make_unique<ContainerShell>(m_container);
connect(m_shell.get(), &DeviceShell::errorOccurred, this, [this] (QProcess::ProcessError error) {
qCWarning(dockerDeviceLog) << "Container shell encountered error:" << error;
m_shell.reset();
DockerApi::recheckDockerDaemon();
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);
m_shell = shell;
m_shell->setCommand(dockerRun);
m_shell->start();
m_shell->waitForStarted();
if (!m_shell->isRunning()) {
if (!m_shell->waitForStarted()) {
DockerApi::recheckDockerDaemon();
LOG("DOCKER SHELL FAILED");
return;
qCWarning(dockerDeviceLog) << "Container shell failed to start";
}
}
@@ -1094,83 +1082,14 @@ bool DockerDevicePrivate::runInContainer(const CommandLine &cmd) const
bool DockerDevicePrivate::runInShell(const CommandLine &cmd) const
{
if (QThread::currentThread() != qApp->thread()) {
bool result = false;
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();
QTC_ASSERT(m_shell, return false);
return m_shell->runInShell(cmd);
}
QByteArray DockerDevicePrivate::outputForRunInShell(const CommandLine &cmd) const
{
if (QThread::currentThread() != qApp->thread()) {
QByteArray result;
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;
QTC_ASSERT(m_shell.get(), return {});
return m_shell->outputForRunInShell(cmd).stdOutput;
}
// Factory