forked from qt-creator/qt-creator
Docker: Add DockerDevice and a simple run config
The "Docker Container" runconfig executes 'docker run <image>' without further attributes, and "works" with docker's hello-world example image. Change-Id: Ib9417d238ac0757db16be1fc21af40c81db02f05 Reviewed-by: Alessandro Portale <alessandro.portale@qt.io>
This commit is contained in:
@@ -309,6 +309,11 @@ void BaseAspect::setEnabled(bool enabled)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool BaseAspect::isReadOnly() const
|
||||||
|
{
|
||||||
|
return d->m_readOnly;
|
||||||
|
}
|
||||||
|
|
||||||
void BaseAspect::setReadOnly(bool readOnly)
|
void BaseAspect::setReadOnly(bool readOnly)
|
||||||
{
|
{
|
||||||
d->m_readOnly = readOnly;
|
d->m_readOnly = readOnly;
|
||||||
@@ -649,7 +654,7 @@ public:
|
|||||||
if (m_uncheckedSemantics == StringAspect::UncheckedSemantics::Disabled)
|
if (m_uncheckedSemantics == StringAspect::UncheckedSemantics::Disabled)
|
||||||
w->setEnabled(enabled && aspect->isEnabled());
|
w->setEnabled(enabled && aspect->isEnabled());
|
||||||
else
|
else
|
||||||
w->setReadOnly(!enabled);
|
w->setReadOnly(!enabled || aspect->isReadOnly());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -96,6 +96,7 @@ public:
|
|||||||
bool isEnabled() const;
|
bool isEnabled() const;
|
||||||
void setEnabled(bool enabled);
|
void setEnabled(bool enabled);
|
||||||
|
|
||||||
|
bool isReadOnly() const;
|
||||||
void setReadOnly(bool enabled);
|
void setReadOnly(bool enabled);
|
||||||
|
|
||||||
void setSpan(int x, int y = 1);
|
void setSpan(int x, int y = 1);
|
||||||
|
@@ -1,10 +1,14 @@
|
|||||||
add_qtc_plugin(Docker
|
add_qtc_plugin(Docker
|
||||||
PLUGIN_DEPENDS Core
|
PLUGIN_DEPENDS Core ProjectExplorer
|
||||||
SOURCES
|
SOURCES
|
||||||
dockerplugin.cpp
|
|
||||||
dockerplugin.h
|
|
||||||
dockersettings.cpp
|
|
||||||
dockersettings.h
|
|
||||||
docker_global.h
|
docker_global.h
|
||||||
dockerconstants.h
|
dockerconstants.h
|
||||||
|
dockerdevice.cpp
|
||||||
|
dockerdevice.h
|
||||||
|
dockerplugin.cpp
|
||||||
|
dockerplugin.h
|
||||||
|
dockerrunconfiguration.cpp
|
||||||
|
dockerrunconfiguration.h
|
||||||
|
dockersettings.cpp
|
||||||
|
dockersettings.h
|
||||||
)
|
)
|
||||||
|
@@ -3,10 +3,15 @@ include(../../qtcreatorplugin.pri)
|
|||||||
DEFINES += QT_RESTRICTED_CAST_FROM_ASCII
|
DEFINES += QT_RESTRICTED_CAST_FROM_ASCII
|
||||||
|
|
||||||
SOURCES += \
|
SOURCES += \
|
||||||
|
dockerdevice.cpp \
|
||||||
dockerplugin.cpp \
|
dockerplugin.cpp \
|
||||||
|
dockerrunconfiguration.cpp \
|
||||||
dockersettings.cpp
|
dockersettings.cpp
|
||||||
|
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
dockerconstants.h \
|
|
||||||
dockerplugin.h \
|
|
||||||
docker_global.h \
|
docker_global.h \
|
||||||
|
dockerconstants.h \
|
||||||
|
dockerdevice.h \
|
||||||
|
dockerplugin.h \
|
||||||
|
dockerrunconfiguration.h \
|
||||||
dockersettings.h
|
dockersettings.h
|
||||||
|
@@ -12,8 +12,12 @@ QtcPlugin {
|
|||||||
files: [
|
files: [
|
||||||
"docker_global.h",
|
"docker_global.h",
|
||||||
"dockerconstants.h",
|
"dockerconstants.h",
|
||||||
|
"dockerdevice.h",
|
||||||
|
"dockerdevice.cpp",
|
||||||
"dockerplugin.h",
|
"dockerplugin.h",
|
||||||
"dockerplugin.cpp",
|
"dockerplugin.cpp",
|
||||||
|
"dockerrunconfiguration.h",
|
||||||
|
"dockerrunconfiguration.cpp",
|
||||||
"dockersettings.h",
|
"dockersettings.h",
|
||||||
"dockersettings.cpp"
|
"dockersettings.cpp"
|
||||||
]
|
]
|
||||||
|
@@ -3,4 +3,5 @@ QTC_LIB_DEPENDS += \
|
|||||||
utils
|
utils
|
||||||
QTC_PLUGIN_DEPENDS += \
|
QTC_PLUGIN_DEPENDS += \
|
||||||
coreplugin \
|
coreplugin \
|
||||||
projectexplorer
|
projectexplorer \
|
||||||
|
qtsupport
|
||||||
|
@@ -33,6 +33,9 @@ const char DOCKER_SETTINGS_ID[] = "Docker.Settings";
|
|||||||
const char ACTION_ID[] = "Docker.Action";
|
const char ACTION_ID[] = "Docker.Action";
|
||||||
const char MENU_ID[] = "Docker.Menu";
|
const char MENU_ID[] = "Docker.Menu";
|
||||||
|
|
||||||
|
const char DOCKER_DEVICE_TYPE[] = "DockerDeviceType";
|
||||||
|
const char DOCKER_RUN_FLAGS[] = "DockerRunFlags";
|
||||||
|
|
||||||
} // namespace Constants
|
} // namespace Constants
|
||||||
} // namespace Docker
|
} // namespace Docker
|
||||||
|
|
||||||
|
526
src/plugins/docker/dockerdevice.cpp
Normal file
526
src/plugins/docker/dockerdevice.cpp
Normal file
@@ -0,0 +1,526 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** 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"
|
||||||
|
|
||||||
|
#include "dockerconstants.h"
|
||||||
|
|
||||||
|
#include <coreplugin/icore.h>
|
||||||
|
#include <coreplugin/messagemanager.h>
|
||||||
|
|
||||||
|
#include <projectexplorer/devicesupport/idevicewidget.h>
|
||||||
|
#include <projectexplorer/runcontrol.h>
|
||||||
|
|
||||||
|
#include <utils/algorithm.h>
|
||||||
|
#include <utils/basetreeview.h>
|
||||||
|
#include <utils/consoleprocess.h>
|
||||||
|
#include <utils/environment.h>
|
||||||
|
#include <utils/hostosinfo.h>
|
||||||
|
#include <utils/layoutbuilder.h>
|
||||||
|
#include <utils/port.h>
|
||||||
|
#include <utils/qtcassert.h>
|
||||||
|
#include <utils/qtcprocess.h>
|
||||||
|
#include <utils/stringutils.h>
|
||||||
|
#include <utils/treemodel.h>
|
||||||
|
|
||||||
|
#include <QDialog>
|
||||||
|
#include <QDialogButtonBox>
|
||||||
|
#include <QHeaderView>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QTextBrowser>
|
||||||
|
|
||||||
|
using namespace Core;
|
||||||
|
using namespace ProjectExplorer;
|
||||||
|
using namespace Utils;
|
||||||
|
|
||||||
|
namespace Docker {
|
||||||
|
namespace Internal {
|
||||||
|
|
||||||
|
const QByteArray pidMarker = "__qtc";
|
||||||
|
|
||||||
|
class DockerDeviceProcess : public ProjectExplorer::DeviceProcess
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
DockerDeviceProcess(const QSharedPointer<const IDevice> &device, QObject *parent = nullptr);
|
||||||
|
~DockerDeviceProcess() {}
|
||||||
|
|
||||||
|
void start(const Runnable &runnable) override;
|
||||||
|
|
||||||
|
void interrupt() override;
|
||||||
|
void terminate() override { m_process.terminate(); }
|
||||||
|
void kill() override;
|
||||||
|
|
||||||
|
QProcess::ProcessState state() const override;
|
||||||
|
QProcess::ExitStatus exitStatus() const override;
|
||||||
|
int exitCode() const override;
|
||||||
|
QString errorString() const override;
|
||||||
|
|
||||||
|
QByteArray readAllStandardOutput() override;
|
||||||
|
QByteArray readAllStandardError() override;
|
||||||
|
|
||||||
|
qint64 write(const QByteArray &data) override { return m_process.write(data); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
QProcess m_process;
|
||||||
|
ConsoleProcess m_consoleProcess;
|
||||||
|
};
|
||||||
|
|
||||||
|
DockerDeviceProcess::DockerDeviceProcess(const QSharedPointer<const IDevice> &device,
|
||||||
|
QObject *parent)
|
||||||
|
: DeviceProcess(device, parent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void DockerDeviceProcess::start(const Runnable &runnable)
|
||||||
|
{
|
||||||
|
QTC_ASSERT(m_process.state() == QProcess::NotRunning, return);
|
||||||
|
DockerDevice::ConstPtr dockerDevice = qSharedPointerCast<const DockerDevice>(device());
|
||||||
|
QTC_ASSERT(dockerDevice, return);
|
||||||
|
|
||||||
|
const QStringList dockerRunFlags = runnable.extraData[Constants::DOCKER_RUN_FLAGS].toStringList();
|
||||||
|
|
||||||
|
const DockerDeviceData &data = dockerDevice->data();
|
||||||
|
CommandLine cmd("docker");
|
||||||
|
cmd.addArg("run");
|
||||||
|
cmd.addArgs(dockerRunFlags);
|
||||||
|
cmd.addArg(data.imageId);
|
||||||
|
if (!runnable.executable.isEmpty())
|
||||||
|
cmd.addArgs(runnable.commandLine());
|
||||||
|
|
||||||
|
disconnect(&m_consoleProcess);
|
||||||
|
disconnect(&m_process);
|
||||||
|
|
||||||
|
if (runInTerminal()) {
|
||||||
|
connect(&m_consoleProcess, &ConsoleProcess::errorOccurred,
|
||||||
|
this, &DeviceProcess::error);
|
||||||
|
connect(&m_consoleProcess, &ConsoleProcess::processStarted,
|
||||||
|
this, &DeviceProcess::started);
|
||||||
|
connect(&m_consoleProcess, &ConsoleProcess::stubStopped,
|
||||||
|
this, &DeviceProcess::finished);
|
||||||
|
|
||||||
|
m_consoleProcess.setAbortOnMetaChars(false);
|
||||||
|
m_consoleProcess.setSettings(Core::ICore::settings());
|
||||||
|
m_consoleProcess.setEnvironment(runnable.environment);
|
||||||
|
m_consoleProcess.setCommand(cmd);
|
||||||
|
m_consoleProcess.start();
|
||||||
|
} else {
|
||||||
|
m_process.setProcessEnvironment(runnable.environment.toProcessEnvironment());
|
||||||
|
connect(&m_process, &QProcess::errorOccurred, this, &DeviceProcess::error);
|
||||||
|
connect(&m_process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
|
||||||
|
this, &DeviceProcess::finished);
|
||||||
|
connect(&m_process, &QProcess::readyReadStandardOutput,
|
||||||
|
this, &DeviceProcess::readyReadStandardOutput);
|
||||||
|
connect(&m_process, &QProcess::readyReadStandardError,
|
||||||
|
this, &DeviceProcess::readyReadStandardError);
|
||||||
|
connect(&m_process, &QProcess::started, this, &DeviceProcess::started);
|
||||||
|
m_process.setWorkingDirectory(runnable.workingDirectory);
|
||||||
|
m_process.start(cmd.executable().toString(), cmd.splitArguments());
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(this, &DeviceProcess::readyReadStandardOutput, this, [this] {
|
||||||
|
MessageManager::writeSilently(QString::fromLocal8Bit(readAllStandardError()));
|
||||||
|
});
|
||||||
|
connect(this, &DeviceProcess::readyReadStandardError, this, [this] {
|
||||||
|
MessageManager::writeDisrupting(QString::fromLocal8Bit(readAllStandardError()));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void DockerDeviceProcess::interrupt()
|
||||||
|
{
|
||||||
|
device()->signalOperation()->interruptProcess(m_process.processId());
|
||||||
|
}
|
||||||
|
|
||||||
|
void DockerDeviceProcess::kill()
|
||||||
|
{
|
||||||
|
m_process.kill();
|
||||||
|
}
|
||||||
|
|
||||||
|
QProcess::ProcessState DockerDeviceProcess::state() const
|
||||||
|
{
|
||||||
|
return m_process.state();
|
||||||
|
}
|
||||||
|
|
||||||
|
QProcess::ExitStatus DockerDeviceProcess::exitStatus() const
|
||||||
|
{
|
||||||
|
return m_process.exitStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
int DockerDeviceProcess::exitCode() const
|
||||||
|
{
|
||||||
|
return m_process.exitCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString DockerDeviceProcess::errorString() const
|
||||||
|
{
|
||||||
|
return m_process.errorString();
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray DockerDeviceProcess::readAllStandardOutput()
|
||||||
|
{
|
||||||
|
return m_process.readAllStandardOutput();
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray DockerDeviceProcess::readAllStandardError()
|
||||||
|
{
|
||||||
|
return m_process.readAllStandardError();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class DockerPortsGatheringMethod : public PortsGatheringMethod
|
||||||
|
{
|
||||||
|
Runnable runnable(QAbstractSocket::NetworkLayerProtocol protocol) const override
|
||||||
|
{
|
||||||
|
// 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
|
||||||
|
Runnable runnable;
|
||||||
|
runnable.executable = FilePath::fromString("sed");
|
||||||
|
runnable.commandLineArguments
|
||||||
|
= "-e 's/.*: [[:xdigit:]]*:\\([[:xdigit:]]\\{4\\}\\).*/\\1/g' /proc/net/tcp*";
|
||||||
|
return runnable;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<Utils::Port> usedPorts(const QByteArray &output) const override
|
||||||
|
{
|
||||||
|
QList<Utils::Port> ports;
|
||||||
|
QList<QByteArray> portStrings = output.split('\n');
|
||||||
|
foreach (const QByteArray &portString, portStrings) {
|
||||||
|
if (portString.size() != 4)
|
||||||
|
continue;
|
||||||
|
bool ok;
|
||||||
|
const Utils::Port port(portString.toInt(&ok, 16));
|
||||||
|
if (ok) {
|
||||||
|
if (!ports.contains(port))
|
||||||
|
ports << port;
|
||||||
|
} else {
|
||||||
|
qWarning("%s: Unexpected string '%s' is not a port.",
|
||||||
|
Q_FUNC_INFO, portString.data());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ports;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class DockerDeviceWidget final : public IDeviceWidget
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit DockerDeviceWidget(const IDevice::Ptr &device)
|
||||||
|
: IDeviceWidget(device)
|
||||||
|
{
|
||||||
|
auto dockerDevice = device.dynamicCast<DockerDevice>();
|
||||||
|
QTC_ASSERT(dockerDevice, return);
|
||||||
|
|
||||||
|
m_idLabel = new QLabel(tr("Image Id:"));
|
||||||
|
m_idLineEdit = new QLineEdit;
|
||||||
|
m_idLineEdit->setText(dockerDevice->data().imageId);
|
||||||
|
m_idLineEdit->setEnabled(false);
|
||||||
|
|
||||||
|
m_repoLabel = new QLabel(tr("Repository:"));
|
||||||
|
m_repoLineEdit = new QLineEdit;
|
||||||
|
m_repoLineEdit->setText(dockerDevice->data().repo);
|
||||||
|
m_repoLineEdit->setEnabled(false);
|
||||||
|
|
||||||
|
using namespace Layouting;
|
||||||
|
|
||||||
|
Form {
|
||||||
|
m_idLabel, m_idLineEdit, Break(),
|
||||||
|
m_repoLabel, m_repoLineEdit, Break(),
|
||||||
|
}.attachTo(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateDeviceFromUi() final {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
QLabel *m_idLabel;
|
||||||
|
QLineEdit *m_idLineEdit;
|
||||||
|
QLabel *m_repoLabel;
|
||||||
|
QLineEdit *m_repoLineEdit;
|
||||||
|
};
|
||||||
|
|
||||||
|
IDeviceWidget *DockerDevice::createWidget()
|
||||||
|
{
|
||||||
|
return new DockerDeviceWidget(sharedFromThis());
|
||||||
|
}
|
||||||
|
|
||||||
|
DockerDevice::DockerDevice(const DockerDeviceData &data)
|
||||||
|
: m_data(data)
|
||||||
|
{
|
||||||
|
setDisplayType(tr("Docker"));
|
||||||
|
setOsType(OsTypeOtherUnix);
|
||||||
|
setDefaultDisplayName(tr("Docker Image"));;
|
||||||
|
setDisplayName(tr("Docker Image \"%1\" (%2)").arg(data.repo).arg(data.imageId));
|
||||||
|
setAllowEmptyCommand(true);
|
||||||
|
|
||||||
|
setOpenTerminal([this](const Environment &env, const QString &workingDir) {
|
||||||
|
DeviceProcess * const proc = createProcess(nullptr);
|
||||||
|
QObject::connect(proc, &DeviceProcess::finished, [proc] {
|
||||||
|
if (!proc->errorString().isEmpty()) {
|
||||||
|
MessageManager::writeDisrupting(
|
||||||
|
tr("Error running remote shell: %1").arg(proc->errorString()));
|
||||||
|
}
|
||||||
|
proc->deleteLater();
|
||||||
|
});
|
||||||
|
QObject::connect(proc, &DeviceProcess::error, [proc] {
|
||||||
|
MessageManager::writeDisrupting(tr("Error starting remote shell."));
|
||||||
|
proc->deleteLater();
|
||||||
|
});
|
||||||
|
|
||||||
|
Runnable runnable;
|
||||||
|
runnable.executable = FilePath::fromString("/bin/sh");
|
||||||
|
runnable.device = sharedFromThis();
|
||||||
|
runnable.environment = env;
|
||||||
|
runnable.workingDirectory = workingDir;
|
||||||
|
runnable.extraData[Constants::DOCKER_RUN_FLAGS] = QStringList({"--interactive", "--tty"});
|
||||||
|
|
||||||
|
proc->setRunInTerminal(true);
|
||||||
|
proc->start(runnable);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (HostOsInfo::isAnyUnixHost()) {
|
||||||
|
addDeviceAction({tr("Open Shell in Container"), [](const IDevice::Ptr &device, QWidget *) {
|
||||||
|
device->openTerminal(Environment(), QString());
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const DockerDeviceData &DockerDevice::data() const
|
||||||
|
{
|
||||||
|
return m_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char DockerDeviceDataImageIdKey[] = "DockerDeviceDataImageId";
|
||||||
|
const char DockerDeviceDataRepoKey[] = "DockerDeviceDataRepo";
|
||||||
|
const char DockerDeviceDataTagKey[] = "DockerDeviceDataTag";
|
||||||
|
const char DockerDeviceDataSizeKey[] = "DockerDeviceDataSize";
|
||||||
|
|
||||||
|
void DockerDevice::fromMap(const QVariantMap &map)
|
||||||
|
{
|
||||||
|
ProjectExplorer::IDevice::fromMap(map);
|
||||||
|
m_data.imageId = map.value(DockerDeviceDataImageIdKey).toString();
|
||||||
|
m_data.repo = map.value(DockerDeviceDataRepoKey).toString();
|
||||||
|
m_data.tag = map.value(DockerDeviceDataTagKey).toString();
|
||||||
|
m_data.size = map.value(DockerDeviceDataSizeKey).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariantMap DockerDevice::toMap() const
|
||||||
|
{
|
||||||
|
QVariantMap map = ProjectExplorer::IDevice::toMap();
|
||||||
|
map.insert(DockerDeviceDataImageIdKey, m_data.imageId);
|
||||||
|
map.insert(DockerDeviceDataRepoKey, m_data.repo);
|
||||||
|
map.insert(DockerDeviceDataTagKey, m_data.tag);
|
||||||
|
map.insert(DockerDeviceDataSizeKey, m_data.size);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
DockerDevice::~DockerDevice() = default;
|
||||||
|
|
||||||
|
DeviceProcess *DockerDevice::createProcess(QObject *parent) const
|
||||||
|
{
|
||||||
|
return new DockerDeviceProcess(sharedFromThis(), parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DockerDevice::canAutoDetectPorts() const
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
PortsGatheringMethod::Ptr DockerDevice::portsGatheringMethod() const
|
||||||
|
{
|
||||||
|
return DockerPortsGatheringMethod::Ptr(new DockerPortsGatheringMethod);
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Factory
|
||||||
|
|
||||||
|
DockerDeviceFactory::DockerDeviceFactory()
|
||||||
|
: IDeviceFactory(Constants::DOCKER_DEVICE_TYPE)
|
||||||
|
{
|
||||||
|
setDisplayName(DockerDevice::tr("Docker Device"));
|
||||||
|
setIcon(QIcon());
|
||||||
|
setCanCreate(true);
|
||||||
|
setConstructionFunction([] { return DockerDevice::create({}); });
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
return imageId;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
if (role == Qt::DisplayRole)
|
||||||
|
return repo;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
if (role == Qt::DisplayRole)
|
||||||
|
return tag;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
if (role == Qt::DisplayRole)
|
||||||
|
return size;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class DockerDeviceSetupWizard final : public QDialog
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
DockerDeviceSetupWizard()
|
||||||
|
: QDialog(ICore::dialogParent())
|
||||||
|
{
|
||||||
|
setWindowTitle(tr("Docker Image Selection"));
|
||||||
|
|
||||||
|
m_model.setHeader({"Image", "Repository", "Tag", "Size"});
|
||||||
|
|
||||||
|
m_view = new TreeView;
|
||||||
|
m_view->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||||
|
m_view->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||||
|
m_view->setModel(&m_model);
|
||||||
|
|
||||||
|
auto output = new QTextBrowser;
|
||||||
|
output->setEnabled(false);
|
||||||
|
output->setVisible(false);
|
||||||
|
|
||||||
|
auto buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel,
|
||||||
|
Qt::Horizontal);
|
||||||
|
|
||||||
|
using namespace Layouting;
|
||||||
|
Column {
|
||||||
|
m_view,
|
||||||
|
output,
|
||||||
|
buttons,
|
||||||
|
}.attachTo(this);
|
||||||
|
|
||||||
|
connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||||
|
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||||
|
buttons->button(QDialogButtonBox::Ok)->setEnabled(false);
|
||||||
|
|
||||||
|
CommandLine cmd{"docker", {"images", "--format", "{{.ID}}\\t{{.Repository}}\\t{{.Tag}}\\t{{.Size}}"}};
|
||||||
|
output->append(tr("Running \"%1\"\n").arg(cmd.toUserOutput()));
|
||||||
|
|
||||||
|
m_process = new QtcProcess(this);
|
||||||
|
m_process->setCommand(cmd);
|
||||||
|
|
||||||
|
connect(m_process, &QtcProcess::readyReadStandardOutput, [this, output] {
|
||||||
|
const QString out = QString::fromUtf8(m_process->readAllStandardOutput().trimmed());
|
||||||
|
output->append(out);
|
||||||
|
for (const QString &line : out.split('\n')) {
|
||||||
|
const QStringList parts = line.trimmed().split('\t');
|
||||||
|
if (parts.size() != 4) {
|
||||||
|
output->append(tr("Unexpected result: %1").arg(line) + '\n');
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
output->append(tr("\nDone."));
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(m_process, &Utils::QtcProcess::readyReadStandardError, [this, output] {
|
||||||
|
const QString out = tr("Error: %1").arg(QString::fromUtf8(m_process->readAllStandardError()));
|
||||||
|
output->append(tr("Error: %1").arg(out));
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(m_view->selectionModel(), &QItemSelectionModel::selectionChanged, [this, buttons] {
|
||||||
|
const QModelIndexList selectedRows = m_view->selectionModel()->selectedRows();
|
||||||
|
QTC_ASSERT(selectedRows.size() == 1, return);
|
||||||
|
buttons->button(QDialogButtonBox::Ok)->setEnabled(selectedRows.size() == 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
m_process->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
DockerDevice::Ptr device() const
|
||||||
|
{
|
||||||
|
const QModelIndexList selectedRows = m_view->selectionModel()->selectedRows();
|
||||||
|
QTC_ASSERT(selectedRows.size() == 1, return {});
|
||||||
|
DockerImageItem *item = m_model.itemForIndex(selectedRows.front());
|
||||||
|
QTC_ASSERT(item, return {});
|
||||||
|
|
||||||
|
auto device = DockerDevice::create(*item);
|
||||||
|
device->setupId(IDevice::ManuallyAdded, Utils::Id());
|
||||||
|
device->setType(Constants::DOCKER_DEVICE_TYPE);
|
||||||
|
device->setMachineType(IDevice::Hardware);
|
||||||
|
return device;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
TreeModel<DockerImageItem> m_model;
|
||||||
|
TreeView *m_view = nullptr;
|
||||||
|
QtcProcess *m_process = nullptr;
|
||||||
|
QString m_selectedId;
|
||||||
|
};
|
||||||
|
|
||||||
|
IDevice::Ptr DockerDeviceFactory::create() const
|
||||||
|
{
|
||||||
|
DockerDeviceSetupWizard wizard;
|
||||||
|
if (wizard.exec() != QDialog::Accepted)
|
||||||
|
return IDevice::Ptr();
|
||||||
|
return wizard.device();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // Internal
|
||||||
|
} // Docker
|
93
src/plugins/docker/dockerdevice.h
Normal file
93
src/plugins/docker/dockerdevice.h
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2021 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.
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <projectexplorer/devicesupport/idevice.h>
|
||||||
|
#include <projectexplorer/devicesupport/idevicefactory.h>
|
||||||
|
#include <projectexplorer/devicesupport/sshdeviceprocess.h>
|
||||||
|
|
||||||
|
#include <utils/aspects.h>
|
||||||
|
#include <utils/qtcprocess.h>
|
||||||
|
|
||||||
|
namespace Docker {
|
||||||
|
namespace Internal {
|
||||||
|
|
||||||
|
class DockerDeviceData
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
QString imageId;
|
||||||
|
QString repo;
|
||||||
|
QString tag;
|
||||||
|
QString size;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DockerDevice : public ProjectExplorer::IDevice
|
||||||
|
{
|
||||||
|
Q_DECLARE_TR_FUNCTIONS(Docker::Internal::DockerDevice)
|
||||||
|
|
||||||
|
public:
|
||||||
|
using Ptr = QSharedPointer<DockerDevice>;
|
||||||
|
using ConstPtr = QSharedPointer<const DockerDevice>;
|
||||||
|
|
||||||
|
~DockerDevice();
|
||||||
|
static Ptr create(const DockerDeviceData &data) { return Ptr(new DockerDevice(data)); }
|
||||||
|
|
||||||
|
ProjectExplorer::IDeviceWidget *createWidget() override;
|
||||||
|
|
||||||
|
bool canCreateProcess() const override { return true; }
|
||||||
|
ProjectExplorer::DeviceProcess *createProcess(QObject *parent) const override;
|
||||||
|
bool canAutoDetectPorts() const override;
|
||||||
|
ProjectExplorer::PortsGatheringMethod::Ptr portsGatheringMethod() const override;
|
||||||
|
bool canCreateProcessModel() const override { return false; }
|
||||||
|
ProjectExplorer::DeviceProcessList *createProcessListModel(QObject *parent) const override;
|
||||||
|
bool hasDeviceTester() const override { return false; }
|
||||||
|
ProjectExplorer::DeviceTester *createDeviceTester() const override;
|
||||||
|
ProjectExplorer::DeviceProcessSignalOperation::Ptr signalOperation() const override;
|
||||||
|
ProjectExplorer::DeviceEnvironmentFetcher::Ptr environmentFetcher() const override;
|
||||||
|
|
||||||
|
const DockerDeviceData &data() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
explicit DockerDevice(const DockerDeviceData &data);
|
||||||
|
|
||||||
|
void fromMap(const QVariantMap &map) final;
|
||||||
|
QVariantMap toMap() const final;
|
||||||
|
|
||||||
|
DockerDeviceData m_data;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DockerDeviceFactory final : public ProjectExplorer::IDeviceFactory
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
DockerDeviceFactory();
|
||||||
|
|
||||||
|
ProjectExplorer::IDevice::Ptr create() const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // Internal
|
||||||
|
} // Docker
|
||||||
|
|
||||||
|
Q_DECLARE_METATYPE(Docker::Internal::DockerDeviceData)
|
@@ -26,9 +26,16 @@
|
|||||||
#include "dockerplugin.h"
|
#include "dockerplugin.h"
|
||||||
|
|
||||||
#include "dockerconstants.h"
|
#include "dockerconstants.h"
|
||||||
|
|
||||||
|
#include "dockerdevice.h"
|
||||||
|
#include "dockerrunconfiguration.h"
|
||||||
#include "dockersettings.h"
|
#include "dockersettings.h"
|
||||||
|
|
||||||
|
#include <projectexplorer/projectexplorerconstants.h>
|
||||||
|
#include <projectexplorer/runcontrol.h>
|
||||||
|
|
||||||
using namespace Core;
|
using namespace Core;
|
||||||
|
using namespace ProjectExplorer;
|
||||||
using namespace Utils;
|
using namespace Utils;
|
||||||
|
|
||||||
namespace Docker {
|
namespace Docker {
|
||||||
@@ -39,6 +46,15 @@ class DockerPluginPrivate
|
|||||||
public:
|
public:
|
||||||
DockerSettings settings;
|
DockerSettings settings;
|
||||||
DockerOptionsPage optionsPage{&settings};
|
DockerOptionsPage optionsPage{&settings};
|
||||||
|
|
||||||
|
DockerDeviceFactory deviceFactory;
|
||||||
|
DockerContainerRunConfigurationFactory containerRunConfigFactory;
|
||||||
|
|
||||||
|
RunWorkerFactory containerRunWorkerFactory{
|
||||||
|
RunWorkerFactory::make<SimpleTargetRunner>(),
|
||||||
|
{ProjectExplorer::Constants::NORMAL_RUN_MODE},
|
||||||
|
{containerRunConfigFactory.runConfigurationId()}
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
DockerPlugin::~DockerPlugin()
|
DockerPlugin::~DockerPlugin()
|
||||||
|
126
src/plugins/docker/dockerrunconfiguration.cpp
Normal file
126
src/plugins/docker/dockerrunconfiguration.cpp
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** 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 "dockerrunconfiguration.h"
|
||||||
|
|
||||||
|
#include "dockerconstants.h"
|
||||||
|
#include "dockerdevice.h"
|
||||||
|
|
||||||
|
#include <projectexplorer/kitinformation.h>
|
||||||
|
#include <projectexplorer/project.h>
|
||||||
|
#include <projectexplorer/runconfigurationaspects.h>
|
||||||
|
#include <projectexplorer/runcontrol.h>
|
||||||
|
#include <projectexplorer/target.h>
|
||||||
|
|
||||||
|
#include <utils/stringutils.h>
|
||||||
|
|
||||||
|
using namespace ProjectExplorer;
|
||||||
|
using namespace Utils;
|
||||||
|
|
||||||
|
namespace Docker {
|
||||||
|
namespace Internal {
|
||||||
|
|
||||||
|
class DockerContainerRunConfiguration : public RunConfiguration
|
||||||
|
{
|
||||||
|
Q_DECLARE_TR_FUNCTIONS(Docker::Internal::DockerRunConfiguration)
|
||||||
|
|
||||||
|
public:
|
||||||
|
DockerContainerRunConfiguration(Target *target, Id id)
|
||||||
|
: RunConfiguration(target, id)
|
||||||
|
{
|
||||||
|
auto rmOption = addAspect<BoolAspect>();
|
||||||
|
rmOption->setSettingsKey("Docker.RunConfiguration.RmOption");
|
||||||
|
rmOption->setDefaultValue(true);
|
||||||
|
rmOption->setLabelText(tr("Automatically remove the container when it exits"));
|
||||||
|
|
||||||
|
auto ttyOption = addAspect<BoolAspect>();
|
||||||
|
ttyOption->setSettingsKey("Docker.RunConfiguration.TtyOption");
|
||||||
|
ttyOption->setLabelText(tr("Allocate a pseudo-TTY"));
|
||||||
|
ttyOption->setVisible(false); // Not yet.
|
||||||
|
|
||||||
|
auto interactiveOption = addAspect<BoolAspect>();
|
||||||
|
interactiveOption->setSettingsKey("Docker.RunConfiguration.InteractiveOption");
|
||||||
|
interactiveOption->setLabelText(tr("Keep STDIN open even if not attached"));
|
||||||
|
interactiveOption->setVisible(false); // Not yet.
|
||||||
|
|
||||||
|
auto effectiveCommand = addAspect<StringAspect>();
|
||||||
|
effectiveCommand->setLabelText(tr("Effective command call:"));
|
||||||
|
effectiveCommand->setDisplayStyle(StringAspect::TextEditDisplay);
|
||||||
|
effectiveCommand->setReadOnly(true);
|
||||||
|
|
||||||
|
setUpdater([this, effectiveCommand] {
|
||||||
|
IDevice::ConstPtr device = DeviceKitAspect::device(kit());
|
||||||
|
QTC_ASSERT(device, return);
|
||||||
|
DockerDevice::ConstPtr dockerDevice = qSharedPointerCast<const DockerDevice>(device);
|
||||||
|
QTC_ASSERT(dockerDevice, return);
|
||||||
|
const DockerDeviceData &data = dockerDevice->data();
|
||||||
|
|
||||||
|
const Runnable r = runnable();
|
||||||
|
const QStringList dockerRunFlags = r.extraData[Constants::DOCKER_RUN_FLAGS].toStringList();
|
||||||
|
|
||||||
|
CommandLine cmd("docker");
|
||||||
|
cmd.addArg("run");
|
||||||
|
cmd.addArgs(dockerRunFlags);
|
||||||
|
cmd.addArg(data.imageId);
|
||||||
|
|
||||||
|
// FIXME: the global one above is apparently not sufficient.
|
||||||
|
effectiveCommand->setReadOnly(true);
|
||||||
|
effectiveCommand->setValue(cmd.toUserOutput());
|
||||||
|
});
|
||||||
|
|
||||||
|
setRunnableModifier([rmOption, interactiveOption, ttyOption](Runnable &runnable) {
|
||||||
|
QStringList runArgs;
|
||||||
|
if (!rmOption->value())
|
||||||
|
runArgs.append("--rm=false");
|
||||||
|
if (interactiveOption->value())
|
||||||
|
runArgs.append("--interactive");
|
||||||
|
if (ttyOption->value())
|
||||||
|
runArgs.append("--tty");
|
||||||
|
runnable.extraData[Constants::DOCKER_RUN_FLAGS].toStringList();
|
||||||
|
});
|
||||||
|
|
||||||
|
setCommandLineGetter([] {
|
||||||
|
return CommandLine();
|
||||||
|
});
|
||||||
|
|
||||||
|
update();
|
||||||
|
connect(rmOption, &BaseAspect::changed, this, &RunConfiguration::update);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool isEnabled() const override { return true; }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
DockerContainerRunConfigurationFactory::DockerContainerRunConfigurationFactory() :
|
||||||
|
FixedRunConfigurationFactory(DockerContainerRunConfiguration::tr("Docker Container"))
|
||||||
|
{
|
||||||
|
registerRunConfiguration<DockerContainerRunConfiguration>
|
||||||
|
("Docker.DockerContainerRunConfiguration");
|
||||||
|
addSupportedTargetDeviceType(Constants::DOCKER_DEVICE_TYPE);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // Internal
|
||||||
|
} // Docker
|
41
src/plugins/docker/dockerrunconfiguration.h
Normal file
41
src/plugins/docker/dockerrunconfiguration.h
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** 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.
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <projectexplorer/runconfiguration.h>
|
||||||
|
|
||||||
|
namespace Docker {
|
||||||
|
namespace Internal {
|
||||||
|
|
||||||
|
class DockerContainerRunConfigurationFactory
|
||||||
|
: public ProjectExplorer::FixedRunConfigurationFactory
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
DockerContainerRunConfigurationFactory();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // Internal
|
||||||
|
} // Docker
|
Reference in New Issue
Block a user