Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions src/SSHDebugPS/ConnectionManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -146,11 +146,12 @@ public static SSHConnection GetSSHConnection(string name)
/// </summary>
/// <param name="hwnd">Parent hwnd or IntPtr.Zero</param>
/// <param name="supportSSHConnections">SSHConnections are supported</param>
/// <param name="runtimeType">Which container runtime to query</param>
/// <param name="connectionString">[out] connection string obtained by the dialog</param>
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
{
Expand Down
14 changes: 14 additions & 0 deletions src/SSHDebugPS/ContainerRuntimeType.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Identifies the container runtime to query when discovering containers.
/// </summary>
public enum ContainerRuntimeType
{
Unknown,
Comment thread
gregg-miskelly marked this conversation as resolved.
Docker
}
}
163 changes: 163 additions & 0 deletions src/SSHDebugPS/ContainerTransportSettingsBase.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
2 changes: 1 addition & 1 deletion src/SSHDebugPS/Docker/DockerConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
12 changes: 6 additions & 6 deletions src/SSHDebugPS/Docker/DockerContainerInstance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public static bool TryCreate(string json, out DockerContainerInstance instance)
return instance != null;
}

private DockerContainerInstance() { }
protected DockerContainerInstance() { }
Comment thread
WardenGnaw marked this conversation as resolved.

#region JsonProperties

Expand All @@ -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; }
Expand Down
78 changes: 78 additions & 0 deletions src/SSHDebugPS/Docker/DockerDiscoveryStrategy.cs
Original file line number Diff line number Diff line change
@@ -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<DockerContainerInstance> GetLocalContainers(string hostname, out int totalContainers)
{
return DockerHelper.GetLocalDockerContainers(hostname, out totalContainers);
}

public IEnumerable<DockerContainerInstance> GetRemoteContainers(IConnection connection, string hostname, out int totalContainers)
{
return DockerHelper.GetRemoteDockerContainers(connection, hostname, out totalContainers);
}

public void AssignPlatforms(IEnumerable<DockerContainerInstance> 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;
}
}
}
}
}
15 changes: 12 additions & 3 deletions src/SSHDebugPS/Docker/DockerExecutionManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
Loading