diff --git a/src/SSHDebugPS/ConnectionManager.cs b/src/SSHDebugPS/ConnectionManager.cs
index 0222926b0..3bd40dec3 100644
--- a/src/SSHDebugPS/ConnectionManager.cs
+++ b/src/SSHDebugPS/ConnectionManager.cs
@@ -38,7 +38,7 @@ public static DockerConnection GetDockerConnection(string name, bool supportSSHC
{
string connectionString;
- bool success = ShowContainerPickerWindow(IntPtr.Zero, supportSSHConnections, out connectionString);
+ bool success = ShowContainerPickerWindow(IntPtr.Zero, supportSSHConnections, ContainerRuntimeType.Docker, out connectionString);
if (success)
{
success = DockerConnection.TryConvertConnectionStringToSettings(connectionString, out settings, out remoteConnection);
@@ -146,11 +146,12 @@ public static SSHConnection GetSSHConnection(string name)
///
/// Parent hwnd or IntPtr.Zero
/// SSHConnections are supported
+ /// Which container runtime to query
/// [out] connection string obtained by the dialog
- public static bool ShowContainerPickerWindow(IntPtr hwnd, bool supportSSHConnections, out string connectionString)
+ public static bool ShowContainerPickerWindow(IntPtr hwnd, bool supportSSHConnections, ContainerRuntimeType runtimeType, out string connectionString)
{
ThreadHelper.ThrowIfNotOnUIThread("Microsoft.SSHDebugPS.ShowContainerPickerWindow");
- ContainerPickerDialogWindow dialog = new ContainerPickerDialogWindow(supportSSHConnections);
+ ContainerPickerDialogWindow dialog = new ContainerPickerDialogWindow(supportSSHConnections, runtimeType);
if (hwnd == IntPtr.Zero) // get the VS main window hwnd
{
diff --git a/src/SSHDebugPS/ContainerRuntimeType.cs b/src/SSHDebugPS/ContainerRuntimeType.cs
new file mode 100644
index 000000000..1a8afa73a
--- /dev/null
+++ b/src/SSHDebugPS/ContainerRuntimeType.cs
@@ -0,0 +1,14 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace Microsoft.SSHDebugPS
+{
+ ///
+ /// Identifies the container runtime to query when discovering containers.
+ ///
+ public enum ContainerRuntimeType
+ {
+ Unknown,
+ Docker
+ }
+}
diff --git a/src/SSHDebugPS/ContainerTransportSettingsBase.cs b/src/SSHDebugPS/ContainerTransportSettingsBase.cs
new file mode 100644
index 000000000..79462f4f9
--- /dev/null
+++ b/src/SSHDebugPS/ContainerTransportSettingsBase.cs
@@ -0,0 +1,163 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Microsoft.SSHDebugPS.Utilities;
+using System.Diagnostics;
+
+namespace Microsoft.SSHDebugPS
+{
+ internal abstract class ContainerTransportSettingsBase : IPipeTransportSettings
+ {
+ protected abstract string SubCommand { get; }
+ protected abstract string SubCommandArgs { get; }
+
+ private readonly string _windowsExe;
+ private readonly string _unixExe;
+ private readonly string _hostnameFormat;
+
+ internal string HostName { get; private set; }
+ internal bool HostIsUnix { get; private set; }
+
+ public ContainerTransportSettingsBase(string hostname, bool hostIsUnix, string windowsExe, string unixExe, string hostnameFormat)
+ {
+ HostIsUnix = hostIsUnix;
+ _windowsExe = windowsExe;
+ _unixExe = unixExe;
+ _hostnameFormat = hostnameFormat;
+ if (!string.IsNullOrWhiteSpace(hostname))
+ {
+ HostName = hostname;
+ }
+ else
+ {
+ HostName = string.Empty;
+ }
+ }
+
+ public ContainerTransportSettingsBase(ContainerTransportSettingsBase settings)
+ : this(settings.HostName, settings.HostIsUnix, settings._windowsExe, settings._unixExe, settings._hostnameFormat)
+ { }
+
+ // 0 = command parameters (e.g. --host/--url)
+ // 1 = subcommand (e.g. exec, cp, ps)
+ // 2 = subcommand parameters
+ private const string _baseCommandFormat = "{0} {1} {2}";
+
+ private string GenerateExeCommandArgs()
+ {
+ var hostnameArg = string.Empty;
+ if (!string.IsNullOrWhiteSpace(this.HostName))
+ hostnameArg = _hostnameFormat.FormatInvariantWithArgs(this.HostName);
+
+ return _baseCommandFormat.FormatInvariantWithArgs(hostnameArg, SubCommand, SubCommandArgs);
+ }
+
+ #region IPipeTransportSettings
+
+ public string CommandArgs => GenerateExeCommandArgs();
+
+ public string Command => HostIsUnix ? _unixExe : _windowsExe;
+
+ #endregion
+ }
+
+ internal abstract class ContainerTargetTransportSettings : ContainerTransportSettingsBase
+ {
+ internal string ContainerName { get; private set; }
+
+ public ContainerTargetTransportSettings(string hostname, string containerName, bool hostIsUnix, string windowsExe, string unixExe, string hostnameFormat)
+ : base(hostname, hostIsUnix, windowsExe, unixExe, hostnameFormat)
+ {
+ ContainerName = containerName;
+ }
+
+ public ContainerTargetTransportSettings(ContainerTargetTransportSettings settings)
+ : base(settings)
+ {
+ ContainerName = settings.ContainerName;
+ }
+
+ protected override string SubCommand => throw new System.NotImplementedException();
+ protected override string SubCommandArgs => throw new System.NotImplementedException();
+ }
+
+ internal abstract class ContainerExecSettings : ContainerTargetTransportSettings
+ {
+ private bool _runInShell;
+ private string _commandToExecute;
+ // 0 = container, 1 = command to execute
+ private const string _subCommandArgsFormat = "{0} {1}";
+ private const string _subCommandArgsFormatWithShell = "{0} /bin/sh -c \"{1}\"";
+ private const string _subCommandArgsFormatWithShellLinuxHost = "{0} /bin/sh -c '{1}'";
+ private const string _interactiveFlag = "-i ";
+
+ private bool _makeInteractive;
+
+ public ContainerExecSettings(ContainerTargetTransportSettings settings, string command, bool runInShell, bool makeInteractive = true)
+ : base(settings)
+ {
+ Debug.Assert(!string.IsNullOrWhiteSpace(command), "Exec command cannot be null");
+ _runInShell = runInShell;
+ _commandToExecute = command;
+ _makeInteractive = makeInteractive;
+ }
+
+ protected override string SubCommand => "exec";
+ protected override string SubCommandArgs
+ {
+ get
+ {
+ string subCommandFormat = this.HostIsUnix ? _subCommandArgsFormatWithShellLinuxHost : _subCommandArgsFormatWithShell;
+ // Escape single quotes on Linux so variable resolution does not happen until it is in the container.
+ string command = this.HostIsUnix ? _commandToExecute.Replace("'", "'\\''") : _commandToExecute;
+ return (_makeInteractive ? _interactiveFlag : string.Empty) +
+ (_runInShell ? subCommandFormat : _subCommandArgsFormat).FormatInvariantWithArgs(ContainerName, command);
+ }
+ }
+ }
+
+ internal abstract class ContainerCopySettings : ContainerTargetTransportSettings
+ {
+ // {0} = container, {1} = source, {2} = destination
+ private const string _copyFormatToContainer = "{1} {0}:{2}";
+
+ private string _sourcePath;
+ private string _destinationPath;
+
+ public ContainerCopySettings(string hostname, string sourcePath, string destinationPath, string containerName, bool hostIsUnix, string windowsExe, string unixExe, string hostnameFormat)
+ : base(hostname, containerName, hostIsUnix, windowsExe, unixExe, hostnameFormat)
+ {
+ _sourcePath = sourcePath;
+ _destinationPath = destinationPath;
+ }
+
+ public ContainerCopySettings(ContainerTargetTransportSettings settings, string sourcePath, string destinationPath)
+ : base(settings)
+ {
+ _sourcePath = sourcePath;
+ _destinationPath = destinationPath;
+ }
+
+ protected override string SubCommand => "cp";
+ protected override string SubCommandArgs => _copyFormatToContainer.FormatInvariantWithArgs(ContainerName, _sourcePath, _destinationPath);
+ }
+
+ internal abstract class ContainerCommandSettings : ContainerTransportSettingsBase
+ {
+ private string _cmd;
+ private string _args;
+
+ public ContainerCommandSettings(string hostname, bool hostIsUnix, string windowsExe, string unixExe, string hostnameFormat)
+ : base(hostname, hostIsUnix, windowsExe, unixExe, hostnameFormat)
+ { }
+
+ public void SetCommand(string cmd, string args)
+ {
+ _cmd = cmd;
+ _args = args;
+ }
+
+ protected override string SubCommand => _cmd;
+ protected override string SubCommandArgs => _args;
+ }
+}
diff --git a/src/SSHDebugPS/Docker/DockerConnection.cs b/src/SSHDebugPS/Docker/DockerConnection.cs
index d152e61ac..846245dbf 100644
--- a/src/SSHDebugPS/Docker/DockerConnection.cs
+++ b/src/SSHDebugPS/Docker/DockerConnection.cs
@@ -198,7 +198,7 @@ private ICommandRunner GetExecCommandRunner(string commandText, bool handleRawOu
return GetCommandRunner(execSettings, handleRawOutput: handleRawOutput);
}
- private ICommandRunner GetCommandRunner(DockerContainerTransportSettings settings, bool handleRawOutput = false)
+ private ICommandRunner GetCommandRunner(IPipeTransportSettings settings, bool handleRawOutput = false)
{
if (OuterConnection == null)
{
diff --git a/src/SSHDebugPS/Docker/DockerContainerInstance.cs b/src/SSHDebugPS/Docker/DockerContainerInstance.cs
index 810975773..e0df057a6 100644
--- a/src/SSHDebugPS/Docker/DockerContainerInstance.cs
+++ b/src/SSHDebugPS/Docker/DockerContainerInstance.cs
@@ -37,7 +37,7 @@ public static bool TryCreate(string json, out DockerContainerInstance instance)
return instance != null;
}
- private DockerContainerInstance() { }
+ protected DockerContainerInstance() { }
#region JsonProperties
@@ -48,19 +48,19 @@ private DockerContainerInstance() { }
public override string Name { get; set; }
[JsonProperty(nameof(Image))]
- public string Image { get; private set; }
+ public virtual string Image { get; protected set; }
[JsonProperty(nameof(Ports))]
- public string Ports { get; set; }
+ public virtual string Ports { get; set; }
[JsonProperty(nameof(Command))]
- public string Command { get; private set; }
+ public virtual string Command { get; protected set; }
[JsonProperty(nameof(Status))]
- public string Status { get; private set; }
+ public virtual string Status { get; protected set; }
[JsonProperty("CreatedAt")]
- public string Created { get; private set; }
+ public virtual string Created { get; protected set; }
[JsonIgnore]
public string Platform { get; set; }
diff --git a/src/SSHDebugPS/Docker/DockerDiscoveryStrategy.cs b/src/SSHDebugPS/Docker/DockerDiscoveryStrategy.cs
new file mode 100644
index 000000000..d47aad0b6
--- /dev/null
+++ b/src/SSHDebugPS/Docker/DockerDiscoveryStrategy.cs
@@ -0,0 +1,78 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using Microsoft.SSHDebugPS.Docker;
+using Microsoft.SSHDebugPS.UI;
+
+namespace Microsoft.SSHDebugPS
+{
+ internal sealed class DockerDiscoveryStrategy : IContainerDiscoveryStrategy
+ {
+ private const string unknownOS = "Unknown";
+
+ public string ConnectionLabel => UIResources.ConnectionLabel;
+ public string HostnameLabel => UIResources.HostnameLabel;
+ public string HostnameTip => UIResources.HostnameTip;
+ public string ConnectionToolTip => UIResources.ConnectionToolTip;
+ public string HostnameAutomationName => UIResources.HostnameAutomationName;
+
+ public IEnumerable GetLocalContainers(string hostname, out int totalContainers)
+ {
+ return DockerHelper.GetLocalDockerContainers(hostname, out totalContainers);
+ }
+
+ public IEnumerable GetRemoteContainers(IConnection connection, string hostname, out int totalContainers)
+ {
+ return DockerHelper.GetRemoteDockerContainers(connection, hostname, out totalContainers);
+ }
+
+ public void AssignPlatforms(IEnumerable containers, string hostname)
+ {
+ if (!containers.Any())
+ return;
+
+ string serverOS;
+ if (DockerHelper.TryGetServerOS(hostname, out serverOS))
+ {
+ bool lcow;
+ DockerHelper.TryGetLCOW(hostname, out lcow);
+ TextInfo textInfo = new CultureInfo("en-US", false).TextInfo;
+
+ if (lcow && serverOS.IndexOf("windows", StringComparison.OrdinalIgnoreCase) >= 0)
+ {
+ foreach (DockerContainerInstance container in containers)
+ {
+ string containerPlatform = string.Empty;
+ if (DockerHelper.TryGetContainerPlatform(hostname, container.Name, out containerPlatform))
+ {
+ container.Platform = textInfo.ToTitleCase(containerPlatform);
+ }
+ else
+ {
+ container.Platform = unknownOS;
+ }
+ }
+ }
+ else
+ {
+ string platform = textInfo.ToTitleCase(serverOS);
+ foreach (DockerContainerInstance container in containers)
+ {
+ container.Platform = platform;
+ }
+ }
+ }
+ else
+ {
+ foreach (DockerContainerInstance container in containers)
+ {
+ container.Platform = unknownOS;
+ }
+ }
+ }
+ }
+}
diff --git a/src/SSHDebugPS/Docker/DockerExecutionManager.cs b/src/SSHDebugPS/Docker/DockerExecutionManager.cs
index 4d36eb4cc..e2f018926 100644
--- a/src/SSHDebugPS/Docker/DockerExecutionManager.cs
+++ b/src/SSHDebugPS/Docker/DockerExecutionManager.cs
@@ -52,18 +52,27 @@ internal class DockerExecutionManager
private PipeAsyncCommand _currentCommand;
private Connection _outerConnection = null;
- private DockerContainerTransportSettings _baseSettings;
+ private ContainerTargetTransportSettings _baseSettings;
private readonly ManualResetEvent _commandCompleteEvent = new ManualResetEvent(false);
- public DockerExecutionManager(DockerContainerTransportSettings baseSettings, Connection outerConnection)
+ public DockerExecutionManager(ContainerTargetTransportSettings baseSettings, Connection outerConnection)
{
_baseSettings = baseSettings;
_outerConnection = outerConnection;
}
+ protected virtual ContainerExecSettings CreateExecSettings(ContainerTargetTransportSettings baseSettings, string command, bool runInShell, bool makeInteractive)
+ {
+ if (!(baseSettings is DockerContainerTransportSettings dockerSettings))
+ {
+ throw new ArgumentException($"Expected {nameof(DockerContainerTransportSettings)} but got {baseSettings.GetType().Name}", nameof(baseSettings));
+ }
+ return new DockerExecSettings(dockerSettings, command, runInShell, makeInteractive);
+ }
+
private ICommandRunner GetExecCommandRunner(string command, bool runInShell, bool makeInteractive)
{
- var execSettings = new DockerExecSettings(_baseSettings, command, runInShell, makeInteractive);
+ var execSettings = CreateExecSettings(_baseSettings, command, runInShell, makeInteractive);
if (_outerConnection == null)
{
diff --git a/src/SSHDebugPS/Docker/DockerHelper.cs b/src/SSHDebugPS/Docker/DockerHelper.cs
index 5bbd7889c..f5a7a06ae 100644
--- a/src/SSHDebugPS/Docker/DockerHelper.cs
+++ b/src/SSHDebugPS/Docker/DockerHelper.cs
@@ -26,7 +26,7 @@ public class DockerHelper
private const string dockerInspectArgs = "-f \"{{json .Platform}}\" ";
private static char[] charsToTrim = { ' ', '\"' };
- private static void RunDockerCommand(DockerCommandSettings settings, Action callback)
+ internal static void RunContainerCommand(IPipeTransportSettings settings, Action callback)
{
LocalCommandRunner commandRunner = new LocalCommandRunner(settings);
@@ -106,7 +106,7 @@ internal static bool TryGetLCOW(string hostname, out bool lcow)
try
{
- RunDockerCommand(settings, delegate (string args)
+ RunContainerCommand(settings, delegate (string args)
{
if (args.Contains("lcow"))
{
@@ -134,7 +134,7 @@ internal static bool TryGetServerOS(string hostname, out string serverOS)
try
{
- RunDockerCommand(settings, delegate (string args)
+ RunContainerCommand(settings, delegate (string args)
{
delegateServerOS = args;
});
@@ -159,7 +159,7 @@ internal static bool TryGetContainerPlatform(string hostname, string containerNa
try
{
- RunDockerCommand(settings, delegate (string args)
+ RunContainerCommand(settings, delegate (string args)
{
delegateContainerPlatform = args;
});
@@ -183,7 +183,7 @@ internal static IEnumerable GetLocalDockerContainers(st
DockerCommandSettings settings = new DockerCommandSettings(hostname, false);
settings.SetCommand(dockerPSCommand, dockerPSArgs);
- RunDockerCommand(settings, delegate (string args)
+ RunContainerCommand(settings, delegate (string args)
{
if (args.Trim()[0] == '{')
{
diff --git a/src/SSHDebugPS/Docker/DockerPortPicker.cs b/src/SSHDebugPS/Docker/DockerPortPicker.cs
index d098bb7f3..4489e14c8 100644
--- a/src/SSHDebugPS/Docker/DockerPortPicker.cs
+++ b/src/SSHDebugPS/Docker/DockerPortPicker.cs
@@ -33,12 +33,13 @@ public class DockerWindowsPortPicker : DockerPortPickerBase
public abstract class DockerPortPickerBase : IDebugPortPicker
{
internal abstract bool SupportSSHConnections { get; }
+ internal virtual ContainerRuntimeType RuntimeType => ContainerRuntimeType.Docker;
int IDebugPortPicker.DisplayPortPicker(IntPtr hwndParentDialog, out string pbstrPortId)
{
ThreadHelper.ThrowIfNotOnUIThread();
// If this is null, then the PortPicker handler shows an error. Set to empty by default
- return ConnectionManager.ShowContainerPickerWindow(hwndParentDialog, SupportSSHConnections, out pbstrPortId) ?
+ return ConnectionManager.ShowContainerPickerWindow(hwndParentDialog, SupportSSHConnections, RuntimeType, out pbstrPortId) ?
VSConstants.S_OK : VSConstants.S_FALSE;
}
diff --git a/src/SSHDebugPS/Docker/TransportSettings/DockerContainerTransportSettings.cs b/src/SSHDebugPS/Docker/TransportSettings/DockerContainerTransportSettings.cs
index 593eea60a..cf61e1c64 100644
--- a/src/SSHDebugPS/Docker/TransportSettings/DockerContainerTransportSettings.cs
+++ b/src/SSHDebugPS/Docker/TransportSettings/DockerContainerTransportSettings.cs
@@ -1,97 +1,38 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-using Microsoft.SSHDebugPS.Utilities;
-using System.Diagnostics;
-
namespace Microsoft.SSHDebugPS.Docker
{
- internal class DockerContainerTransportSettings : DockerTransportSettingsBase
+ internal sealed class DockerContainerTransportSettings : ContainerTargetTransportSettings
{
- internal string ContainerName { get; private set; }
+ internal const string WindowsExeName = "docker.exe";
+ internal const string UnixExeName = "docker";
+ internal const string HostFlag = "--host \"{0}\"";
public DockerContainerTransportSettings(string hostname, string containerName, bool hostIsUnix)
- : base(hostname, hostIsUnix)
- {
- ContainerName = containerName;
- }
+ : base(hostname, containerName, hostIsUnix, WindowsExeName, UnixExeName, HostFlag)
+ { }
public DockerContainerTransportSettings(DockerContainerTransportSettings settings)
: base(settings)
- {
- ContainerName = settings.ContainerName;
- }
-
- protected override string SubCommand => throw new System.NotImplementedException();
- protected override string SubCommandArgs => throw new System.NotImplementedException();
+ { }
}
- internal class DockerExecSettings : DockerContainerTransportSettings
+ internal sealed class DockerExecSettings : ContainerExecSettings
{
- private bool _runInShell;
- private string _commandToExecute;
- // 0 = container, 1 = command to execute
- private const string _subCommandArgsFormat = "{0} {1}";
- private const string _subCommandArgsFormatWithShell = "{0} /bin/sh -c \"{1}\"";
- private const string _subCommandArgsFormatWithShellLinuxHost = "{0} /bin/sh -c '{1}'"; // Single quote the argument on Linux so variable resolution does not happen until it is in the container.
- private const string _interactiveFlag = "-i ";
-
- private bool _makeInteractive;
-
public DockerExecSettings(DockerContainerTransportSettings settings, string command, bool runInShell, bool makeInteractive = true)
- : base(settings)
- {
- Debug.Assert(!string.IsNullOrWhiteSpace(command), "Exec command cannot be null");
- _runInShell = runInShell;
- _commandToExecute = command;
- _makeInteractive = makeInteractive;
- }
-
- protected override string SubCommand => "exec";
- protected override string SubCommandArgs
- {
- get
- {
- string subCommandFormat = this.HostIsUnix ? _subCommandArgsFormatWithShellLinuxHost : _subCommandArgsFormatWithShell;
- // Because _subCommandArgsFormatWithShellLinuxHost single quotes the the subcommand arguments, we need to escape the command's single quotes
- // by closing the single quotes and adding an escaped single quote and then reopening the single quote.
- string command = this.HostIsUnix ? _commandToExecute.Replace("'", "'\\''") : _commandToExecute;
- return (_makeInteractive ? _interactiveFlag : string.Empty) +
- (_runInShell ? subCommandFormat : _subCommandArgsFormat).FormatInvariantWithArgs(ContainerName, command);
- }
- }
+ : base(settings, command, runInShell, makeInteractive)
+ { }
}
- internal class DockerCopySettings : DockerContainerTransportSettings
+ internal sealed class DockerCopySettings : ContainerCopySettings
{
- // {0} = container, {1} = source, {2} = destination
- private string _copyFormatToContainer = "{1} {0}:{2}";
-
- private string _sourcePath;
- private string _destinationPath;
-
- ///
- /// Settings to copy from host to the docker container
- ///
- /// Local path on host
- /// Remote path within the docker container
- /// Name of container
- /// Host is Unix
public DockerCopySettings(string hostname, string sourcePath, string destinationPath, string containerName, bool hostIsUnix)
- : base(hostname, containerName, hostIsUnix)
- {
- _sourcePath = sourcePath;
- _destinationPath = destinationPath;
- }
+ : base(hostname, sourcePath, destinationPath, containerName, hostIsUnix, DockerContainerTransportSettings.WindowsExeName, DockerContainerTransportSettings.UnixExeName, DockerContainerTransportSettings.HostFlag)
+ { }
public DockerCopySettings(DockerContainerTransportSettings settings, string sourcePath, string destinationPath)
- : base(settings)
- {
- _sourcePath = sourcePath;
- _destinationPath = destinationPath;
- }
-
- protected override string SubCommand => "cp";
- protected override string SubCommandArgs => _copyFormatToContainer.FormatInvariantWithArgs(ContainerName, _sourcePath, _destinationPath);
+ : base(settings, sourcePath, destinationPath)
+ { }
}
}
diff --git a/src/SSHDebugPS/Docker/TransportSettings/DockerTransportSettings.cs b/src/SSHDebugPS/Docker/TransportSettings/DockerTransportSettings.cs
index 63e5f0c43..9b1024f85 100644
--- a/src/SSHDebugPS/Docker/TransportSettings/DockerTransportSettings.cs
+++ b/src/SSHDebugPS/Docker/TransportSettings/DockerTransportSettings.cs
@@ -1,78 +1,12 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-using Microsoft.SSHDebugPS.Utilities;
-
namespace Microsoft.SSHDebugPS.Docker
{
- internal abstract class DockerTransportSettingsBase : IPipeTransportSettings
- {
- protected abstract string SubCommand { get; }
- protected abstract string SubCommandArgs { get; }
-
- internal string HostName { get; private set; }
- internal bool HostIsUnix { get; private set; }
-
- public DockerTransportSettingsBase(string hostname, bool hostIsUnix)
- {
- HostIsUnix = hostIsUnix;
- if (!string.IsNullOrWhiteSpace(hostname))
- {
- HostName = hostname;
- }
- else
- {
- HostName = string.Empty;
- }
- }
-
- public DockerTransportSettingsBase(DockerTransportSettingsBase settings)
- : this(settings.HostName, settings.HostIsUnix)
- { }
-
- private static string WindowsExe => "docker.exe";
- private static string UnixExe => "docker";
-
- // 0 = docker command parameters
- // 1 = docker subcommand
- // 2 = docker subcommand parameters
- private const string _baseCommandFormat = "{0} {1} {2}";
- // 0 = hostname property
- private const string _hostnameFormat = "--host \"{0}\"";
- private string GenerateExeCommandArgs()
- {
- var hostnameArg = string.Empty;
- if (!string.IsNullOrWhiteSpace(this.HostName))
- hostnameArg = _hostnameFormat.FormatInvariantWithArgs(this.HostName);
-
- return _baseCommandFormat.FormatInvariantWithArgs(hostnameArg, SubCommand, SubCommandArgs);
- }
-
- #region IPipeTransportSettings
-
- public string CommandArgs => GenerateExeCommandArgs();
-
- public string Command => HostIsUnix ? UnixExe : WindowsExe;
- #endregion
- }
-
- internal class DockerCommandSettings : DockerTransportSettingsBase
+ internal sealed class DockerCommandSettings : ContainerCommandSettings
{
- private string _cmd;
- private string _args;
-
public DockerCommandSettings(string hostname, bool hostIsUnix)
- : base(hostname, hostIsUnix)
+ : base(hostname, hostIsUnix, DockerContainerTransportSettings.WindowsExeName, DockerContainerTransportSettings.UnixExeName, DockerContainerTransportSettings.HostFlag)
{ }
-
- public void SetCommand(string cmd, string args)
- {
- _cmd = cmd;
- _args = args;
- }
-
- protected override string SubCommand => _cmd;
- protected override string SubCommandArgs => _args;
}
}
-
diff --git a/src/SSHDebugPS/IContainerDiscoveryStrategy.cs b/src/SSHDebugPS/IContainerDiscoveryStrategy.cs
new file mode 100644
index 000000000..c7fd87bb7
--- /dev/null
+++ b/src/SSHDebugPS/IContainerDiscoveryStrategy.cs
@@ -0,0 +1,21 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Collections.Generic;
+using Microsoft.SSHDebugPS.Docker;
+
+namespace Microsoft.SSHDebugPS
+{
+ internal interface IContainerDiscoveryStrategy
+ {
+ string ConnectionLabel { get; }
+ string HostnameLabel { get; }
+ string HostnameTip { get; }
+ string ConnectionToolTip { get; }
+ string HostnameAutomationName { get; }
+
+ IEnumerable GetLocalContainers(string hostname, out int totalContainers);
+ IEnumerable GetRemoteContainers(IConnection connection, string hostname, out int totalContainers);
+ void AssignPlatforms(IEnumerable containers, string hostname);
+ }
+}
diff --git a/src/SSHDebugPS/UI/ContainerPickerDialogWindow.xaml b/src/SSHDebugPS/UI/ContainerPickerDialogWindow.xaml
index cfdc1e8d6..81387168c 100644
--- a/src/SSHDebugPS/UI/ContainerPickerDialogWindow.xaml
+++ b/src/SSHDebugPS/UI/ContainerPickerDialogWindow.xaml
@@ -512,14 +512,14 @@
VerticalAlignment="Center"
Style="{StaticResource MainLabelStyle}"
Target="{Binding ElementName=ConnectionTypeComboBox}"
- Content="{x:Static local:UIResources.ConnectionLabel}" />
+ Content="{Binding ConnectionLabelText}" />
+ Content="{Binding HostnameLabelText}" />
+ ToolTip="{Binding HostnameTipText}">
diff --git a/src/SSHDebugPS/UI/ContainerPickerDialogWindow.xaml.cs b/src/SSHDebugPS/UI/ContainerPickerDialogWindow.xaml.cs
index feec9e5b7..ee0aae59c 100644
--- a/src/SSHDebugPS/UI/ContainerPickerDialogWindow.xaml.cs
+++ b/src/SSHDebugPS/UI/ContainerPickerDialogWindow.xaml.cs
@@ -25,9 +25,9 @@ namespace Microsoft.SSHDebugPS.UI
///
public partial class ContainerPickerDialogWindow : DialogWindow
{
- public ContainerPickerDialogWindow(bool supportSSHConnections)
+ public ContainerPickerDialogWindow(bool supportSSHConnections, ContainerRuntimeType runtimeType)
{
- _model = new ContainerPickerViewModel(supportSSHConnections);
+ _model = new ContainerPickerViewModel(supportSSHConnections, runtimeType);
this.DataContext = _model;
this.Loaded += OnWindowLoaded;
diff --git a/src/SSHDebugPS/UI/ViewModels/ContainerPickerViewModel.cs b/src/SSHDebugPS/UI/ViewModels/ContainerPickerViewModel.cs
index 2fefd0890..0f19c2f20 100644
--- a/src/SSHDebugPS/UI/ViewModels/ContainerPickerViewModel.cs
+++ b/src/SSHDebugPS/UI/ViewModels/ContainerPickerViewModel.cs
@@ -22,10 +22,11 @@ public class ContainerPickerViewModel : INotifyPropertyChanged
{
private Lazy _sshAvailable;
- public ContainerPickerViewModel(bool supportSSHConnections)
+ public ContainerPickerViewModel(bool supportSSHConnections, ContainerRuntimeType runtimeType)
{
ThreadHelper.ThrowIfNotOnUIThread();
SupportSSHConnections = supportSSHConnections;
+ _discoveryStrategy = CreateDiscoveryStrategy(runtimeType);
InitializeConnections();
ContainerInstances = new ObservableCollection();
@@ -142,7 +143,29 @@ private bool ComputeContainerConnectionString()
// The formatted string for the ConnectionType dialog
public string SelectedContainerConnectionString { get; private set; }
- private const string unknownOS = "Unknown";
+ private readonly IContainerDiscoveryStrategy _discoveryStrategy;
+
+ private static IContainerDiscoveryStrategy CreateDiscoveryStrategy(ContainerRuntimeType runtimeType)
+ {
+ switch (runtimeType)
+ {
+ case ContainerRuntimeType.Docker:
+ return new DockerDiscoveryStrategy();
+ default:
+ Debug.Fail($"Unsupported container runtime type: {runtimeType}");
+ return null;
+ }
+ }
+
+ public string ConnectionLabelText => _discoveryStrategy?.ConnectionLabel ?? UIResources.ConnectionLabel;
+
+ public string HostnameLabelText => _discoveryStrategy?.HostnameLabel ?? UIResources.HostnameLabel;
+
+ public string HostnameTipText => _discoveryStrategy?.HostnameTip ?? UIResources.HostnameTip;
+
+ public string ConnectionToolTipText => _discoveryStrategy?.ConnectionToolTip ?? UIResources.ConnectionToolTip;
+
+ public string HostnameAutomationNameText => _discoveryStrategy?.HostnameAutomationName ?? UIResources.HostnameAutomationName;
private void RefreshContainersListInternal()
{
@@ -152,11 +175,19 @@ private void RefreshContainersListInternal()
IContainerViewModel selectedContainer = SelectedContainerInstance;
SelectedContainerInstance = null;
+ var viewModels = new List();
+
+ if (_discoveryStrategy == null)
+ {
+ UpdateStatusMessage(string.Format(CultureInfo.CurrentCulture, UIResources.ContainersFoundStatusText, 0), isError: true);
+ return;
+ }
+
IEnumerable containers;
if (SelectedConnection is LocalConnectionViewModel)
{
- containers = DockerHelper.GetLocalDockerContainers(Hostname, out totalContainers);
+ containers = _discoveryStrategy.GetLocalContainers(Hostname, out totalContainers);
}
else
{
@@ -167,59 +198,16 @@ private void RefreshContainersListInternal()
UpdateStatusMessage(UIResources.SSHConnectionFailedStatusText, isError: true);
return;
}
- containers = DockerHelper.GetRemoteDockerContainers(connection, Hostname, out totalContainers);
+ containers = _discoveryStrategy.GetRemoteContainers(connection, Hostname, out totalContainers);
}
- if (containers.Any())
+ if (containers != null)
{
- string serverOS;
-
- if (DockerHelper.TryGetServerOS(Hostname, out serverOS))
- {
- bool lcow;
- bool getLCOW = DockerHelper.TryGetLCOW(Hostname, out lcow);
- TextInfo textInfo = new CultureInfo("en-US", false).TextInfo;
- serverOS = textInfo.ToTitleCase(serverOS);
-
- /* Note: LCOW is the abbreviation for Linux Containers on Windows
- *
- * In LCOW, both Linux and Windows containers can run simultaneously in a Docker (Windows) Engine.
- * Thus, the container platform must be queried directly.
- * Otherwise, the container platform must match that of the server engine.
- */
- if (lcow && serverOS.Contains("Windows"))
- {
- foreach (DockerContainerInstance container in containers)
- {
- string containerPlatform = string.Empty;
- if (DockerHelper.TryGetContainerPlatform(Hostname, container.Name, out containerPlatform))
- {
- container.Platform = textInfo.ToTitleCase(containerPlatform);
- }
- else
- {
- container.Platform = unknownOS;
- }
- }
- }
- else
- {
- foreach (DockerContainerInstance container in containers)
- {
- container.Platform = serverOS;
- }
- }
- }
- else
- {
- foreach (DockerContainerInstance container in containers)
- {
- container.Platform = unknownOS;
- }
- }
+ _discoveryStrategy.AssignPlatforms(containers, Hostname);
+ viewModels.AddRange(containers.Select(item => (IContainerViewModel)new DockerContainerViewModel(item)));
}
- ContainerInstances = new ObservableCollection(containers.Select(item => new DockerContainerViewModel(item)).ToList());
+ ContainerInstances = new ObservableCollection(viewModels);
OnPropertyChanged(nameof(ContainerInstances));
if (ContainerInstances.Count > 0)