Adding Linux PowerShell support
This change introduces netstandard1.6 along with support for open PowerShell, included new functionality for Linux. This includes: - Changing cancellation pattern for stdin to explicitly close instead of relying on cancellation token handling. - Adding basic input/output parsing for Linux. - Adding support for unix sockets by default when on Linux.
This commit is contained in:
Родитель
23a55896e2
Коммит
8dc8819e6d
|
@ -1,4 +1,3 @@
|
|||
|
||||
pingme.txt
|
||||
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
// ${fileDirname}: the current opened file's dirname
|
||||
// ${fileExtname}: the current opened file's extension
|
||||
// ${cwd}: the current working directory of the spawned process
|
||||
|
||||
{
|
||||
"version": "0.1.0",
|
||||
"command": "cmd",
|
||||
|
@ -13,22 +12,41 @@
|
|||
"showOutput": "silent",
|
||||
"args": [
|
||||
"/c"
|
||||
],
|
||||
],
|
||||
"tasks": [
|
||||
{
|
||||
"taskName": "restore",
|
||||
"suppressTaskName": true,
|
||||
"args" : ["dotnet", "restore"],
|
||||
"args": [
|
||||
"dotnet",
|
||||
"restore"
|
||||
],
|
||||
"showOutput": "always",
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"taskName": "build",
|
||||
"suppressTaskName": true,
|
||||
"args" : [
|
||||
"dotnet", "publish", "${cwd}\\src\\Docker.PowerShell", "-o", "${cwd}\\src\\Docker.PowerShell\\bin\\Module\\Docker", "-r", "win",
|
||||
"&&", "powershell", "-executionpolicy", "bypass", "-c", "New-ExternalHelp", "-Path", "${cwd}\\src\\Docker.PowerShell\\Help", "-OutputPath", "${cwd}\\src\\Docker.PowerShell\\bin\\Module\\Docker", "-Force"
|
||||
],
|
||||
"args": [
|
||||
"dotnet",
|
||||
"publish",
|
||||
"${cwd}\\src\\Docker.PowerShell",
|
||||
"-f",
|
||||
"net46",
|
||||
"-o",
|
||||
"${cwd}\\src\\Docker.PowerShell\\bin\\Module\\Docker",
|
||||
"&&",
|
||||
"powershell",
|
||||
"-executionpolicy",
|
||||
"bypass",
|
||||
"-c",
|
||||
"New-ExternalHelp",
|
||||
"-Path",
|
||||
"${cwd}\\src\\Docker.PowerShell\\Help",
|
||||
"-OutputPath",
|
||||
"${cwd}\\src\\Docker.PowerShell\\bin\\Module\\Docker",
|
||||
"-Force"
|
||||
],
|
||||
"showOutput": "always",
|
||||
"isBuildCommand": true,
|
||||
"problemMatcher": "$msCompile"
|
||||
|
@ -36,26 +54,59 @@
|
|||
{
|
||||
"taskName": "test",
|
||||
"suppressTaskName": true,
|
||||
"args": ["start", "powershell", "-executionpolicy", "bypass", "-noexit", "-c", "ipmo", ".\\src\\Docker.PowerShell\\bin\\Module\\Docker"],
|
||||
"args": [
|
||||
"start",
|
||||
"powershell",
|
||||
"-executionpolicy",
|
||||
"bypass",
|
||||
"-noexit",
|
||||
"-c",
|
||||
"ipmo",
|
||||
".\\src\\Docker.PowerShell\\bin\\Module\\Docker"
|
||||
],
|
||||
"showOutput": "never",
|
||||
"isTestCommand": true
|
||||
},
|
||||
{
|
||||
"taskName": "clean",
|
||||
"suppressTaskName": true,
|
||||
"args": ["del", "/Q", "/S", ".\\src\\*project.lock.json", "&&", "rmdir", "/Q", "/S", ".\\src\\Docker.PowerShell\\bin"],
|
||||
"args": [
|
||||
"del",
|
||||
"/Q",
|
||||
"/S",
|
||||
".\\src\\*project.lock.json",
|
||||
"&&",
|
||||
"rmdir",
|
||||
"/Q",
|
||||
"/S",
|
||||
".\\src\\Docker.PowerShell\\bin"
|
||||
],
|
||||
"showOutput": "always"
|
||||
},
|
||||
{
|
||||
"taskName": "update-help",
|
||||
"suppressTaskName": true,
|
||||
"args": [
|
||||
"powershell", "-executionpolicy", "bypass", "-c", "ipmo", ".\\src\\Docker.PowerShell\\bin\\Module\\Docker", ";",
|
||||
"New-MarkdownHelp", "-Module", "Docker", "-OutputFolder", ".\\src\\Docker.PowerShell\\Help", "-ErrorAction", "SilentlyContinue", ";",
|
||||
"Update-MarkdownHelp", "-Path", ".\\src\\Docker.PowerShell\\Help"
|
||||
"powershell",
|
||||
"-executionpolicy",
|
||||
"bypass",
|
||||
"-c",
|
||||
"ipmo",
|
||||
".\\src\\Docker.PowerShell\\bin\\Module\\Docker",
|
||||
";",
|
||||
"New-MarkdownHelp",
|
||||
"-Module",
|
||||
"Docker",
|
||||
"-OutputFolder",
|
||||
".\\src\\Docker.PowerShell\\Help",
|
||||
"-ErrorAction",
|
||||
"SilentlyContinue",
|
||||
";",
|
||||
"Update-MarkdownHelp",
|
||||
"-Path",
|
||||
".\\src\\Docker.PowerShell\\Help"
|
||||
],
|
||||
"showOutput": "always"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<packageSources>
|
||||
<add key="ps-feed" value="https://powershell.myget.org/F/powershell-core/api/v3/index.json" />
|
||||
</packageSources>
|
||||
</configuration>
|
32
README.md
32
README.md
|
@ -35,7 +35,7 @@ feedback).
|
|||
|
||||
The dev builds are updated for every commit to master and are released to
|
||||
https://ci.appveyor.com/nuget/docker-powershell-dev. To install the latest
|
||||
build, in PowerShell run:
|
||||
build, in PowerShell 5.0 run:
|
||||
|
||||
> Register-PSRepository -Name DockerPS-Dev -SourceLocation https://ci.appveyor.com/nuget/docker-powershell-dev
|
||||
|
||||
|
@ -46,11 +46,31 @@ After this, you can update to new development builds with just:
|
|||
|
||||
> Update-Module Docker
|
||||
|
||||
#### Linux and Mac OS X
|
||||
|
||||
If you are using cross-platform [PowerShell 6.0](https://github.com/PowerShell/PowerShell), you will need to use a different
|
||||
feed and manually unpack the module to the correct location:
|
||||
|
||||
> Register-PSRepository -Name DockerPS-Dev -SourceLocation https://ci.appveyor.com/nuget/docker-powershell-dev
|
||||
|
||||
> Save-Module Docker-Core -Path $env:PSModulePath.split(";")[-1]
|
||||
|
||||
> Rename-Item "$($env:PSModulePath.split(";")[-1])/Docker-Core" "$($env:PSModulePath.split(";")[-1])/Docker"
|
||||
|
||||
After this, updating to a new development build will require closing any PowerShell windows that have
|
||||
loaded the module and deleting the folder:
|
||||
|
||||
> Remove-Item -force -recur "$($env:PSModulePath.split(";")[-1])/Docker"
|
||||
|
||||
Then you can re-run the steps to find, download, and rename the module from the section above.
|
||||
|
||||
(NOTE: As the PowerShellGet support story improves for PowerShell 6.0 we will update these instructions.)
|
||||
|
||||
#### Need an offline workflow?
|
||||
|
||||
From an internet connected machine:
|
||||
|
||||
> Save-Package Docker -Source DockerPS-Dev -Path .
|
||||
> Save-Module Docker -Path .
|
||||
|
||||
Copy the entire folder `.\Docker` to the destination at: `%ProgramFiles%\WindowsPowerShell\Modules`
|
||||
|
||||
|
@ -93,7 +113,13 @@ Once these are installed, you can run:
|
|||
|
||||
> dotnet restore
|
||||
|
||||
> dotnet publish .\src\Docker.PowerShell -o .\src\Docker.PowerShell\bin\Module\Docker -r win
|
||||
> dotnet publish .\src\Docker.PowerShell -o .\src\Docker.PowerShell\bin\Module\Docker -f net46
|
||||
|
||||
or for a module targetted at cross-platform PowerShell:
|
||||
|
||||
> dotnet restore
|
||||
|
||||
> dotnet publish .\src\Docker.PowerShell -o .\src\Docker.PowerShell\bin\Module\Docker -f netstandard1.6
|
||||
|
||||
This will produce the PowerShell module at
|
||||
`.\src\Docker.PowerShell\bin\Module\Docker` in the project folder.
|
||||
|
|
19
appveyor.yml
19
appveyor.yml
|
@ -1,13 +1,12 @@
|
|||
version: '0.0.0.{build}'
|
||||
pull_requests:
|
||||
do_not_increment_build_number: true
|
||||
image: WMF 5
|
||||
init:
|
||||
- ps: (new-object net.webclient).DownloadFile('https://dotnetcli.blob.core.windows.net/dotnet/beta/Installers/Latest/dotnet-dev-win-x64.latest.exe', "c:/dotnet-install.exe")
|
||||
- ps: Start-Process c:\dotnet-install.exe -ArgumentList "/install","/quiet" -Wait
|
||||
- ps: Install-PackageProvider NuGet -Force
|
||||
- ps: Install-Module platyPS -Force
|
||||
- ps: (new-object net.webclient).DownloadFile('https://dotnetcli.blob.core.windows.net/dotnet/Sdk/rel-1.0.0/dotnet-dev-win-x64.latest.exe', "c:/dotnet-install.exe")
|
||||
- ps: Start-Process c:\dotnet-install.exe -ArgumentList "/install","/quiet" -Wait
|
||||
install:
|
||||
- ps: Install-Module platyPS -Force
|
||||
- cmd: git submodule update --init --recursive
|
||||
nuget:
|
||||
disable_publish_on_pr: true
|
||||
|
@ -31,14 +30,18 @@ build_script:
|
|||
$projectFile = "src\Docker.PowerShell\project.json"
|
||||
$project = Get-Content $projectFile -Raw | ConvertFrom-Json
|
||||
$project.version = $version
|
||||
ConvertTo-Json $project | Out-File -Encoding UTF8 $projectFile
|
||||
ConvertTo-Json $project -Depth 100 | Out-File -Encoding UTF8 $projectFile
|
||||
|
||||
$manifest = "src\Docker.PowerShell\Docker.psd1"
|
||||
(Get-Content $manifest -Raw) -replace "ModuleVersion.+","ModuleVersion = '$psversion'" | Out-File $manifest
|
||||
- ps: dotnet restore
|
||||
- ps: dotnet publish -r win -o $pwd\bin -c Release $pwd\src\Docker.PowerShell
|
||||
- ps: New-ExternalHelp -Path src\Docker.PowerShell\Help -OutputPath bin
|
||||
- ps: nuget pack src/Docker.PowerShell/Docker.nuspec -BasePath bin -OutputDirectory bin -Symbols -Version $psversion
|
||||
- ps: dotnet build **/project.json
|
||||
- ps: dotnet publish -f net46 -o $pwd\bin\net46 -c Release $pwd\src\Docker.PowerShell
|
||||
- ps: New-ExternalHelp -Path src\Docker.PowerShell\Help -OutputPath bin\net46
|
||||
- ps: nuget pack src/Docker.PowerShell/Docker.nuspec -BasePath bin\net46 -OutputDirectory bin -Symbols -Version $psversion
|
||||
- ps: dotnet publish -f netstandard1.6 -o $pwd\bin\netstandard1.6 -c Release $pwd\src\Docker.PowerShell
|
||||
- ps: New-ExternalHelp -Path src\Docker.PowerShell\Help -OutputPath bin\netstandard1.6
|
||||
- ps: nuget pack src/Docker.PowerShell/Docker-Core.nuspec -BasePath bin\netstandard1.6 -OutputDirectory bin -Symbols -Version $psversion
|
||||
test_script:
|
||||
- ps: Register-PSRepository -Name test -SourceLocation $pwd\bin
|
||||
- ps: Install-Module -Name Docker -Repository test -Force
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 4260a2f45148623c92dd7bbc1d9370af392ccfd1
|
||||
Subproject commit 6a3fa67ec576c6d64c5a78ab3a7b78aeb9e0e908
|
|
@ -4,15 +4,29 @@ using System.Security.Cryptography.X509Certificates;
|
|||
using Docker.DotNet;
|
||||
using Docker.DotNet.X509;
|
||||
|
||||
#if !NET46
|
||||
using System.Runtime.InteropServices;
|
||||
#endif
|
||||
|
||||
public class DockerFactory
|
||||
{
|
||||
private const string ApiVersion = "1.24";
|
||||
private const string KeyFileName = "key.pfx";
|
||||
private const string certPass = "p@ssw0rd";
|
||||
|
||||
private static readonly bool IsWindows =
|
||||
#if NET46
|
||||
true;
|
||||
#else
|
||||
RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
||||
#endif
|
||||
|
||||
private static readonly string DefaultHost = IsWindows ? "npipe://./pipe/docker_engine" : "unix://var/run/docker.sock";
|
||||
|
||||
public static DockerClient CreateClient(string HostAddress, string CertificateLocation)
|
||||
{
|
||||
HostAddress = HostAddress ?? Environment.GetEnvironmentVariable("DOCKER_HOST") ?? "npipe://./pipe/docker_engine";
|
||||
HostAddress = HostAddress ?? Environment.GetEnvironmentVariable("DOCKER_HOST") ?? DefaultHost;
|
||||
|
||||
CertificateLocation = CertificateLocation ?? Environment.GetEnvironmentVariable("DOCKER_CERT_PATH");
|
||||
|
||||
CertificateCredentials cred = null;
|
||||
|
|
|
@ -28,7 +28,7 @@ namespace Docker.PowerShell.Cmdlets
|
|||
|
||||
using (var fs = File.Create(filePath))
|
||||
using (var stream = await DkrClient.Miscellaneous.GetImagesAsTarballAsync(names.ToArray(), CmdletCancellationToken))
|
||||
using (CmdletCancellationToken.Register(() => stream.Close()))
|
||||
using (CmdletCancellationToken.Register(() => stream.Dispose()))
|
||||
{
|
||||
await stream.CopyToAsync(fs);
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ namespace Docker.PowerShell.Cmdlets
|
|||
using (var pushStream = await loadTask)
|
||||
{
|
||||
// ReadLineAsync is not cancellable without closing the whole stream, so register a callback to do just that.
|
||||
using (CmdletCancellationToken.Register(() => pushStream.Close()))
|
||||
using (CmdletCancellationToken.Register(() => pushStream.Dispose()))
|
||||
using (var pullReader = new StreamReader(pushStream, new UTF8Encoding(false)))
|
||||
{
|
||||
string line;
|
||||
|
|
|
@ -118,7 +118,7 @@ namespace Docker.PowerShell.Cmdlets
|
|||
WriteProgress(progressRecord);
|
||||
|
||||
// ReadLineAsync is not cancellable without closing the whole stream, so register a callback to do just that.
|
||||
using (CmdletCancellationToken.Register(() => buildStream.Close()))
|
||||
using (CmdletCancellationToken.Register(() => buildStream.Dispose()))
|
||||
using (var buildReader = new StreamReader(buildStream, new UTF8Encoding(false)))
|
||||
{
|
||||
string line;
|
||||
|
|
|
@ -48,7 +48,7 @@ namespace Docker.PowerShell.Cmdlets
|
|||
using (var pullStream = await pullTask)
|
||||
{
|
||||
// ReadLineAsync is not cancellable without closing the whole stream, so register a callback to do just that.
|
||||
using (CmdletCancellationToken.Register(() => pullStream.Close()))
|
||||
using (CmdletCancellationToken.Register(() => pullStream.Dispose()))
|
||||
using (var pullReader = new StreamReader(pullStream, new UTF8Encoding(false)))
|
||||
{
|
||||
string line;
|
||||
|
|
|
@ -62,7 +62,7 @@ namespace Docker.PowerShell.Cmdlets
|
|||
using (var pushStream = await pushTask)
|
||||
{
|
||||
// ReadLineAsync is not cancellable without closing the whole stream, so register a callback to do just that.
|
||||
using (CmdletCancellationToken.Register(() => pushStream.Close()))
|
||||
using (CmdletCancellationToken.Register(() => pushStream.Dispose()))
|
||||
using (var pullReader = new StreamReader(pushStream, new UTF8Encoding(false)))
|
||||
{
|
||||
string line;
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0"?>
|
||||
<package >
|
||||
<metadata>
|
||||
<id>Docker-Core</id>
|
||||
<version>0.0.0.0</version>
|
||||
<authors>Microsoft</authors>
|
||||
<owners>microsoft</owners>
|
||||
<licenseUrl>https://raw.githubusercontent.com/Microsoft/Docker-PowerShell/master/LICENSE</licenseUrl>
|
||||
<projectUrl>https://github.com/Microsoft/Docker-PowerShell</projectUrl>
|
||||
<requireLicenseAcceptance>true</requireLicenseAcceptance>
|
||||
<copyright>Copyright (c) Microsoft</copyright>
|
||||
<description>This package contains cross-platform Docker PowerShell cmdlets that can be used to on any host running open-source PowerShell 6.0.</description>
|
||||
</metadata>
|
||||
</package>
|
|
@ -14,7 +14,7 @@ ModuleVersion = '0.0.0.0'
|
|||
|
||||
# Minimum PowerShell version. This should match the reference assembly version
|
||||
# in project.json (we require 5.0 due to dependencies on parameter completion).
|
||||
PowerShellVersion = '5.0'
|
||||
PowerShellVersion = '5.0.0'
|
||||
|
||||
# ID used to uniquely identify this module
|
||||
GUID = '7cc6f829-b4b5-493d-9a99-f92dc54d7e10'
|
||||
|
|
|
@ -46,8 +46,6 @@ namespace Docker.PowerShell.Support
|
|||
writer.Close(e);
|
||||
throw;
|
||||
}
|
||||
|
||||
writer.Close();
|
||||
}
|
||||
}, cancellationToken);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Docker.PowerShell.Support
|
||||
{
|
||||
|
@ -10,96 +9,30 @@ namespace Docker.PowerShell.Support
|
|||
Out,
|
||||
}
|
||||
|
||||
internal class ConsoleStream : IDisposable
|
||||
internal abstract class ConsoleStream : IDisposable
|
||||
{
|
||||
private IntPtr _handle;
|
||||
private int _oldMode;
|
||||
private int _currentMode;
|
||||
public Stream Stream { get; private set; }
|
||||
public ConsoleDirection Direction { get; private set; }
|
||||
protected IntPtr _handle;
|
||||
protected int _oldMode;
|
||||
protected int _currentMode;
|
||||
public Stream Stream { get; internal set; }
|
||||
public ConsoleDirection Direction { get; internal set; }
|
||||
|
||||
public ConsoleStream(ConsoleDirection dir)
|
||||
{
|
||||
Direction = dir;
|
||||
_handle = Win32ConsoleInterop.GetStdHandle(dir == ConsoleDirection.In ? Win32ConsoleInterop.STD_INPUT_HANDLE : Win32ConsoleInterop.STD_OUTPUT_HANDLE);
|
||||
if (!Win32ConsoleInterop.GetConsoleMode(_handle, out _oldMode))
|
||||
{
|
||||
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
|
||||
}
|
||||
|
||||
_currentMode = _oldMode;
|
||||
|
||||
// Now that it is known that this is a console, reopen the console handle for
|
||||
// asynchronous access so that IO to it can be canceled.
|
||||
var newHandle = Win32ConsoleInterop.CreateFile(
|
||||
dir == ConsoleDirection.In ? "CONIN$" : "CONOUT$",
|
||||
dir == ConsoleDirection.In ? Win32ConsoleInterop.GENERIC_READ : Win32ConsoleInterop.GENERIC_WRITE,
|
||||
FileShare.ReadWrite,
|
||||
new IntPtr(),
|
||||
FileMode.Open,
|
||||
Win32ConsoleInterop.FILE_FLAG_OVERLAPPED,
|
||||
new IntPtr());
|
||||
|
||||
if (newHandle.IsInvalid)
|
||||
{
|
||||
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
|
||||
}
|
||||
|
||||
Stream = new FileStream(newHandle, dir == ConsoleDirection.In ? FileAccess.Read : FileAccess.Write, 1, true);
|
||||
}
|
||||
|
||||
public void EnableRawInputMode()
|
||||
public virtual void EnableRawInputMode()
|
||||
{
|
||||
if (Direction != ConsoleDirection.In)
|
||||
{
|
||||
throw new InvalidOperationException("cannot set raw mode on output handle");
|
||||
}
|
||||
|
||||
// Put the console in character mode with no input processing.
|
||||
var newMode = _currentMode & ~(Win32ConsoleInterop.ENABLE_PROCESSED_INPUT |
|
||||
Win32ConsoleInterop.ENABLE_LINE_INPUT |
|
||||
Win32ConsoleInterop.ENABLE_ECHO_INPUT |
|
||||
Win32ConsoleInterop.ENABLE_MOUSE_INPUT);
|
||||
|
||||
if (!Win32ConsoleInterop.SetConsoleMode(_handle, newMode))
|
||||
{
|
||||
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
|
||||
}
|
||||
|
||||
_currentMode = newMode;
|
||||
}
|
||||
|
||||
public void EnableVTMode()
|
||||
public virtual void EnableVTMode()
|
||||
{
|
||||
int newMode;
|
||||
if (Direction == ConsoleDirection.Out)
|
||||
{
|
||||
// Put the console in character mode with no input processing.
|
||||
newMode = _currentMode | Win32ConsoleInterop.ENABLE_PROCESSED_OUTPUT | Win32ConsoleInterop.ENABLE_VIRTUAL_TERMINAL_PROCESSING | Win32ConsoleInterop.DISABLE_NEWLINE_AUTO_RETURN;
|
||||
}
|
||||
else
|
||||
{
|
||||
newMode = _currentMode | Win32ConsoleInterop.ENABLE_VIRTUAL_TERMINAL_INPUT;
|
||||
}
|
||||
|
||||
if (!Win32ConsoleInterop.SetConsoleMode(_handle, newMode))
|
||||
{
|
||||
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
|
||||
}
|
||||
|
||||
_currentMode = newMode;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
public virtual void Dispose()
|
||||
{
|
||||
Stream.Dispose();
|
||||
if (_currentMode != _oldMode)
|
||||
{
|
||||
if (Win32ConsoleInterop.SetConsoleMode(_handle, _oldMode))
|
||||
{
|
||||
_currentMode = _oldMode;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
|
||||
namespace Docker.PowerShell.Support
|
||||
{
|
||||
internal sealed class LinuxConsoleStream : ConsoleStream
|
||||
{
|
||||
public LinuxConsoleStream(ConsoleDirection dir) : base(dir)
|
||||
{
|
||||
Stream = dir == ConsoleDirection.In ? Console.OpenStandardInput() : Console.OpenStandardOutput();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,18 +4,38 @@ using System.Threading;
|
|||
using System.Threading.Tasks;
|
||||
using Docker.DotNet;
|
||||
|
||||
#if !NET46
|
||||
using System.Runtime.InteropServices;
|
||||
#endif
|
||||
|
||||
namespace Docker.PowerShell.Support
|
||||
{
|
||||
internal static class MultiplexedStreamExtensions
|
||||
{
|
||||
private static readonly bool IsWindows =
|
||||
#if NET46
|
||||
true;
|
||||
#else
|
||||
RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
||||
#endif
|
||||
|
||||
public static async Task CopyToConsoleAsync(this MultiplexedStream stream, bool tty, bool openStdin, CancellationToken cancellationToken)
|
||||
{
|
||||
Stream stdin = Stream.Null, stdout = Stream.Null, stderr = Stream.Null;
|
||||
ConsoleStream conin = null, conout = null;
|
||||
Task stdinReadTask = null;
|
||||
try
|
||||
{
|
||||
// TODO: What if we are not attached to a console? If config.Tty is false, this should not be an error.
|
||||
conout = new ConsoleStream(ConsoleDirection.Out);
|
||||
|
||||
if (IsWindows)
|
||||
{
|
||||
conout = new Win32ConsoleStream(ConsoleDirection.Out);
|
||||
}
|
||||
else
|
||||
{
|
||||
conout = new LinuxConsoleStream(ConsoleDirection.Out);
|
||||
}
|
||||
stdout = Console.OpenStandardOutput(); // Don't use conout's Stream because FileStream always buffers on net46.
|
||||
if (tty)
|
||||
{
|
||||
|
@ -26,11 +46,17 @@ namespace Docker.PowerShell.Support
|
|||
stderr = Console.OpenStandardError();
|
||||
}
|
||||
|
||||
Task stdinRead = null;
|
||||
CancellationTokenSource inputCancelToken = null;
|
||||
if (openStdin)
|
||||
{
|
||||
conin = new ConsoleStream(ConsoleDirection.In);
|
||||
if (IsWindows)
|
||||
{
|
||||
conin = new Win32ConsoleStream(ConsoleDirection.In);
|
||||
}
|
||||
else
|
||||
{
|
||||
conin = new LinuxConsoleStream(ConsoleDirection.In);
|
||||
}
|
||||
|
||||
stdin = conin.Stream;
|
||||
conin.EnableRawInputMode();
|
||||
if (tty)
|
||||
|
@ -38,33 +64,31 @@ namespace Docker.PowerShell.Support
|
|||
conin.EnableVTMode();
|
||||
}
|
||||
|
||||
inputCancelToken = new CancellationTokenSource();
|
||||
stdinRead = stream.CopyFromAsync(stdin, inputCancelToken.Token).ContinueWith(x => stream.CloseWrite());
|
||||
stdinReadTask = stream.CopyFromAsync(stdin, CancellationToken.None).ContinueWith(x => stream.CloseWrite());
|
||||
}
|
||||
|
||||
await stream.CopyOutputToAsync(stdout, stdout, stderr, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (stdinRead != null)
|
||||
}
|
||||
finally
|
||||
{
|
||||
conout?.Dispose();
|
||||
conin?.Dispose();
|
||||
stdout.Dispose();
|
||||
stderr.Dispose();
|
||||
stdin.Dispose();
|
||||
if (stdinReadTask != null)
|
||||
{
|
||||
inputCancelToken.Cancel();
|
||||
try
|
||||
{
|
||||
await stdinRead.ConfigureAwait(false);
|
||||
// Make sure the read from stdin has finished before returning.
|
||||
await stdinReadTask.ConfigureAwait(false);
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
// Ignore.
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
conin?.Dispose();
|
||||
conout?.Dispose();
|
||||
stdin.Dispose();
|
||||
stdout.Dispose();
|
||||
stderr.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -60,14 +60,17 @@ namespace Docker.PowerShell.Support
|
|||
return _pipe.ReadAsync(buffer, offset, count, cancellationToken);
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
_pipe.CloseReader();
|
||||
}
|
||||
|
||||
public void Close(Exception e)
|
||||
{
|
||||
_pipe.CloseReader(e);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_pipe.CloseReader();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -57,15 +57,18 @@ namespace Docker.PowerShell.Support
|
|||
{
|
||||
return _pipe.WriteAsync(buffer, offset, count, cancellationToken);
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
_pipe.CloseWriter();
|
||||
}
|
||||
|
||||
public void Close(Exception e)
|
||||
{
|
||||
_pipe.CloseWriter(e);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_pipe.CloseWriter();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
|
||||
namespace Docker.PowerShell.Support
|
||||
{
|
||||
internal static class Win32ConsoleInterop
|
||||
{
|
||||
[DllImport("api-ms-win-core-file-l1-1-0.dll", EntryPoint = "CreateFileW", SetLastError = true, CharSet = CharSet.Unicode, BestFitMapping = false)]
|
||||
public static extern SafeFileHandle CreateFile(
|
||||
string lpFileName,
|
||||
int dwDesiredAccess,
|
||||
System.IO.FileShare dwShareMode,
|
||||
IntPtr securityAttrs,
|
||||
System.IO.FileMode dwCreationDisposition,
|
||||
int dwFlagsAndAttributes,
|
||||
IntPtr hTemplateFile);
|
||||
|
||||
[DllImport("api-ms-win-core-console-l1-1-0.dll", SetLastError = true)]
|
||||
public extern static bool GetConsoleMode(IntPtr handle, out int mode);
|
||||
|
||||
[DllImport("api-ms-win-core-console-l1-1-0.dll", SetLastError = true)]
|
||||
public static extern bool SetConsoleMode(IntPtr handle, int mode);
|
||||
|
||||
[DllImport("api-ms-win-core-processenvironment-l1-1-0.dll", SetLastError = true)]
|
||||
public static extern IntPtr GetStdHandle(int nStdHandle);
|
||||
|
||||
public const int STD_INPUT_HANDLE = -10;
|
||||
public const int STD_OUTPUT_HANDLE = -11;
|
||||
public const int STD_ERROR_HANDLE = -12;
|
||||
|
||||
public const int GENERIC_READ = unchecked((int)0x80000000);
|
||||
public const int GENERIC_WRITE = 0x40000000;
|
||||
|
||||
public const int FILE_FLAG_OVERLAPPED = 0x40000000;
|
||||
|
||||
// Input flags.
|
||||
public const int ENABLE_PROCESSED_INPUT = 0x1;
|
||||
public const int ENABLE_LINE_INPUT = 0x2;
|
||||
public const int ENABLE_ECHO_INPUT = 0x4;
|
||||
public const int ENABLE_MOUSE_INPUT = 0x10;
|
||||
public const int ENABLE_VIRTUAL_TERMINAL_INPUT = 0x200;
|
||||
|
||||
// Output flags.
|
||||
public const int ENABLE_PROCESSED_OUTPUT = 0x1;
|
||||
public const int ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x4;
|
||||
public const int DISABLE_NEWLINE_AUTO_RETURN = 0x8;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
|
||||
namespace Docker.PowerShell.Support
|
||||
{
|
||||
internal sealed class Win32ConsoleStream : ConsoleStream
|
||||
{
|
||||
public Win32ConsoleStream(ConsoleDirection dir) : base(dir)
|
||||
{
|
||||
_handle = GetStdHandle(dir == ConsoleDirection.In ? STD_INPUT_HANDLE : STD_OUTPUT_HANDLE);
|
||||
if (!GetConsoleMode(_handle, out _oldMode))
|
||||
{
|
||||
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
|
||||
}
|
||||
|
||||
_currentMode = _oldMode;
|
||||
|
||||
// Now that it is known that this is a console, reopen the console handle for
|
||||
// asynchronous access so that IO to it can be canceled.
|
||||
var newHandle = CreateFile(
|
||||
dir == ConsoleDirection.In ? "CONIN$" : "CONOUT$",
|
||||
dir == ConsoleDirection.In ? GENERIC_READ : GENERIC_WRITE,
|
||||
FileShare.ReadWrite,
|
||||
new IntPtr(),
|
||||
FileMode.Open,
|
||||
FILE_FLAG_OVERLAPPED,
|
||||
new IntPtr());
|
||||
|
||||
if (newHandle.IsInvalid)
|
||||
{
|
||||
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
|
||||
}
|
||||
|
||||
Stream = new FileStream(newHandle, dir == ConsoleDirection.In ? FileAccess.Read : FileAccess.Write, 1, true);
|
||||
}
|
||||
|
||||
override public void EnableRawInputMode()
|
||||
{
|
||||
if (Direction != ConsoleDirection.In)
|
||||
{
|
||||
throw new InvalidOperationException("cannot set raw mode on output handle");
|
||||
}
|
||||
|
||||
// Put the console in character mode with no input processing.
|
||||
var newMode = _currentMode & ~(ENABLE_PROCESSED_INPUT |
|
||||
ENABLE_LINE_INPUT |
|
||||
ENABLE_ECHO_INPUT |
|
||||
ENABLE_MOUSE_INPUT);
|
||||
|
||||
if (!SetConsoleMode(_handle, newMode))
|
||||
{
|
||||
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
|
||||
}
|
||||
|
||||
_currentMode = newMode;
|
||||
}
|
||||
|
||||
override public void EnableVTMode()
|
||||
{
|
||||
int newMode;
|
||||
if (Direction == ConsoleDirection.Out)
|
||||
{
|
||||
// Put the console in character mode with no input processing.
|
||||
newMode = _currentMode | ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN;
|
||||
}
|
||||
else
|
||||
{
|
||||
newMode = _currentMode | ENABLE_VIRTUAL_TERMINAL_INPUT;
|
||||
}
|
||||
|
||||
if (!SetConsoleMode(_handle, newMode))
|
||||
{
|
||||
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
|
||||
}
|
||||
|
||||
_currentMode = newMode;
|
||||
}
|
||||
|
||||
override public void Dispose()
|
||||
{
|
||||
if (_currentMode != _oldMode)
|
||||
{
|
||||
if (SetConsoleMode(_handle, _oldMode))
|
||||
{
|
||||
_currentMode = _oldMode;
|
||||
}
|
||||
}
|
||||
base.Dispose();
|
||||
}
|
||||
|
||||
[DllImport("api-ms-win-core-file-l1-1-0.dll", EntryPoint = "CreateFileW", SetLastError = true, CharSet = CharSet.Unicode, BestFitMapping = false)]
|
||||
private static extern SafeFileHandle CreateFile(
|
||||
string lpFileName,
|
||||
int dwDesiredAccess,
|
||||
System.IO.FileShare dwShareMode,
|
||||
IntPtr securityAttrs,
|
||||
System.IO.FileMode dwCreationDisposition,
|
||||
int dwFlagsAndAttributes,
|
||||
IntPtr hTemplateFile);
|
||||
|
||||
[DllImport("api-ms-win-core-console-l1-1-0.dll", SetLastError = true)]
|
||||
private static extern bool GetConsoleMode(IntPtr handle, out int mode);
|
||||
|
||||
[DllImport("api-ms-win-core-console-l1-1-0.dll", SetLastError = true)]
|
||||
private static extern bool SetConsoleMode(IntPtr handle, int mode);
|
||||
|
||||
[DllImport("api-ms-win-core-processenvironment-l1-1-0.dll", SetLastError = true)]
|
||||
private static extern IntPtr GetStdHandle(int nStdHandle);
|
||||
|
||||
private const int STD_INPUT_HANDLE = -10;
|
||||
private const int STD_OUTPUT_HANDLE = -11;
|
||||
private const int STD_ERROR_HANDLE = -12;
|
||||
|
||||
private const int GENERIC_READ = unchecked((int)0x80000000);
|
||||
private const int GENERIC_WRITE = 0x40000000;
|
||||
|
||||
private const int FILE_FLAG_OVERLAPPED = 0x40000000;
|
||||
|
||||
// Input flags.
|
||||
private const int ENABLE_PROCESSED_INPUT = 0x1;
|
||||
private const int ENABLE_LINE_INPUT = 0x2;
|
||||
private const int ENABLE_ECHO_INPUT = 0x4;
|
||||
private const int ENABLE_MOUSE_INPUT = 0x10;
|
||||
private const int ENABLE_VIRTUAL_TERMINAL_INPUT = 0x200;
|
||||
|
||||
// Output flags.
|
||||
private const int ENABLE_PROCESSED_OUTPUT = 0x1;
|
||||
private const int ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x4;
|
||||
private const int DISABLE_NEWLINE_AUTO_RETURN = 0x8;
|
||||
}
|
||||
}
|
|
@ -1,19 +1,36 @@
|
|||
{
|
||||
"version": "0.0.0-alpha",
|
||||
"description": "Docker.PowerShell Cmdlet Library",
|
||||
"runtimes": {
|
||||
"win": {}
|
||||
},
|
||||
"dependencies": {
|
||||
"Docker.DotNet.X509": "2.124.2",
|
||||
"Docker.DotNet": "2.124.2",
|
||||
"Tar": "0.1.0-*",
|
||||
"Microsoft.PowerShell.5.ReferenceAssemblies": "1.0.0"
|
||||
"Tar": "0.1.0-*"
|
||||
},
|
||||
"frameworks": {
|
||||
"net46": {}
|
||||
"netstandard1.6": {
|
||||
"imports": [
|
||||
"portable-net45+win8"
|
||||
],
|
||||
"dependencies": {
|
||||
"Microsoft.PowerShell.SDK": {
|
||||
"version": "1.0.0-alpha9",
|
||||
"type": "platform"
|
||||
}
|
||||
}
|
||||
},
|
||||
"net46": {
|
||||
"dependencies": {
|
||||
"Microsoft.PowerShell.5.ReferenceAssemblies": {
|
||||
"version": "1.0.0",
|
||||
"type": "build"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"publishOptions": {
|
||||
"include": ["Docker.psd1", "Docker.Format.ps1xml"]
|
||||
"include": [
|
||||
"Docker.psd1",
|
||||
"Docker.Format.ps1xml"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,16 +1,14 @@
|
|||
{
|
||||
"version": "0.1.0",
|
||||
"frameworks": {
|
||||
"netstandard1.3": {
|
||||
"imports": [ "dotnet", "netcore50" ],
|
||||
"netstandard1.6": {
|
||||
"dependencies": {
|
||||
"System.IO": "4.0.10",
|
||||
"System.IO.FileSystem": "4.0.0",
|
||||
"System.Linq": "4.0.0",
|
||||
"System.Runtime": "4.0.20",
|
||||
"System.Text.Encoding": "4.0.10",
|
||||
"System.Text.Encoding.Extensions": "4.0.10",
|
||||
"System.Threading.Tasks": "4.0.10"
|
||||
"NETStandard.Library": "1.6.0"
|
||||
}
|
||||
},
|
||||
"netstandard1.3": {
|
||||
"dependencies": {
|
||||
"NETStandard.Library": "1.6.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче