diff --git a/src/MIDebugEngine/Microsoft.MIDebugEngine.pkgdef b/src/MIDebugEngine/Microsoft.MIDebugEngine.pkgdef
index ce626ec28..85c7fe331 100644
--- a/src/MIDebugEngine/Microsoft.MIDebugEngine.pkgdef
+++ b/src/MIDebugEngine/Microsoft.MIDebugEngine.pkgdef
@@ -54,6 +54,8 @@
"1"="{A2BBC114-47E4-473F-A49C-69EE89711243}"
; WSL Port supplier
"2"="{267B1341-AC92-44DC-94DF-2EE4205DD17E}"
+; Podman Port Supplier
+"3"="{D4F2F3A5-6B7C-4E8D-9F0A-1B2C3D4E5F6A}"
; Registration to use lldb with the port suppliers
[$RootKey$\AD7Metrics\Engine\{5D630903-189D-4837-9785-699B05BEC2A9}]
@@ -83,6 +85,8 @@
"0"="{3FDDF14E-E758-4695-BE0C-7509920432C9}"
; WSL Port supplier
"1"="{267B1341-AC92-44DC-94DF-2EE4205DD17E}"
+; Podman Port Supplier
+"2"="{D4F2F3A5-6B7C-4E8D-9F0A-1B2C3D4E5F6A}"
[$RootKey$\AD7Metrics\Engine\{5D630903-189D-4837-9785-699B05BEC2A9}\IncompatibleList]
"MI Debug Engine - gdb"="{91744D97-430F-42C1-9779-A5813EBD6AB2}"
diff --git a/src/SSHDebugPS/ConnectionManager.cs b/src/SSHDebugPS/ConnectionManager.cs
index 3bd40dec3..bc8be3627 100644
--- a/src/SSHDebugPS/ConnectionManager.cs
+++ b/src/SSHDebugPS/ConnectionManager.cs
@@ -14,6 +14,7 @@
using liblinux;
using liblinux.Persistence;
using Microsoft.SSHDebugPS.Docker;
+using Microsoft.SSHDebugPS.Podman;
using Microsoft.SSHDebugPS.SSH;
using Microsoft.SSHDebugPS.UI;
using Microsoft.SSHDebugPS.Utilities;
@@ -65,6 +66,46 @@ public static DockerConnection GetDockerConnection(string name, bool supportSSHC
}
}
+ public static PodmanConnection GetPodmanConnection(string name, bool supportSSHConnections)
+ {
+ if (string.IsNullOrWhiteSpace(name))
+ return null;
+
+ PodmanContainerTransportSettings settings;
+ Connection remoteConnection;
+
+ ThreadHelper.ThrowIfNotOnUIThread();
+ if (!PodmanConnection.TryConvertConnectionStringToSettings(name, out settings, out remoteConnection) || settings == null)
+ {
+ string connectionString;
+
+ bool success = ShowContainerPickerWindow(IntPtr.Zero, supportSSHConnections, ContainerRuntimeType.Podman, out connectionString);
+ if (success)
+ {
+ success = PodmanConnection.TryConvertConnectionStringToSettings(connectionString, out settings, out remoteConnection);
+ }
+
+ if (!success || settings == null)
+ {
+ VSMessageBoxHelper.PostErrorMessage(StringResources.Error_ContainerConnectionStringInvalidTitle, StringResources.Error_ContainerConnectionStringInvalidMessage);
+ return null;
+ }
+ }
+
+ string displayName = PodmanConnection.CreateConnectionString(settings.ContainerName, remoteConnection?.Name, settings.HostName);
+ if (PodmanHelper.IsContainerRunning(settings.HostName, settings.ContainerName, remoteConnection))
+ {
+ return new PodmanConnection(settings, remoteConnection, displayName);
+ }
+ else
+ {
+ VSMessageBoxHelper.PostErrorMessage(
+ StringResources.Error_ContainerUnavailableTitle,
+ StringResources.Error_ContainerUnavailableMessage.FormatCurrentCultureWithArgs(settings.ContainerName));
+ return null;
+ }
+ }
+
public static SSHConnection GetSSHConnection(string name)
{
ThreadHelper.ThrowIfNotOnUIThread();
diff --git a/src/SSHDebugPS/ContainerRuntimeType.cs b/src/SSHDebugPS/ContainerRuntimeType.cs
index 1a8afa73a..612c91035 100644
--- a/src/SSHDebugPS/ContainerRuntimeType.cs
+++ b/src/SSHDebugPS/ContainerRuntimeType.cs
@@ -8,7 +8,7 @@ namespace Microsoft.SSHDebugPS
///
public enum ContainerRuntimeType
{
- Unknown,
- Docker
+ Docker,
+ Podman
}
}
diff --git a/src/SSHDebugPS/Docker/DockerContainerInstance.cs b/src/SSHDebugPS/Docker/ContainerInstance.cs
similarity index 50%
rename from src/SSHDebugPS/Docker/DockerContainerInstance.cs
rename to src/SSHDebugPS/Docker/ContainerInstance.cs
index e0df057a6..1e830d4cb 100644
--- a/src/SSHDebugPS/Docker/DockerContainerInstance.cs
+++ b/src/SSHDebugPS/Docker/ContainerInstance.cs
@@ -11,18 +11,18 @@
namespace Microsoft.SSHDebugPS.Docker
{
- public class DockerContainerInstance : ContainerInstance
+ public class ContainerInstance : IContainerInstance
{
///
- /// Create a DockerContainerInstance from the results of docker ps in JSON format
+ /// Create a ContainerInstance from the results of docker ps in JSON format
///
- public static bool TryCreate(string json, out DockerContainerInstance instance)
+ public static bool TryCreate(string json, out ContainerInstance instance)
{
instance = null;
try
{
JObject obj = JObject.Parse(json);
- instance = obj.ToObject();
+ instance = obj.ToObject();
}
catch (Exception e)
{
@@ -37,15 +37,15 @@ public static bool TryCreate(string json, out DockerContainerInstance instance)
return instance != null;
}
- protected DockerContainerInstance() { }
+ protected ContainerInstance() { }
#region JsonProperties
[JsonProperty("ID")]
- public override string Id { get; set; }
+ public virtual string Id { get; set; }
[JsonProperty("Names")]
- public override string Name { get; set; }
+ public virtual string Name { get; set; }
[JsonProperty(nameof(Image))]
public virtual string Image { get; protected set; }
@@ -67,24 +67,71 @@ protected DockerContainerInstance() { }
#endregion
- // Docker container names: only [a-zA-Z0-9][a-zA-Z0-9_.-] are allowed. It is also case sensitive
- protected override bool EqualsInternal(ContainerInstance instance)
+ #region IEquatable
+
+ public static bool operator ==(ContainerInstance left, ContainerInstance right)
{
- if (instance is DockerContainerInstance other)
+ if (left is null || right is null)
{
- // the id can be a partial on a container
- return String.Equals(Id, other.Id, StringComparison.Ordinal) ||
- Id.StartsWith(other.Id, StringComparison.Ordinal) ||
- other.Id.StartsWith(Id, StringComparison.Ordinal);
+ return ReferenceEquals(left, right);
}
+ return left.Equals(right);
+ }
+
+ public static bool operator !=(ContainerInstance left, ContainerInstance right)
+ {
+ return !(left == right);
+ }
+
+ public bool Equals(IContainerInstance instance)
+ {
+ if (instance is ContainerInstance container)
+ {
+ return this.EqualsInternal(container);
+ }
+
+ return false;
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (obj is IContainerInstance instance)
+ {
+ return this.Equals(instance);
+ }
return false;
}
- protected override int GetHashCodeInternal()
+ public override int GetHashCode()
+ {
+ return GetHashCodeInternal();
+ }
+
+ #endregion
+
+ #region Helper Methods
+
+ // Container names: only [a-zA-Z0-9][a-zA-Z0-9_.-] are allowed. It is also case sensitive
+ protected virtual bool EqualsInternal(ContainerInstance instance)
+ {
+ if (GetType() != instance.GetType())
+ {
+ return false;
+ }
+
+ // the id can be a partial on a container
+ return String.Equals(Id, instance.Id, StringComparison.Ordinal) ||
+ Id.StartsWith(instance.Id, StringComparison.Ordinal) ||
+ instance.Id.StartsWith(Id, StringComparison.Ordinal);
+ }
+
+ protected virtual int GetHashCodeInternal()
{
// Since IDs can be partial, we don't have a good way to get a good hash code.
return string.IsNullOrWhiteSpace(Id) ? 0 : Id.Substring(0,1).GetHashCode();
}
+
+ #endregion
}
}
diff --git a/src/SSHDebugPS/Docker/DockerConnection.cs b/src/SSHDebugPS/Docker/DockerConnection.cs
index 846245dbf..bf99504fb 100644
--- a/src/SSHDebugPS/Docker/DockerConnection.cs
+++ b/src/SSHDebugPS/Docker/DockerConnection.cs
@@ -20,8 +20,8 @@ internal class DockerConnection : PipeConnection
internal const string SshPrefixRegex = @"^[Ss]{2}[Hh]\s*=\s*";
internal const string SshPrefix = "ssh=";
- internal const string DockerHostPrefixRegex = @"^host\s*=\s*";
- internal const string DockerHostPrefix = "host=";
+ internal const string HostPrefixRegex = @"^host\s*=\s*";
+ internal const string HostPrefix = "host=";
internal const char Separator = ';';
internal static string CreateConnectionString(string containerName, string remoteConnectionName, string hostName)
@@ -34,7 +34,7 @@ internal static string CreateConnectionString(string containerName, string remot
if (!string.IsNullOrWhiteSpace(hostName))
{
- connectionString += Separator + DockerHostPrefix + hostName;
+ connectionString += Separator + HostPrefix + hostName;
}
return connectionString;
@@ -56,7 +56,7 @@ internal static bool TryConvertConnectionStringToSettings(string connectionStrin
if (connectionStrings.Length <= 3 && connectionStrings.Length > 0)
{
Regex SshRegex = new Regex(SshPrefixRegex);
- Regex dockerHostRegex = new Regex(DockerHostPrefixRegex);
+ Regex hostRegex = new Regex(HostPrefixRegex);
foreach (var item in connectionStrings)
{
@@ -66,9 +66,9 @@ internal static bool TryConvertConnectionStringToSettings(string connectionStrin
Match match = SshRegex.Match(segment);
remoteConnection = ConnectionManager.GetSSHConnection(segment.Substring(match.Length));
}
- else if (dockerHostRegex.IsMatch(segment))
+ else if (hostRegex.IsMatch(segment))
{
- Match match = dockerHostRegex.Match(segment);
+ Match match = hostRegex.Match(segment);
hostName = segment.Substring(match.Length);
}
else if (segment.Contains("="))
diff --git a/src/SSHDebugPS/Docker/DockerDiscoveryStrategy.cs b/src/SSHDebugPS/Docker/DockerDiscoveryStrategy.cs
index d47aad0b6..e6847967d 100644
--- a/src/SSHDebugPS/Docker/DockerDiscoveryStrategy.cs
+++ b/src/SSHDebugPS/Docker/DockerDiscoveryStrategy.cs
@@ -20,17 +20,17 @@ internal sealed class DockerDiscoveryStrategy : IContainerDiscoveryStrategy
public string ConnectionToolTip => UIResources.ConnectionToolTip;
public string HostnameAutomationName => UIResources.HostnameAutomationName;
- public IEnumerable GetLocalContainers(string hostname, out int totalContainers)
+ public IEnumerable GetLocalContainers(string hostname, out int totalContainers)
{
return DockerHelper.GetLocalDockerContainers(hostname, out totalContainers);
}
- public IEnumerable GetRemoteContainers(IConnection connection, string hostname, out int 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)
+ public void AssignPlatforms(IEnumerable containers, string hostname)
{
if (!containers.Any())
return;
@@ -44,7 +44,7 @@ public void AssignPlatforms(IEnumerable containers, str
if (lcow && serverOS.IndexOf("windows", StringComparison.OrdinalIgnoreCase) >= 0)
{
- foreach (DockerContainerInstance container in containers)
+ foreach (ContainerInstance container in containers)
{
string containerPlatform = string.Empty;
if (DockerHelper.TryGetContainerPlatform(hostname, container.Name, out containerPlatform))
@@ -60,7 +60,7 @@ public void AssignPlatforms(IEnumerable containers, str
else
{
string platform = textInfo.ToTitleCase(serverOS);
- foreach (DockerContainerInstance container in containers)
+ foreach (ContainerInstance container in containers)
{
container.Platform = platform;
}
@@ -68,7 +68,7 @@ public void AssignPlatforms(IEnumerable containers, str
}
else
{
- foreach (DockerContainerInstance container in containers)
+ foreach (ContainerInstance container in containers)
{
container.Platform = unknownOS;
}
diff --git a/src/SSHDebugPS/Docker/DockerHelper.cs b/src/SSHDebugPS/Docker/DockerHelper.cs
index f5a7a06ae..c433cb9d6 100644
--- a/src/SSHDebugPS/Docker/DockerHelper.cs
+++ b/src/SSHDebugPS/Docker/DockerHelper.cs
@@ -174,11 +174,11 @@ internal static bool TryGetContainerPlatform(string hostname, string containerNa
return true;
}
- internal static IEnumerable GetLocalDockerContainers(string hostname, out int totalContainers)
+ internal static IEnumerable GetLocalDockerContainers(string hostname, out int totalContainers)
{
totalContainers = 0;
int containerCount = 0;
- List containers = new List();
+ List containers = new List();
DockerCommandSettings settings = new DockerCommandSettings(hostname, false);
settings.SetCommand(dockerPSCommand, dockerPSArgs);
@@ -187,7 +187,7 @@ internal static IEnumerable GetLocalDockerContainers(st
{
if (args.Trim()[0] == '{')
{
- if (DockerContainerInstance.TryCreate(args, out DockerContainerInstance containerInstance))
+ if (ContainerInstance.TryCreate(args, out ContainerInstance containerInstance))
{
containers.Add(containerInstance);
}
@@ -205,7 +205,7 @@ internal static IEnumerable GetLocalDockerContainers(st
// Another fallback option would be to: docker inspect --format {{.State.Status}} which should return "running"
internal static bool IsContainerRunning(string hostName, string containerName, Connection remoteConnection)
{
- IEnumerable containers;
+ IEnumerable containers;
if (remoteConnection != null)
{
containers = GetRemoteDockerContainers(remoteConnection, hostName, out _);
@@ -228,7 +228,7 @@ internal static bool IsContainerRunning(string hostName, string containerName, C
return false;
}
- internal static IEnumerable GetRemoteDockerContainers(IConnection connection, string hostname, out int totalContainers)
+ internal static IEnumerable GetRemoteDockerContainers(IConnection connection, string hostname, out int totalContainers)
{
totalContainers = 0;
SSHConnection sshConnection = connection as SSHConnection;
@@ -239,7 +239,7 @@ internal static IEnumerable GetRemoteDockerContainers(I
return null;
}
- List containers = new List();
+ List containers = new List();
DockerCommandSettings settings = new DockerCommandSettings(hostname, true);
settings.SetCommand(dockerPSCommand, dockerPSArgs);
@@ -300,7 +300,7 @@ internal static IEnumerable GetRemoteDockerContainers(I
foreach (var item in outputLines)
{
- if (DockerContainerInstance.TryCreate(item, out DockerContainerInstance containerInstance))
+ if (ContainerInstance.TryCreate(item, out ContainerInstance containerInstance))
{
containers.Add(containerInstance);
}
diff --git a/src/SSHDebugPS/IContainerDiscoveryStrategy.cs b/src/SSHDebugPS/IContainerDiscoveryStrategy.cs
index c7fd87bb7..c64cff6ec 100644
--- a/src/SSHDebugPS/IContainerDiscoveryStrategy.cs
+++ b/src/SSHDebugPS/IContainerDiscoveryStrategy.cs
@@ -14,8 +14,8 @@ internal interface IContainerDiscoveryStrategy
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);
+ 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/Microsoft.SSHDebugPS.pkgdef b/src/SSHDebugPS/Microsoft.SSHDebugPS.pkgdef
index 66c5f6034..bd613b575 100644
--- a/src/SSHDebugPS/Microsoft.SSHDebugPS.pkgdef
+++ b/src/SSHDebugPS/Microsoft.SSHDebugPS.pkgdef
@@ -7,6 +7,11 @@
"PortPickerCLSID"="{91BDF293-E6A0-49C4-B033-6F36CFC4FF98}"
"Name"="Docker (Linux Container)"
+[$RootKey$\AD7Metrics\PortSupplier\{D4F2F3A5-6B7C-4E8D-9F0A-1B2C3D4E5F6A}]
+"CLSID"="{C9E1E1E4-3E5A-4F2B-8D1A-5C6F7A8B9D0E}"
+"PortPickerCLSID"="{E2A3B4C5-6D7E-4F8A-9B0C-1D2E3F4A5B6C}"
+"Name"="Podman (Linux Container)"
+
[$RootKey$\AD7Metrics\PortSupplier\{267B1341-AC92-44DC-94DF-2EE4205DD17E}]
"CLSID"="{B8587A49-00BD-4DEE-94B9-6EBF49003E04}"
"Name"="Windows Subsystem for Linux (WSL)"
@@ -47,6 +52,18 @@
"InprocServer32"="$WinDir$\SYSTEM32\MSCOREE.DLL"
"CodeBase"="$PackageFolder$\Microsoft.SSHDebugPS.dll"
+[$RootKey$\CLSID\{C9E1E1E4-3E5A-4F2B-8D1A-5C6F7A8B9D0E}]
+"Assembly"="Microsoft.SSHDebugPS"
+"Class"="Microsoft.SSHDebugPS.Podman.PodmanPortSupplier"
+"InprocServer32"="$WinDir$\SYSTEM32\MSCOREE.DLL"
+"CodeBase"="$PackageFolder$\Microsoft.SSHDebugPS.dll"
+
+[$RootKey$\CLSID\{E2A3B4C5-6D7E-4F8A-9B0C-1D2E3F4A5B6C}]
+"Assembly"="Microsoft.SSHDebugPS"
+"Class"="Microsoft.SSHDebugPS.Podman.PodmanLinuxPortPicker"
+"InprocServer32"="$WinDir$\SYSTEM32\MSCOREE.DLL"
+"CodeBase"="$PackageFolder$\Microsoft.SSHDebugPS.dll"
+
[$RootKey$\RuntimeConfiguration\dependentAssembly\codeBase\{7E3052B2-FB42-4E38-B22C-1FD281BD4413}]
"name"="Microsoft.SSHDebugPS"
; With local development workflow and release workflow, there are two publicKeyTokens but no way to specify both.
diff --git a/src/SSHDebugPS/Podman/PodmanConnection.cs b/src/SSHDebugPS/Podman/PodmanConnection.cs
new file mode 100644
index 000000000..839a776e3
--- /dev/null
+++ b/src/SSHDebugPS/Podman/PodmanConnection.cs
@@ -0,0 +1,155 @@
+// 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.IO;
+using System.Threading;
+using System.Diagnostics;
+using Microsoft.SSHDebugPS.Docker;
+using Microsoft.SSHDebugPS.Utilities;
+using Microsoft.VisualStudio.Debugger.Interop.UnixPortSupplier;
+using Microsoft.VisualStudio.Shell;
+
+namespace Microsoft.SSHDebugPS.Podman
+{
+ internal sealed class PodmanConnection : PipeConnection
+ {
+ #region Statics
+
+ internal static string CreateConnectionString(string containerName, string remoteConnectionName, string hostName)
+ {
+ // Reuses the same format as Docker connection strings
+ return DockerConnection.CreateConnectionString(containerName, remoteConnectionName, hostName);
+ }
+
+ internal static bool TryConvertConnectionStringToSettings(string connectionString, out PodmanContainerTransportSettings settings, out Connection remoteConnection)
+ {
+ ThreadHelper.ThrowIfNotOnUIThread();
+
+ if (DockerConnection.TryConvertConnectionStringToSettings(connectionString, out DockerContainerTransportSettings dockerSettings, out remoteConnection))
+ {
+ settings = new PodmanContainerTransportSettings(dockerSettings.HostName, dockerSettings.ContainerName, remoteConnection != null);
+ return true;
+ }
+
+ settings = null;
+ return false;
+ }
+
+ #endregion
+
+ private readonly string _containerName;
+ private readonly PodmanExecutionManager _executionManager;
+ private readonly PodmanContainerTransportSettings _settings;
+
+ public PodmanConnection(PodmanContainerTransportSettings settings, Connection outerConnection, string name)
+ : base(outerConnection, name)
+ {
+ _settings = settings;
+ _containerName = settings.ContainerName;
+ _executionManager = new PodmanExecutionManager(settings, outerConnection);
+ }
+
+ public override int ExecuteCommand(string commandText, int timeout, out string commandOutput, out string errorMessage)
+ {
+ return _executionManager.ExecuteCommand(commandText, timeout, out commandOutput, out errorMessage);
+ }
+
+ ///
+ public override void BeginExecuteAsyncCommand(string commandText, bool runInShell, IDebugUnixShellCommandCallback callback, out IDebugUnixShellAsyncCommand asyncCommand)
+ {
+ if (IsClosed)
+ {
+ throw new ObjectDisposedException(nameof(PipeConnection));
+ }
+
+ var commandRunner = GetExecCommandRunner(commandText, handleRawOutput: runInShell == false);
+ asyncCommand = new PipeAsyncCommand(commandRunner, callback);
+ }
+
+ public override void CopyFile(string sourcePath, string destinationPath)
+ {
+ PodmanCopySettings settings;
+ string tmpFile = null;
+
+ if (!Directory.Exists(sourcePath) && !File.Exists(sourcePath))
+ {
+ throw new ArgumentException(StringResources.Error_CopyFile_SourceNotFound.FormatCurrentCultureWithArgs(sourcePath), nameof(sourcePath));
+ }
+
+ if (OuterConnection != null)
+ {
+ tmpFile = "/tmp" + "/" + StringResources.CopyFile_TempFilePrefix + Guid.NewGuid();
+ OuterConnection.CopyFile(sourcePath, tmpFile);
+ settings = new PodmanCopySettings(_settings, tmpFile, destinationPath);
+ }
+ else
+ {
+ settings = new PodmanCopySettings(_settings, sourcePath, destinationPath);
+ }
+
+ ICommandRunner runner = GetCommandRunner(settings);
+
+ ManualResetEvent resetEvent = new ManualResetEvent(false);
+ int exitCode = -1;
+ runner.Closed += (e, args) =>
+ {
+ exitCode = args;
+ resetEvent.Set();
+ try
+ {
+ if (OuterConnection != null && !string.IsNullOrEmpty(tmpFile))
+ {
+ string output;
+ string errorMessage;
+ int exit = OuterConnection.ExecuteCommand("rm " + tmpFile, 5000, out output, out errorMessage);
+ Debug.Assert(exit == 0, FormattableString.Invariant($"Removing file exited with {exit} and message {output}. {errorMessage}"));
+ }
+ }
+ catch (Exception ex)
+ {
+ Debug.Fail("Exception thrown while cleaning up temp file. " + ex.Message);
+ }
+ };
+
+ runner.Start();
+
+ bool complete = resetEvent.WaitOne(Timeout.Infinite);
+ if (!complete || exitCode != 0)
+ {
+ throw new CommandFailedException(StringResources.Error_CopyFileFailed);
+ }
+ }
+
+ public override string GetUserHomeDirectory()
+ {
+ return ExecuteCommand("eval echo '~'", Timeout.Infinite);
+ }
+
+ private ICommandRunner GetExecCommandRunner(string commandText, bool handleRawOutput = false)
+ {
+ var execSettings = new PodmanExecSettings(this._settings, commandText, handleRawOutput);
+ return GetCommandRunner(execSettings, handleRawOutput: handleRawOutput);
+ }
+
+ private ICommandRunner GetCommandRunner(IPipeTransportSettings settings, bool handleRawOutput = false)
+ {
+ if (OuterConnection == null)
+ {
+ return LocalCommandRunner.CreateInstance(handleRawOutput, settings);
+ }
+ else
+ {
+ return new RemoteCommandRunner(settings, OuterConnection, handleRawOutput);
+ }
+ }
+
+ protected override string ProcFSErrorMessage
+ {
+ get
+ {
+ return String.Concat(base.ProcFSErrorMessage, Environment.NewLine, StringResources.Error_EnsurePodmanContainerIsLinux);
+ }
+ }
+ }
+}
diff --git a/src/SSHDebugPS/Podman/PodmanContainerInstance.cs b/src/SSHDebugPS/Podman/PodmanContainerInstance.cs
new file mode 100644
index 000000000..72f004ee9
--- /dev/null
+++ b/src/SSHDebugPS/Podman/PodmanContainerInstance.cs
@@ -0,0 +1,63 @@
+// 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.Diagnostics;
+using System.Linq;
+using Microsoft.DebugEngineHost;
+using Microsoft.SSHDebugPS.Docker;
+using Microsoft.SSHDebugPS.Utilities;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace Microsoft.SSHDebugPS.Podman
+{
+ public class PodmanContainerInstance : ContainerInstance
+ {
+ public static bool TryCreate(string json, out PodmanContainerInstance instance)
+ {
+ instance = null;
+ try
+ {
+ JObject obj = JObject.Parse(json);
+ instance = obj.ToObject();
+ }
+ catch (Exception e)
+ {
+ HostTelemetry.SendEvent(TelemetryHelper.Event_PodmanPSParseFailure, new KeyValuePair[] {
+ new KeyValuePair(TelemetryHelper.Property_ExceptionName, e.GetType().Name)
+ });
+
+ string error = e.ToString();
+ VsOutputWindowWrapper.WriteLine(StringResources.Error_PodmanPSParseFailed.FormatCurrentCultureWithArgs(json, error), StringResources.Podman_PSName);
+ Debug.Fail(error);
+ }
+ return instance != null;
+ }
+
+ [JsonProperty("Command")]
+ [JsonConverter(typeof(PodmanJsonConverter))]
+ public override string Command { get; protected set; }
+
+ [JsonProperty("Ports")]
+ [JsonConverter(typeof(PodmanJsonConverter))]
+ public override string Ports { get; set; }
+
+ [JsonProperty("Names")]
+ [JsonConverter(typeof(PodmanJsonConverter))]
+ public override string Name { get; set; }
+
+ protected override bool EqualsInternal(ContainerInstance instance)
+ {
+ if (instance is PodmanContainerInstance other)
+ {
+ return String.Equals(Id, other.Id, StringComparison.Ordinal) ||
+ Id.StartsWith(other.Id, StringComparison.Ordinal) ||
+ other.Id.StartsWith(Id, StringComparison.Ordinal);
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/SSHDebugPS/Podman/PodmanDiscoveryStrategy.cs b/src/SSHDebugPS/Podman/PodmanDiscoveryStrategy.cs
new file mode 100644
index 000000000..6d4571e52
--- /dev/null
+++ b/src/SSHDebugPS/Podman/PodmanDiscoveryStrategy.cs
@@ -0,0 +1,37 @@
+// 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;
+using Microsoft.SSHDebugPS.UI;
+
+namespace Microsoft.SSHDebugPS.Podman
+{
+ internal sealed class PodmanDiscoveryStrategy : IContainerDiscoveryStrategy
+ {
+ public string ConnectionLabel => UIResources.Podman_ConnectionLabel;
+ public string HostnameLabel => UIResources.Podman_HostnameLabel;
+ public string HostnameTip => UIResources.Podman_HostnameTip;
+ public string ConnectionToolTip => UIResources.Podman_ConnectionToolTip;
+ public string HostnameAutomationName => UIResources.Podman_HostnameAutomationName;
+
+ public IEnumerable GetLocalContainers(string hostname, out int totalContainers)
+ {
+ return PodmanHelper.GetLocalPodmanContainers(hostname, out totalContainers);
+ }
+
+ public IEnumerable GetRemoteContainers(IConnection connection, string hostname, out int totalContainers)
+ {
+ return PodmanHelper.GetRemotePodmanContainers(connection, hostname, out totalContainers);
+ }
+
+ public void AssignPlatforms(IEnumerable containers, string hostname)
+ {
+ // Podman only supports Linux containers
+ foreach (ContainerInstance container in containers)
+ {
+ container.Platform = "Linux";
+ }
+ }
+ }
+}
diff --git a/src/SSHDebugPS/Podman/PodmanExecutionManager.cs b/src/SSHDebugPS/Podman/PodmanExecutionManager.cs
new file mode 100644
index 000000000..aef6ccc8f
--- /dev/null
+++ b/src/SSHDebugPS/Podman/PodmanExecutionManager.cs
@@ -0,0 +1,19 @@
+// 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.Docker;
+
+namespace Microsoft.SSHDebugPS.Podman
+{
+ internal sealed class PodmanExecutionManager : DockerExecutionManager
+ {
+ public PodmanExecutionManager(PodmanContainerTransportSettings baseSettings, Connection outerConnection)
+ : base(baseSettings, outerConnection)
+ { }
+
+ protected override ContainerExecSettings CreateExecSettings(ContainerTargetTransportSettings baseSettings, string command, bool runInShell, bool makeInteractive)
+ {
+ return new PodmanExecSettings((PodmanContainerTransportSettings)baseSettings, command, runInShell, makeInteractive);
+ }
+ }
+}
diff --git a/src/SSHDebugPS/Podman/PodmanHelper.cs b/src/SSHDebugPS/Podman/PodmanHelper.cs
new file mode 100644
index 000000000..5d25de158
--- /dev/null
+++ b/src/SSHDebugPS/Podman/PodmanHelper.cs
@@ -0,0 +1,155 @@
+// 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.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using Microsoft.SSHDebugPS.Docker;
+using Microsoft.SSHDebugPS.SSH;
+using Microsoft.SSHDebugPS.Utilities;
+
+namespace Microsoft.SSHDebugPS.Podman
+{
+ public class PodmanHelper
+ {
+ private const string podmanPSCommand = "ps";
+ private const string podmanPSArgs = "-f status=running --no-trunc --format \"{{json .}}\"";
+
+
+ internal static IEnumerable GetLocalPodmanContainers(string hostname, out int totalContainers)
+ {
+ totalContainers = 0;
+ int containerCount = 0;
+ List containers = new List();
+
+ PodmanCommandSettings settings = new PodmanCommandSettings(hostname, false);
+ settings.SetCommand(podmanPSCommand, podmanPSArgs);
+
+ DockerHelper.RunContainerCommand(settings, delegate (string args)
+ {
+ if (args.Trim()[0] == '{')
+ {
+ if (PodmanContainerInstance.TryCreate(args, out PodmanContainerInstance containerInstance))
+ {
+ containers.Add(containerInstance);
+ }
+ containerCount++;
+ }
+ });
+
+ totalContainers = containerCount;
+ return containers;
+ }
+
+ ///
+ /// Checks if the specified container is in the list of containers from the target host.
+ ///
+ internal static bool IsContainerRunning(string hostName, string containerName, Connection remoteConnection)
+ {
+ IEnumerable containers;
+ if (remoteConnection != null)
+ {
+ containers = GetRemotePodmanContainers(remoteConnection, hostName, out _);
+ }
+ else
+ {
+ containers = GetLocalPodmanContainers(hostName, out _);
+ }
+
+ if (containers != null)
+ {
+ if (containers.Any(container => string.Equals(container.Name, containerName, StringComparison.Ordinal)
+ || container.Id.StartsWith(containerName, StringComparison.Ordinal)))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ internal static IEnumerable GetRemotePodmanContainers(IConnection connection, string hostname, out int totalContainers)
+ {
+ totalContainers = 0;
+ SSHConnection sshConnection = connection as SSHConnection;
+ List outputLines = new List();
+ StringBuilder errorSB = new StringBuilder();
+ if (sshConnection == null)
+ {
+ return null;
+ }
+
+ List containers = new List();
+
+ PodmanCommandSettings settings = new PodmanCommandSettings(hostname, true);
+ settings.SetCommand(podmanPSCommand, podmanPSArgs);
+
+ RemoteCommandRunner commandRunner = new RemoteCommandRunner(settings, sshConnection, handleRawOutput: false);
+
+ ManualResetEvent resetEvent = new ManualResetEvent(false);
+ int exitCode = 0;
+ commandRunner.ErrorOccured += ((sender, args) =>
+ {
+ errorSB.Append(args);
+ });
+
+ commandRunner.Closed += ((sender, args) =>
+ {
+ exitCode = args;
+ resetEvent.Set();
+ });
+
+ commandRunner.OutputReceived += ((sender, line) =>
+ {
+ if (!string.IsNullOrWhiteSpace(line))
+ {
+ Debug.Assert(line.IndexOf('\n') < 0, "Why does `line` have embedded newline characters?");
+
+ if (line.Trim()[0] != '{')
+ {
+ errorSB.Append(line);
+ }
+
+ outputLines.Add(line);
+ }
+ });
+
+ commandRunner.Start();
+
+ bool cancellationRequested = false;
+ VS.VSOperationWaiter.Wait(UIResources.QueryingForContainersMessage, false, (cancellationToken) =>
+ {
+ while (!resetEvent.WaitOne(2000) && !cancellationToken.IsCancellationRequested)
+ { }
+ cancellationRequested = cancellationToken.IsCancellationRequested;
+ });
+
+ if (!cancellationRequested)
+ {
+ if (exitCode != 0)
+ {
+ string exceptionMessage = UIResources.CommandExecutionErrorWithExitCodeFormat.FormatCurrentCultureWithArgs(
+ "{0} {1}".FormatInvariantWithArgs(settings.Command, settings.CommandArgs),
+ exitCode,
+ errorSB.ToString());
+
+ throw new CommandFailedException(exceptionMessage);
+ }
+
+ foreach (var item in outputLines)
+ {
+ if (PodmanContainerInstance.TryCreate(item, out PodmanContainerInstance containerInstance))
+ {
+ containers.Add(containerInstance);
+ totalContainers++;
+ }
+ }
+ }
+
+ return containers;
+ }
+ }
+}
diff --git a/src/SSHDebugPS/Podman/PodmanJsonConverter.cs b/src/SSHDebugPS/Podman/PodmanJsonConverter.cs
new file mode 100644
index 000000000..c82819bba
--- /dev/null
+++ b/src/SSHDebugPS/Podman/PodmanJsonConverter.cs
@@ -0,0 +1,52 @@
+// 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.Linq;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace Microsoft.SSHDebugPS.Podman
+{
+ // Handles JSON values that may be a string, array of strings, or array of objects (port mappings).
+ internal sealed class PodmanJsonConverter : JsonConverter
+ {
+ public override bool CanConvert(Type objectType) => objectType == typeof(string);
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ var token = JToken.Load(reader);
+ switch (token.Type)
+ {
+ case JTokenType.String:
+ return token.Value();
+ case JTokenType.Array:
+ return string.Join(", ", token.Select(t =>
+ {
+ if (t.Type == JTokenType.String)
+ return t.Value();
+ if (t.Type == JTokenType.Object)
+ {
+ // Handle Podman port mapping objects: {"host_ip":"0.0.0.0","container_port":80,"host_port":8080,"range":1,"protocol":"tcp"}
+ var hostIp = t.Value("host_ip") ?? "0.0.0.0";
+ var hostPort = t.Value("host_port");
+ var containerPort = t.Value("container_port");
+ var protocol = t.Value("protocol") ?? "tcp";
+ if (hostPort.HasValue && containerPort.HasValue)
+ return $"{hostIp}:{hostPort}->{containerPort}/{protocol}";
+ }
+ return t.ToString();
+ }));
+ case JTokenType.Null:
+ return string.Empty;
+ default:
+ return token.ToString();
+ }
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ writer.WriteValue(value?.ToString());
+ }
+ }
+}
diff --git a/src/SSHDebugPS/Podman/PodmanPort.cs b/src/SSHDebugPS/Podman/PodmanPort.cs
new file mode 100644
index 000000000..edda987a3
--- /dev/null
+++ b/src/SSHDebugPS/Podman/PodmanPort.cs
@@ -0,0 +1,20 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Microsoft.VisualStudio.Shell;
+
+namespace Microsoft.SSHDebugPS.Podman
+{
+ internal sealed class PodmanPort : AD7Port
+ {
+ public PodmanPort(AD7PortSupplier portSupplier, string name, bool isInAddPort)
+ : base(portSupplier, name, isInAddPort)
+ { }
+
+ protected override Connection GetConnectionInternal()
+ {
+ ThreadHelper.ThrowIfNotOnUIThread();
+ return ConnectionManager.GetPodmanConnection(Name, supportSSHConnections: true);
+ }
+ }
+}
diff --git a/src/SSHDebugPS/Podman/PodmanPortPicker.cs b/src/SSHDebugPS/Podman/PodmanPortPicker.cs
new file mode 100644
index 000000000..cd4fd2868
--- /dev/null
+++ b/src/SSHDebugPS/Podman/PodmanPortPicker.cs
@@ -0,0 +1,18 @@
+// 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.Runtime.InteropServices;
+using Microsoft.SSHDebugPS.Docker;
+using Microsoft.VisualStudio.Shell;
+
+namespace Microsoft.SSHDebugPS.Podman
+{
+ [ComVisible(true)]
+ [Guid("E2A3B4C5-6D7E-4F8A-9B0C-1D2E3F4A5B6C")]
+ public class PodmanLinuxPortPicker : DockerPortPickerBase
+ {
+ internal override bool SupportSSHConnections => true;
+ internal override ContainerRuntimeType RuntimeType => ContainerRuntimeType.Podman;
+ }
+}
diff --git a/src/SSHDebugPS/Podman/PodmanPortSupplier.cs b/src/SSHDebugPS/Podman/PodmanPortSupplier.cs
new file mode 100644
index 000000000..82031b256
--- /dev/null
+++ b/src/SSHDebugPS/Podman/PodmanPortSupplier.cs
@@ -0,0 +1,58 @@
+// 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.Runtime.InteropServices;
+using Microsoft.VisualStudio.Debugger.Interop;
+
+namespace Microsoft.SSHDebugPS.Podman
+{
+ [ComVisible(true)]
+ [Guid("C9E1E1E4-3E5A-4F2B-8D1A-5C6F7A8B9D0E")]
+ internal sealed class PodmanPortSupplier : AD7PortSupplier
+ {
+ private readonly Guid _Id = new Guid("D4F2F3A5-6B7C-4E8D-9F0A-1B2C3D4E5F6A");
+
+ protected override Guid Id { get { return _Id; } }
+ protected override string Name { get { return StringResources.Podman_PSName; } }
+ protected override string Description { get { return StringResources.Podman_PSDescription; } }
+
+ public PodmanPortSupplier() : base()
+ { }
+
+ public override int AddPort(IDebugPortRequest2 request, out IDebugPort2 port)
+ {
+ string name;
+ HR.Check(request.GetPortName(out name));
+
+ if (!string.IsNullOrWhiteSpace(name))
+ {
+ AD7Port newPort = new PodmanPort(this, name, isInAddPort: true);
+
+ if (newPort.IsConnected)
+ {
+ port = newPort;
+ return HR.S_OK;
+ }
+ }
+
+ port = null;
+ return HR.E_REMOTE_CONNECT_USER_CANCELED;
+ }
+
+ public override unsafe int EnumPersistedPorts(BSTR_ARRAY portNames, out IEnumDebugPorts2 portEnum)
+ {
+ IDebugPort2[] ports = new IDebugPort2[portNames.dwCount];
+ for (int c = 0; c < portNames.dwCount; c++)
+ {
+ char* bstrPortName = ((char**)portNames.Members)[c];
+ string name = new string(bstrPortName);
+
+ ports[c] = new PodmanPort(this, name, isInAddPort: false);
+ }
+
+ portEnum = new AD7PortEnum(ports);
+ return HR.S_OK;
+ }
+ }
+}
diff --git a/src/SSHDebugPS/Podman/PodmanTransportSettings.cs b/src/SSHDebugPS/Podman/PodmanTransportSettings.cs
new file mode 100644
index 000000000..1dd1d1248
--- /dev/null
+++ b/src/SSHDebugPS/Podman/PodmanTransportSettings.cs
@@ -0,0 +1,46 @@
+// 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.Podman
+{
+ internal sealed class PodmanContainerTransportSettings : ContainerTargetTransportSettings
+ {
+ internal const string WindowsExeName = "podman.exe";
+ internal const string UnixExeName = "podman";
+ internal const string HostFlag = "--url \"{0}\"";
+
+ public PodmanContainerTransportSettings(string hostname, string containerName, bool hostIsUnix)
+ : base(hostname, containerName, hostIsUnix, WindowsExeName, UnixExeName, HostFlag)
+ { }
+
+ public PodmanContainerTransportSettings(PodmanContainerTransportSettings settings)
+ : base(settings)
+ { }
+ }
+
+ internal sealed class PodmanExecSettings : ContainerExecSettings
+ {
+ public PodmanExecSettings(PodmanContainerTransportSettings settings, string command, bool runInShell, bool makeInteractive = true)
+ : base(settings, command, runInShell, makeInteractive)
+ { }
+ }
+
+ internal sealed class PodmanCopySettings : ContainerCopySettings
+ {
+ public PodmanCopySettings(string hostname, string sourcePath, string destinationPath, string containerName, bool hostIsUnix)
+ : base(hostname, sourcePath, destinationPath, containerName, hostIsUnix, PodmanContainerTransportSettings.WindowsExeName, PodmanContainerTransportSettings.UnixExeName, PodmanContainerTransportSettings.HostFlag)
+ { }
+
+ public PodmanCopySettings(PodmanContainerTransportSettings settings, string sourcePath, string destinationPath)
+ : base(settings, sourcePath, destinationPath)
+ { }
+ }
+
+ internal sealed class PodmanCommandSettings : ContainerCommandSettings
+ {
+ public PodmanCommandSettings(string hostname, bool hostIsUnix)
+ : base(hostname, hostIsUnix, PodmanContainerTransportSettings.WindowsExeName, PodmanContainerTransportSettings.UnixExeName, PodmanContainerTransportSettings.HostFlag)
+ { }
+ }
+}
+
diff --git a/src/SSHDebugPS/StringResources.Designer.cs b/src/SSHDebugPS/StringResources.Designer.cs
index 78a19bb65..5706b852b 100644
--- a/src/SSHDebugPS/StringResources.Designer.cs
+++ b/src/SSHDebugPS/StringResources.Designer.cs
@@ -114,6 +114,33 @@ internal static string Docker_PSName {
}
}
+ ///
+ /// Looks up a localized string similar to Podman (Linux Container).
+ ///
+ internal static string Podman_PSName {
+ get {
+ return ResourceManager.GetString("Podman_PSName", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The Podman (Linux Container) connection type allows Visual Studio to connect to Podman containers running locally or remotely (using SSH)..
+ ///
+ internal static string Podman_PSDescription {
+ get {
+ return ResourceManager.GetString("Podman_PSDescription", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Failed to parse output of '{0}': {1}.
+ ///
+ internal static string Error_PodmanPSParseFailed {
+ get {
+ return ResourceManager.GetString("Error_PodmanPSParseFailed", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Command failed to execute.
///
@@ -195,6 +222,15 @@ internal static string Error_EnsureDockerContainerIsLinux {
}
}
+ ///
+ /// Looks up a localized string similar to Ensure the selected Podman Connection target is a Linux container..
+ ///
+ internal static string Error_EnsurePodmanContainerIsLinux {
+ get {
+ return ResourceManager.GetString("Error_EnsurePodmanContainerIsLinux", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Unable to parse exit code..
///
diff --git a/src/SSHDebugPS/StringResources.resx b/src/SSHDebugPS/StringResources.resx
index af44a77c5..571c143fb 100644
--- a/src/SSHDebugPS/StringResources.resx
+++ b/src/SSHDebugPS/StringResources.resx
@@ -139,6 +139,16 @@
Docker (Linux Container)
+
+ Podman (Linux Container)
+
+
+ The Podman (Linux Container) connection type allows Visual Studio to connect to Podman containers running locally or remotely (using SSH).
+
+
+ Failed to parse output of '{0}': {1}
+ {0} is the JSON line that failed to parse. {1} is the exception details.
+
Command failed to execute
@@ -165,6 +175,9 @@
Ensure the selected Docker Connection target is a Linux container.
+
+ Ensure the selected Podman Connection target is a Linux container.
+
Failed to parse json '{0}'.\r\nError: '{1}'
{0} is a json output item from the output 'docker ps' and {1} is the error message
diff --git a/src/SSHDebugPS/UI/ContainerInstance.cs b/src/SSHDebugPS/UI/ContainerInstance.cs
index 4ec33929e..fa182104a 100644
--- a/src/SSHDebugPS/UI/ContainerInstance.cs
+++ b/src/SSHDebugPS/UI/ContainerInstance.cs
@@ -1,79 +1,11 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Diagnostics;
-using System.Globalization;
-using System.Linq;
-using System.Runtime.CompilerServices;
-using System.Text;
-using System.Threading.Tasks;
-
namespace Microsoft.SSHDebugPS.Docker
{
- public interface IContainerInstance : IEquatable
+ public interface IContainerInstance : System.IEquatable
{
string Id { get; }
string Name { get; }
}
-
- public abstract class ContainerInstance : IContainerInstance
- {
- public abstract string Id { get; set; }
- public abstract string Name { get; set; }
-
- #region IEquatable
-
- public static bool operator ==(ContainerInstance left, ContainerInstance right)
- {
- if (left is null || right is null)
- {
- return ReferenceEquals(left, right);
- }
-
- return left.Equals(right);
- }
-
- public static bool operator !=(ContainerInstance left, ContainerInstance right)
- {
- return !(left == right);
- }
-
- public bool Equals(IContainerInstance instance)
- {
- if (!ReferenceEquals(null, instance) && instance is ContainerInstance container)
- {
- return this.EqualsInternal(container);
- }
-
- return false;
- }
-
- public override bool Equals(object obj)
- {
- if (obj is IContainerInstance instance)
- {
- return this.Equals(instance);
- }
- return false;
- }
-
- public override int GetHashCode()
- {
- return GetHashCodeInternal();
- }
-
- #endregion
-
- #region Helper Methods
-
- protected abstract bool EqualsInternal(ContainerInstance instance);
- protected abstract int GetHashCodeInternal();
-
- #endregion
- }
}
diff --git a/src/SSHDebugPS/UI/UIResources.Designer.cs b/src/SSHDebugPS/UI/UIResources.Designer.cs
index 12e90b5c2..fce792a7a 100644
--- a/src/SSHDebugPS/UI/UIResources.Designer.cs
+++ b/src/SSHDebugPS/UI/UIResources.Designer.cs
@@ -267,6 +267,51 @@ public static string HostnameAutomationName {
}
}
+ ///
+ /// Looks up a localized string similar to Podman _CLI host:.
+ ///
+ public static string Podman_ConnectionLabel {
+ get {
+ return ResourceManager.GetString("Podman_ConnectionLabel", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Podman _host (Optional):.
+ ///
+ public static string Podman_HostnameLabel {
+ get {
+ return ResourceManager.GetString("Podman_HostnameLabel", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Specify a URL for connecting to a different Podman host. .
+ ///
+ public static string Podman_HostnameTip {
+ get {
+ return ResourceManager.GetString("Podman_HostnameTip", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Location from which to run the Podman CLI....
+ ///
+ public static string Podman_ConnectionToolTip {
+ get {
+ return ResourceManager.GetString("Podman_ConnectionToolTip", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Optional Podman Host name.
+ ///
+ public static string Podman_HostnameAutomationName {
+ get {
+ return ResourceManager.GetString("Podman_HostnameAutomationName", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Docker _host (Optional):.
///
diff --git a/src/SSHDebugPS/UI/UIResources.resx b/src/SSHDebugPS/UI/UIResources.resx
index 7ce103459..e06689608 100644
--- a/src/SSHDebugPS/UI/UIResources.resx
+++ b/src/SSHDebugPS/UI/UIResources.resx
@@ -239,6 +239,22 @@
Optional Docker Host name
+
+ Podman _CLI host:
+
+
+ Podman _host (Optional):
+ Hostname for Podman daemon configuration
+
+
+ Specify a URL for connecting to a different Podman host.
+
+
+ Location from which to run the Podman CLI. To manage remote connections, in the menu go to Tools -> Options and find Cross Platform -> Connection Manager.
+
+
+ Optional Podman Host name
+
Container List
diff --git a/src/SSHDebugPS/UI/ViewModels/ContainerPickerViewModel.cs b/src/SSHDebugPS/UI/ViewModels/ContainerPickerViewModel.cs
index 0f19c2f20..ea58f08de 100644
--- a/src/SSHDebugPS/UI/ViewModels/ContainerPickerViewModel.cs
+++ b/src/SSHDebugPS/UI/ViewModels/ContainerPickerViewModel.cs
@@ -11,6 +11,7 @@
using System.Windows.Threading;
using liblinux.Persistence;
using Microsoft.SSHDebugPS.Docker;
+using Microsoft.SSHDebugPS.Podman;
using Microsoft.SSHDebugPS.SSH;
using Microsoft.SSHDebugPS.Utilities;
using System.Globalization;
@@ -151,6 +152,8 @@ private static IContainerDiscoveryStrategy CreateDiscoveryStrategy(ContainerRunt
{
case ContainerRuntimeType.Docker:
return new DockerDiscoveryStrategy();
+ case ContainerRuntimeType.Podman:
+ return new PodmanDiscoveryStrategy();
default:
Debug.Fail($"Unsupported container runtime type: {runtimeType}");
return null;
@@ -183,7 +186,7 @@ private void RefreshContainersListInternal()
return;
}
- IEnumerable containers;
+ IEnumerable containers;
if (SelectedConnection is LocalConnectionViewModel)
{
diff --git a/src/SSHDebugPS/UI/ViewModels/ContainerViewModel.cs b/src/SSHDebugPS/UI/ViewModels/ContainerViewModel.cs
index b36798a10..1a0f4df51 100644
--- a/src/SSHDebugPS/UI/ViewModels/ContainerViewModel.cs
+++ b/src/SSHDebugPS/UI/ViewModels/ContainerViewModel.cs
@@ -131,9 +131,9 @@ public bool IsSelected
}
public class DockerContainerViewModel
- : ContainerViewModel
+ : ContainerViewModel
{
- public DockerContainerViewModel(DockerContainerInstance instance)
+ public DockerContainerViewModel(ContainerInstance instance)
: base(instance)
{ }
diff --git a/src/SSHDebugPS/Utilities/TelemetryHelper.cs b/src/SSHDebugPS/Utilities/TelemetryHelper.cs
index 85a13f327..8f019b1c2 100644
--- a/src/SSHDebugPS/Utilities/TelemetryHelper.cs
+++ b/src/SSHDebugPS/Utilities/TelemetryHelper.cs
@@ -6,6 +6,7 @@ namespace Microsoft.SSHDebugPS.Utilities
internal static class TelemetryHelper
{
public const string Event_DockerPSParseFailure = @"VS/Diagnostics/Debugger/SSHDebugPS/DockerPSParseFailure";
+ public const string Event_PodmanPSParseFailure = @"VS/Diagnostics/Debugger/SSHDebugPS/PodmanPSParseFailure";
public const string Event_ProcFSError = @"VS/Diagnostics/Debugger/SSHDebugPS/ProcFSError";
public static readonly string Property_ExceptionName = "vs.diagnostics.debugger.ExceptionName";
}