2021-03-29 09:11:36 +02:00
|
|
|
/****************************************************************************
|
|
|
|
|
**
|
|
|
|
|
** Copyright (C) 2016 The Qt Company Ltd.
|
|
|
|
|
** Contact: https://www.qt.io/licensing/
|
|
|
|
|
**
|
|
|
|
|
** This file is part of Qt Creator.
|
|
|
|
|
**
|
|
|
|
|
** Commercial License Usage
|
|
|
|
|
** Licensees holding valid commercial Qt licenses may use this file in
|
|
|
|
|
** accordance with the commercial license agreement provided with the
|
|
|
|
|
** Software or, alternatively, in accordance with the terms contained in
|
|
|
|
|
** a written agreement between you and The Qt Company. For licensing terms
|
|
|
|
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
|
|
|
|
** information use the contact form at https://www.qt.io/contact-us.
|
|
|
|
|
**
|
|
|
|
|
** GNU General Public License Usage
|
|
|
|
|
** Alternatively, this file may be used under the terms of the GNU
|
|
|
|
|
** General Public License version 3 as published by the Free Software
|
|
|
|
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
|
|
|
|
** included in the packaging of this file. Please review the following
|
|
|
|
|
** information to ensure the GNU General Public License requirements will
|
|
|
|
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
|
|
|
|
**
|
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
|
|
|
|
#include "dockerdevice.h"
|
|
|
|
|
|
2022-07-13 10:43:31 +02:00
|
|
|
#include "dockerapi.h"
|
2021-03-29 09:11:36 +02:00
|
|
|
#include "dockerconstants.h"
|
2022-04-06 10:15:29 +02:00
|
|
|
#include "dockerdevicewidget.h"
|
2022-07-13 10:43:31 +02:00
|
|
|
#include "dockertr.h"
|
2022-04-06 10:15:29 +02:00
|
|
|
#include "kitdetector.h"
|
2021-03-29 09:11:36 +02:00
|
|
|
|
2021-05-17 13:49:43 +02:00
|
|
|
#include <extensionsystem/pluginmanager.h>
|
|
|
|
|
|
2021-03-29 09:11:36 +02:00
|
|
|
#include <coreplugin/icore.h>
|
|
|
|
|
#include <coreplugin/messagemanager.h>
|
|
|
|
|
|
2021-05-31 13:01:49 +02:00
|
|
|
#include <projectexplorer/devicesupport/devicemanager.h>
|
2021-03-29 09:11:36 +02:00
|
|
|
#include <projectexplorer/devicesupport/idevicewidget.h>
|
2021-05-18 06:16:25 +02:00
|
|
|
#include <projectexplorer/kitinformation.h>
|
|
|
|
|
#include <projectexplorer/kitmanager.h>
|
2021-03-29 09:11:36 +02:00
|
|
|
#include <projectexplorer/runcontrol.h>
|
2021-04-29 07:36:34 +02:00
|
|
|
#include <projectexplorer/toolchain.h>
|
|
|
|
|
#include <projectexplorer/toolchainmanager.h>
|
|
|
|
|
|
|
|
|
|
#include <qtsupport/baseqtversion.h>
|
2021-05-18 06:16:25 +02:00
|
|
|
#include <qtsupport/qtkitinformation.h>
|
2021-04-29 07:36:34 +02:00
|
|
|
#include <qtsupport/qtversionfactory.h>
|
|
|
|
|
#include <qtsupport/qtversionmanager.h>
|
2021-03-29 09:11:36 +02:00
|
|
|
|
|
|
|
|
#include <utils/algorithm.h>
|
|
|
|
|
#include <utils/basetreeview.h>
|
2022-05-05 15:08:46 +02:00
|
|
|
#include <utils/deviceshell.h>
|
2021-03-29 09:11:36 +02:00
|
|
|
#include <utils/environment.h>
|
2021-10-05 12:52:26 +02:00
|
|
|
#include <utils/fileutils.h>
|
2021-03-29 09:11:36 +02:00
|
|
|
#include <utils/hostosinfo.h>
|
2022-03-14 14:02:22 +01:00
|
|
|
#include <utils/infolabel.h>
|
2021-03-29 09:11:36 +02:00
|
|
|
#include <utils/layoutbuilder.h>
|
2021-05-31 10:10:53 +02:00
|
|
|
#include <utils/overridecursor.h>
|
2021-10-05 12:52:26 +02:00
|
|
|
#include <utils/pathlisteditor.h>
|
2021-03-29 09:11:36 +02:00
|
|
|
#include <utils/port.h>
|
2022-04-28 16:15:46 +02:00
|
|
|
#include <utils/processinterface.h>
|
2021-03-29 09:11:36 +02:00
|
|
|
#include <utils/qtcassert.h>
|
|
|
|
|
#include <utils/qtcprocess.h>
|
2021-04-29 07:36:34 +02:00
|
|
|
#include <utils/temporaryfile.h>
|
2021-03-29 09:11:36 +02:00
|
|
|
#include <utils/treemodel.h>
|
2021-10-05 12:52:26 +02:00
|
|
|
#include <utils/utilsicons.h>
|
2021-03-29 09:11:36 +02:00
|
|
|
|
2021-05-31 10:10:53 +02:00
|
|
|
#include <QApplication>
|
2021-06-29 10:47:48 +02:00
|
|
|
#include <QCheckBox>
|
2021-12-03 10:21:57 +01:00
|
|
|
#include <QComboBox>
|
2021-06-14 08:33:44 +02:00
|
|
|
#include <QDateTime>
|
2021-03-29 09:11:36 +02:00
|
|
|
#include <QDialog>
|
|
|
|
|
#include <QDialogButtonBox>
|
2021-04-29 07:36:34 +02:00
|
|
|
#include <QFileSystemWatcher>
|
2021-03-29 09:11:36 +02:00
|
|
|
#include <QHeaderView>
|
2021-09-22 10:31:06 +02:00
|
|
|
#include <QHostAddress>
|
2021-06-28 11:53:29 +02:00
|
|
|
#include <QLoggingCategory>
|
2021-09-22 10:31:06 +02:00
|
|
|
#include <QNetworkInterface>
|
2021-03-29 09:11:36 +02:00
|
|
|
#include <QPushButton>
|
2021-07-20 15:23:26 +02:00
|
|
|
#include <QRandomGenerator>
|
2021-07-22 14:08:53 +02:00
|
|
|
#include <QRegularExpression>
|
2021-03-29 09:11:36 +02:00
|
|
|
#include <QTextBrowser>
|
2021-04-29 07:36:34 +02:00
|
|
|
#include <QThread>
|
2021-09-29 17:09:45 +02:00
|
|
|
#include <QToolButton>
|
2021-03-29 09:11:36 +02:00
|
|
|
|
2021-07-15 10:51:39 +02:00
|
|
|
#include <numeric>
|
|
|
|
|
|
2021-06-28 09:58:54 +02:00
|
|
|
#ifdef Q_OS_UNIX
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
#include <sys/types.h>
|
|
|
|
|
#endif
|
|
|
|
|
|
2021-03-29 09:11:36 +02:00
|
|
|
using namespace Core;
|
|
|
|
|
using namespace ProjectExplorer;
|
2021-04-29 07:36:34 +02:00
|
|
|
using namespace QtSupport;
|
2021-03-29 09:11:36 +02:00
|
|
|
using namespace Utils;
|
|
|
|
|
|
2022-07-13 10:43:31 +02:00
|
|
|
namespace Docker::Internal {
|
2021-03-29 09:11:36 +02:00
|
|
|
|
2022-04-28 16:15:46 +02:00
|
|
|
const QString s_pidMarker = "__qtc$$qtc__";
|
|
|
|
|
|
2021-06-28 11:53:29 +02:00
|
|
|
static Q_LOGGING_CATEGORY(dockerDeviceLog, "qtc.docker.device", QtWarningMsg);
|
2021-09-22 14:42:21 +02:00
|
|
|
#define LOG(x) qCDebug(dockerDeviceLog) << this << x << '\n'
|
2021-03-29 09:11:36 +02:00
|
|
|
|
2022-05-05 15:08:46 +02:00
|
|
|
class ContainerShell : public Utils::DeviceShell
|
|
|
|
|
{
|
|
|
|
|
public:
|
2022-07-04 14:24:51 +02:00
|
|
|
ContainerShell(DockerSettings *settings, const QString &containerId)
|
2022-07-04 11:09:11 +02:00
|
|
|
: m_settings(settings)
|
|
|
|
|
, m_containerId(containerId)
|
2022-05-05 15:08:46 +02:00
|
|
|
{
|
|
|
|
|
}
|
2021-07-01 17:59:15 +02:00
|
|
|
|
2022-05-05 15:08:46 +02:00
|
|
|
private:
|
|
|
|
|
void setupShellProcess(QtcProcess *shellProcess) final
|
|
|
|
|
{
|
2022-07-04 11:09:11 +02:00
|
|
|
shellProcess->setCommand({m_settings->dockerBinaryPath.filePath(), {"container", "start", "-i", "-a", m_containerId}});
|
2022-05-05 15:08:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
2022-07-04 14:24:51 +02:00
|
|
|
DockerSettings *m_settings;
|
2022-05-05 15:08:46 +02:00
|
|
|
QString m_containerId;
|
|
|
|
|
};
|
2021-07-15 12:25:23 +02:00
|
|
|
|
2021-05-31 10:10:53 +02:00
|
|
|
class DockerDevicePrivate : public QObject
|
|
|
|
|
{
|
|
|
|
|
public:
|
2022-07-04 14:24:51 +02:00
|
|
|
DockerDevicePrivate(DockerDevice *parent, DockerSettings *settings)
|
2022-05-05 15:08:46 +02:00
|
|
|
: q(parent)
|
2022-07-04 11:09:11 +02:00
|
|
|
, m_settings(settings)
|
2022-02-22 17:22:02 +01:00
|
|
|
{}
|
2021-05-31 10:10:53 +02:00
|
|
|
|
2021-07-20 14:27:11 +02:00
|
|
|
~DockerDevicePrivate() { stopCurrentContainer(); }
|
2021-05-31 10:10:53 +02:00
|
|
|
|
2021-07-15 10:10:47 +02:00
|
|
|
bool runInContainer(const CommandLine &cmd) const;
|
2022-05-13 15:13:35 +02:00
|
|
|
bool runInShell(const CommandLine &cmd, const QByteArray &stdInData = {}) const;
|
2022-02-03 18:09:06 +01:00
|
|
|
QByteArray outputForRunInShell(const CommandLine &cmd) const;
|
2021-05-31 10:10:53 +02:00
|
|
|
|
2022-01-11 15:57:21 +01:00
|
|
|
void updateContainerAccess();
|
2021-05-31 10:10:53 +02:00
|
|
|
|
2021-07-21 12:38:04 +02:00
|
|
|
void startContainer();
|
2021-07-09 13:18:22 +02:00
|
|
|
void stopCurrentContainer();
|
2021-05-31 10:10:53 +02:00
|
|
|
void fetchSystemEnviroment();
|
|
|
|
|
|
|
|
|
|
DockerDevice *q;
|
|
|
|
|
DockerDeviceData m_data;
|
2022-07-04 14:24:51 +02:00
|
|
|
DockerSettings *m_settings;
|
2021-05-31 10:10:53 +02:00
|
|
|
|
|
|
|
|
// For local file access
|
2022-05-05 15:08:46 +02:00
|
|
|
|
|
|
|
|
std::unique_ptr<ContainerShell> m_shell;
|
|
|
|
|
|
2021-05-31 10:10:53 +02:00
|
|
|
QString m_container;
|
2022-02-09 11:57:41 +01:00
|
|
|
|
2021-05-31 10:10:53 +02:00
|
|
|
Environment m_cachedEnviroment;
|
2021-09-14 14:14:15 +02:00
|
|
|
|
|
|
|
|
bool m_useFind = true; // prefer find over ls and hacks, but be able to use ls as fallback
|
2021-05-31 10:10:53 +02:00
|
|
|
};
|
|
|
|
|
|
2022-04-28 16:15:46 +02:00
|
|
|
class DockerProcessImpl : public Utils::ProcessInterface
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
DockerProcessImpl(DockerDevicePrivate *device);
|
|
|
|
|
virtual ~DockerProcessImpl();
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
void start() override;
|
|
|
|
|
qint64 write(const QByteArray &data) override;
|
|
|
|
|
void sendControlSignal(ControlSignal controlSignal) override;
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
CommandLine fullLocalCommandLine(bool interactive);
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
DockerDevicePrivate *m_devicePrivate = nullptr;
|
|
|
|
|
// Store the IDevice::ConstPtr in order to extend the lifetime of device for as long
|
|
|
|
|
// as this object is alive.
|
|
|
|
|
IDevice::ConstPtr m_device;
|
|
|
|
|
|
|
|
|
|
QtcProcess m_process;
|
|
|
|
|
qint64 m_remotePID = -1;
|
|
|
|
|
bool m_hasReceivedFirstOutput = false;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
CommandLine DockerProcessImpl::fullLocalCommandLine(bool interactive)
|
|
|
|
|
{
|
|
|
|
|
QStringList args;
|
|
|
|
|
|
|
|
|
|
if (!m_setup.m_workingDirectory.isEmpty()) {
|
2022-06-15 11:47:05 +02:00
|
|
|
QTC_CHECK(DeviceManager::deviceForPath(m_setup.m_workingDirectory) == m_device);
|
2022-04-28 16:15:46 +02:00
|
|
|
args.append({"cd", m_setup.m_workingDirectory.path()});
|
|
|
|
|
args.append("&&");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
args.append({"echo", s_pidMarker, "&&"});
|
|
|
|
|
|
2022-05-09 15:02:23 +02:00
|
|
|
const Environment &env = m_setup.m_environment;
|
2022-04-28 16:15:46 +02:00
|
|
|
for (auto it = env.constBegin(); it != env.constEnd(); ++it)
|
|
|
|
|
args.append(env.key(it) + "='" + env.expandedValueForKey(env.key(it)) + '\'');
|
|
|
|
|
|
|
|
|
|
args.append("exec");
|
|
|
|
|
args.append({m_setup.m_commandLine.executable().path(), m_setup.m_commandLine.arguments()});
|
|
|
|
|
|
|
|
|
|
CommandLine shCmd("/bin/sh", {"-c", args.join(" ")});
|
|
|
|
|
return m_devicePrivate->q->withDockerExecCmd(shCmd, interactive);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DockerProcessImpl::DockerProcessImpl(DockerDevicePrivate *device)
|
|
|
|
|
: m_devicePrivate(device)
|
|
|
|
|
, m_device(device->q->sharedFromThis())
|
|
|
|
|
, m_process(this)
|
|
|
|
|
{
|
|
|
|
|
connect(&m_process, &QtcProcess::started, this, [this] {
|
|
|
|
|
qCDebug(dockerDeviceLog) << "Process started:" << m_process.commandLine();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
connect(&m_process, &QtcProcess::readyReadStandardOutput, this, [this] {
|
|
|
|
|
if (!m_hasReceivedFirstOutput) {
|
|
|
|
|
QByteArray output = m_process.readAllStandardOutput();
|
|
|
|
|
qsizetype idx = output.indexOf('\n');
|
|
|
|
|
QByteArray firstLine = output.left(idx);
|
|
|
|
|
QByteArray rest = output.mid(idx+1);
|
|
|
|
|
qCDebug(dockerDeviceLog) << "Process first line received:" << m_process.commandLine() << firstLine;
|
|
|
|
|
if (firstLine.startsWith("__qtc")) {
|
|
|
|
|
bool ok = false;
|
|
|
|
|
m_remotePID = firstLine.mid(5, firstLine.size() -5 -5).toLongLong(&ok);
|
|
|
|
|
|
|
|
|
|
if (ok)
|
|
|
|
|
emit started(m_remotePID);
|
|
|
|
|
|
|
|
|
|
if (rest.size() > 0)
|
|
|
|
|
emit readyRead(rest, {});
|
|
|
|
|
|
|
|
|
|
m_hasReceivedFirstOutput = true;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
emit readyRead(m_process.readAllStandardOutput(), {});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
connect(&m_process, &QtcProcess::readyReadStandardError, this, [this] {
|
|
|
|
|
emit readyRead({}, m_process.readAllStandardError());
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
connect(&m_process, &QtcProcess::done, this, [this] {
|
|
|
|
|
qCDebug(dockerDeviceLog) << "Process exited:" << m_process.commandLine() << "with code:" << m_process.resultData().m_exitCode;
|
|
|
|
|
emit done(m_process.resultData());
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DockerProcessImpl::~DockerProcessImpl()
|
|
|
|
|
{
|
|
|
|
|
if (m_process.state() == QProcess::Running)
|
|
|
|
|
sendControlSignal(ControlSignal::Kill);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void DockerProcessImpl::start()
|
|
|
|
|
{
|
|
|
|
|
m_process.setProcessImpl(m_setup.m_processImpl);
|
|
|
|
|
m_process.setProcessMode(m_setup.m_processMode);
|
|
|
|
|
m_process.setTerminalMode(m_setup.m_terminalMode);
|
2022-06-03 14:28:30 +02:00
|
|
|
m_process.setReaperTimeout(m_setup.m_reaperTimeout);
|
2022-04-28 16:15:46 +02:00
|
|
|
m_process.setWriteData(m_setup.m_writeData);
|
|
|
|
|
m_process.setProcessChannelMode(m_setup.m_processChannelMode);
|
|
|
|
|
m_process.setExtraData(m_setup.m_extraData);
|
|
|
|
|
m_process.setStandardInputFile(m_setup.m_standardInputFile);
|
|
|
|
|
m_process.setAbortOnMetaChars(m_setup.m_abortOnMetaChars);
|
|
|
|
|
if (m_setup.m_lowPriority)
|
|
|
|
|
m_process.setLowPriority();
|
|
|
|
|
|
|
|
|
|
m_process.setCommand(fullLocalCommandLine(m_setup.m_processMode == ProcessMode::Writer));
|
|
|
|
|
m_process.start();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
qint64 DockerProcessImpl::write(const QByteArray &data)
|
|
|
|
|
{
|
|
|
|
|
return m_process.writeRaw(data);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void DockerProcessImpl::sendControlSignal(ControlSignal controlSignal)
|
|
|
|
|
{
|
|
|
|
|
int signal = controlSignalToInt(controlSignal);
|
|
|
|
|
m_devicePrivate->runInShell(
|
|
|
|
|
{"kill", {QString("-%1").arg(signal), QString("%2").arg(m_remotePID)}});
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-29 09:11:36 +02:00
|
|
|
IDeviceWidget *DockerDevice::createWidget()
|
|
|
|
|
{
|
|
|
|
|
return new DockerDeviceWidget(sharedFromThis());
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-29 13:58:45 +02:00
|
|
|
Tasks DockerDevice::validate() const
|
|
|
|
|
{
|
|
|
|
|
Tasks result;
|
|
|
|
|
if (d->m_data.mounts.isEmpty()) {
|
|
|
|
|
result << Task(Task::Error,
|
2022-07-13 10:43:31 +02:00
|
|
|
Tr::tr("The docker device has not set up shared directories."
|
|
|
|
|
"This will not work for building."),
|
2021-09-29 13:58:45 +02:00
|
|
|
{}, -1, {});
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-08 09:41:46 +01:00
|
|
|
// DockerDeviceData
|
|
|
|
|
|
2022-02-24 10:14:25 +01:00
|
|
|
QString DockerDeviceData::repoAndTag() const
|
2022-02-08 09:41:46 +01:00
|
|
|
{
|
|
|
|
|
if (repo == "<none>")
|
|
|
|
|
return imageId;
|
|
|
|
|
|
|
|
|
|
if (tag == "<none>")
|
|
|
|
|
return repo;
|
|
|
|
|
|
|
|
|
|
return repo + ':' + tag;
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-29 07:36:34 +02:00
|
|
|
// DockerDevice
|
|
|
|
|
|
2022-07-04 14:24:51 +02:00
|
|
|
DockerDevice::DockerDevice(DockerSettings *settings, const DockerDeviceData &data)
|
2022-07-04 11:09:11 +02:00
|
|
|
: d(new DockerDevicePrivate(this, settings))
|
2021-03-29 09:11:36 +02:00
|
|
|
{
|
2021-04-29 07:36:34 +02:00
|
|
|
d->m_data = data;
|
|
|
|
|
|
2022-07-13 10:43:31 +02:00
|
|
|
setDisplayType(Tr::tr("Docker"));
|
2021-03-29 09:11:36 +02:00
|
|
|
setOsType(OsTypeOtherUnix);
|
2022-07-13 10:43:31 +02:00
|
|
|
setDefaultDisplayName(Tr::tr("Docker Image"));;
|
|
|
|
|
setDisplayName(Tr::tr("Docker Image \"%1\" (%2)").arg(data.repoAndTag()).arg(data.imageId));
|
2021-03-29 09:11:36 +02:00
|
|
|
setAllowEmptyCommand(true);
|
|
|
|
|
|
2022-07-04 11:09:11 +02:00
|
|
|
setOpenTerminal([this, settings](const Environment &env, const FilePath &workingDir) {
|
2022-02-09 12:29:40 +01:00
|
|
|
Q_UNUSED(env); // TODO: That's the runnable's environment in general. Use it via -e below.
|
|
|
|
|
updateContainerAccess();
|
|
|
|
|
if (d->m_container.isEmpty()) {
|
2022-07-13 10:43:31 +02:00
|
|
|
MessageManager::writeDisrupting(Tr::tr("Error starting remote shell. No container."));
|
2022-02-09 12:29:40 +01:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-06 09:42:48 +02:00
|
|
|
QtcProcess *proc = new QtcProcess(d);
|
2022-02-18 00:56:14 +01:00
|
|
|
proc->setTerminalMode(TerminalMode::On);
|
2022-02-10 19:25:03 +01:00
|
|
|
|
2022-04-06 09:42:48 +02:00
|
|
|
QObject::connect(proc, &QtcProcess::done, [proc] {
|
|
|
|
|
if (proc->error() != QProcess::UnknownError && MessageManager::instance())
|
2022-07-13 10:43:31 +02:00
|
|
|
MessageManager::writeDisrupting(Tr::tr("Error starting remote shell."));
|
2021-03-29 09:11:36 +02:00
|
|
|
proc->deleteLater();
|
|
|
|
|
});
|
|
|
|
|
|
2022-02-09 12:29:40 +01:00
|
|
|
const QString wd = workingDir.isEmpty() ? "/" : workingDir.path();
|
2022-07-04 11:09:11 +02:00
|
|
|
proc->setCommand({settings->dockerBinaryPath.filePath(), {"exec", "-it", "-w", wd, d->m_container, "/bin/sh"}});
|
2022-02-09 12:29:40 +01:00
|
|
|
proc->setEnvironment(Environment::systemEnvironment()); // The host system env. Intentional.
|
|
|
|
|
proc->start();
|
2021-03-29 09:11:36 +02:00
|
|
|
});
|
|
|
|
|
|
2022-07-13 10:43:31 +02:00
|
|
|
addDeviceAction({Tr::tr("Open Shell in Container"), [](const IDevice::Ptr &device, QWidget *) {
|
2022-02-10 12:00:00 +01:00
|
|
|
device->openTerminal(device->systemEnvironment(), FilePath());
|
|
|
|
|
}});
|
2021-03-29 09:11:36 +02:00
|
|
|
}
|
|
|
|
|
|
2021-04-29 07:36:34 +02:00
|
|
|
DockerDevice::~DockerDevice()
|
|
|
|
|
{
|
|
|
|
|
delete d;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-04 14:24:51 +02:00
|
|
|
void DockerDevice::shutdown()
|
|
|
|
|
{
|
|
|
|
|
d->stopCurrentContainer();
|
|
|
|
|
d->m_settings = nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-29 09:11:36 +02:00
|
|
|
const DockerDeviceData &DockerDevice::data() const
|
|
|
|
|
{
|
2021-04-29 07:36:34 +02:00
|
|
|
return d->m_data;
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-29 12:12:59 +02:00
|
|
|
DockerDeviceData &DockerDevice::data()
|
|
|
|
|
{
|
|
|
|
|
return d->m_data;
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-11 15:57:21 +01:00
|
|
|
void DockerDevice::updateContainerAccess() const
|
2021-04-29 07:36:34 +02:00
|
|
|
{
|
2022-01-11 15:57:21 +01:00
|
|
|
d->updateContainerAccess();
|
2021-05-18 06:16:25 +02:00
|
|
|
}
|
|
|
|
|
|
2021-07-09 13:18:22 +02:00
|
|
|
void DockerDevicePrivate::stopCurrentContainer()
|
|
|
|
|
{
|
2022-07-04 14:24:51 +02:00
|
|
|
if (!m_settings || m_container.isEmpty() || !DockerApi::isDockerDaemonAvailable(false).value_or(false))
|
2021-07-09 13:18:22 +02:00
|
|
|
return;
|
|
|
|
|
|
2022-05-05 15:08:46 +02:00
|
|
|
m_shell.reset();
|
2021-07-20 14:27:11 +02:00
|
|
|
|
2021-07-09 13:18:22 +02:00
|
|
|
QtcProcess proc;
|
2022-07-04 11:09:11 +02:00
|
|
|
proc.setCommand({m_settings->dockerBinaryPath.filePath(), {"container", "stop", m_container}});
|
2021-07-09 13:18:22 +02:00
|
|
|
|
|
|
|
|
m_container.clear();
|
|
|
|
|
|
|
|
|
|
proc.runBlocking();
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-22 10:31:06 +02:00
|
|
|
static QString getLocalIPv4Address()
|
|
|
|
|
{
|
|
|
|
|
const QList<QHostAddress> addresses = QNetworkInterface::allAddresses();
|
|
|
|
|
for (auto &a : addresses) {
|
|
|
|
|
if (a.isInSubnet(QHostAddress("192.168.0.0"), 16))
|
|
|
|
|
return a.toString();
|
|
|
|
|
if (a.isInSubnet(QHostAddress("10.0.0.0"), 8))
|
|
|
|
|
return a.toString();
|
|
|
|
|
if (a.isInSubnet(QHostAddress("172.16.0.0"), 12))
|
|
|
|
|
return a.toString();
|
|
|
|
|
}
|
|
|
|
|
return QString();
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-21 12:38:04 +02:00
|
|
|
void DockerDevicePrivate::startContainer()
|
2021-05-18 06:16:25 +02:00
|
|
|
{
|
2022-07-04 14:24:51 +02:00
|
|
|
if (!m_settings)
|
|
|
|
|
return;
|
|
|
|
|
|
2022-02-02 10:53:36 +01:00
|
|
|
const QString display = HostOsInfo::isLinuxHost() ? QString(":0")
|
|
|
|
|
: QString(getLocalIPv4Address() + ":0.0");
|
2022-07-04 11:09:11 +02:00
|
|
|
CommandLine dockerCreate{m_settings->dockerBinaryPath.filePath(), {"create",
|
2021-09-29 17:09:45 +02:00
|
|
|
"-i",
|
|
|
|
|
"--rm",
|
|
|
|
|
"-e", QString("DISPLAY=%1").arg(display),
|
|
|
|
|
"-e", "XAUTHORITY=/.Xauthority",
|
|
|
|
|
"--net", "host"}};
|
2021-06-17 10:34:46 +02:00
|
|
|
|
2021-06-28 09:58:54 +02:00
|
|
|
#ifdef Q_OS_UNIX
|
2021-09-29 17:09:45 +02:00
|
|
|
// no getuid() and getgid() on Windows.
|
2021-06-29 10:47:48 +02:00
|
|
|
if (m_data.useLocalUidGid)
|
2021-09-29 17:09:45 +02:00
|
|
|
dockerCreate.addArgs({"-u", QString("%1:%2").arg(getuid()).arg(getgid())});
|
2021-06-28 09:58:54 +02:00
|
|
|
#endif
|
|
|
|
|
|
2021-10-05 13:39:13 +02:00
|
|
|
for (QString mount : qAsConst(m_data.mounts)) {
|
|
|
|
|
if (mount.isEmpty())
|
|
|
|
|
continue;
|
2021-10-08 11:50:07 +02:00
|
|
|
mount = q->mapToDevicePath(FilePath::fromUserInput(mount));
|
2021-09-29 17:09:45 +02:00
|
|
|
dockerCreate.addArgs({"-v", mount + ':' + mount});
|
2021-07-12 15:27:19 +02:00
|
|
|
}
|
2021-10-18 09:44:10 +02:00
|
|
|
FilePath dumperPath = FilePath::fromString("/tmp/qtcreator/debugger");
|
|
|
|
|
dockerCreate.addArgs({"-v", q->debugDumperPath().toUserOutput() + ':' + dumperPath.path()});
|
|
|
|
|
q->setDebugDumperPath(dumperPath);
|
2021-06-17 10:34:46 +02:00
|
|
|
|
2022-02-24 10:14:25 +01:00
|
|
|
dockerCreate.addArgs({"--entrypoint", "/bin/sh", m_data.repoAndTag()});
|
2021-06-17 10:34:46 +02:00
|
|
|
|
2021-09-29 17:09:45 +02:00
|
|
|
LOG("RUNNING: " << dockerCreate.toUserOutput());
|
|
|
|
|
QtcProcess createProcess;
|
|
|
|
|
createProcess.setCommand(dockerCreate);
|
|
|
|
|
createProcess.runBlocking();
|
|
|
|
|
|
2022-07-07 08:25:03 +02:00
|
|
|
if (createProcess.result() != ProcessResult::FinishedWithSuccess) {
|
|
|
|
|
qCWarning(dockerDeviceLog) << "Failed creating docker container:";
|
|
|
|
|
qCWarning(dockerDeviceLog) << "Exit Code:" << createProcess.exitCode();
|
|
|
|
|
qCWarning(dockerDeviceLog) << createProcess.allOutput();
|
2021-09-29 17:09:45 +02:00
|
|
|
return;
|
2022-07-07 08:25:03 +02:00
|
|
|
}
|
2021-09-29 17:09:45 +02:00
|
|
|
|
2022-06-17 14:17:14 +02:00
|
|
|
m_container = createProcess.cleanedStdOut().trimmed();
|
2021-09-29 17:09:45 +02:00
|
|
|
if (m_container.isEmpty())
|
|
|
|
|
return;
|
|
|
|
|
LOG("Container via process: " << m_container);
|
|
|
|
|
|
2022-07-04 11:09:11 +02:00
|
|
|
m_shell = std::make_unique<ContainerShell>(m_settings, m_container);
|
2022-06-17 13:11:08 +02:00
|
|
|
connect(m_shell.get(), &DeviceShell::done, this, [this] (const ProcessResultData &resultData) {
|
|
|
|
|
if (resultData.m_error != QProcess::UnknownError)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
qCWarning(dockerDeviceLog) << "Container shell encountered error:" << resultData.m_error;
|
2022-05-05 15:08:46 +02:00
|
|
|
m_shell.reset();
|
2021-06-29 13:19:01 +02:00
|
|
|
|
2022-05-05 15:08:46 +02:00
|
|
|
DockerApi::recheckDockerDaemon();
|
2022-07-13 10:43:31 +02:00
|
|
|
MessageManager::writeFlashing(Tr::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."));
|
2022-05-05 15:08:46 +02:00
|
|
|
});
|
2021-04-29 07:36:34 +02:00
|
|
|
|
2022-05-17 10:07:27 +02:00
|
|
|
if (!m_shell->start()) {
|
2022-05-05 15:08:46 +02:00
|
|
|
qCWarning(dockerDeviceLog) << "Container shell failed to start";
|
2021-06-17 10:34:46 +02:00
|
|
|
}
|
2021-07-21 12:38:04 +02:00
|
|
|
}
|
|
|
|
|
|
2022-01-11 15:57:21 +01:00
|
|
|
void DockerDevicePrivate::updateContainerAccess()
|
2021-07-21 12:38:04 +02:00
|
|
|
{
|
2021-09-29 15:53:30 +02:00
|
|
|
if (!m_container.isEmpty())
|
|
|
|
|
return;
|
|
|
|
|
|
2022-04-07 14:04:20 +02:00
|
|
|
if (DockerApi::isDockerDaemonAvailable(false).value_or(false) == false)
|
2021-07-21 12:38:04 +02:00
|
|
|
return;
|
|
|
|
|
|
2022-02-22 17:22:02 +01:00
|
|
|
if (m_shell)
|
2021-08-10 16:50:23 +02:00
|
|
|
return;
|
2022-01-11 14:40:03 +01:00
|
|
|
|
2022-02-22 17:22:02 +01:00
|
|
|
startContainer();
|
2021-04-29 07:36:34 +02:00
|
|
|
}
|
|
|
|
|
|
2021-07-09 13:18:22 +02:00
|
|
|
void DockerDevice::setMounts(const QStringList &mounts) const
|
|
|
|
|
{
|
|
|
|
|
d->m_data.mounts = mounts;
|
|
|
|
|
d->stopCurrentContainer(); // Force re-start with new mounts.
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-28 16:15:46 +02:00
|
|
|
CommandLine DockerDevice::withDockerExecCmd(const Utils::CommandLine &cmd, bool interactive) const
|
|
|
|
|
{
|
2022-07-04 14:24:51 +02:00
|
|
|
if (!d->m_settings)
|
|
|
|
|
return {};
|
|
|
|
|
|
2022-04-28 16:15:46 +02:00
|
|
|
QStringList args;
|
|
|
|
|
|
|
|
|
|
args << "exec";
|
|
|
|
|
if (interactive)
|
|
|
|
|
args << "-i";
|
|
|
|
|
args << d->m_container;
|
|
|
|
|
|
2022-07-04 11:09:11 +02:00
|
|
|
CommandLine dcmd{d->m_settings->dockerBinaryPath.filePath(), args};
|
2022-04-28 16:15:46 +02:00
|
|
|
dcmd.addCommandLineAsArgs(cmd);
|
|
|
|
|
return dcmd;
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-29 09:11:36 +02:00
|
|
|
const char DockerDeviceDataImageIdKey[] = "DockerDeviceDataImageId";
|
|
|
|
|
const char DockerDeviceDataRepoKey[] = "DockerDeviceDataRepo";
|
|
|
|
|
const char DockerDeviceDataTagKey[] = "DockerDeviceDataTag";
|
|
|
|
|
const char DockerDeviceDataSizeKey[] = "DockerDeviceDataSize";
|
2021-06-29 10:47:48 +02:00
|
|
|
const char DockerDeviceUseOutsideUser[] = "DockerDeviceUseUidGid";
|
2021-06-29 12:12:59 +02:00
|
|
|
const char DockerDeviceMappedPaths[] = "DockerDeviceMappedPaths";
|
2021-03-29 09:11:36 +02:00
|
|
|
|
|
|
|
|
void DockerDevice::fromMap(const QVariantMap &map)
|
|
|
|
|
{
|
|
|
|
|
ProjectExplorer::IDevice::fromMap(map);
|
2021-04-29 07:36:34 +02:00
|
|
|
d->m_data.repo = map.value(DockerDeviceDataRepoKey).toString();
|
|
|
|
|
d->m_data.tag = map.value(DockerDeviceDataTagKey).toString();
|
2022-02-08 09:41:46 +01:00
|
|
|
d->m_data.imageId = map.value(DockerDeviceDataImageIdKey).toString();
|
2021-04-29 07:36:34 +02:00
|
|
|
d->m_data.size = map.value(DockerDeviceDataSizeKey).toString();
|
2021-06-29 10:47:48 +02:00
|
|
|
d->m_data.useLocalUidGid = map.value(DockerDeviceUseOutsideUser,
|
|
|
|
|
HostOsInfo::isLinuxHost()).toBool();
|
2021-06-29 12:12:59 +02:00
|
|
|
d->m_data.mounts = map.value(DockerDeviceMappedPaths).toStringList();
|
2021-03-29 09:11:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QVariantMap DockerDevice::toMap() const
|
|
|
|
|
{
|
|
|
|
|
QVariantMap map = ProjectExplorer::IDevice::toMap();
|
2021-04-29 07:36:34 +02:00
|
|
|
map.insert(DockerDeviceDataRepoKey, d->m_data.repo);
|
|
|
|
|
map.insert(DockerDeviceDataTagKey, d->m_data.tag);
|
2022-02-08 09:41:46 +01:00
|
|
|
map.insert(DockerDeviceDataImageIdKey, d->m_data.imageId);
|
2021-04-29 07:36:34 +02:00
|
|
|
map.insert(DockerDeviceDataSizeKey, d->m_data.size);
|
2021-06-29 10:47:48 +02:00
|
|
|
map.insert(DockerDeviceUseOutsideUser, d->m_data.useLocalUidGid);
|
2021-06-29 12:12:59 +02:00
|
|
|
map.insert(DockerDeviceMappedPaths, d->m_data.mounts);
|
2021-03-29 09:11:36 +02:00
|
|
|
return map;
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-28 16:15:46 +02:00
|
|
|
ProcessInterface *DockerDevice::createProcessInterface() const
|
|
|
|
|
{
|
|
|
|
|
return new DockerProcessImpl(d);
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-29 09:11:36 +02:00
|
|
|
bool DockerDevice::canAutoDetectPorts() const
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-10 12:53:27 +02:00
|
|
|
PortsGatheringMethod DockerDevice::portsGatheringMethod() const
|
|
|
|
|
{
|
|
|
|
|
return {
|
|
|
|
|
[this](QAbstractSocket::NetworkLayerProtocol protocol) -> CommandLine {
|
|
|
|
|
// We might encounter the situation that protocol is given IPv6
|
|
|
|
|
// but the consumer of the free port information decides to open
|
|
|
|
|
// an IPv4(only) port. As a result the next IPv6 scan will
|
|
|
|
|
// report the port again as open (in IPv6 namespace), while the
|
|
|
|
|
// same port in IPv4 namespace might still be blocked, and
|
|
|
|
|
// re-use of this port fails.
|
|
|
|
|
// GDBserver behaves exactly like this.
|
|
|
|
|
|
|
|
|
|
Q_UNUSED(protocol)
|
|
|
|
|
|
|
|
|
|
// /proc/net/tcp* covers /proc/net/tcp and /proc/net/tcp6
|
|
|
|
|
return {filePath("sed"),
|
|
|
|
|
"-e 's/.*: [[:xdigit:]]*:\\([[:xdigit:]]\\{4\\}\\).*/\\1/g' /proc/net/tcp*",
|
|
|
|
|
CommandLine::Raw};
|
|
|
|
|
},
|
|
|
|
|
|
2022-05-12 08:42:51 +02:00
|
|
|
&Port::parseFromSedOutput
|
2022-05-10 12:53:27 +02:00
|
|
|
};
|
|
|
|
|
};
|
2021-03-29 09:11:36 +02:00
|
|
|
|
|
|
|
|
DeviceProcessList *DockerDevice::createProcessListModel(QObject *) const
|
|
|
|
|
{
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DeviceTester *DockerDevice::createDeviceTester() const
|
|
|
|
|
{
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DeviceProcessSignalOperation::Ptr DockerDevice::signalOperation() const
|
|
|
|
|
{
|
|
|
|
|
return DeviceProcessSignalOperation::Ptr();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DeviceEnvironmentFetcher::Ptr DockerDevice::environmentFetcher() const
|
|
|
|
|
{
|
|
|
|
|
return DeviceEnvironmentFetcher::Ptr();
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-29 07:36:34 +02:00
|
|
|
FilePath DockerDevice::mapToGlobalPath(const FilePath &pathOnDevice) const
|
|
|
|
|
{
|
2021-05-19 10:56:00 +02:00
|
|
|
if (pathOnDevice.needsDevice()) {
|
|
|
|
|
// Already correct form, only sanity check it's ours...
|
|
|
|
|
QTC_CHECK(handlesFile(pathOnDevice));
|
2021-04-29 07:36:34 +02:00
|
|
|
return pathOnDevice;
|
|
|
|
|
}
|
2022-02-24 10:14:25 +01:00
|
|
|
|
2021-05-19 10:56:00 +02:00
|
|
|
FilePath result;
|
|
|
|
|
result.setPath(pathOnDevice.path());
|
2022-08-04 13:35:42 +02:00
|
|
|
result.setScheme(Constants::DOCKER_DEVICE_SCHEME);
|
2022-02-24 10:14:25 +01:00
|
|
|
result.setHost(d->m_data.repoAndTag());
|
|
|
|
|
|
|
|
|
|
// The following would work, but gives no hint on repo and tag
|
|
|
|
|
// result.setScheme("docker");
|
|
|
|
|
// result.setHost(d->m_data.imageId);
|
|
|
|
|
|
|
|
|
|
// The following would work, but gives no hint on repo, tag and imageid
|
|
|
|
|
// result.setScheme("device");
|
|
|
|
|
// result.setHost(id().toString());
|
|
|
|
|
|
2021-05-19 10:56:00 +02:00
|
|
|
return result;
|
2021-04-29 07:36:34 +02:00
|
|
|
}
|
|
|
|
|
|
2021-10-08 10:31:30 +02:00
|
|
|
QString DockerDevice::mapToDevicePath(const Utils::FilePath &globalPath) const
|
|
|
|
|
{
|
2021-10-08 11:50:07 +02:00
|
|
|
// make sure to convert windows style paths to unix style paths with the file system case:
|
|
|
|
|
// C:/dev/src -> /c/dev/src
|
2021-10-13 09:56:52 +02:00
|
|
|
const FilePath normalized = FilePath::fromString(globalPath.path()).normalizedPathName();
|
2021-10-08 10:31:30 +02:00
|
|
|
QString path = normalized.path();
|
|
|
|
|
if (normalized.startsWithDriveLetter()) {
|
|
|
|
|
const QChar lowerDriveLetter = path.at(0).toLower();
|
|
|
|
|
path = '/' + lowerDriveLetter + path.mid(2); // strip C:
|
|
|
|
|
}
|
|
|
|
|
return path;
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-31 11:16:44 +02:00
|
|
|
Utils::FilePath DockerDevice::rootPath() const
|
|
|
|
|
{
|
|
|
|
|
FilePath root;
|
|
|
|
|
root.setScheme(Constants::DOCKER_DEVICE_SCHEME);
|
|
|
|
|
root.setHost(d->m_data.repoAndTag());
|
2022-08-04 13:35:42 +02:00
|
|
|
root.setPath(u"/");
|
2022-05-31 11:16:44 +02:00
|
|
|
return root;
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-29 07:36:34 +02:00
|
|
|
bool DockerDevice::handlesFile(const FilePath &filePath) const
|
|
|
|
|
{
|
2022-08-04 13:35:42 +02:00
|
|
|
if (filePath.scheme() == u"device" && filePath.host() == id().toString())
|
2022-02-24 10:14:25 +01:00
|
|
|
return true;
|
2022-08-04 13:35:42 +02:00
|
|
|
|
|
|
|
|
if (filePath.scheme() == Constants::DOCKER_DEVICE_SCHEME
|
|
|
|
|
&& filePath.host() == d->m_data.imageId)
|
2022-02-24 10:14:25 +01:00
|
|
|
return true;
|
2022-08-04 13:35:42 +02:00
|
|
|
|
|
|
|
|
if (filePath.scheme() == Constants::DOCKER_DEVICE_SCHEME
|
|
|
|
|
&& filePath.host() == QString(d->m_data.repo + ':' + d->m_data.tag))
|
2022-02-24 10:14:25 +01:00
|
|
|
return true;
|
2022-08-04 13:35:42 +02:00
|
|
|
|
2022-02-24 10:14:25 +01:00
|
|
|
return false;
|
2021-04-29 07:36:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool DockerDevice::isExecutableFile(const FilePath &filePath) const
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(handlesFile(filePath), return false);
|
2022-01-11 15:57:21 +01:00
|
|
|
updateContainerAccess();
|
2021-05-31 16:49:25 +02:00
|
|
|
const QString path = filePath.path();
|
2021-07-20 15:23:26 +02:00
|
|
|
return d->runInShell({"test", {"-x", path}});
|
2021-04-29 07:36:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool DockerDevice::isReadableFile(const FilePath &filePath) const
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(handlesFile(filePath), return false);
|
2022-01-11 15:57:21 +01:00
|
|
|
updateContainerAccess();
|
2021-05-31 16:49:25 +02:00
|
|
|
const QString path = filePath.path();
|
2021-07-20 15:23:26 +02:00
|
|
|
return d->runInShell({"test", {"-r", path, "-a", "-f", path}});
|
2021-04-29 07:36:34 +02:00
|
|
|
}
|
|
|
|
|
|
2021-06-22 07:09:00 +02:00
|
|
|
bool DockerDevice::isWritableFile(const Utils::FilePath &filePath) const
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(handlesFile(filePath), return false);
|
2022-01-11 15:57:21 +01:00
|
|
|
updateContainerAccess();
|
2021-06-22 07:09:00 +02:00
|
|
|
const QString path = filePath.path();
|
2021-07-20 15:23:26 +02:00
|
|
|
return d->runInShell({"test", {"-w", path, "-a", "-f", path}});
|
2021-06-22 07:09:00 +02:00
|
|
|
}
|
|
|
|
|
|
2021-04-29 07:36:34 +02:00
|
|
|
bool DockerDevice::isReadableDirectory(const FilePath &filePath) const
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(handlesFile(filePath), return false);
|
2022-01-11 15:57:21 +01:00
|
|
|
updateContainerAccess();
|
2021-05-31 16:49:25 +02:00
|
|
|
const QString path = filePath.path();
|
2021-07-20 15:23:26 +02:00
|
|
|
return d->runInShell({"test", {"-r", path, "-a", "-d", path}});
|
2021-04-29 07:36:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool DockerDevice::isWritableDirectory(const FilePath &filePath) const
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(handlesFile(filePath), return false);
|
2022-01-11 15:57:21 +01:00
|
|
|
updateContainerAccess();
|
2021-05-31 16:49:25 +02:00
|
|
|
const QString path = filePath.path();
|
2021-07-20 15:23:26 +02:00
|
|
|
return d->runInShell({"test", {"-w", path, "-a", "-d", path}});
|
2021-04-29 07:36:34 +02:00
|
|
|
}
|
|
|
|
|
|
2021-07-14 07:41:23 +02:00
|
|
|
bool DockerDevice::isFile(const FilePath &filePath) const
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(handlesFile(filePath), return false);
|
2022-01-11 15:57:21 +01:00
|
|
|
updateContainerAccess();
|
2021-07-14 07:41:23 +02:00
|
|
|
const QString path = filePath.path();
|
2021-07-20 15:23:26 +02:00
|
|
|
return d->runInShell({"test", {"-f", path}});
|
2021-07-14 07:41:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool DockerDevice::isDirectory(const FilePath &filePath) const
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(handlesFile(filePath), return false);
|
2022-01-11 15:57:21 +01:00
|
|
|
updateContainerAccess();
|
2021-07-14 07:41:23 +02:00
|
|
|
const QString path = filePath.path();
|
2021-07-20 15:23:26 +02:00
|
|
|
return d->runInShell({"test", {"-d", path}});
|
2021-07-14 07:41:23 +02:00
|
|
|
}
|
|
|
|
|
|
2021-04-29 07:36:34 +02:00
|
|
|
bool DockerDevice::createDirectory(const FilePath &filePath) const
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(handlesFile(filePath), return false);
|
2022-01-11 15:57:21 +01:00
|
|
|
updateContainerAccess();
|
2021-05-31 16:49:25 +02:00
|
|
|
const QString path = filePath.path();
|
2021-07-15 10:10:47 +02:00
|
|
|
return d->runInContainer({"mkdir", {"-p", path}});
|
2021-04-29 07:36:34 +02:00
|
|
|
}
|
|
|
|
|
|
2021-06-01 09:40:17 +02:00
|
|
|
bool DockerDevice::exists(const FilePath &filePath) const
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(handlesFile(filePath), return false);
|
2022-01-11 15:57:21 +01:00
|
|
|
updateContainerAccess();
|
2021-06-01 09:40:17 +02:00
|
|
|
const QString path = filePath.path();
|
2021-07-20 15:23:26 +02:00
|
|
|
return d->runInShell({"test", {"-e", path}});
|
2021-06-01 09:40:17 +02:00
|
|
|
}
|
|
|
|
|
|
2021-06-29 09:41:53 +02:00
|
|
|
bool DockerDevice::ensureExistingFile(const FilePath &filePath) const
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(handlesFile(filePath), return false);
|
2022-01-11 15:57:21 +01:00
|
|
|
updateContainerAccess();
|
2021-06-29 09:41:53 +02:00
|
|
|
const QString path = filePath.path();
|
2021-07-20 15:23:26 +02:00
|
|
|
return d->runInShell({"touch", {path}});
|
2021-06-29 09:41:53 +02:00
|
|
|
}
|
|
|
|
|
|
2021-06-14 08:33:44 +02:00
|
|
|
bool DockerDevice::removeFile(const FilePath &filePath) const
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(handlesFile(filePath), return false);
|
2022-01-11 15:57:21 +01:00
|
|
|
updateContainerAccess();
|
2021-07-15 10:10:47 +02:00
|
|
|
return d->runInContainer({"rm", {filePath.path()}});
|
2021-06-14 08:33:44 +02:00
|
|
|
}
|
|
|
|
|
|
2021-06-28 15:26:44 +02:00
|
|
|
bool DockerDevice::removeRecursively(const FilePath &filePath) const
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(handlesFile(filePath), return false);
|
|
|
|
|
QTC_ASSERT(filePath.path().startsWith('/'), return false);
|
2022-01-11 15:57:21 +01:00
|
|
|
updateContainerAccess();
|
2021-07-16 07:42:59 +02:00
|
|
|
|
|
|
|
|
const QString path = filePath.cleanPath().path();
|
|
|
|
|
// We are expecting this only to be called in a context of build directories or similar.
|
|
|
|
|
// Chicken out in some cases that _might_ be user code errors.
|
|
|
|
|
QTC_ASSERT(path.startsWith('/'), return false);
|
2021-07-22 10:33:23 +02:00
|
|
|
const int levelsNeeded = path.startsWith("/home/") ? 4 : 3;
|
2021-07-16 07:42:59 +02:00
|
|
|
QTC_ASSERT(path.count('/') >= levelsNeeded, return false);
|
|
|
|
|
|
|
|
|
|
return d->runInContainer({"rm", {"-rf", "--", path}});
|
2021-06-28 15:26:44 +02:00
|
|
|
}
|
|
|
|
|
|
2021-06-14 08:33:44 +02:00
|
|
|
bool DockerDevice::copyFile(const FilePath &filePath, const FilePath &target) const
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(handlesFile(filePath), return false);
|
|
|
|
|
QTC_ASSERT(handlesFile(target), return false);
|
2022-01-11 15:57:21 +01:00
|
|
|
updateContainerAccess();
|
2021-07-15 10:10:47 +02:00
|
|
|
return d->runInContainer({"cp", {filePath.path(), target.path()}});
|
2021-06-14 08:33:44 +02:00
|
|
|
}
|
|
|
|
|
|
2021-06-28 13:57:10 +02:00
|
|
|
bool DockerDevice::renameFile(const FilePath &filePath, const FilePath &target) const
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(handlesFile(filePath), return false);
|
|
|
|
|
QTC_ASSERT(handlesFile(target), return false);
|
2022-01-11 15:57:21 +01:00
|
|
|
updateContainerAccess();
|
2021-07-15 10:10:47 +02:00
|
|
|
return d->runInContainer({"mv", {filePath.path(), target.path()}});
|
2021-06-28 13:57:10 +02:00
|
|
|
}
|
|
|
|
|
|
2021-06-14 08:33:44 +02:00
|
|
|
QDateTime DockerDevice::lastModified(const FilePath &filePath) const
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(handlesFile(filePath), return {});
|
2022-01-11 15:57:21 +01:00
|
|
|
updateContainerAccess();
|
2022-06-09 17:00:23 +02:00
|
|
|
const QByteArray output = d->outputForRunInShell({"stat", {"-L", "-c", "%Y", filePath.path()}});
|
2021-10-04 09:42:32 +02:00
|
|
|
qint64 secs = output.toLongLong();
|
2021-07-16 07:35:44 +02:00
|
|
|
const QDateTime dt = QDateTime::fromSecsSinceEpoch(secs, Qt::UTC);
|
|
|
|
|
return dt;
|
2021-06-14 08:33:44 +02:00
|
|
|
}
|
|
|
|
|
|
2021-06-29 15:57:41 +02:00
|
|
|
FilePath DockerDevice::symLinkTarget(const FilePath &filePath) const
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(handlesFile(filePath), return {});
|
2022-01-11 15:57:21 +01:00
|
|
|
updateContainerAccess();
|
2022-02-03 18:09:06 +01:00
|
|
|
const QByteArray output = d->outputForRunInShell({"readlink", {"-n", "-e", filePath.path()}});
|
|
|
|
|
const QString out = QString::fromUtf8(output.data(), output.size());
|
|
|
|
|
return out.isEmpty() ? FilePath() : filePath.withNewPath(out);
|
2021-06-07 15:47:06 +02:00
|
|
|
}
|
|
|
|
|
|
2021-07-23 12:06:38 +02:00
|
|
|
qint64 DockerDevice::fileSize(const FilePath &filePath) const
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(handlesFile(filePath), return -1);
|
2022-01-11 15:57:21 +01:00
|
|
|
updateContainerAccess();
|
2022-06-09 17:00:23 +02:00
|
|
|
const QByteArray output = d->outputForRunInShell({"stat", {"-L", "-c", "%s", filePath.path()}});
|
2021-07-23 12:06:38 +02:00
|
|
|
return output.toLongLong();
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-23 17:55:25 +02:00
|
|
|
QFileDevice::Permissions DockerDevice::permissions(const FilePath &filePath) const
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(handlesFile(filePath), return {});
|
2022-01-11 15:57:21 +01:00
|
|
|
updateContainerAccess();
|
2021-07-23 17:55:25 +02:00
|
|
|
|
2022-06-09 17:00:23 +02:00
|
|
|
const QByteArray output = d->outputForRunInShell({"stat", {"-L", "-c", "%a", filePath.path()}});
|
2021-07-23 17:55:25 +02:00
|
|
|
const uint bits = output.toUInt(nullptr, 8);
|
|
|
|
|
QFileDevice::Permissions perm = {};
|
|
|
|
|
#define BIT(n, p) if (bits & (1<<n)) perm |= QFileDevice::p
|
|
|
|
|
BIT(0, ExeOther);
|
|
|
|
|
BIT(1, WriteOther);
|
|
|
|
|
BIT(2, ReadOther);
|
|
|
|
|
BIT(3, ExeGroup);
|
|
|
|
|
BIT(4, WriteGroup);
|
|
|
|
|
BIT(5, ReadGroup);
|
|
|
|
|
BIT(6, ExeUser);
|
|
|
|
|
BIT(7, WriteUser);
|
|
|
|
|
BIT(8, ReadUser);
|
|
|
|
|
#undef BIT
|
|
|
|
|
return perm;
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-12 10:29:18 +02:00
|
|
|
bool DockerDevice::setPermissions(const FilePath &filePath, QFileDevice::Permissions permissions) const
|
|
|
|
|
{
|
2022-03-31 17:24:59 +02:00
|
|
|
Q_UNUSED(permissions)
|
2021-08-12 10:29:18 +02:00
|
|
|
QTC_ASSERT(handlesFile(filePath), return {});
|
2022-01-11 15:57:21 +01:00
|
|
|
updateContainerAccess();
|
2021-08-12 10:29:18 +02:00
|
|
|
QTC_CHECK(false); // FIXME: Implement.
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-14 12:15:40 +01:00
|
|
|
void DockerDevice::iterateWithFind(const FilePath &filePath,
|
|
|
|
|
const std::function<bool(const Utils::FilePath &)> &callBack,
|
2022-01-21 12:22:54 +01:00
|
|
|
const FileFilter &filter) const
|
2021-09-14 14:14:15 +02:00
|
|
|
{
|
2021-12-14 12:15:40 +01:00
|
|
|
QTC_ASSERT(callBack, return);
|
2021-09-14 14:14:15 +02:00
|
|
|
QTC_CHECK(filePath.isAbsolutePath());
|
2022-01-21 12:22:54 +01:00
|
|
|
QStringList arguments{filePath.path()};
|
|
|
|
|
|
|
|
|
|
const QDir::Filters filters = filter.fileFilters;
|
2021-09-14 14:14:15 +02:00
|
|
|
if (filters & QDir::NoSymLinks)
|
|
|
|
|
arguments.prepend("-H");
|
|
|
|
|
else
|
|
|
|
|
arguments.prepend("-L");
|
|
|
|
|
|
2022-05-04 11:10:57 +02:00
|
|
|
arguments.append({"-mindepth", "1"});
|
|
|
|
|
|
2022-01-21 12:22:54 +01:00
|
|
|
if (!filter.iteratorFlags.testFlag(QDirIterator::Subdirectories))
|
|
|
|
|
arguments.append({"-maxdepth", "1"});
|
2021-09-14 14:14:15 +02:00
|
|
|
|
|
|
|
|
QStringList filterOptions;
|
2022-05-04 11:10:57 +02:00
|
|
|
|
|
|
|
|
if (!(filters & QDir::Hidden))
|
|
|
|
|
filterOptions << "!" << "-name" << ".*";
|
|
|
|
|
|
|
|
|
|
QStringList filterFilesAndDirs;
|
2021-09-14 14:14:15 +02:00
|
|
|
if (filters & QDir::Dirs)
|
2022-05-04 11:10:57 +02:00
|
|
|
filterFilesAndDirs << "-type" << "d";
|
2021-09-14 14:14:15 +02:00
|
|
|
if (filters & QDir::Files) {
|
2022-05-04 11:10:57 +02:00
|
|
|
if (!filterFilesAndDirs.isEmpty())
|
|
|
|
|
filterFilesAndDirs << "-o";
|
|
|
|
|
filterFilesAndDirs << "-type" << "f";
|
2021-09-14 14:14:15 +02:00
|
|
|
}
|
2022-05-04 11:10:57 +02:00
|
|
|
if (!filterFilesAndDirs.isEmpty())
|
|
|
|
|
filterOptions << "(" << filterFilesAndDirs << ")";
|
2021-09-14 14:14:15 +02:00
|
|
|
|
2022-05-04 11:10:57 +02:00
|
|
|
QStringList accessOptions;
|
2021-09-14 14:14:15 +02:00
|
|
|
if (filters & QDir::Readable)
|
2022-05-04 11:10:57 +02:00
|
|
|
accessOptions << "-readable";
|
|
|
|
|
if (filters & QDir::Writable) {
|
|
|
|
|
if (!accessOptions.isEmpty())
|
|
|
|
|
accessOptions << "-o";
|
|
|
|
|
accessOptions << "-writable";
|
|
|
|
|
}
|
|
|
|
|
if (filters & QDir::Executable) {
|
|
|
|
|
if (!accessOptions.isEmpty())
|
|
|
|
|
accessOptions << "-o";
|
|
|
|
|
accessOptions << "-executable";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!accessOptions.isEmpty())
|
|
|
|
|
filterOptions << "(" << accessOptions << ")";
|
2021-09-14 14:14:15 +02:00
|
|
|
|
|
|
|
|
QTC_CHECK(filters ^ QDir::AllDirs);
|
|
|
|
|
QTC_CHECK(filters ^ QDir::Drives);
|
|
|
|
|
QTC_CHECK(filters ^ QDir::NoDot);
|
|
|
|
|
QTC_CHECK(filters ^ QDir::NoDotDot);
|
|
|
|
|
QTC_CHECK(filters ^ QDir::Hidden);
|
|
|
|
|
QTC_CHECK(filters ^ QDir::System);
|
|
|
|
|
|
|
|
|
|
const QString nameOption = (filters & QDir::CaseSensitive) ? QString{"-name"}
|
|
|
|
|
: QString{"-iname"};
|
2022-01-21 12:22:54 +01:00
|
|
|
if (!filter.nameFilters.isEmpty()) {
|
2021-09-14 14:14:15 +02:00
|
|
|
const QRegularExpression oneChar("\\[.*?\\]");
|
2022-01-27 10:10:08 +01:00
|
|
|
bool addedFirst = false;
|
|
|
|
|
for (const QString ¤t : filter.nameFilters) {
|
|
|
|
|
if (current.indexOf(oneChar) != -1) {
|
|
|
|
|
LOG("Skipped" << current << "due to presence of [] wildcard");
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (addedFirst)
|
2022-01-24 10:01:37 +01:00
|
|
|
filterOptions << "-o";
|
|
|
|
|
filterOptions << nameOption << current;
|
2022-01-27 10:10:08 +01:00
|
|
|
addedFirst = true;
|
2021-09-14 14:14:15 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
arguments << filterOptions;
|
2022-02-03 18:09:06 +01:00
|
|
|
const QByteArray output = d->outputForRunInShell({"find", arguments});
|
|
|
|
|
const QString out = QString::fromUtf8(output.data(), output.size());
|
|
|
|
|
if (!output.isEmpty() && !out.startsWith(filePath.path())) { // missing find, unknown option
|
|
|
|
|
LOG("Setting 'do not use find'" << out.left(out.indexOf('\n')));
|
2021-09-14 14:14:15 +02:00
|
|
|
d->m_useFind = false;
|
2021-12-14 12:15:40 +01:00
|
|
|
return;
|
2021-09-14 14:14:15 +02:00
|
|
|
}
|
|
|
|
|
|
2022-02-03 18:09:06 +01:00
|
|
|
const QStringList entries = out.split("\n", Qt::SkipEmptyParts);
|
2021-12-14 12:15:40 +01:00
|
|
|
for (const QString &entry : entries) {
|
|
|
|
|
if (entry.startsWith("find: "))
|
|
|
|
|
continue;
|
2022-01-24 10:01:37 +01:00
|
|
|
const FilePath fp = FilePath::fromString(entry);
|
|
|
|
|
|
|
|
|
|
if (!callBack(fp.onDevice(filePath)))
|
2021-12-14 12:15:40 +01:00
|
|
|
break;
|
|
|
|
|
}
|
2021-09-14 14:14:15 +02:00
|
|
|
}
|
|
|
|
|
|
2021-12-14 12:15:40 +01:00
|
|
|
void DockerDevice::iterateDirectory(const FilePath &filePath,
|
|
|
|
|
const std::function<bool(const FilePath &)> &callBack,
|
2022-01-21 12:22:54 +01:00
|
|
|
const FileFilter &filter) const
|
2021-04-29 07:36:34 +02:00
|
|
|
{
|
2021-12-14 12:15:40 +01:00
|
|
|
QTC_ASSERT(handlesFile(filePath), return);
|
2022-01-11 15:57:21 +01:00
|
|
|
updateContainerAccess();
|
2021-06-29 11:10:27 +02:00
|
|
|
|
2021-09-14 14:14:15 +02:00
|
|
|
if (d->m_useFind) {
|
2022-01-21 12:22:54 +01:00
|
|
|
iterateWithFind(filePath, callBack, filter);
|
2021-12-14 12:15:40 +01:00
|
|
|
// d->m_useFind will be set to false if 'find' is not found. In this
|
|
|
|
|
// case fall back to 'ls' below.
|
2021-09-14 14:14:15 +02:00
|
|
|
if (d->m_useFind)
|
2021-12-14 12:15:40 +01:00
|
|
|
return;
|
2021-09-14 14:14:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// if we do not have find - use ls as fallback
|
2022-02-03 18:09:06 +01:00
|
|
|
const QByteArray output = d->outputForRunInShell({"ls", {"-1", "-b", "--", filePath.path()}});
|
2022-01-21 13:11:39 +01:00
|
|
|
const QStringList entries = QString::fromUtf8(output).split('\n', Qt::SkipEmptyParts);
|
|
|
|
|
FileUtils::iterateLsOutput(filePath, entries, filter, callBack);
|
2021-04-29 07:36:34 +02:00
|
|
|
}
|
|
|
|
|
|
2021-07-09 08:30:11 +02:00
|
|
|
QByteArray DockerDevice::fileContents(const FilePath &filePath, qint64 limit, qint64 offset) const
|
2021-04-29 07:36:34 +02:00
|
|
|
{
|
|
|
|
|
QTC_ASSERT(handlesFile(filePath), return {});
|
2022-01-11 15:57:21 +01:00
|
|
|
updateContainerAccess();
|
2021-04-29 07:36:34 +02:00
|
|
|
|
2021-07-15 10:51:39 +02:00
|
|
|
QStringList args = {"if=" + filePath.path(), "status=none"};
|
|
|
|
|
if (limit > 0 || offset > 0) {
|
|
|
|
|
const qint64 gcd = std::gcd(limit, offset);
|
|
|
|
|
args += {QString("bs=%1").arg(gcd),
|
|
|
|
|
QString("count=%1").arg(limit / gcd),
|
|
|
|
|
QString("seek=%1").arg(offset / gcd)};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QtcProcess proc;
|
2022-04-28 16:15:46 +02:00
|
|
|
proc.setCommand(withDockerExecCmd({"dd", args}));
|
|
|
|
|
proc.start();
|
2021-07-15 10:51:39 +02:00
|
|
|
proc.waitForFinished();
|
|
|
|
|
|
|
|
|
|
QByteArray output = proc.readAllStandardOutput();
|
|
|
|
|
return output;
|
2021-04-29 07:36:34 +02:00
|
|
|
}
|
|
|
|
|
|
2021-07-16 08:34:04 +02:00
|
|
|
bool DockerDevice::writeFileContents(const FilePath &filePath, const QByteArray &data) const
|
2021-06-15 18:29:51 +02:00
|
|
|
{
|
|
|
|
|
QTC_ASSERT(handlesFile(filePath), return {});
|
2022-01-11 15:57:21 +01:00
|
|
|
updateContainerAccess();
|
2021-06-15 18:29:51 +02:00
|
|
|
|
2022-05-13 15:13:35 +02:00
|
|
|
QTC_ASSERT(handlesFile(filePath), return {});
|
|
|
|
|
return d->runInShell({"dd", {"of=" + filePath.path()}}, data);
|
2021-06-15 18:29:51 +02:00
|
|
|
}
|
|
|
|
|
|
2021-05-26 17:18:40 +02:00
|
|
|
Environment DockerDevice::systemEnvironment() const
|
|
|
|
|
{
|
2022-06-01 15:19:31 +02:00
|
|
|
if (!d->m_cachedEnviroment.isValid())
|
2021-05-26 17:18:40 +02:00
|
|
|
d->fetchSystemEnviroment();
|
|
|
|
|
|
2022-06-01 15:19:31 +02:00
|
|
|
QTC_CHECK(d->m_cachedEnviroment.isValid());
|
2021-05-26 17:18:40 +02:00
|
|
|
return d->m_cachedEnviroment;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-31 10:10:53 +02:00
|
|
|
void DockerDevice::aboutToBeRemoved() const
|
|
|
|
|
{
|
2021-07-01 17:59:15 +02:00
|
|
|
KitDetector detector(sharedFromThis());
|
2022-02-24 10:14:25 +01:00
|
|
|
detector.undoAutoDetect(id().toString());
|
2021-05-31 10:10:53 +02:00
|
|
|
}
|
|
|
|
|
|
2021-05-26 17:18:40 +02:00
|
|
|
void DockerDevicePrivate::fetchSystemEnviroment()
|
|
|
|
|
{
|
2021-10-04 09:42:32 +02:00
|
|
|
if (m_shell) {
|
2022-02-03 18:09:06 +01:00
|
|
|
const QByteArray output = outputForRunInShell({"env", {}});
|
|
|
|
|
const QString out = QString::fromUtf8(output.data(), output.size());
|
|
|
|
|
m_cachedEnviroment = Environment(out.split('\n', Qt::SkipEmptyParts), q->osType());
|
2021-10-04 09:42:32 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-22 04:33:47 +02:00
|
|
|
QtcProcess proc;
|
2022-04-28 16:15:46 +02:00
|
|
|
proc.setCommand(q->withDockerExecCmd({"env", {}}));
|
|
|
|
|
proc.start();
|
2021-06-21 12:08:10 +02:00
|
|
|
proc.waitForFinished();
|
2022-06-17 14:17:14 +02:00
|
|
|
const QString remoteOutput = proc.cleanedStdOut();
|
2022-04-28 16:15:46 +02:00
|
|
|
|
2021-05-26 17:18:40 +02:00
|
|
|
m_cachedEnviroment = Environment(remoteOutput.split('\n', Qt::SkipEmptyParts), q->osType());
|
2021-08-19 05:56:25 +02:00
|
|
|
|
2022-06-17 14:17:14 +02:00
|
|
|
const QString remoteError = proc.cleanedStdErr();
|
2021-08-19 05:56:25 +02:00
|
|
|
if (!remoteError.isEmpty())
|
|
|
|
|
qWarning("Cannot read container environment: %s\n", qPrintable(remoteError));
|
2021-05-26 17:18:40 +02:00
|
|
|
}
|
|
|
|
|
|
2021-07-15 10:10:47 +02:00
|
|
|
bool DockerDevicePrivate::runInContainer(const CommandLine &cmd) const
|
2021-04-29 07:36:34 +02:00
|
|
|
{
|
2022-07-04 14:24:51 +02:00
|
|
|
if (!m_settings || !DockerApi::isDockerDaemonAvailable(false).value_or(false))
|
2021-07-15 10:10:47 +02:00
|
|
|
return false;
|
2022-07-04 14:24:51 +02:00
|
|
|
|
2022-07-04 11:09:11 +02:00
|
|
|
CommandLine dcmd{m_settings->dockerBinaryPath.filePath(), {"exec", m_container}};
|
2021-08-12 13:04:22 +02:00
|
|
|
dcmd.addCommandLineAsArgs(cmd);
|
2021-04-29 07:36:34 +02:00
|
|
|
|
|
|
|
|
QtcProcess proc;
|
|
|
|
|
proc.setCommand(dcmd);
|
2022-01-18 17:40:19 +01:00
|
|
|
proc.setWorkingDirectory(FilePath::fromString(QDir::tempPath()));
|
2021-04-29 07:36:34 +02:00
|
|
|
proc.start();
|
2021-05-06 15:38:12 +02:00
|
|
|
proc.waitForFinished();
|
2021-04-29 07:36:34 +02:00
|
|
|
|
|
|
|
|
LOG("Run sync:" << dcmd.toUserOutput() << " result: " << proc.exitCode());
|
2021-07-15 10:10:47 +02:00
|
|
|
const int exitCode = proc.exitCode();
|
|
|
|
|
return exitCode == 0;
|
2021-04-29 07:36:34 +02:00
|
|
|
}
|
|
|
|
|
|
2022-05-13 15:13:35 +02:00
|
|
|
bool DockerDevicePrivate::runInShell(const CommandLine &cmd, const QByteArray& stdInData) const
|
2021-07-20 15:23:26 +02:00
|
|
|
{
|
2022-05-05 15:08:46 +02:00
|
|
|
QTC_ASSERT(m_shell, return false);
|
2022-05-13 15:13:35 +02:00
|
|
|
return m_shell->runInShell(cmd, stdInData);
|
2021-07-20 15:23:26 +02:00
|
|
|
}
|
|
|
|
|
|
2022-02-03 18:09:06 +01:00
|
|
|
QByteArray DockerDevicePrivate::outputForRunInShell(const CommandLine &cmd) const
|
2021-07-20 15:23:26 +02:00
|
|
|
{
|
2022-05-05 15:08:46 +02:00
|
|
|
QTC_ASSERT(m_shell.get(), return {});
|
2022-05-13 15:13:35 +02:00
|
|
|
return m_shell->outputForRunInShell(cmd).stdOut;
|
2021-07-20 15:23:26 +02:00
|
|
|
}
|
|
|
|
|
|
2021-03-29 09:11:36 +02:00
|
|
|
// Factory
|
|
|
|
|
|
|
|
|
|
class DockerImageItem final : public TreeItem, public DockerDeviceData
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
DockerImageItem() {}
|
|
|
|
|
|
|
|
|
|
QVariant data(int column, int role) const final
|
|
|
|
|
{
|
|
|
|
|
switch (column) {
|
|
|
|
|
case 0:
|
|
|
|
|
if (role == Qt::DisplayRole)
|
2022-02-08 09:41:46 +01:00
|
|
|
return repo;
|
2021-03-29 09:11:36 +02:00
|
|
|
break;
|
|
|
|
|
case 1:
|
|
|
|
|
if (role == Qt::DisplayRole)
|
2022-02-08 09:41:46 +01:00
|
|
|
return tag;
|
2021-03-29 09:11:36 +02:00
|
|
|
break;
|
|
|
|
|
case 2:
|
|
|
|
|
if (role == Qt::DisplayRole)
|
2022-02-08 09:41:46 +01:00
|
|
|
return imageId;
|
2021-03-29 09:11:36 +02:00
|
|
|
break;
|
|
|
|
|
case 3:
|
|
|
|
|
if (role == Qt::DisplayRole)
|
|
|
|
|
return size;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return QVariant();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class DockerDeviceSetupWizard final : public QDialog
|
|
|
|
|
{
|
|
|
|
|
public:
|
2022-07-04 14:24:51 +02:00
|
|
|
DockerDeviceSetupWizard(DockerSettings *settings)
|
2021-03-29 09:11:36 +02:00
|
|
|
: QDialog(ICore::dialogParent())
|
2022-07-04 11:09:11 +02:00
|
|
|
, m_settings(settings)
|
2021-03-29 09:11:36 +02:00
|
|
|
{
|
2022-07-13 10:43:31 +02:00
|
|
|
setWindowTitle(Tr::tr("Docker Image Selection"));
|
2021-04-29 07:36:34 +02:00
|
|
|
resize(800, 600);
|
2021-03-29 09:11:36 +02:00
|
|
|
|
2022-02-08 09:41:46 +01:00
|
|
|
m_model.setHeader({"Repository", "Tag", "Image", "Size"});
|
2021-03-29 09:11:36 +02:00
|
|
|
|
|
|
|
|
m_view = new TreeView;
|
2021-04-29 07:36:34 +02:00
|
|
|
m_view->setModel(&m_model);
|
|
|
|
|
m_view->header()->setStretchLastSection(true);
|
|
|
|
|
m_view->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
2021-03-29 09:11:36 +02:00
|
|
|
m_view->setSelectionBehavior(QAbstractItemView::SelectRows);
|
|
|
|
|
m_view->setSelectionMode(QAbstractItemView::SingleSelection);
|
|
|
|
|
|
2021-05-31 10:10:53 +02:00
|
|
|
m_log = new QTextBrowser;
|
2022-07-07 08:25:03 +02:00
|
|
|
m_log->setVisible(dockerDeviceLog().isDebugEnabled());
|
2021-03-29 09:11:36 +02:00
|
|
|
|
2022-03-14 14:02:22 +01:00
|
|
|
const QString fail = QString{"Docker: "}
|
2022-07-05 15:37:08 +02:00
|
|
|
+ QCoreApplication::translate("Debugger", "Process failed to start.");
|
2022-03-14 14:02:22 +01:00
|
|
|
auto errorLabel = new Utils::InfoLabel(fail, Utils::InfoLabel::Error, this);
|
|
|
|
|
errorLabel->setVisible(false);
|
|
|
|
|
|
2021-05-31 10:10:53 +02:00
|
|
|
m_buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
2021-03-29 09:11:36 +02:00
|
|
|
|
|
|
|
|
using namespace Layouting;
|
|
|
|
|
Column {
|
|
|
|
|
m_view,
|
2021-05-31 10:10:53 +02:00
|
|
|
m_log,
|
2022-03-14 14:02:22 +01:00
|
|
|
errorLabel,
|
2021-05-31 10:10:53 +02:00
|
|
|
m_buttons,
|
2021-03-29 09:11:36 +02:00
|
|
|
}.attachTo(this);
|
|
|
|
|
|
2021-05-31 10:10:53 +02:00
|
|
|
connect(m_buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
|
|
|
|
connect(m_buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
|
|
|
|
m_buttons->button(QDialogButtonBox::Ok)->setEnabled(false);
|
2021-03-29 09:11:36 +02:00
|
|
|
|
2022-07-04 11:09:11 +02:00
|
|
|
CommandLine cmd{m_settings->dockerBinaryPath.filePath(), {"images", "--format", "{{.ID}}\\t{{.Repository}}\\t{{.Tag}}\\t{{.Size}}"}};
|
2022-07-13 10:43:31 +02:00
|
|
|
m_log->append(Tr::tr("Running \"%1\"\n").arg(cmd.toUserOutput()));
|
2021-03-29 09:11:36 +02:00
|
|
|
|
|
|
|
|
m_process = new QtcProcess(this);
|
|
|
|
|
m_process->setCommand(cmd);
|
|
|
|
|
|
2021-05-31 10:10:53 +02:00
|
|
|
connect(m_process, &QtcProcess::readyReadStandardOutput, [this] {
|
2021-03-29 09:11:36 +02:00
|
|
|
const QString out = QString::fromUtf8(m_process->readAllStandardOutput().trimmed());
|
2021-05-31 10:10:53 +02:00
|
|
|
m_log->append(out);
|
2021-03-29 09:11:36 +02:00
|
|
|
for (const QString &line : out.split('\n')) {
|
|
|
|
|
const QStringList parts = line.trimmed().split('\t');
|
|
|
|
|
if (parts.size() != 4) {
|
2022-07-13 10:43:31 +02:00
|
|
|
m_log->append(Tr::tr("Unexpected result: %1").arg(line) + '\n');
|
2021-03-29 09:11:36 +02:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
auto item = new DockerImageItem;
|
|
|
|
|
item->imageId = parts.at(0);
|
|
|
|
|
item->repo = parts.at(1);
|
|
|
|
|
item->tag = parts.at(2);
|
|
|
|
|
item->size = parts.at(3);
|
|
|
|
|
m_model.rootItem()->appendChild(item);
|
|
|
|
|
}
|
2022-07-13 10:43:31 +02:00
|
|
|
m_log->append(Tr::tr("Done."));
|
2021-03-29 09:11:36 +02:00
|
|
|
});
|
|
|
|
|
|
2021-05-31 10:10:53 +02:00
|
|
|
connect(m_process, &Utils::QtcProcess::readyReadStandardError, this, [this] {
|
2022-07-13 10:43:31 +02:00
|
|
|
const QString out = Tr::tr("Error: %1").arg(m_process->cleanedStdErr());
|
|
|
|
|
m_log->append(Tr::tr("Error: %1").arg(out));
|
2021-03-29 09:11:36 +02:00
|
|
|
});
|
|
|
|
|
|
2022-04-04 17:03:16 +02:00
|
|
|
connect(m_process, &QtcProcess::done, errorLabel, [errorLabel, this] {
|
|
|
|
|
errorLabel->setVisible(m_process->result() != ProcessResult::FinishedWithSuccess);
|
2022-03-14 14:02:22 +01:00
|
|
|
});
|
|
|
|
|
|
2021-05-31 10:10:53 +02:00
|
|
|
connect(m_view->selectionModel(), &QItemSelectionModel::selectionChanged, [this] {
|
2021-03-29 09:11:36 +02:00
|
|
|
const QModelIndexList selectedRows = m_view->selectionModel()->selectedRows();
|
|
|
|
|
QTC_ASSERT(selectedRows.size() == 1, return);
|
2021-05-31 10:10:53 +02:00
|
|
|
m_buttons->button(QDialogButtonBox::Ok)->setEnabled(selectedRows.size() == 1);
|
2021-03-29 09:11:36 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
m_process->start();
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-26 09:05:35 +01:00
|
|
|
IDevice::Ptr device() const
|
2021-03-29 09:11:36 +02:00
|
|
|
{
|
|
|
|
|
const QModelIndexList selectedRows = m_view->selectionModel()->selectedRows();
|
|
|
|
|
QTC_ASSERT(selectedRows.size() == 1, return {});
|
|
|
|
|
DockerImageItem *item = m_model.itemForIndex(selectedRows.front());
|
|
|
|
|
QTC_ASSERT(item, return {});
|
|
|
|
|
|
2022-07-04 11:09:11 +02:00
|
|
|
auto device = DockerDevice::create(m_settings, *item);
|
2022-02-24 10:14:25 +01:00
|
|
|
device->setupId(IDevice::ManuallyAdded);
|
2021-03-29 09:11:36 +02:00
|
|
|
device->setType(Constants::DOCKER_DEVICE_TYPE);
|
|
|
|
|
device->setMachineType(IDevice::Hardware);
|
2021-05-17 13:49:43 +02:00
|
|
|
|
2021-03-29 09:11:36 +02:00
|
|
|
return device;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
TreeModel<DockerImageItem> m_model;
|
|
|
|
|
TreeView *m_view = nullptr;
|
2021-05-31 10:10:53 +02:00
|
|
|
QTextBrowser *m_log = nullptr;
|
|
|
|
|
QDialogButtonBox *m_buttons;
|
2022-07-04 14:24:51 +02:00
|
|
|
DockerSettings *m_settings;
|
2021-05-31 10:10:53 +02:00
|
|
|
|
2021-03-29 09:11:36 +02:00
|
|
|
QtcProcess *m_process = nullptr;
|
|
|
|
|
QString m_selectedId;
|
|
|
|
|
};
|
|
|
|
|
|
2022-01-26 09:05:35 +01:00
|
|
|
// Factory
|
|
|
|
|
|
2022-07-04 14:24:51 +02:00
|
|
|
DockerDeviceFactory::DockerDeviceFactory(DockerSettings *settings)
|
2022-01-26 09:05:35 +01:00
|
|
|
: IDeviceFactory(Constants::DOCKER_DEVICE_TYPE)
|
|
|
|
|
{
|
2022-07-13 10:43:31 +02:00
|
|
|
setDisplayName(Tr::tr("Docker Device"));
|
2022-01-26 09:05:35 +01:00
|
|
|
setIcon(QIcon());
|
2022-07-04 11:09:11 +02:00
|
|
|
setCreator([settings] {
|
|
|
|
|
DockerDeviceSetupWizard wizard(settings);
|
2022-01-26 09:05:35 +01:00
|
|
|
if (wizard.exec() != QDialog::Accepted)
|
|
|
|
|
return IDevice::Ptr();
|
|
|
|
|
return wizard.device();
|
|
|
|
|
});
|
2022-07-04 14:24:51 +02:00
|
|
|
setConstructionFunction([settings, this] {
|
|
|
|
|
auto device = DockerDevice::create(settings, {});
|
|
|
|
|
QMutexLocker lk(&m_deviceListMutex);
|
|
|
|
|
m_existingDevices.push_back(device);
|
|
|
|
|
return device;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void DockerDeviceFactory::shutdownExistingDevices()
|
|
|
|
|
{
|
|
|
|
|
QMutexLocker lk(&m_deviceListMutex);
|
|
|
|
|
for (const auto &weakDevice : m_existingDevices) {
|
|
|
|
|
if (QSharedPointer<DockerDevice> device = weakDevice.lock())
|
|
|
|
|
device->shutdown();
|
|
|
|
|
}
|
2022-01-26 09:05:35 +01:00
|
|
|
}
|
|
|
|
|
|
2022-07-13 10:43:31 +02:00
|
|
|
} // Docker::Internal
|