Docker.PowerShell.CLI.psm1
using namespace System.Management.Automation using namespace System.Management.Automation.Language using namespace System.Collections.Generic using namespace System.Collections using namespace System.Diagnostics using namespace System.Collections.ObjectModel class BooleanArgumentCompleter : IArgumentCompleter { [IEnumerable[CompletionResult]] CompleteArgument( [string]$commandName, [string]$parameterName, [string]$wordToComplete, [CommandAst]$commandAst, [IDictionary]$fakeBoundParameters ) { if ($wordToComplete) { return [CompletionResult[]]::new(0) } $CompletionResults = [List[CompletionResult]]::new(3) $CompletionResults.Add([CompletionResult]::new('$true', 'true', 'ParameterValue', 'true')) $CompletionResults.Add([CompletionResult]::new('$false', 'false', 'ParameterValue', 'false')) $Command = Get-Command -Name $commandAst.GetCommandName() $Parameter = $Command.Parameters[$parameterName] if ($Parameter.ParameterType -eq [Nullable[bool]]) { $CompletionResults.Add([CompletionResult]::new('$null', 'null', 'ParameterValue', 'null')) } return $CompletionResults } } class DateTimeArgumentCompleter : IArgumentCompleter { [IEnumerable[CompletionResult]] CompleteArgument( [string]$commandName, [string]$parameterName, [string]$wordToComplete, [CommandAst]$commandAst, [IDictionary]$fakeBoundParameters ) { if ($wordToComplete) { return [CompletionResult[]]::new(0) } $CompletionResults = [CompletionResult[]]::new(10) for ($i = 0; $i -lt $CompletionResults.Count; $i++) { $Date = [DateTime]::Today.AddDays(-$i) $DateString = $Date.ToString('yyyy-MM-dd') $CompletionResults[$i] = [CompletionResult]::new($DateString, $DateString, 'ParameterValue', $DateString) } return $CompletionResults } } class DockerBinArgumentCompleter : IArgumentCompleter { [IEnumerable[CompletionResult]] CompleteArgument( [string] $commandName, [string] $parameterName, [string] $wordToComplete, [CommandAst] $commandAst, [IDictionary] $fakeBoundParameters) { $wc = ConvertTo-WordToCompleteWildcard -WordToComplete $wordToComplete $DockerContainerParameters = @{} if ($FakeBoundParameters.ContainsKey('Context')) { $DockerContainerParameters['Context'] = $FakeBoundParameters['Context'] } if ($FakeBoundParameters.ContainsKey('Id')) { $DockerContainerParameters['Id'] = $FakeBoundParameters['Id'] } if ($FakeBoundParameters.ContainsKey('Name')) { $DockerContainerParameters['Name'] = $FakeBoundParameters['Name'] } $Container = Get-DockerContainer @DockerContainerParameters if (!$?) { return [CompletionResult[]]::new(0) } if (!$Container) { return [CompletionResult[]]::new(0) } $DockerArguments = @( if ($FakeBoundParameters['Context']) { '--context'; $FakeBoundParameters['Context'] } 'exec' '-w' '/bin' '-t' $Container.Id 'ls' '--format=single-column' ) Write-Debug "docker $DockerArguments" $BinContents = docker $DockerArguments if (!$?) { return [CompletionResult[]]::new(0) } $Completions = [List[CompletionResult]]::new($BinContents.Count) foreach ($item in $BinContents) { if ($Item -like $wc) { $CompletionText = ConvertTo-CompletionText -InputObject $Item -WordToComplete $wordToComplete $Completions.Add([CompletionResult]::new($CompletionText, $Item, 'ParameterValue', $Item)) } } return $Completions } } class DockerBuildAddHostTransformation : ArgumentTransformationAttribute { [object] Transform([EngineIntrinsics]$EngineIntrinsics, [object]$InputData) { if ($InputData -as [Dictionary[string, ipaddress]]) { return $InputData } $Dictionary = [Dictionary[string, IPAddress]]::new() # InputData may be a hasthable of valid dns name to IP address if ($InputData -is [hashtable]) { foreach ($Key in $InputData.Keys) { $Dictionary[$Key] = [ipaddress]$InputData[$Key] } } else { foreach ($Item in $InputData) { if ($Item -is [string]) { $Parts = $Item.Split(':') if ($Parts.Length -ne 2) { throw "Invalid host specification '$Item'. Input must be in the form 'hostname:ipaddress'." } $Dictionary[$Parts[0]] = [ipaddress]$Parts[1] } else { try { $Dictionary.Add($Item) } catch { throw [ArgumentException]::new("Invalid host specification '$Item'. Input must be in the form 'hostname:ipaddress'.", $_.Exception) } } } } return $Dictionary } } class DockerContainer { hidden [PSObject]$PSSourceValue [string]$Id [string[]]$Names [string[]]$Labels [string]$Image [string]$Command [string]$Status [string[]]$Ports [string[]]$Networks [DateTimeOffset]$CreatedAt [string] ToString() { return $this.Id } DockerContainer([psobject]$deserializedJson) { $this.PSSourceValue = $deserializedJson $this.PSObject.TypeNames.Insert(0, 'Docker.Container') $this.PSObject.TypeNames.Insert(1, 'Docker.PowerShell.CLI.Container') foreach ($Property in $deserializedJson.PSObject.Properties) { if ($Property.Name -eq 'Names') { $this.Names = $Property.Value -split ',' } elseif ($Property.Name -eq 'Labels') { $this.Labels = $Property.Value -split ',' } elseif ($Property.Name -eq 'Ports') { $this.Ports = $Property.Value -split ',' } elseif ($Property.Name -eq 'CreatedAt') { $this.CreatedAt = [DateTimeOffset][string]$Property.Value.Split(' ')[0..2] } elseif ($Property.Name -eq 'Networks') { $this.Networks = $Property.Value -split ',' } elseif ($this.PSObject.Properties[$Property.Name]) { $this.$($Property.Name) = $Property.Value } else { $this.PSObject.Properties.Add($Property.Copy()) } } } } class DockerContainerCompleter : IArgumentCompleter { [IEnumerable[CompletionResult]] CompleteArgument( [string]$commandName, [string]$parameterName, [string]$wordToComplete, [CommandAst]$commandAst, [IDictionary]$fakeBoundParameters ) { $wc = ConvertTo-WordToCompleteWildcard $wordToComplete $ProxyParameters = @{} if ($FakeBoundParameters['Context']) { $ProxyParameters['Context'] = $FakeBoundParameters['Context'] } $Containers = Get-DockerContainer @ProxyParameters $CompletionResults = [List[CompletionResult]]::new(); foreach ($Container in $Containers) { $IsMatch = $Container.Names -like $wc -or $Container.id -like $wc if (-not $IsMatch) { continue } if ($parameterName -in 'ContainerId', 'Id') { $CompletionText = @($Container.Id) } else { $CompletionText = @($Container.Names) } foreach ($Completion in $CompletionText) { $SafeCompletionText = ConvertTo-CompletionText -InputObject $Completion -WordToComplete $wordToComplete $ListItemText = if ($Completion -eq $Container.Id) { "$Completion (name: $($Container.Names -join ', '))" } else { "$Completion (id: $($Container.Id))" } $CompletionResults.Add( [CompletionResult]::new( $SafeCompletionText, $ListItemText, 'ParameterValue', $Completion ) ) } } return $CompletionResults } } class DockerContainerNetworkConnection { hidden [PSObject]$PSSourceValue [string]$ContainerName [string]$ContainerId [string]$ImageName [string]$ImageId [string]$NetworkName [string]$NetworkId [string]$EndpointId [string]$MacAddress [ipaddress]$IPAddress [string[]]$Aliases [string] ToString() { return $this.EndpointId } DockerContainerNetworkConnection( [PSObject]$deserializedJson, [string]$ContainerId, [string]$ContainerName, [string]$ImageId, [string]$ImageName, [string]$NetworkName) { $this.PSSourceValue = $deserializedJson $this.ContainerId = $ContainerId $this.ContainerName = $ContainerName $this.ImageId = $ImageId $this.ImageName = $ImageName $this.NetworkName = $NetworkName $this.PSObject.TypeNames.Insert(0, 'Docker.ContainerNetworkConnection') $this.PSObject.TypeNames.Insert(1, 'Docker.PowerShell.CLI.ContainerNetworkConnection') foreach ($Property in $deserializedJson.PSObject.Properties) { if ($this.PSObject.Properties[$Property.Name]) { $this.$($Property.Name) = $Property.Value } else { $this.PSObject.Properties.Add($Property.Copy()) } } } } class DockerContext { hidden [PSObject] $PSSourceValue [bool]$Current [string]$Name [string]$Description [string]$DockerEndpoint [string]$KubernetesEndpoint [string]$ContextType [string]$StackOrchestrator [string] ToString() { return $this.Name } DockerContext([PSObject]$deserializedJson) { $this.PSSourceValue = $deserializedJson $this.PSTypeNames.Insert(0, 'Docker.Context') $this.PSTypeNames.Insert(1, 'Docker.PowerShell.CLI.Context') foreach ($Property in $deserializedJson.PSObject.Properties) { if ($this.PSObject.Properties[$Property.Name]) { $this.$($Property.Name) = $Property.Value } else { $this.PSObject.Properties.Add($Property.Copy()) } } } } class DockerContextCompleter : IArgumentCompleter { [IEnumerable[CompletionResult]] CompleteArgument( [string]$commandName, [string]$parameterName, [string]$wordToComplete, [CommandAst]$commandAst, [IDictionary]$fakeBoundParameters ) { $wc = ConvertTo-WordToCompleteWildcard -WordToComplete $wordToComplete Write-Debug 'docker context list --quiet' $Contexts = docker context list --quiet Write-Debug 'docker context show' $CurrentContext = docker context show $Results = [List[CompletionResult]]::new() foreach ($Context in $Contexts) { if ($Context -notlike $wc) { continue } if ($Context -eq $CurrentContext) { $DisplayText = "$($global:PSStyle.Foreground.BrightCyan)$Context$($global:PSStyle.Reset)" } else { $DisplayText = $Context } $CompletionText = ConvertTo-CompletionText -InputObject $Context -WordToComplete $wordToComplete $Results.Add( [CompletionResult]::new( $CompletionText, $DisplayText, 'ParameterValue', $DisplayText ) ) } return $Results } } class DockerImage { hidden [PSObject]$PSSourceObject [string]$Id [string]$Repository [string[]]$Containers [DateTimeOffset]$CreatedAt [string[]]$Labels [string]$Digest [string]$Tag [string[]]$Mounts DockerImage([PSObject]$deserializedJson) { $this.PSSourceObject = $deserializedJson $this.PSTypeNames.Insert(0, 'Docker.Image') $this.PSTypeNames.Insert(1, 'Docker.PowerShell.CLI.Image') foreach ($Property in $deserializedJson.PSObject.Properties) { if ($Property.Name -eq 'Containers') { if ($Property.Value -eq 'N/A') { $this.Containers = @() } else { $this.Containers = $Property.Value -split ',' } } elseif ($Property.Name -eq 'CreatedAt') { $this.CreatedAt = [DateTimeOffset][string]$Property.Value.Split(' ')[0..2] } elseif ($Property.Name -eq 'Labels') { $this.Labels = $Property.Value -split ',' } elseif ($Property.Name -eq 'Mounts') { $this.Mounts = $Property.Value -split ',' } elseif ($this.PSObject.Properties[$Property.Name]) { $this.($Property.Name) = $Property.Value } else { $this.PSObject.Properties.Add($Property.Copy()) } } } } class DockerImageCompleter : IArgumentCompleter { [IEnumerable[CompletionResult]] CompleteArgument( [string]$commandName, [string]$parameterName, [string]$wordToComplete, [CommandAst]$commandAst, [IDictionary]$fakeBoundParameters ) { $wc = ConvertTo-WordToCompleteWildcard $wordToComplete $ProxyParameters = @{} if ($FakeBoundParameters['Context']) { $ProxyParameters['Context'] = $FakeBoundParameters['Context'] } $Images = Get-DockerImage @ProxyParameters $CompletionResults = [List[CompletionResult]]::new(); $CompletedTags = [HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase) foreach ($Image in $Images) { $IsMatch = $Image.Name -like $wc -or $Image.id -like $wc if (-not $IsMatch) { continue } if ($parameterName -in 'ImageId', 'Id') { $CompletionText = $Image.Id $ListItemText = "$($Image.Id) ($($Image.Repository))" } # DockerImageCompleter is designed to complete from all images, so we don't # need to worry about filtering the tag down to tags for the specified image. elseif ($parameterName -eq 'Tag') { $CompletionText = $Image.Tag if (!$CompletedTags.Add($CompletionText)) { continue } $ListItemText = $Image.Tag } elseif ($parameterName -in 'Repository', 'Name', 'ImageName', 'RepositoryName') { $CompletionText = $Image.Repository $ListItemText = "$($Image.Repository) ($($Image.Id))" } else { $CompletionText = $Image.FullName $ListItemText = "$($Image.FullName) ($($Image.Id))" } $SafeCompletionText = ConvertTo-CompletionText -InputObject $CompletionText -WordToComplete $wordToComplete $CompletionResults.Add( [CompletionResult]::new( $SafeCompletionText, $ListItemText, 'ParameterValue', $CompletionText ) ) } return $CompletionResults } } class DockerNetwork { hidden [psobject]$PSSourceValue [string]$Id [string]$Name [string]$Driver [string]$Scope [string[]]$Labels [DateTimeOffset]$CreatedAt [string] ToString() { return $this.Id } DockerNetwork([psobject]$deserializedJson) { $this.PSSourceValue = $deserializedJson $this.PSObject.TypeNames.Insert(0, 'Docker.Network') $this.PSObject.TypeNames.Insert(1, 'Docker.PowerShell.CLI.Network') foreach ($Property in $deserializedJson.PSObject.Properties) { if ($Property -is [psnoteproperty]) { # Handle special property values if ($Property.Name -eq 'Driver' -and $Property.Value -eq 'null') { $this.Driver = $null continue } if ($Property.Name -eq 'Labels') { $this.Labels = $Property.Value -split ',' continue } if ($Property.Name -eq 'CreatedAt') { $this.CreatedAt = [DateTimeOffset][string]$Property.Value.Split(' ')[0..2] continue } # Handle normal property values if ($this.PSObject.Properties[$Property.Name]) { $this.($Property.Name) = $Property.Value } else { $Duplicate = $Property.Copy() $asBool = $false if ([bool]::TryParse($Duplicate.Value, [ref]$asBool)) { $Duplicate.Value = $asBool } $this.PSObject.Properties.Add($Duplicate) } } else { Write-Warning "Unsupported property type '$($Property.GetType().Name)' for property '$($Property.Name)'. Contact the module author." } } } } class DockerNetworkAuxAddressTransformation : ArgumentTransformationAttribute { [object] Transform([EngineIntrinsics]$engineIntrinsics, [object]$inputData) { $Result = [Dictionary[string, HashSet[IPAddress]]]::new([StringComparer]::OrdinalIgnoreCase) if ($inputData -is [object[]]) { foreach ($item in $inputData) { if ($_ -match '^(?<key>.+)=(?<value>.+)$') { if (!$Result[$Matches['key']]) { $Result[$Matches['key']] = @($Matches['value']) } else { $Result[$Matches['key']].Add($Matches['value']) } } else { # Cannot handle the string, return the original input return $inputData } } } elseif ($inputData -is [hashtable]) { foreach ($keyvalue in $inputData) { if ($result[$keyvalue.Key]) { $Result[$keyvalue.Key].Add($keyvalue.Value) } else { $Result[$keyvalue.Key] = @($keyvalue.Value) } } } else { return $inputData } return $Result } } class DockerNetworkCompleter : IArgumentCompleter { [IEnumerable[CompletionResult]] CompleteArgument( [string] $CommandName, [string] $ParameterName, [string] $WordToComplete, [CommandAst] $CommandAst, [IDictionary] $FakeBoundParameters ) { $wc = ConvertTo-WordToCompleteWildcard -WordToComplete $WordToComplete $CompletionResults = [List[CompletionResult]]::new() $DockerNetworkParameters = @{} if ($FakeBoundParameters['Context']) { $DockerNetworkParameters['Context'] = $FakeBoundParameters['Context'] } $networks = Get-DockerNetwork @DockerNetworkParameters foreach ($network in $networks) { $IsMatch = $false $RawCompletionText = '' switch ($ParameterName) { { $_ -in @('Id', 'NetworkId') } { $IsMatch = $network.Id -like $wc $RawCompletionText = $network.Id } 'Driver' { $IsMatch = $network.Driver -ne 'null' -and $network.Driver -like $wc $RawCompletionText = $network.Driver } 'Scope' { $IsMatch = $network.Scope -like $wc $RawCompletionText = $network.Scope } default { $IsMatch = $network.Name -like $wc $RawCompletionText = $network.Name } } if (!$IsMatch) { continue } $CompletionText = ConvertTo-CompletionText -InputObject $RawCompletionText -WordToComplete $WordToComplete $CompletionResults.Add( [CompletionResult]::new( $CompletionText, $RawCompletionText, 'ParameterValue', $RawCompletionText ) ) } return $CompletionResults } } class EmptyHashtableArgumentCompleter : IArgumentCompleter { [IEnumerable[CompletionResult]] CompleteArgument( [string]$commandName, [string]$parameterName, [string]$wordToComplete, [CommandAst]$commandAst, [IDictionary]$fakeBoundParameters ) { if ($wordToComplete) { return [CompletionResult[]]::new(0) } return [CompletionResult[]]@( [CompletionResult]::new('@{}', '@{} # empty hashtable', 'ParameterValue', '@{} # empty hashtable') ) } } class EmptyIpAddressArgumentCompleter : IArgumentCompleter { [IEnumerable[CompletionResult]] CompleteArgument( [string]$commandName, [string]$parameterName, [string]$wordToComplete, [CommandAst]$commandAst, [IDictionary]$fakeBoundParameters ) { if ($wordToComplete) { return [CompletionResult[]]::new(0) } return [CompletionResult[]]@( [CompletionResult]::new('''0.0.0.0''', '''0.0.0.0''', 'ParameterValue', '''0.0.0.0''') ) } } class EmptyScriptBlockArgumentCompleter : IArgumentCompleter { [IEnumerable[CompletionResult]] CompleteArgument( [string]$commandName, [string]$parameterName, [string]$wordToComplete, [CommandAst]$commandAst, [IDictionary]$fakeBoundParameters ) { if ($wordToComplete) { return [CompletionResult[]]::new(0) } return [CompletionResult[]]@( [CompletionResult]::new('{}', '{} # empty ScriptBlock', 'ParameterValue', '{} # empty ScriptBlock') ) } } class EmptyStringArgumentCompleter : IArgumentCompleter { [IEnumerable[CompletionResult]] CompleteArgument( [string]$commandName, [string]$parameterName, [string]$wordToComplete, [CommandAst]$commandAst, [IDictionary]$fakeBoundParameters ) { if ($wordToComplete) { return [CompletionResult[]]::new(0) } return [CompletionResult[]]@( [CompletionResult]::new('''''', ''''' # empty string', 'ParameterValue', ''''' # empty string') ) } } class LowerCaseTransformation : ArgumentTransformationAttribute { [Object] Transform([EngineIntrinsics]$engineIntrinsics, [System.Object]$inputData) { $result = foreach ($item in $inputData) { ([string]$item).ToLower() } return $result } } class NumericArgumentCompleter : IArgumentCompleter { [IEnumerable[CompletionResult]] CompleteArgument( [string]$commandName, [string]$parameterName, [string]$wordToComplete, [CommandAst]$commandAst, [IDictionary]$fakeBoundParameters ) { if ($wordToComplete) { return [CompletionResult[]]::new(0) } return [CompletionResult[]]@( [CompletionResult]::new(1, 1, 'ParameterValue', 1) [CompletionResult]::new(2, 2, 'ParameterValue', 2) [CompletionResult]::new(3, 3, 'ParameterValue', 3) [CompletionResult]::new(4, 4, 'ParameterValue', 4) [CompletionResult]::new(5, 5, 'ParameterValue', 5) [CompletionResult]::new(6, 6, 'ParameterValue', 6) [CompletionResult]::new(7, 7, 'ParameterValue', 7) [CompletionResult]::new(8, 8, 'ParameterValue', 8) [CompletionResult]::new(9, 9, 'ParameterValue', 9) [CompletionResult]::new(10, 10, 'ParameterValue', 10) ) } } class ValidateDockerContext : ValidateArgumentsAttribute { [void ]Validate([object]$Context, [EngineIntrinsics]$EngineIntrinsics) { if ($Context -as [string]) { Write-Debug 'docker context list --quiet' $Contexts = docker context list --quiet if ($Contexts -notcontains $Context) { throw "Context '$Context' does not exist" } } } } function Assert-DockerPullJob { [CmdletBinding()] param() process { if ('Docker.PowerShell.CLI.DockerPullJob' -as [type]) { return } Add-Type -TypeDefinition @' using System; using System.Management.Automation; using System.Diagnostics; namespace Docker.PowerShell.CLI { public sealed class DockerPullJob : Job { // Docker process private readonly Process _process; // Previous line of output. If successfully exiting, this line will // contain the image name. private string _lastOutput; // Abstract member, does not apply public override string Location { get { return _process.MachineName; } } public override string StatusMessage { get { if (_process.HasExited) { return string.Format("Exited ({0})", _process.ExitCode); } else { return "Running"; } } } public override bool HasMoreData { get { return Output.Count > 0 || Error.Count > 0 || Debug.Count > 0; } } public override void StopJob() { _process.Kill(); SetJobState(JobState.Stopped); } public int GetProcessId() { return _process.Id; } private static string GetName(string arguments) { return string.Format("docker {0}", arguments); } public DockerPullJob(string command, string arguments) : base(command, GetName(arguments)) { PSJobTypeName = "DockerJob"; var startInfo = new ProcessStartInfo("docker", arguments); startInfo.RedirectStandardOutput = true; startInfo.RedirectStandardError = true; startInfo.UseShellExecute = false; startInfo.CreateNoWindow = true; _process = new Process(); _process.StartInfo = startInfo; _process.EnableRaisingEvents = true; _process.Exited += OnProcessExited; _process.OutputDataReceived += OnOutputDataReceived; _process.ErrorDataReceived += OnErrorDataReceieved; SetJobState(JobState.Running); _process.Start(); _process.BeginOutputReadLine(); _process.BeginErrorReadLine(); } private void OnOutputDataReceived(object sender, DataReceivedEventArgs e) { if (e == null || e.Data == null) { return; } _lastOutput = e.Data; var record = new DebugRecord(e.Data);; Debug.Add(record); } private void OnErrorDataReceieved(object sender, DataReceivedEventArgs e) { if (e == null || e.Data == null) { return; } var exn = new Exception(e.Data); var err = new ErrorRecord( exn, "DockerPullError", ErrorCategory.FromStdErr, null ); Error.Add(err); } private void OnProcessExited(object sender, EventArgs e) { if (_process.ExitCode == 0) { var image = _lastOutput; var pso = new PSObject(image); Output.Add(pso); SetJobState(JobState.Completed); } else { SetJobState(JobState.Failed); } } protected override void Dispose(bool disposing) { if (disposing) { _process.Exited -= OnProcessExited; _process.OutputDataReceived -= OnOutputDataReceived; _process.ErrorDataReceived -= OnErrorDataReceieved; _process.Dispose(); } base.Dispose(disposing); } } } '@ } } function Assert-DockerPushJob { [CmdletBinding()] param() process { if ('Docker.PowerShell.CLI.DockerPushJob' -as [type]) { return } Add-Type -TypeDefinition @' using System; using System.Management.Automation; using System.Diagnostics; namespace Docker.PowerShell.CLI { public sealed class DockerPushJob : Job { // Docker process private readonly Process _process; // Abstract member, does not apply public override string Location { get { return _process.MachineName; } } public override string StatusMessage { get { if (_process.HasExited) { return string.Format("Exited ({0})", _process.ExitCode); } else { return "Running"; } } } public override bool HasMoreData { get { return Error.Count > 0 || Debug.Count > 0; } } public override void StopJob() { _process.Kill(); SetJobState(JobState.Stopped); } public int GetProcessId() { return _process.Id; } private static string GetName(string arguments) { return string.Format("docker {0}", arguments); } public DockerPushJob(string command, string arguments) : base(command, GetName(arguments)) { PSJobTypeName = "DockerJob"; var startInfo = new ProcessStartInfo("docker", arguments); startInfo.RedirectStandardOutput = true; startInfo.RedirectStandardError = true; startInfo.UseShellExecute = false; startInfo.CreateNoWindow = true; _process = new Process(); _process.StartInfo = startInfo; _process.EnableRaisingEvents = true; _process.Exited += OnProcessExited; _process.OutputDataReceived += OnOutputDataReceived; _process.ErrorDataReceived += OnErrorDataReceieved; SetJobState(JobState.Running); _process.Start(); _process.BeginOutputReadLine(); _process.BeginErrorReadLine(); } private void OnOutputDataReceived(object sender, DataReceivedEventArgs e) { if (e == null || e.Data == null) { return; } var record = new DebugRecord(e.Data);; Debug.Add(record); } private void OnErrorDataReceieved(object sender, DataReceivedEventArgs e) { if (e == null || e.Data == null) { return; } var exn = new Exception(e.Data); var err = new ErrorRecord( exn, "DockerPullError", ErrorCategory.FromStdErr, null ); Error.Add(err); } private void OnProcessExited(object sender, EventArgs e) { if (_process.ExitCode == 0) { SetJobState(JobState.Completed); } else { SetJobState(JobState.Failed); } } protected override void Dispose(bool disposing) { if (disposing) { _process.Exited -= OnProcessExited; _process.OutputDataReceived -= OnOutputDataReceived; _process.ErrorDataReceived -= OnErrorDataReceieved; _process.Dispose(); } base.Dispose(disposing); } } } '@ } } function ConvertTo-CompletionText { [CmdletBinding()] param( [Parameter(Mandatory, ValueFromPipeline, Position = 0)] [AllowNull()] [AllowEmptyString()] [string] $InputObject, [Parameter(Mandatory)] [AllowNull()] [AllowEmptyString()] [string] $WordToComplete ) process { $QuoteType = $null foreach ($QuoteChar in @('''', '"')) { if ($WordToComplete.StartsWith($QuoteChar)) { $QuoteType = $QuoteChar break } } switch ($QuoteType) { '''' { '''{0}''' -f [CodeGeneration]::EscapeSingleQuotedStringContent($InputObject) } '"' { '"{0}"' -f (($InputObject -replace '"', '""') -replace '`', '``') } default { if ($InputObject -match '[\s''"]') { '''{0}''' -f [CodeGeneration]::EscapeSingleQuotedStringContent($InputObject) } else { $InputObject } } } } } function ConvertTo-DockerWildcard { [CmdletBinding()] param( [Parameter(ValueFromPipeline)] [string] $Expression ) process { $Expression -split '(?<!`)\*' | ForEach-Object { if ($_) { $_ -replace '`\*', '*' } } } } function ConvertTo-WordToCompleteWildcard { [CmdletBinding()] param( [Parameter(Mandatory, ValueFromPipeline, Position = 0)] [AllowNull()] [AllowEmptyString()] [string] $WordToComplete ) process { if ($WordToComplete -eq '') { return '*' } $StartIndex = 0 $Length = $WordToComplete.Length - 1 foreach ($QuoteChar in @('''', '"')) { if ($WordToComplete.StartsWith($QuoteChar)) { $StartIndex = 1 if ($WordToComplete.EndsWith($QuoteChar)) { $Length = $WordToComplete.Length - 2 } else { $Length = $WordToComplete.Length - 1 } } } $TextToComplete = $WordToComplete.Substring($StartIndex, $Length) "$TextToComplete*" } } function Get-DockerContainerInternal { [CmdletBinding()] param( [Parameter()] [string[]] $Name, [Parameter()] [string[]] $Id, [Parameter()] [string] $Context, [Parameter()] [switch] $EscapeId ) process { $Parameters = @{} if ($Id) { $Parameters['Id'] = $Id | ForEach-Object { if ($EscapeId) { [WildcardPattern]::Escape($_) } else { $_ } } } if ($Name) { $Parameters['Name'] = $Name } if ($Context) { $Parameters['Context'] = $Context } Get-DockerContainer @Parameters } } function Get-DockerContainerSingle { [CmdletBinding()] param( [Parameter()] [string] $Name, [Parameter()] [string] $Id, [Parameter()] [string] $Context, [Parameter()] [switch] $AllowNone ) process { $Containers = Get-DockerContainerInternal -Name $Name -Id $Id -Context $Context -EscapeId $Message = if ($Name) { "name '$Name'" } else { "id '$Id'" } $TargetObject = if ($Name) { $Name } else { $Id } if ($Containers.Count -gt 1) { Write-Error "More than one container found for $Message." -Category InvalidArgument -ErrorId 'AmbiguousContainer' -TargetObject $TargetObject } if ($Containers.Count -eq 0 -and !$AllowNone) { Write-Error "No container found for $Message." -Category ObjectNotFound -ErrorId 'ContainerNotFound' -TargetObject $TargetObject } $Containers } } function Get-DockerImageInternal { [CmdletBinding()] param( [Parameter()] [string] $Name, [Parameter()] [string] $Tag, [Parameter()] [string[]] $Id, [Parameter()] [string[]] $FullName, [Parameter()] [string] $Context, [Parameter()] [switch] $EscapeId ) process { $Parameters = @{} if ($Id) { $Parameters['Id'] = $Id | ForEach-Object { if ($EscapeId) { [WildcardPattern]::Escape($_) } else { $_ } } } if ($Name) { $Parameters['Name'] = $Name $Parameters['Tag'] = $Tag } if ($FullName) { $Parameters['FullName'] = $FullName } if ($Context) { $Parameters['Context'] = $Context } Get-DockerImage @Parameters } } function Get-DockerNetworkInternal { [CmdletBinding( DefaultParameterSetName = 'Id', RemotingCapability = [RemotingCapability]::None, PositionalBinding = $false )] [OutputType([DockerNetwork])] param( [Parameter()] [string[]] $Name, [Parameter()] [string[]] $Id, [Parameter()] [switch] $EscapeId, [Parameter()] [string] $Context ) process { $Parameters = @{} if ($Name) { $Parameters['Name'] = $Name } if ($Id) { $Parameters['Id'] = $Id | ForEach-Object { if ($EscapeId) { [WildcardPattern]::Escape($_) } else { $_ } } } if ($Context) { $Parameters['Context'] = $Context } Get-DockerNetwork @Parameters } } $Docker = (Get-Command 'docker' -ErrorAction Stop).Path function Invoke-Docker { [CmdletBinding()] param( [Parameter(Mandatory, Position = 0, ValueFromRemainingArguments)] [string[]] $ArgumentList, [Parameter()] [ValidateDockerContext()] [string] $Context ) process { $List = [List[string]]$ArgumentList if ($Context) { $List.Insert(0, '--context') $List.Insert(1, $Context) } Write-Debug "$Docker $List" & $Docker $List } } function Test-MultipleWildcard { [CmdletBinding()] param( [Parameter()] [string[]] $WildcardPattern, [Parameter()] [string[]] $ActualValue ) process { if (!$WildcardPattern) { return $true } if (!$ActualValue) { return $false } foreach ($w in $WildcardPattern) { foreach ($a in $ActualValue) { if ($a -like $w) { return $true } } } return $false } } function Connect-DockerContainer { [CmdletBinding()] [OutputType([System.Management.Automation.Internal.AutomationNull])] [Alias('ccdc')] param( [Parameter(Mandatory)] [ArgumentCompleter([DockerContainerCompleter])] [string] $ContainerName, [Parameter()] [ValidateNotNullOrEmpty()] [ArgumentCompleter([DockerContextCompleter])] [string] $Context ) process { Invoke-Docker attach $ContainerName -Context $Context } } function Enter-DockerContainer { [CmdletBinding( DefaultParameterSetName = 'Id', RemotingCapability = [RemotingCapability]::OwnedByCommand, PositionalBinding = $false )] [OutputType([System.Management.Automation.Internal.AutomationNull])] [Alias('etdc')] param( [Parameter(Mandatory, Position = 0, ParameterSetName = 'Name')] [SupportsWildcards()] [Alias('ContainerName')] [ArgumentCompleter([DockerContainerCompleter])] [string] $Name, [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'Id')] [ArgumentCompleter([DockerContainerCompleter])] [Alias('Container', 'ContainerId')] [string] $Id, [Parameter(Mandatory, Position = 1)] [ArgumentCompleter([DockerBinArgumentCompleter])] [string[]] $Command, [Parameter()] [ValidateNotNullOrEmpty()] [ArgumentCompleter([DockerContextCompleter])] [string] $Context ) process { $Container = Get-DockerContainerSingle -Name $Name -Id $Id -Context $Context if (!$?) { return } if ($Container.State -ne 'running') { Write-Error "Cannot enter container $($Container.Id) ($($Container.Names)) because it is not running." return } $ArgumentList = @( 'exec' '-it' $Container.Id $Command ) Invoke-Docker -ArgumentList $ArgumentList -Context $Context } } function Get-DockerContainer { [CmdletBinding( DefaultParameterSetName = 'Id', RemotingCapability = [RemotingCapability]::OwnedByCommand, PositionalBinding = $false )] [Alias('gdc')] [OutputType([DockerContainer])] param( [Parameter(Position = 0, ParameterSetName = 'Name')] [ValidateNotNullOrEmpty()] [Alias('ContainerName')] [SupportsWildcards()] [ArgumentCompleter([DockerContainerCompleter])] [LowerCaseTransformation()] [string[]] $Name, [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Id')] [ValidateNotNullOrEmpty()] [Alias('Container', 'ContainerId')] [SupportsWildcards()] [ArgumentCompleter([DockerContainerCompleter])] [LowerCaseTransformation()] [string[]] $Id, [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Name')] [ValidateNotNullOrEmpty()] [SupportsWildcards()] [ArgumentCompleter([EmptyStringArgumentCompleter])] [string[]] $Label, [Parameter()] [ValidateSet('running', 'created', 'restarting', 'removing', 'paused', 'exited', 'dead')] [Alias('Status')] [LowerCaseTransformation()] [string[]] $State, [Parameter()] [ValidateNotNullOrEmpty()] [ArgumentCompleter([DockerContextCompleter])] [string] $Context ) process { [List[string]]$cl = @( 'container' 'list' '--no-trunc' '--format' '{{ json . }}' '--all' ) $ReportNotMatched = [HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase) foreach ($s in $State) { $cl.Add('--filter') $cl.Add("status=$s") } foreach ($n in $Name) { if (![WildcardPattern]::ContainsWildcardCharacters($n)) { [void]$ReportNotMatched.Add($n) $cl.Add('--filter') $cl.Add("name=$n") continue } # since --filter x=y matches ANY filter x, we can just add the longest part # here (least likely to match other filters) and provide specific PowerShell # filtering later $filter = $n -split '[*?[\]]' | Sort-Object -Property Length -Descending | Select-Object -First 1 $cl.Add('--filter') $cl.Add("name=$filter") } foreach ($l in $Label) { # Label filter does not support partial match if (![WildcardPattern]::ContainsWildcardCharacters($l)) { $cl.Add('--filter') $cl.Add("label=$w") } } $IdFilter = [List[string]]::new() $UseIdFilter = $true foreach ($i in $Id) { if (![WildcardPattern]::ContainsWildcardCharacters($i)) { [void]$ReportNotMatched.Add($i) $IdFilter.Add($i) continue } if (!$UseIdFilter) { continue } # filter id=x only matches 'x...', not '...x'. We can therefore only # filter from the beginning of the string to the first wildcard character $FilterParts = $i -split '[*?[\]]' if ($FilterParts[0].Length -eq 0) { $UseIdFilter = $false continue } else { $IdFilter.Add($FilterParts[0]) } } if ($UseIdFilter) { foreach ($i in $IdFilter) { $cl.Add('--filter') $cl.Add("id=$i") } } Invoke-Docker -ArgumentList $cl -Context $Context | ForEach-Object { [DockerContainer]$pso = $_ | ConvertFrom-Json $pso.PSObject.Members.Add([PSNoteProperty]::new('PSDockerContext', $Context)) if (-not (Test-MultipleWildcard -WildcardPattern $Name -ActualValue $pso.Names)) { return } if (-not (Test-MultipleWildcard -WildcardPattern $Id -ActualValue $pso.Id)) { return } if (-not (Test-MultipleWildcard -WildcardPattern $Label -ActualValue $pso.Labels)) { return } $ToRemove = if ($PSCmdlet.ParameterSetName -eq 'Id') { $pso.Id } else { $pso.Names } foreach ($removable in $ToRemove) { [void]$ReportNotMatched.Remove($removable) } $pso } foreach ($r in $ReportNotMatched) { $ErrorId = if ($Id) { 'Id' } else { 'Name' } $WriteError = @{ Message = "The docker container '$r' was not found." Exception = [ItemNotFoundException]'The docker container was not found.' Category = 'ObjectNotFound' ErrorId = "Container${ErrorId}NotFound" TargetObject = $r } Write-Error @WriteError } } } function Get-DockerContainerLog { [CmdletBinding( DefaultParameterSetName = 'Id', PositionalBinding = $false, RemotingCapability = [RemotingCapability]::OwnedByCommand )] [OutputType([string])] param( [Parameter(Mandatory, Position = 0, ParameterSetName = 'Name')] [SupportsWildcards()] [Alias('ContainerName')] [ArgumentCompleter([DockerContainerCompleter])] [string] $Name, [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'Id')] [Alias('Container', 'ContainerId')] [ArgumentCompleter([DockerContainerCompleter])] [string] $Id, [Parameter()] [ArgumentCompleter([DateTimeArgumentCompleter])] [DateTime] $Since, [Parameter()] [ArgumentCompleter([DateTimeArgumentCompleter])] [DateTime] $Until, [Parameter()] [Alias('Tail')] [ArgumentCompleter([NumericArgumentCompleter])] [int] $Last, [Parameter()] [switch] $Follow, [Parameter()] [ValidateNotNullOrEmpty()] [ArgumentCompleter([DockerContextCompleter])] [string] $Context ) process { $Container = Get-DockerContainerSingle -Name $Name -Id $Id -Context $Context if (!$?) { return } $ArgumentList = @( 'container' 'logs' $Container.Id '--timestamps' '--details' ) if ($Follow) { $ArgumentList += '--follow' } if ($Last) { $ArgumentList += '--tail' $ArgumentList += $Last } if ($Since) { $ArgumentList += '--since' $ArgumentList += $Since.ToString('yyyy-MM-ddTHH:mm:ss') } if ($Until) { $ArgumentList += '--until' $ArgumentList += $Until.ToString('yyyy-MM-ddTHH:mm:ss') } Write-Debug "$ArgumentList" Invoke-Docker -ArgumentList $ArgumentList -Context $Context } } function New-DockerContainer { [CmdletBinding( RemotingCapability = [RemotingCapability]::OwnedByCommand, PositionalBinding = $false, SupportsShouldProcess, ConfirmImpact = [ConfirmImpact]::Medium )] [OutputType([DockerContainer])] [Alias('ndc')] param( [Parameter(Mandatory, Position = 0)] [ArgumentCompleter([DockerImageCompleter])] [string] $ImageName, [Parameter(Position = 1)] [ValidateNotNullOrEmpty()] [Alias('ContainerName')] [ArgumentCompleter([EmptyStringArgumentCompleter])] [string] $Name, [Parameter()] [ArgumentCompleter([EmptyStringArgumentCompleter])] [string] $HostName, [Parameter()] [ArgumentCompleter([EmptyIpAddressArgumentCompleter])] [IPAddress] $IPAddress, [Parameter()] [switch] $Interactive, [Parameter()] [ArgumentCompleter([EmptyHashtableArgumentCompleter])] [Hashtable] $Labels, [Parameter()] [ArgumentCompleter([EmptyHashtableArgumentCompleter])] [Hashtable] $Environment, [Parameter()] [ValidateSet('always', 'missing', 'never')] [string] $PullBehavior, [Parameter()] [switch] $ReadOnly, [Parameter()] [switch] $AutoRemove, [Parameter()] [ArgumentCompleter([NumericArgumentCompleter])] [int] $TimeoutSeconds, [Parameter()] [ArgumentCompleter([EmptyStringArgumentCompleter])] [string] $WorkingDirectory, # Any additional parameters to pass to the docker cli [Parameter()] [ArgumentCompleter([EmptyStringArgumentCompleter])] [string[]] $Parameters, [Parameter()] [ValidateNotNullOrEmpty()] [ArgumentCompleter([DockerContextCompleter])] [string] $Context ) process { [List[string]]$ArgumentList = @( 'container' 'create' ) if ($Name) { $ArgumentList.Add('--name') $ArgumentList.Add($Name) } if ($HostName) { $ArgumentList.Add('--hostname') $ArgumentList.Add($HostName) } if ($IPAddress) { $ArgumentList.Add('--ip') $ArgumentList.Add($IPAddress) } if ($Interactive) { $ArgumentList.Add('--interactive') } if ($Labels) { foreach ($k in $Labels.Keys) { $ArgumentList.Add('--label') $ArgumentList.Add("$k=$($Labels[$k])") } } if ($Environment) { foreach ($k in $Environment.Keys) { $ArgumentList.Add('--env') $ArgumentList.Add("$k=$($Environment[$k])") } } if ($PullBehavior) { $ArgumentList.Add('--pull') $ArgumentList.Add($PullBehavior) } if ($ReadOnly) { $ArgumentList.Add('--read-only') } if ($AutoRemove) { $ArgumentList.Add('--rm') } if ($TimeoutSeconds) { $ArgumentList.Add('--timeout') $ArgumentList.Add($TimeoutSeconds) } if ($WorkingDirectory) { $ArgumentList.Add('--workdir') $ArgumentList.Add($WorkingDirectory) } if ($Parameters) { foreach ($p in $Parameters) { $ArgumentList.Add($p) } } $ArgumentList.Add($ImageName) if (!$PSCmdlet.ShouldProcess( "Creating container '$Name' from image '$ImageName'.", "Create container '$Name' from image '$ImageName'?", "docker $ArgumentList")) { return } $Id = Invoke-Docker -ArgumentList $ArgumentList -Context $Context if ($?) { Get-DockerContainerInternal -Id $Id -Context $Context } } } function Remove-DockerContainer { [CmdletBinding( DefaultParameterSetName = 'Id', RemotingCapability = [RemotingCapability]::OwnedByCommand, PositionalBinding = $false, SupportsShouldProcess, ConfirmImpact = [ConfirmImpact]::Medium )] param( [Parameter(Mandatory, Position = 0, ParameterSetName = 'Name')] [Alias('ContainerName')] [SupportsWildcards()] [ArgumentCompleter([DockerContainerCompleter])] [string[]] $Name, [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'Id')] [Alias('Container', 'ContainerId')] [ArgumentCompleter([DockerContainerCompleter])] [string[]] $Id, [Parameter(Mandatory, ParameterSetName = 'Prune')] [Alias('Unused', 'Prune')] [switch] $StoppedContainers, [Parameter()] [switch] $Force, [Parameter()] [ValidateNotNullOrEmpty()] [ArgumentCompleter([DockerContextCompleter])] [string] $Context ) process { if ($StoppedContainers) { $ArgumentList = @( 'container' 'prune' if ($Force) { '--force' } ) if ($PSCmdlet.ShouldProcess( 'Removing stopped containers.', 'Remove stopped containers?', "docker $ArgumentList")) { Invoke-Docker -ArgumentList $ArgumentList -Context $Context | Out-Null } return } $Containers = Get-DockerContainerInternal -Name $Name -Id $Id -Context $Context -EscapeId if ($Containers.Count -eq 0) { # If no containers, the user input wildcard(s) or an error was reported by internal Get Write-Verbose 'No containers to process.' return } $ArgumentList = @( 'container' 'rm' $Containers.Id if ($Force) { '--force' } ) $ShouldProcessTarget = if ($Containers.Count -eq 1) { "container '$($Containers.Id)' ($($Containers.Names))" } else { "$($Containers.Count) containers" } if (!$PSCmdlet.ShouldProcess( "Removing $ShouldProcessTarget.", "Remove $ShouldProcessTarget?", "docker $ArgumentList")) { return; } Invoke-Docker -ArgumentList $ArgumentList -Context $Context | Write-Debug } } function Rename-DockerContainer { [CmdletBinding( DefaultParameterSetName = 'Id', RemotingCapability = [RemotingCapability]::OwnedByCommand, PositionalBinding = $false, SupportsShouldProcess, ConfirmImpact = [ConfirmImpact]::Medium )] [OutputType([DockerContainer])] [Alias('rndc')] param( [Parameter(Mandatory, Position = 0, ParameterSetName = 'Name')] [SupportsWildcards()] [Alias('ContainerName')] [ArgumentCompleter([DockerContainerCompleter])] [string] $Name, [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'Id')] [ArgumentCompleter([DockerContainerCompleter])] [Alias('Container', 'ContainerId')] [string] $Id, [Parameter(Mandatory, Position = 1)] [ArgumentCompleter([EmptyStringArgumentCompleter])] [string] $NewName, [Parameter()] [ValidateNotNullOrEmpty()] [ArgumentCompleter([DockerContextCompleter])] [string] $Context, [Parameter()] [switch] $PassThru ) process { $Container = Get-DockerContainerSingle -Name $Name -Id $Id -Context $Context if (!$?) { return } if ($PSCmdlet.ShouldProcess( "Renaming docker container '$($Container.Id)' from '$($Container.Names)' to $($NewName).", "Rename docker container '$($Container.Id)' from '$($Container.Names)' to $($NewName)?", "docker $ArgumentList")) { Invoke-Docker rename $Container.Id $NewName -Context $Context if ($PassThru) { Get-DockerContainerSingle -Name $NewName -Context $Context } } } } function Restart-DockerContainer { [CmdletBinding( DefaultParameterSetName = 'Id', RemotingCapability = [RemotingCapability]::OwnedByCommand, PositionalBinding = $false, SupportsShouldProcess, ConfirmImpact = [ConfirmImpact]::Medium )] [OutputType([DockerContainer])] [Alias('rtdc')] param( [Parameter(Position = 0, Mandatory, ParameterSetName = 'Name')] [Alias('ContainerName')] [SupportsWildcards()] [ArgumentCompleter([DockerContainerCompleter])] [string[]] $Name, [Parameter(ValueFromPipelineByPropertyName, Mandatory, ParameterSetName = 'Id')] [Alias('Container', 'ContainerId')] [ArgumentCompleter([DockerContainerCompleter])] [string[]] $Id, [Parameter()] [ArgumentCompleter([NumericArgumentCompleter])] [int] $TimeoutSeconds, [Parameter()] [switch] $PassThru, [Parameter()] [ValidateNotNullOrEmpty()] [ArgumentCompleter([DockerContextCompleter])] [string] $Context ) process { $Containers = Get-DockerContainerInternal -Name $Name -Id $Id -Context $Context -EscapeId if ($Containers.Count -eq 0) { # If no containers, the user input wildcard(s) or an error was reported by internal Get Write-Verbose 'No containers to process.' return } $ArgumentList = @( 'container', 'restart' ) if ($TimeoutSeconds) { $ArgumentList += '--time' $ArgumentList += $TimeoutSeconds } $ArgumentList += $Containers.Id $ShouldProcessTarget = if ($Containers.Count -eq 1) { "container '$($Containers.Id)' ($($Containers.Names))" } else { "$($Containers.Count) containers" } if (!$PSCmdlet.ShouldProcess( "Restarting $ShouldProcessTarget.", "Restart $ShouldProcessTarget?", "docker $ArgumentList")) { return } # Stream results as they become available Invoke-Docker -ArgumentList $ArgumentList -Context $Context | ForEach-Object { if ($PassThru) { Get-DockerContainerInternal -Id $_ -Context $Context } } } } function Resume-DockerContainer { [CmdletBinding( DefaultParameterSetName = 'Id', RemotingCapability = [RemotingCapability]::OwnedByCommand, PositionalBinding = $false, SupportsShouldProcess, ConfirmImpact = [ConfirmImpact]::Medium )] [OutputType([DockerContainer])] [Alias('rudc')] param( [Parameter(Position = 0, Mandatory, ParameterSetName = 'Name')] [SupportsWildcards()] [Alias('ContainerName')] [ArgumentCompleter([DockerContainerCompleter])] [string[]] $Name, [Parameter(ValueFromPipelineByPropertyName, Mandatory, ParameterSetName = 'Id')] [ArgumentCompleter([DockerContainerCompleter])] [Alias('Container', 'ContainerId')] [string[]] $Id, [Parameter()] [switch] $PassThru, [Parameter()] [ValidateNotNullOrEmpty()] [ArgumentCompleter([DockerContextCompleter])] [string] $Context ) process { $Containers = Get-DockerContainerInternal -Id $Id -Name $Name -Context $Context -EscapeId # Ensure we have containers to process if ($Containers.Count -eq 0) { # If no containers, the user input wildcard(s) or an error was reported by internal Get Write-Verbose 'No containers to process.' return } if ($Containers.Count -gt 1) { $ContainerIdentification = "$($Containers.Count) containers" } else { $ContainerIdentification = "container $($Containers.Id) ($($Containers.Names))" } if (!$PSCmdlet.ShouldProcess( "Unpausing all processes in $ContainerIdentification.", "Unpause all processes in $ContainerIdentification?", "docker $ArgumentList" )) { return } Invoke-Docker unpause $Containers.Id -Context $Context | ForEach-Object { if ($PassThru) { Get-DockerContainerInternal -Id $_ -Context $Context } } } } function Start-DockerContainer { [CmdletBinding( DefaultParameterSetName = 'Id', RemotingCapability = [RemotingCapability]::OwnedByCommand, PositionalBinding = $false, SupportsShouldProcess, ConfirmImpact = [ConfirmImpact]::Medium )] [OutputType([DockerContainer])] [Alias('sadc')] param( [Parameter(Mandatory, Position = 0, ParameterSetName = 'Name')] [Parameter(Mandatory, Position = 0, ParameterSetName = 'Name+Interactive')] [Alias('ContainerName')] [SupportsWildcards()] [ArgumentCompleter([DockerContainerCompleter])] [string[]] $Name, [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'Id')] [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'Id+Interactive')] [Alias('Container', 'ContainerId')] [ArgumentCompleter([DockerContainerCompleter])] [string[]] $Id, # Maps to the --attach and --interactive parameters. (In the context of PowerShell, it does not make # sense to stream output to the console without attaching to the container.) [Parameter(Mandatory, ParameterSetName = 'Id+Interactive')] [Parameter(Mandatory, ParameterSetName = 'Name+Interactive')] [switch] $Interactive, [Parameter(ParameterSetName = 'Id')] [Parameter(ParameterSetName = 'Name')] [switch] $PassThru, [Parameter()] [ValidateNotNullOrEmpty()] [ArgumentCompleter([DockerContextCompleter])] [string] $Context ) process { $Containers = Get-DockerContainerInternal -Name $Name -Id $Id -Context $Context -EscapeId if ($Containers.Count -eq 0) { # If no containers, the user input wildcard(s) or an error was reported by internal Get Write-Verbose 'No containers to process.' return } $ArgumentList = @( 'container' 'start' if ($Interactive) { '--attach'; '--interactive' } $Containers.Id ) $ShouldProcessTarget = if ($Containers.Count -eq 1) { "container '$($Containers.Id)' ($($Containers.Names))" } else { "$($Containers.Count) containers" } if (!$PSCmdlet.ShouldProcess( "Starting $ShouldProcessTarget.", "Start $ShouldProcessTarget?", "docker $ArgumentList" )) { return } Invoke-Docker -ArgumentList $ArgumentList -Context $Context | ForEach-Object { if ($PassThru) { Get-DockerContainerInternal -Id $_ -Context $Context } } } } function Stop-DockerContainer { [CmdletBinding( DefaultParameterSetName = 'Id', RemotingCapability = [RemotingCapability]::OwnedByCommand, PositionalBinding = $false, SupportsShouldProcess, ConfirmImpact = [ConfirmImpact]::Medium )] [OutputType([DockerContainer])] [Alias('spdc')] param( [Parameter(Position = 0, Mandatory, ParameterSetName = 'Name')] [Alias('ContainerName')] [SupportsWildcards()] [ArgumentCompleter([DockerContainerCompleter])] [string[]] $Name, [Parameter(ValueFromPipelineByPropertyName, Mandatory, ParameterSetName = 'Id')] [Alias('Container', 'ContainerId')] [ArgumentCompleter([DockerContainerCompleter])] [string[]] $Id, [Parameter()] [switch] $Force, [Parameter()] [ArgumentCompleter([NumericArgumentCompleter])] [int] $TimeoutSeconds, [Parameter()] [switch] $PassThru, [Parameter()] [ValidateNotNullOrEmpty()] [ArgumentCompleter([DockerContextCompleter])] [string] $Context ) process { $Containers = Get-DockerContainerInternal -Name $Name -Id $Id -Context $Context -EscapeId if ($Containers.Count -eq 0) { # If no containers, the user input wildcard(s) or an error was reported by internal Get Write-Verbose 'No containers to process.' return } $StopOrKill = if ($Force) { 'kill' } else { 'stop' } $Time = if (!$Force -and $TimeoutSeconds -gt 0) { '--time'; $TimeoutSeconds } $ArgumentList = @( 'container' $StopOrKill $Time ) $ArgumentList += $Containers.Id $ShouldProcessTarget = if ($Containers.Count -eq 1) { "container '$($Containers.Id)' ($($Containers.Names))" } else { "$($Containers.Count) containers" } if (!$PSCmdlet.ShouldProcess( "$StopOrKill $ShouldProcessTarget.", "$StopOrKill $ShouldProcessTarget?", "docker $ArgumentList" )) { return } # Stream results as they become available Invoke-Docker -ArgumentList $ArgumentList -Context $Context | ForEach-Object { if ($PassThru) { Get-DockerContainerInternal -Id $_ -Context $Context } } } } function Suspend-DockerContainer { [CmdletBinding( DefaultParameterSetName = 'Id', RemotingCapability = [RemotingCapability]::OwnedByCommand, PositionalBinding = $false, SupportsShouldProcess, ConfirmImpact = [ConfirmImpact]::Medium )] [OutputType([DockerContainer])] [Alias('ssdc')] param( [Parameter(Position = 0, Mandatory, ParameterSetName = 'Name')] [SupportsWildcards()] [Alias('ContainerName')] [ArgumentCompleter([DockerContainerCompleter])] [string[]] $Name, [Parameter(ValueFromPipelineByPropertyName, Mandatory, ParameterSetName = 'Id')] [Alias('Container', 'ContainerId')] [ArgumentCompleter([DockerContainerCompleter])] [string[]] $Id, [Parameter()] [switch] $PassThru, [Parameter()] [ValidateNotNullOrEmpty()] [ArgumentCompleter([DockerContextCompleter])] [string] $Context ) process { $Containers = Get-DockerContainerInternal -Id $Id -Name $Name -Context $Context -EscapeId if ($Containers.Count -eq 0) { # If no containers, the user input wildcard(s) or an error was reported by internal Get Write-Verbose 'No containers to process.' return } if ($Containers.Count -gt 1) { $ContainerIdentification = "$($Containers.Count) containers" } else { $ContainerIdentification = "container $($Containers.Id) ($($Containers.Names))" } if (!$PSCmdlet.ShouldProcess( "Pausing all processes in $ContainerIdentification.", "Pause all processes in $ContainerIdentification?", "docker $ArgumentList" )) { return } Invoke-Docker pause $Containers.Id -Context $Context | ForEach-Object { if ($PassThru) { Get-DockerContainerInternal -Id $_ -Context $Context } } } } function Wait-DockerContainer { [CmdletBinding( DefaultParameterSetName = 'Id', RemotingCapability = [RemotingCapability]::OwnedByCommand, PositionalBinding = $false )] [OutputType([System.Management.Automation.Internal.AutomationNull])] [Alias('wdc')] param( [Parameter(Mandatory, Position = 0, ParameterSetName = 'Name')] [SupportsWildcards()] [Alias('ContainerName')] [ArgumentCompleter([DockerContainerCompleter])] [string[]] $Name, [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'Id')] [Alias('Container', 'ContainerId')] [ArgumentCompleter([DockerContainerCompleter])] [string[]] $Id, [Parameter()] [ValidateNotNullOrEmpty()] [ArgumentCompleter([DockerContextCompleter])] [string] $Context ) begin { $ContainerIds = [HashSet[string]]::new() } process { $Containers = Get-DockerContainerInternal -Name $Name -Id $Id -EscapeId -Context $Context foreach ($Container in $Containers) { $ContainerIds.Add($Container.Id) } } end { if ($Containers.Count -eq 0) { Write-Verbose 'No containers to process.' return } $ArgumentList = @('wait'; $Containers.Id) Invoke-Docker -ArgumentList $ArgumentList -Context $Context | Write-Debug } } function Get-DockerContext { [CmdletBinding( RemotingCapability = [RemotingCapability]::OwnedByCommand, PositionalBinding = $false )] [OutputType([DockerContext])] [Alias('gdcx')] param( [Parameter(Position = 0)] [ValidateNotNullOrEmpty()] [SupportsWildcards()] [Alias('ContextName')] [ArgumentCompleter([DockerContextCompleter])] [string[]] $Name ) process { $ReportNotMatched = [HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase) foreach ($i in $Name) { if (![WildcardPattern]::ContainsWildcardCharacters($i)) { [void]$ReportNotMatched.Add($i) } } Invoke-Docker context list --format '{{ json . }}' | ForEach-Object { [DockerContext]$pso = ConvertFrom-Json $_ if (-not (Test-MultipleWildcard -WildcardPattern $Name -ActualValue $pso.Name)) { return } [void]$ReportNotMatched.Remove($pso.Name) $pso } foreach ($Unmatched in $ReportNotMatched) { Write-Error "No context found with name '$Unmatched'." -Category ObjectNotFound -ErrorId 'ContextNotFound' -TargetObject $Unmatched } } } function New-DockerContext { [CmdletBinding( RemotingCapability = [RemotingCapability]::OwnedByCommand, PositionalBinding = $false, SupportsShouldProcess = $true, ConfirmImpact = [ConfirmImpact]::Medium )] [OutputType([DockerContext])] [Alias('ndcx')] param( [Parameter(Mandatory, Position = 0, ValueFromPipelineByPropertyName)] [ArgumentCompleter([EmptyStringArgumentCompleter])] [string] $Name, [Parameter(Mandatory)] [Alias('DockerEndpoint', 'Host')] [ValidatePattern('^[^,]+$')] [ArgumentCompleter([EmptyStringArgumentCompleter])] [string] $DockerHost, [Parameter()] [Alias('swarm', 'kubernetes', 'all')] [string] $DefaultStackOrchestrator, [Parameter()] [ArgumentCompleter([EmptyStringArgumentCompleter])] [string] $Description ) process { $ArgumentList = @( 'context' 'create' $Name if ($DefaultStackOrchestrator) { '--default-stack-orchestrator'; $DefaultStackOrchestrator } if ($Description) { '--description'; $Description } if ($DockerHost) { '--docker'; "host=$DockerHost" } if ($Kubernetes) { '--kubernetes'; $Kubernetes } ) if ($PSCmdlet.ShouldProcess( "Creating docker context '$Name' with host '$($DockerHost)'.", "Create docker context '$Name' with host '$($DockerHost)'?", "docker $ArgumentList" )) { Invoke-Docker -ArgumentList $ArgumentList 2>&1 | Write-Debug if ($?) { Get-DockerContext -Name $Name } } } } function Remove-DockerContext { [CmdletBinding( RemotingCapability = [RemotingCapability]::OwnedByCommand, PositionalBinding = $false, SupportsShouldProcess, ConfirmImpact = [ConfirmImpact]::Medium )] [OutputType([System.Management.Automation.Internal.AutomationNull])] [Alias('rdcx')] param( [Parameter(Position = 0, Mandatory, ValueFromPipelineByPropertyName)] [Alias('ContextName')] [ArgumentCompleter([DockerContextCompleter])] [string[]] $Name ) begin { $RemoveContexts = [HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase) } process { $Contexts = Get-DockerContext -Name $Name foreach ($Context in $Contexts) { if ($Context.Current) { $WriteError = @{ Message = "The docker context '$($Context.Name)' cannot be removed because it is currently in use." Exception = [InvalidOperationException]'The docker context cannot be removed because it is currently in use.' RecommendedAction = 'Change the current docker context with ''Use-DockerContext'' and try again.' TargetObject = $Context ErrorId = 'ContextInUse' Category = 'ResourceBusy' } Write-Error @WriteError continue } if ($RemoveContexts.Contains($Context.Name)) { Write-Warning "Context '$($Context.Name)' is already being removed." continue } if ($PSCmdlet.ShouldProcess( "Removing docker context '$($Context.Name)'.", "Remove docker context '$($Context.Name)'?", "docker context remove $($Context.Name)" )) { [void]$RemoveContexts.Add($Context.Name) } } } end { if ($RemoveContexts.Count -eq 0) { return } $ArgumentList = @( 'context' 'remove' $RemoveContexts ) Invoke-Docker -ArgumentList $ArgumentList | Write-Debug } } function Use-DockerContext { [CmdletBinding( DefaultParameterSetName = 'Default', RemotingCapability = [RemotingCapability]::OwnedByCommand, PositionalBinding = $false )] [OutputType([DockerContext], ParameterSetName = 'Default')] [OutputType([System.Management.Automation.Internal.AutomationNull], ParameterSetName = 'ScriptBlock')] [Alias('udcx')] param( [Parameter(Mandatory, Position = 0, ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [Alias('ContextName')] [ValidateDockerContext()] [ArgumentCompleter([DockerContextCompleter])] [string] $Name, [Parameter(Mandatory, Position = 1, ParameterSetName = 'ScriptBlock')] [ValidateNotNull()] [ArgumentCompleter([EmptyScriptBlockArgumentCompleter])] [ScriptBlock] $ScriptBlock, [Parameter(ParameterSetName = 'Default')] [switch] $PassThru ) process { if ($ScriptBlock) { $Context = Invoke-Docker context show Invoke-Docker context use $Name | Write-Debug try { & $ScriptBlock } finally { Invoke-Docker context use $Context } } else { Invoke-Docker context use $Name | Write-Debug if ($? -and $PassThru) { Get-DockerContext -Name $Name } } } } function Build-DockerImage { [CmdletBinding( SupportsShouldProcess, RemotingCapability = [RemotingCapability]::OwnedByCommand, ConfirmImpact = [ConfirmImpact]::Medium )] [OutputType([DockerImage])] [Alias('bddi')] param( [Parameter()] [string] $Path = '.', [Parameter()] [string] $DockerFile = './Dockerfile', # Tags are not mandatory by the docker engine so this argument may be $null, # but personally I always want a tag and if I forget one I'm annoyed at myself. [Parameter(Mandatory)] [AllowNull()] [ArgumentCompleter([EmptyStringArgumentCompleter])] [string[]] $Tag, [Parameter()] [switch] $NoCache, [Parameter()] [switch] $Pull, [Parameter()] [ValidateNotNullOrEmpty()] [ArgumentCompleter([DockerContextCompleter])] [string] $Context, [Parameter()] [Alias('Dns', 'CustomDns')] [ArgumentCompleter([EmptyHashtableArgumentCompleter])] [DockerBuildAddHostTransformation()] [Dictionary[string, ipaddress]] $AddHosts, [Parameter()] [Alias('BuildArgs')] [ArgumentCompleter([EmptyHashtableArgumentCompleter])] [hashtable] $Parameters, [Parameter()] [switch] $PassThru ) process { $ArgumentList = @( 'image' 'build' $Path if ($DockerFile) { '--file'; $Dockerfile } if ($NoCache) { '--no-cache' } if ($Pull) { '--pull' } if ($Tag) { $Tag | ForEach-Object { '--tag'; $_ } } if ($AddHosts) { $AddHosts.Keys | ForEach-Object { '--add-host'; "${_}:$($AddHosts[$_])" } } if ($Parameters) { $Parameters.Keys | ForEach-Object { '--build-arg'; "${_}=$($Parameters[$_])" } } '--quiet' ) # Oh how I wish there were a way to write progress AND get the id of the final image if ($PassThru) { $Id = Invoke-Docker -ArgumentList $ArgumentList -Context $Context if ($?) { Get-DockerImage -Id $Id } } else { Invoke-Docker -ArgumentList $ArgumentList -Context $Context | Write-Debug } } } function Copy-DockerImage { [CmdletBinding( SupportsShouldProcess, RemotingCapability = [RemotingCapability]::OwnedByCommand, ConfirmImpact = [ConfirmImpact]::Low, PositionalBinding = $false )] [OutputType([DockerImage])] [Alias('cpdi')] param( # Full name of the source image [Parameter(Mandatory, ValueFromPipelineByPropertyName, Position = 0, ParameterSetName = 'FullName')] [SupportsWildcards()] [Alias('Reference', 'SourceReference', 'SourceFullName', 'SourceImage', 'Source')] [ArgumentCompleter([DockerImageCompleter])] [string] $FullName, # FullName for the destination image (the copy to be created) [Parameter(Mandatory, Position = 1, ValueFromPipelineByPropertyName)] [Alias('TargetImage', 'TargetName', 'DestinationFullName', 'DestinationImage', 'DestinationReference', 'TargetReference')] [ArgumentCompleter([DockerImageCompleter])] [string] $DestinationName, # Id of the source image [Parameter(Mandatory, ParameterSetName = 'Id')] [Alias('ImageId')] [ArgumentCompleter([DockerImageCompleter])] [string] $Id, [Parameter()] [switch] $PassThru, [Parameter()] [ValidateNotNullOrEmpty()] [ArgumentCompleter([DockerContextCompleter])] [string] $Context ) dynamicparam { [string]$DestinationName = $PSBoundParameters['DestinationName']; if ($DestinationName.Contains(':')) { return $null } $DynamicParameters = [RuntimeDefinedParameterDictionary]::new() $DynamicParameters.Add('DestinationTag', [RuntimeDefinedParameter]::new( 'DestinationTag', [string], [Collection[Attribute]]@( [ParameterAttribute]@{ Mandatory = $true Position = 2 ValueFromPipelineByPropertyName = $true }, [AliasAttribute]::new('Tag') ) )) return $DynamicParameters } process { $Images = Get-DockerImageInternal -Id $Id -FullName $FullName -EscapeId -Context $Context | Sort-Object -Property Id -Unique # Handle no images if ($Images.Count -eq 0) { Write-Verbose 'No images to process.' return } # Handle ambiguous images if ($Images.Count -gt 1) { $Message = if ($Id) { "Id '$Id'" } else { "Name '$FullName'" } $TargetObject = if ($Id) { $Id } else { $FullName } Write-Error "More than one image found for $Message." -Category InvalidArgument -ErrorId 'AmbiguousImage' -TargetObject $TargetObject return } # Handle dynamic parameter in case someone wants to provide destination name and tag as separate arguments if ($PSBoundParameters['DestinationTag']) { $DestinationName = "${DestinationName}:$($PSBoundParameters['DestinationTag'])" } # ShouldProcess? $ResolvedImage = $Images[0] if (!$PSCmdlet.ShouldProcess( "Creating new tag '$DestinationName' for image '$($ResolvedImage.FullName)' ($($ResolvedImage.Id)).", "Create new tag '$DestinationName' for image '$($ResolvedImage.FullName)' ($($ResolvedImage.Id))?", "docker image tag '$($ResolvedImage.Id)' '$DestinationName'" )) { return } # execute $ArgumentList = @( 'image' 'tag' $ResolvedImage.Id $DestinationName ) Invoke-Docker -ArgumentList $ArgumentList -Context $Context # PassThru if ($? -and $PassThru) { Get-DockerImage -FullName $DestinationName -Context $Context } } } function Export-DockerImage { [CmdletBinding( DefaultParameterSetName = 'FullName', SupportsShouldProcess, RemotingCapability = [RemotingCapability]::OwnedByCommand, ConfirmImpact = [ConfirmImpact]::Low, PositionalBinding = $false )] [OutputType([System.IO.FileInfo])] [Alias('epdi')] param( [Parameter(Mandatory, Position = 0, ValueFromPipelineByPropertyName, ParameterSetName = 'FullName')] [SupportsWildcards()] [Alias('Reference')] [ArgumentCompleter([DockerImageCompleter])] [string] $FullName, [Parameter(Mandatory, ParameterSetName = 'Id')] [Alias('ImageId')] [ArgumentCompleter([DockerImageCompleter])] [string] $Id, [Parameter(Mandatory, Position = 1)] [Alias('Path')] [string] $Destination, [Parameter()] [switch] $Force, [Parameter()] [switch] $PassThru, [Parameter()] [ValidateNotNullOrEmpty()] [ArgumentCompleter([DockerContextCompleter])] [string] $Context ) process { $Images = Get-DockerImageInternal -Id $Id -FullName $FullName -EscapeId | Sort-Object -Property Id -Unique # Handle no images if ($Images.Count -eq 0) { Write-Verbose 'No images to process.' return } # Handle ambiguous images if ($Images.Count -gt 1) { $Message = if ($Id) { "Id '$Id'" } else { "Name '$FullName'" } $TargetObject = if ($Id) { $Id } else { $FullName } Write-Error "More than one image found for $Message." -Category InvalidArgument -ErrorId 'AmbiguousImage' -TargetObject $TargetObject return } # Resolve destination path $OutputPath = $PSCmdlet.GetUnresolvedProviderPathFromPSPath($Destination) if (!$?) { return } if (Test-Path $OutputPath -PathType Container) { Write-Error "Destination path '$Destination' is a directory." -Category InvalidArgument -ErrorId 'DestinationIsDirectory' -TargetObject $OutputPath return } if (!$Force -and (Test-Path $OutputPath -PathType Leaf)) { Write-Error "Destination path '$Destination' already exists." -Category InvalidArgument -ErrorId 'DestinationExists' -TargetObject $OutputPath return } # ShouldProcess? $ResolvedImage = $Images[0] if (!$PSCmdlet.ShouldProcess( "Exporting image '$($ResolvedImage.FullName)' ($($ResolvedImage.Id)) to '$Destination'.", "Export image '$($ResolvedImage.FullName)' ($($ResolvedImage.Id)) to '$Destination'?", "docker image save --output '$Outputpath' '$($ResolvedImage.Id)'" )) { return } Invoke-Docker image save --output $OutputPath $ResolvedImage.Id -Context $Context if ($PassThru) { Get-Item $OutputPath } } } function Find-DockerImage { [CmdletBinding( RemotingCapability = [RemotingCapability]::OwnedByCommand, PositionalBinding = $false )] param( [Parameter(Mandatory, Position = 0)] [ArgumentCompleter([EmptyStringArgumentCompleter])] [string] $Keyword, [Parameter()] [Alias('Automated')] [ArgumentCompleter([BooleanArgumentCompleter])] [Nullable[bool]] $IsAutomated, [Parameter()] [Alias('Official')] [ArgumentCompleter([BooleanArgumentCompleter])] [Nullable[bool]] $IsOfficial, [Parameter()] [Alias('Stars')] [ArgumentCompleter([NumericArgumentCompleter])] [int] $MinimumStars, [Parameter()] [ValidateRange(1, [int]::MaxValue)] [Alias('First', 'Take')] [ArgumentCompleter([NumericArgumentCompleter])] [int] $Limit = 100, [Parameter()] [ValidateNotNullOrEmpty()] [ArgumentCompleter([DockerContextCompleter])] [string] $Context ) process { $ArgumentList = @( 'search' '--no-trunc' "--limit=$Limit" '--format' '{{ json . }}' if ($IsAutomated.HasValue) { "--filter=is-automated=$IsAutomated" } if ($IsOfficial.HasValue) { "--filter=is-official=$IsOfficial" } if ($MinimumStars) { "--filter=stars=$MinimumStars" } $Keyword ) $Count = 0 Invoke-Docker -ArgumentList $ArgumentList -Context $Context | ForEach-Object { $pso = $_ | ConvertFrom-Json $pso.PSTypeNames.Insert(0, 'Docker.RemoteImage') $pso if ((++$Count) -eq $Limit -and !($PSBoundParameters.ContainsKey('Limit'))) { Write-Warning "The number of results has reached the default limit of $Limit. There may be more results available. Use the -Limit parameter to increase the limit." } } } } function Get-DockerImage { [CmdletBinding( DefaultParameterSetName = 'Search', PositionalBinding = $false, RemotingCapability = [RemotingCapability]::OwnedByCommand )] [OutputType([DockerImage])] [Alias('gdi')] param( [Parameter(ValueFromPipeline, Position = 0, ParameterSetName = 'Search')] [SupportsWildcards()] [ArgumentCompleter([DockerImageCompleter])] [string[]] $InputObject, [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'FullName')] [SupportsWildcards()] [Alias('Reference')] [ArgumentCompleter([DockerImageCompleter])] [string[]] $FullName, [Parameter(Position = 0, ParameterSetName = 'Name')] [SupportsWildcards()] [Alias('RepositoryName', 'ImageName')] [ArgumentCompleter([DockerImageCompleter])] [string[]] $Name, [Parameter(Position = 1, ParameterSetName = 'Name')] [SupportsWildcards()] [ArgumentCompleter([DockerImageCompleter])] [string[]] $Tag, [Parameter(ParameterSetName = 'Id')] [SupportsWildcards()] [Alias('ImageId')] [ArgumentCompleter([DockerImageCompleter])] [string[]] $Id, [Parameter()] [Alias('All')] [switch] $IncludeIntermediateImages, [Parameter()] [Alias('Untagged')] [switch] $Dangling, [Parameter()] [ValidateNotNullOrEmpty()] [ArgumentCompleter([DockerContextCompleter])] [string] $Context ) process { [List[string]]$ArgumentList = @( 'image', 'list', '--no-trunc' '--format' '{{ json . }}' if ($IncludeIntermediateImages) { '--all' } if ($Dangling) { '--filter'; 'dangling=true' } ) # Track unmatched filters $ReportNotMatched = [HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase) foreach ($_ in $InputObject) { if ($_ -match '^[0-9a-f]{12}$' -or $_ -match '^sha256:[0-9a-f]64$') { $Id += $_ } elseif ($_.Contains(':')) { $FullName += $_ } else { $Name += $_ } } foreach ($i in $FullName) { # it could be an id, probably of a nameless image if (!$i.StartsWith('sha256:')) { $ArgumentList += '--filter' $ArgumentList += "reference=$i" } if (![WildcardPattern]::ContainsWildcardCharacters($i)) { [void]$ReportNotMatched.Add($i) } } if ($Tag.Count -in @(0, 1) -and $Name.Count -gt 0) { $TagPattern = if ($Tag) { $Tag } else { '*' } $Name | ForEach-Object { $ArgumentList += '--filter' $ArgumentList += "reference=${_}:$TagPattern" } } foreach ($i in $Name) { if (![WildcardPattern]::ContainsWildcardCharacters($i)) { [void]$ReportNotMatched.Add($i) } } for ($i = 0; $i -lt $Id.Length; $i++) { # a 12-character hex string is the default displayed image id # is not the actual image's id but a pattern for it: handle # such appropriately if ($id[$i].Length -eq 12 -and ![WildcardPattern]::ContainsWildcardCharacters($id[$i])) { $id[$i] = "sha256:$($id[$i])*" } # Track unmatched filters if (![WildcardPattern]::ContainsWildcardCharacters($id[$i])) { [void]$ReportNotMatched.Add($id[$i]) } } Invoke-Docker -ArgumentList $ArgumentList -Context $Context | ForEach-Object { [DockerImage]$pso = ConvertFrom-Json $_ if (-not (Test-MultipleWildcard -WildcardPattern $Name -ActualValue $pso.Repository)) { return } if (-not (Test-MultipleWildcard -WildcardPattern $Tag -ActualValue $pso.Tag)) { return } if (-not (Test-MultipleWildcard -WildcardPattern $Id -ActualValue $pso.Id)) { return } if (-not (Test-MultipleWildcard -WildcardPattern $FullName -ActualValue $pso.FullName)) { return } [void]$ReportNotMatched.Remove($pso.Id) [void]$ReportNotMatched.Remove($pso.Repository) [void]$ReportNotMatched.Remove($pso.FullName) $pso.PSObject.Members.Add([PSNoteProperty]::new('PSDockerContext', $Context)) $pso } foreach ($Unmatched in $ReportNotMatched) { Write-Error "No image found for $($PSCmdlet.ParameterSetName) '$Unmatched'." -Category ObjectNotFound -TargetObject $Unmatched -ErrorId 'ImageNotFound' } } } function Import-DockerImage { [CmdletBinding( DefaultParameterSetName = 'LiteralPath', SupportsShouldProcess, RemotingCapability = [RemotingCapability]::OwnedByCommand, ConfirmImpact = [ConfirmImpact]::Low, PositionalBinding = $false )] [OutputType([DockerImage])] [Alias('ipdi')] param( [Parameter(Mandatory, Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'Path')] [SupportsWildcards()] [Alias('FilePath')] [string] $Path, [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'LiteralPath')] [Alias('PSPath')] [string] $LiteralPath, [Parameter()] [switch] $PassThru, [Parameter()] [ValidateNotNullOrEmpty()] [ArgumentCompleter([DockerContextCompleter])] [string] $Context ) process { if ($Path) { $FullPath = Resolve-Path -Path $Path } else { $FullPath = Resolve-Path -LiteralPath $LiteralPath } if (!$?) { return } # ShouldProcess if (!$PSCmdlet.ShouldProcess( "Importing image from '$FullPath'.", "Import image from '$FullPath'?", "docker image load --input '$FullPath'" )) { return } $ArgumentList = @( 'image' 'load' '--quiet' '--input' $FullPath ) $Output = Invoke-Docker -ArgumentList $ArgumentList -Context $Context if ($? -and $PassThru) { $Id = $Output.Replace('Loaded image ID: ', '') Get-DockerImage -Id $Id } } } function Install-DockerImage { [CmdletBinding( DefaultParameterSetName = 'FullName', RemotingCapability = [RemotingCapability]::OwnedByCommand, PositionalBinding = $false, SupportsShouldProcess, ConfirmImpact = [ConfirmImpact]::Medium )] [OutputType([DockerImage], ParameterSetName = 'FullName')] [OutputType([DockerImage], ParameterSetName = 'NameTag')] [OutputType([DockerImage], ParameterSetName = 'NameAllTags')] [OutputType([DockerImage], ParameterSetName = 'NameDigest')] [OutputType('Docker.PowerShell.CLI.DockerPullJob', ParameterSetName = 'FullNameJob', 'NameTagJob', 'NameAllTagsJob', 'NameDigestJob')] [Alias('isdi')] param( [Parameter(Position = 0, ParameterSetName = 'FullName')] [Parameter(Position = 0, ParameterSetName = 'FullNameJob')] [ArgumentCompleter([EmptyStringArgumentCompleter])] [string[]] $FullName, [Parameter(ValueFromPipelineByPropertyName, Position = 0, ParameterSetName = 'NameTag')] [Parameter(ValueFromPipelineByPropertyName, Position = 0, ParameterSetName = 'NameAllTags')] [Parameter(ValueFromPipelineByPropertyName, Position = 0, ParameterSetName = 'NameDigest')] [Parameter(ValueFromPipelineByPropertyName, Position = 0, ParameterSetName = 'NameTagJob')] [Parameter(ValueFromPipelineByPropertyName, Position = 0, ParameterSetName = 'NameAllTagsJob')] [Parameter(ValueFromPipelineByPropertyName, Position = 0, ParameterSetName = 'NameDigestJob')] [ArgumentCompleter([EmptyStringArgumentCompleter])] [string] $Name, [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'NameTag')] [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'NameTagJob')] [ValidateScript({ $_ -notmatch '[:@ ]' })] [ArgumentCompleter([EmptyStringArgumentCompleter])] [string] $Tag, [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'NameDigest')] [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'NameDigestJob')] [ValidateScript({ $_ -match '^(sha256:)?[0-9a-f]+$' })] [ArgumentCompleter([EmptyStringArgumentCompleter])] [string] $Digest, [Parameter(Mandatory, ParameterSetName = 'NameAllTags')] [Parameter(Mandatory, ParameterSetName = 'NameAllTagsJob')] [switch] $AllTags, [Parameter()] [switch] $DisableContentTrust, [Parameter()] [ArgumentCompleter([EmptyStringArgumentCompleter])] [string] $Platform, [Parameter()] [switch] $PassThru, [Parameter(Mandatory, ParameterSetName = 'FullNameJob')] [Parameter(Mandatory, ParameterSetName = 'NameTagJob')] [Parameter(Mandatory, ParameterSetName = 'NameAllTagsJob')] [Parameter(Mandatory, ParameterSetName = 'NameDigestJob')] [switch] $AsJob, [Parameter()] [ValidateNotNullOrEmpty()] [ArgumentCompleter([DockerContextCompleter])] [string] $Context ) process { $ArgumentList = @( 'image' 'pull' if ($DisableContentTrust) { '--disable-content-trust' } if ($Platform) { '--platform'; $Platform } ) if ($Name -and $Tag) { $FullName = "${Name}:$Tag" } if ($Name -and $Digest) { $FullName = "$Name@$Digest" } foreach ($f in $FullName) { if (!$PSCmdlet.ShouldProcess( "Installing image '$f'.", "Install image '$f'?", "docker $ArgumentList $f" )) { continue } $FullArgumentList = @( $ArgumentList $f ) if ($AsJob) { Assert-DockerPullJob $Job = [Docker.PowerShell.CLI.DockerPullJob]::new( $MyInvocation.Line, $FullArgumentList ) $PSCmdlet.JobRepository.Add($Job) $Job } else { Invoke-Docker -ArgumentList $FullArgumentList -Context $Context | Tee-Object -Variable DockerOutput | Write-Debug if ($? -and $PassThru) { Get-DockerImage -FullName $DockerOutput[-1] } } } } } function Publish-DockerImage { [CmdletBinding( DefaultParameterSetName = 'FullName', RemotingCapability = [RemotingCapability]::OwnedByCommand, PositionalBinding = $false, SupportsShouldProcess, ConfirmImpact = [ConfirmImpact]::Medium )] [OutputType([DockerImage], ParameterSetName = 'FullName')] [OutputType('Docker.PowerShell.CLI.DockerPushJob', ParameterSetName = 'FullNameJob')] [Alias('pbdi')] param( [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 0, ParameterSetName = 'FullName')] [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 0, ParameterSetName = 'FullNameJob')] [Alias('Reference')] [ArgumentCompleter([DockerImageCompleter])] [string[]] $FullName, [Parameter(Mandatory, ValueFromPipelineByPropertyName, Position = 0, ParameterSetName = 'Name')] [Parameter(Mandatory, ValueFromPipelineByPropertyName, Position = 0, ParameterSetName = 'NameJob')] [Parameter(Mandatory, ValueFromPipelineByPropertyName, Position = 0, ParameterSetName = 'AllTags')] [Parameter(Mandatory, ValueFromPipelineByPropertyName, Position = 0, ParameterSetName = 'AllTagsJob')] [Alias('ImageName', 'RepositoryName')] [ArgumentCompleter([DockerImageCompleter])] [string] $Name, [Parameter(Mandatory, ValueFromPipelineByPropertyName, Position = 1, ParameterSetName = 'Name')] [Parameter(Mandatory, ValueFromPipelineByPropertyName, Position = 1, ParameterSetName = 'NameJob')] [ValidateScript({ $_ -notmatch '[:@ ]' })] [ArgumentCompleter([DockerImageCompleter])] [string] $Tag, [Parameter(Mandatory, ParameterSetName = 'AllTags')] [Parameter(Mandatory, ParameterSetName = 'AllTagsJob')] [switch] $AllTags, [Parameter(Mandatory, ParameterSetName = 'Id')] [Parameter(Mandatory, ParameterSetName = 'IdJob')] [Alias('ImageId')] [ArgumentCompleter([DockerImageCompleter])] [string] $Id, [Parameter()] [switch] $DisableContentTrust, [Parameter(ParameterSetName = 'Id')] [Parameter(ParameterSetName = 'AllTags')] [Parameter(ParameterSetName = 'FullName')] [Parameter(ParameterSetName = 'Name')] [switch] $PassThru, [Parameter(Mandatory, ParameterSetName = 'NameJob')] [Parameter(Mandatory, ParameterSetName = 'AllTagsJob')] [Parameter(Mandatory, ParameterSetName = 'FullNameJob')] [Parameter(Mandatory, ParameterSetName = 'IdJob')] [switch] $AsJob, [Parameter()] [ValidateNotNullOrEmpty()] [ArgumentCompleter([DockerContextCompleter])] [string] $Context ) begin { $HasPublishedFullName = [HashSet[string]]::new() } process { $ArgumentList = @( 'image' 'push' if ($DisableContentTrust) { '--disable-content-trust' } if ($AllTags) { '--all-tags' } ) if ($Name -and $Tag) { $FullName = "${Name}:$Tag" } elseif ($Name) { $FullName = $Name } if ($Id) { $FullName = Get-DockerImageInternal -Id $Id -Context $Context -EscapeId | ForEach-Object FullName } foreach ($f in $FullName) { # Only publish once, in case of duplicate in pipeline if ($HasPublishedFullName.Contains($f)) { Write-Warning "Image '$f' has already just been published." continue } else { [void]$HasPublishedFullName.Add($f) } # Make sure the image exists if ($AllTags) { $Image = Get-DockerImageInternal -Name $f -Context $Context } else { $Image = Get-DockerImageInternal -FullName $f -Context $Context } if (!$? -or !$Image) { continue } if (!$PSCmdlet.ShouldProcess( "Publishing image '$f'.", "Publish image '$f'?", "docker $ArgumentList $f" )) { continue } $FullArgumentList = @( $ArgumentList $f ) if ($AsJob) { Assert-DockerPushJob $Job = [Docker.PowerShell.CLI.DockerPushJob]::new( $MyInvocation.Line, $FullArgumentList ) $PSCmdlet.JobRepository.Add($Job) $Job } else { Invoke-Docker -ArgumentList $FullArgumentList -Context $Context | Write-Debug if ($PassThru) { $Image } } } } } function Remove-DockerImage { [CmdletBinding( DefaultParameterSetName = 'Id', RemotingCapability = [RemotingCapability]::OwnedByCommand, PositionalBinding = $false, SupportsShouldProcess, ConfirmImpact = [ConfirmImpact]::Medium )] [OutputType([System.Management.Automation.Internal.AutomationNull])] [Alias('rdi')] param( [Parameter(Mandatory, Position = 0, ParameterSetName = 'FullName', ValueFromPipelineByPropertyName)] [SupportsWildcards()] [Alias('Reference')] [ArgumentCompleter([DockerImageCompleter])] [string[]] $FullName, [Parameter(Mandatory, ParameterSetName = 'Name')] [Alias('RepositoryName', 'ImageName')] [SupportsWildcards()] [ArgumentCompleter([DockerImageCompleter])] [string] $Name, [Parameter(Mandatory, ParameterSetName = 'Name')] [SupportsWildcards()] [ArgumentCompleter([DockerImageCompleter])] [string] $Tag, [Parameter(Mandatory, ParameterSetName = 'Id')] [Alias('ImageId')] [ArgumentCompleter([DockerImageCompleter])] [string[]] $Id, [Parameter()] [switch] $Force, [Parameter()] [ValidateNotNullOrEmpty()] [ArgumentCompleter([DockerContextCompleter])] [string] $Context ) begin { $ArgumentList = [List[string]]::new() $ArgumentList.Add('image') $ArgumentList.Add('remove') if ($Force) { $ArgumentList.Add('--force') } } process { $Images = Get-DockerImageInternal -Name $Name -Tag $Tag -Id $Id -FullName $FullName -Context $Context -EscapeId if ($Images.Count -eq 0) { Write-Verbose 'No images to process.' } foreach ($Image in $Images) { if ($PSCmdlet.ShouldProcess( "Removing docker image $($Image.FullName) ($($Image.Id)).", "Remove docker image $($Image.FullName) ($($Image.Id))?", "docker image remove $($Image.FullName)" )) { $ArgumentList.Add($Image.FullName) } } } end { if ($ArgumentList.Count -eq 2) { # no images return } Invoke-Docker -ArgumentList $ArgumentList -Context $Context | Write-Debug } } function Add-DockerNetworkConnection { [CmdletBinding( DefaultParameterSetName = 'Name+Name', RemotingCapability = [RemotingCapability]::OwnedByCommand, PositionalBinding = $false, SupportsShouldProcess, ConfirmImpact = [ConfirmImpact]::Medium )] [Alias('adnc')] [OutputType([DockerContainerNetworkConnection])] param( [Parameter( Mandatory, Position = 0, ParameterSetName = 'Name+Id' )] [Parameter( Mandatory, Position = 0, ParameterSetName = 'Name+Name' )] [ArgumentCompleter([DockerNetworkCompleter])] [string[]] $NetworkName, [Parameter( Mandatory, Position = 0, ParameterSetName = 'Id+Name' )] [Parameter( Mandatory, Position = 1, ParameterSetName = 'Name+Name' )] [ArgumentCompleter([DockerContainerCompleter])] [string[]] $ContainerName, [Parameter( Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'Id+Id' )] [ArgumentCompleter([DockerNetworkCompleter])] [string[]] $NetworkId, [Parameter( Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'Name+Id' )] [Parameter( Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'Id+Id' )] [ArgumentCompleter([DockerContainerCompleter])] [string[]] $ContainerId, # Network-scoped alias for the container. [Parameter()] [string[]] $Alias, # IP address for the container on the network. [Parameter()] [ValidateScript({ $_.AddressFamily -in @('InterNetwork', 'InterNetworkv6' ) })] [IPAddress] $IPAddress, [Parameter()] [switch] $PassThru, [Parameter()] [ValidateNotNullOrEmpty()] [ArgumentCompleter([DockerContextCompleter])] [string] $Context ) begin { $PassThruConnections = [Dictionary[string, HashSet[string]]]::new([StringComparer]::OrdinalIgnoreCase) } process { $Networks = Get-DockerNetworkInternal -Name $NetworkName -Id $NetworkId -EscapeId $Containers = Get-DockerContainerInternal -Name $ContainerName -Id $ContainerId -EscapeId if ($Networks.Count -eq 0) { Write-Verbose 'No networks to process.' return } if ($Containers.Count -eq 0) { Write-Verbose 'No containers to process.' return } $RequiresSingle = $Alias -or $IPAddress if ($RequiresSingle -and $Containers.Count -gt 1) { throw [InvalidOperationException]::new('Cannot specify multiple containers when using -Alias or -IPAddress.') } foreach ($Network in $Networks) { foreach ($Container in $Containers) { $ArgumentList = @( 'network' 'connect' foreach ($a in $Alias) { '--alias'; $a } if ($IPAddress.AddressFamily -eq 'InterNetwork') { '--ip'; $IPAddress.ToString() } if ($IPAddress.AddressFamily -eq 'InterNetworkv6') { '--ip6'; $IPAddress.ToString() } $Network.Id $Container.Id ) if (!$PSCmdlet.ShouldProcess( "Connecting docker container '$($Container.Name)' ($($Container.Id)) to network '$($Network.Name)' ($($Network.Id)).", "Connect docker container '$($Container.Name)' ($($Container.Id)) to network '$($Network.Name)' ($($Network.Id))?", "docker $ArgumentList" )) { continue } Invoke-Docker network connect $Network.Id $Container.Id -Context $Context if ($PassThru) { if (!$PassThruConnections.ContainsKey($Container.Id)) { $PassThruConnections[$Container.Id] = [HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase) } [void]$PassThruConnections[$Container.Id].Add($Network.Id) } } } } end { foreach ($Container in $PassThruConnections.Keys) { Get-DockerNetworkConnection -ContainerId $Container -NetworkId $PassThruConnections[$Container] } } } function Get-DockerNetwork { [CmdletBinding( DefaultParameterSetName = 'Id', RemotingCapability = [RemotingCapability]::None, PositionalBinding = $false )] [OutputType([DockerNetwork])] [Alias('gdn')] param( [Parameter(Position = 0, ParameterSetName = 'Name')] [SupportsWildcards()] [ArgumentCompleter([DockerNetworkCompleter])] [string[]] $Name, [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Id')] [SupportsWildcards()] [Alias('NetworkId')] [ArgumentCompleter([DockerNetworkCompleter])] [string[]] $Id, [Parameter()] [ArgumentCompleter([DockerNetworkCompleter])] [string[]] $Driver, [Parameter()] [ArgumentCompleter([DockerNetworkCompleter])] [string[]] $Scope, [Parameter()] [ValidateSet('custom', 'builtin')] [string[]] $Type, [Parameter()] [ValidateNotNullOrEmpty()] [ArgumentCompleter([DockerContextCompleter])] [string] $Context ) process { $ReportNotMatched = [HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase) foreach ($i in $Id) { if (![WildcardPattern]::ContainsWildcardCharacters($i)) { [void]$ReportNotMatched.Add($i) } } foreach ($n in $Name) { if (![WildcardPattern]::ContainsWildcardCharacters($n)) { [void]$ReportNotMatched.Add($n) } } $ArgumentList = @( 'network' 'list' '--format' '{{ json . }}' '--no-trunc' foreach ($i in $Id | ConvertTo-DockerWildcard) { '--filter' "id=$i" } foreach ($n in $Name | ConvertTo-DockerWildcard) { '--filter' "name=$n" } foreach ($d in $Driver) { '--filter' "driver=$d" } foreach ($s in $Scope) { '--filter' "scope=$s" } foreach ($t in $Type) { '--filter' "type=$t" } ) Invoke-Docker -ArgumentList $ArgumentList -Context $Context | ConvertFrom-Json | ForEach-Object { $Network = [DockerNetwork]::new($_) if (-not (Test-MultipleWildcard -WildcardPattern $Name -ActualValue $Network.Name)) { return } if (-not (Test-MultipleWildcard -WildcardPattern $Id -ActualValue $Network.Id)) { return } [void]$ReportNotMatched.Remove($Network.Id) [void]$ReportNotMatched.Remove($Network.Name) $Network } foreach ($NotMatched in $ReportNotMatched) { $Exception = [ItemNotFoundException]::new( 'The docker network was not found.' ) $ErrorRecord = [ErrorRecord]::new( $Exception, 'NetworkNotFound', [ErrorCategory]::ObjectNotFound, $NotMatched ) $ErrorRecord.ErrorDetails = "No network found with the $($PSCmdlet.ParameterSetName) '$NotMatched'." Write-Error -ErrorRecord $ErrorRecord } } } function Get-DockerNetworkConnection { [CmdletBinding( DefaultParameterSetName = 'Name', RemotingCapability = [RemotingCapability]::OwnedByCommand, PositionalBinding = $false )] [OutputType([DockerContainerNetworkConnection])] [Alias('gdnc')] param( [Parameter(Position = 0, ParameterSetName = 'Name')] [ValidateNotNullOrEmpty()] [ArgumentCompleter([DockerNetworkCompleter])] [string[]] $NetworkName, [Parameter(Position = 1, ParameterSetName = 'Name')] [ValidateNotNullOrEmpty()] [ArgumentCompleter([DockerContainerCompleter])] [string[]] $ContainerName, [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Parent')] [ValidateNotNullOrEmpty()] [ArgumentCompleter([DockerContainerCompleter])] [string[]] $ContainerId, [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Parent')] [ValidateNotNullOrEmpty()] [ArgumentCompleter([DockerNetworkCompleter])] [string[]] $NetworkId, [Parameter()] [ValidateNotNullOrEmpty()] [ArgumentCompleter([DockerContextCompleter])] $Context ) begin { $EndpointIds = [HashSet[string]]::new() } process { $Containers = Get-DockerContainerInternal -Context $Context -Id $ContainerId -Name $ContainerName -ErrorAction SilentlyContinue -ErrorVariable DockerContainerErrors foreach ($e in $DockerContainerErrors) { $exn = [ItemNotFoundException]::new( "No docker network connection found for container '$($e.TargetObject)' because the container does not exist.", $exn.Exception ) $er = [ErrorRecord]::new( $exn, 'NetworkConnectionNotFound.Container', [ErrorCategory]::ObjectNotFound, $e.TargetObject ) $PSCmdlet.WriteError($er) } if ($Containers.Count -eq 0) { return } $NotMatchedNetworkName = [HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase) $NetworkName.Where({ ![WildcardPattern]::ContainsWildcardCharacters($_) }).ForEach({ [void]$NotMatchedNetworkName.Add($_) }) $NotMatchedEndpointId = [HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase) $EndpointId.Where({ ![WildcardPattern]::ContainsWildcardCharacters($_) }).ForEach({ [void]$NotMatchedEndpointId.Add($_) }) $NotMatchedNetworkId = [HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase) $NetworkId.Where({ ![WildcardPattern]::ContainsWildcardCharacters($_) }).ForEach({ [void]$NotMatchedNetworkId.Add($_) }) Invoke-Docker container inspect $Containers.Id ` | ConvertFrom-Json ` | ForEach-Object { $ConnectionContainerId = $_.Id $ConnectionContainerName = $_.Name $ConnectionImageId = $_.Image $ConnectionImageName = $_.Config.Image foreach ($Property in $_.NetworkSettings.Networks.PSObject.Properties) { $ConnectionNetworkName = $Property.Name $Connection = [DockerContainerNetworkConnection]::new( $Property.Value, $ConnectionContainerId, ($ConnectionContainerName -replace '^/', ''), $ConnectionImageId, $ConnectionImageName, $ConnectionNetworkName ) $Connection.PSObject.Properties.Add([psnoteproperty]::new('PSDockerContext', $Context)) if ($EndpointIds.Add($Connection.EndpointId) -and (Test-MultipleWildcard -WildcardPattern $EndpointId -ActualValue $Connection.EndpointId) -and (Test-MultipleWildcard -WildcardPattern $NetworkName -ActualValue $Connection.NetworkName) -and (Test-MultipleWildcard -WildcardPattern $NetworkId -ActualValue $Connection.NetworkId)) { [void]$NotMatchedEndpointId.Remove($Connection.EndpointId) [void]$NotMatchedNetworkName.Remove($Connection.NetworkName) [void]$NotMatchedNetworkId.Remove($Connection.NetworkId) $Connection } } } foreach ($i in $NotMatchedEndpointId) { Write-Error -Exception ([ItemNotFoundException]"No docker network connection found with with endpoint '$i'.") -Category ObjectNotFound -ErrorId 'NetworkConnectionNotFound.EndpointId' -TargetObject $i } foreach ($i in $NotMatchedNetworkName) { Write-Error -Exception ([ItemNotFoundException]"No docker network connection found for network '$i'.") -Category ObjectNotFound -ErrorId 'NetworkConnectionNotFound.NetworkName' -TargetObject $i } foreach ($i in $NotMatchedNetworkId) { Write-Error -Exception ([ItemNotFoundException]"No docker network connection found for network '$i'.") -Category ObjectNotFound -ErrorId 'NetworkConnectionNotFound.NetworkId' -TargetObject $i } } } function New-DockerNetwork { [CmdletBinding( DefaultParameterSetName = 'Default', RemotingCapability = [RemotingCapability]::None, PositionalBinding = $false, SupportsShouldProcess, ConfirmImpact = [ConfirmImpact]::Medium )] [OutputType([DockerNetwork])] [Alias('ndn')] param( [Parameter(Position = 0, Mandatory)] [ArgumentCompleter([EmptyStringArgumentCompleter])] [string] $Name, [Parameter()] # [ValidateSet('bridge', 'host', 'none', 'overlay', 'macvlan', 'ipvlan', 'null')] [ArgumentCompleter([DockerNetworkCompleter])] [PSDefaultValue(Value = 'bridge')] [string] $Driver, [Parameter()] [ValidateSet('local', 'global')] [string] $Scope, [Parameter()] [switch] $Attachable, [Parameter()] [switch] $Internal, [Parameter()] [switch] $IPv6, [Parameter()] [switch] $Ingress, # Driver-specific options [Parameter()] [ArgumentCompleter([EmptyStringArgumentCompleter])] [string[]] $Options, [Parameter()] [ArgumentCompleter([EmptyIpAddressArgumentCompleter])] [ipaddress[]] $Gateway, # Subnet in CIDR format that represents a network segment [Parameter()] [ValidatePattern('^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/\d+$')] [ArgumentCompleter([EmptyStringArgumentCompleter])] [string[]] $Subnet, [Parameter()] [ValidatePattern('^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/\d+$')] [ArgumentCompleter([EmptyStringArgumentCompleter])] [string[]] $IpRange, [Parameter()] [Alias('AuxAddress')] [ArgumentCompleter([EmptyHashtableArgumentCompleter])] [DockerNetworkAuxAddressTransformation()] [Dictionary[string, HashSet[IPAddress]]] $AuxiliaryAddressMapping, [Parameter()] [ValidateNotNullOrEmpty()] [ArgumentCompleter([DockerContextCompleter])] [string] $Context ) process { $ArgumentList = @( 'network' 'create' if ($Attachable) { '--attachable' } if ($AuxiliaryAddressMapping) { foreach ($k in $AuxiliaryAddressMapping.Keys) { foreach ($v in $AuxiliaryAddressMapping[$k]) { '--aux-address' "$k=$v" } } } if ($Driver) { '--driver'; $Driver.ToLower() } foreach ($g in $Gateway) { '--gateway' $g.ToString() } if ($Ingress) { '--ingress' } if ($Internal) { '--internal' } foreach ($i in $IpRange) { '--ip-range' $i } if ($IPv6) { '--ipv6' } if ($Options) { foreach ($o in $Options) { '--opt' $o } } if ($Scope) { '--scope'; $Scope.ToLower() } foreach ($s in $Subnet) { '--subnet'; $s } $Name ) if (!$Driver) { $Driver = 'bridge' } if (!$PSCmdlet.ShouldProcess( "Creating docker network '$Name' with driver '$Driver'.", "Create docker network '$Name' with driver '$Driver'?", "docker $ArgumentList" )) { return } $Id = Invoke-Docker -ArgumentList $ArgumentList -Context $Context if ($?) { Get-DockerNetworkInternal -Id $Id -Context $Context } } } function Remove-DockerNetwork { [CmdletBinding( DefaultParameterSetName = 'Id', RemotingCapability = [RemotingCapability]::None, PositionalBinding = $false, SupportsShouldProcess, ConfirmImpact = [ConfirmImpact]::Medium )] [OutputType([System.Management.Automation.Internal.AutomationNull])] [Alias('rdn')] param( [Parameter(Mandatory, Position = 0, ParameterSetName = 'Name')] [ArgumentCompleter([DockerNetworkCompleter])] [string[]] $Name, [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'Id')] [ArgumentCompleter([DockerNetworkCompleter])] [string[]] $Id, [Parameter(ParameterSetName = 'Prune')] [Alias('Prune')] [switch] $UnusedNetworks, [Parameter()] [ValidateNotNullOrEmpty()] [ArgumentCompleter([DockerContextCompleter])] [string] $Context ) begin { $RemoveNetworks = [HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase) } process { if ($Prune) { if (!$PSCmdlet.ShouldProcess( 'Removing unused docker networks.', 'Remove unused docker networks?', 'docker network prune' )) { return } Invoke-Docker network prune '--force' -Context $Context | Write-Debug return } $Networks = Get-DockerNetworkInternal -Name $Name -Id $Id -Context $Context -EscapeId foreach ($Network in $Networks) { if (!$PSCmdlet.ShouldProcess( "Removing docker network '$($Network.Name)' ($($Network.Id)).", "Remove docker network '$($Network.Name)' ($($Network.Id))?", "docker network remove '$($Network.Id)'" )) { continue } [void]$RemoveNetworks.Add($Network.Id) } } end { if ($RemoveNetworks.Count -eq 0) { return } $ArgumentList = @( 'network' 'remove' $RemoveNetworks ) Invoke-Docker -ArgumentList $ArgumentList -Context $Context | Write-Debug } } function Remove-DockerNetworkConnection { [CmdletBinding( DefaultParameterSetName = 'Name+Name', RemotingCapability = [RemotingCapability]::OwnedByCommand, PositionalBinding = $false, SupportsShouldProcess, ConfirmImpact = [ConfirmImpact]::Medium )] [Alias('rdnc')] [OutputType([System.Management.Automation.Internal.AutomationNull])] param( [Parameter( Mandatory, Position = 0, ParameterSetName = 'Name+Id' )] [Parameter( Mandatory, Position = 0, ParameterSetName = 'Name+Name' )] [SupportsWildcards()] [ArgumentCompleter([DockerNetworkCompleter])] [string[]] $NetworkName, [Parameter( Mandatory, Position = 0, ParameterSetName = 'Id+Name' )] [Parameter( Mandatory, Position = 1, ParameterSetName = 'Name+Name' )] [SupportsWildcards()] [ArgumentCompleter([DockerContainerCompleter])] [string[]] $ContainerName, [Parameter( Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'Id+Id' )] [ArgumentCompleter([DockerNetworkCompleter])] [string[]] $NetworkId, [Parameter( Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'Name+Id' )] [Parameter( Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'Id+Id' )] [ArgumentCompleter([DockerContainerCompleter])] [string[]] $ContainerId, [Parameter()] [ValidateNotNullOrEmpty()] [ArgumentCompleter([DockerContextCompleter])] [string] $Context ) process { $Networks = Get-DockerNetworkInternal -Name $NetworkName -Id $NetworkId -EscapeId $Containers = Get-DockerContainerInternal -Name $ContainerName -Id $ContainerId -EscapeId if ($Networks.Count -eq 0) { Write-Verbose 'No networks to process.' return } if ($Containers.Count -eq 0) { Write-Verbose 'No containers to process.' return } $RequiresSingle = $Alias -or $IPAddress if ($RequiresSingle -and $Containers.Count -gt 1) { throw [InvalidOperationException]::new('Cannot specify multiple containers when using -Alias or -IPAddress.') } foreach ($Network in $Networks) { foreach ($Container in $Containers) { $ArgumentList = @( 'network' 'connect' $Network.Id $Container.Id ) if (!$PSCmdlet.ShouldProcess( "Disconnecting docker container '$($Container.Name)' ($($Container.Id)) from network '$($Network.Name)' ($($Network.Id)).", "Disconnect docker container '$($Container.Name)' ($($Container.Id)) from network '$($Network.Name)' ($($Network.Id))?", "docker $ArgumentList" )) { continue } Invoke-Docker network disconnect $Network.Id $Container.Id -Context $Context } } } } function Get-DockerPath { [CmdletBinding()] param() process { Get-Item $Docker } } function Get-DockerVersion { [CmdletBinding( RemotingCapability = [RemotingCapability]::OwnedByCommand )] [OutputType('Docker.DockerVersion')] param( [Parameter()] [ValidateNotNullOrEmpty()] [ArgumentCompleter([DockerContextCompleter])] [string] $Context ) process { Invoke-Docker version --format '{{ json . }}' -Context $Context | ForEach-Object { $pso = $_ | ConvertFrom-Json $pso.PSTypeNames.Insert(0, 'Docker.DockerVersion') $pso.Client.PSTypeNames.Insert(0, 'Docker.ClientVersion') $pso.Server.PSTypeNames.Insert(0, 'Docker.ServerVersion') $ModuleVersionInfo = [pscustomobject]@{ Version = $MyInvocation.MyCommand.Module.Version Prerelease = $MyInvocation.MyCommand.Module.PrivateData.PSData.Prerelease PSTypeName = 'Docker.ModuleVersion' } $pso.PSObject.Members.Add([PSNoteProperty]::new('Module', $ModuleVersionInfo)) $pso } } } function Invoke-DockerCommand { [CmdletBinding( RemotingCapability = [RemotingCapability]::OwnedByCommand )] param( [Parameter(Mandatory)] [ArgumentCompleter([DockerContainerCompleter])] [string] $ContainerName, [Parameter(ValueFromRemainingArguments)] [ArgumentCompleter([EmptyStringArgumentCompleter])] [string[]] $ArgumentList, [Parameter()] [ValidateNotNullOrEmpty()] [ArgumentCompleter([DockerContextCompleter])] [string] $Context ) $PassArgumentList = [List[string]]::new($ArgumentList) $PassArgumentList.Insert(0, 'exec') $PassArgumentList.Insert(1, $ContainerName) Invoke-Docker -ArgumentList $PassArgumentList -Context $Context } function Set-DockerPath { [CmdletBinding( DefaultParameterSetName = 'LiteralPath', RemotingCapability = [RemotingCapability]::None, SupportsShouldProcess, ConfirmImpact = [ConfirmImpact]::Medium )] param( [Parameter(Mandatory, Position = 0, ParameterSetName = 'Path')] [string] $Path, [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'LiteralPath')] [Alias('PSPath')] [string] $LiteralPath, [Parameter()] [switch] $PassThru ) process { if ($Path) { $ActualPath = Resolve-Path $Path -ErrorAction Stop } else { $ActualPath = Resolve-Path $LiteralPath -ErrorAction Stop } $ProviderPath = $ActualPath.ProviderPath # Test if the path is docker Write-Debug "$ProviderPath --version" $DockerVersionOutput = & $ProviderPath --version if ($DockerVersionOutput -notmatch 'Docker version') { $exn = [ArgumentException]'The provided path is not a valid docker executable.' $err = [ErrorRecord]::new( $exn, 'InvalidDockerPath', [ErrorCategory]::InvalidArgument, $ActualPath ) $err.ErrorDetails = "The provided path '$ProviderPath' is not a valid docker executable." $PSCmdlet.ThrowTerminatingError($err) return } if (!$PSCmdlet.ShouldProcess( "Setting docker executable to '$ProviderPath' for the 'Docker.PowerShell.CLI' module.", "Use the docker executable at '$ProviderPath' for the 'Docker.PowerShell.CLI' module?", 'Set-DockerPath' )) { return } # Set the $Docker variable which contains the path to the docker executable Set-Variable -Name Docker -Value $ProviderPath -Scope 1 if ($PassThru) { Get-Item -LiteralPath $ActualPath.Path } } } $ExportModuleMember = @{ Function = @( 'Connect-DockerContainer', 'Enter-DockerContainer', 'Get-DockerContainer', 'Get-DockerContainerLog', 'New-DockerContainer', 'Remove-DockerContainer', 'Rename-DockerContainer', 'Restart-DockerContainer', 'Resume-DockerContainer', 'Start-DockerContainer', 'Stop-DockerContainer', 'Suspend-DockerContainer', 'Wait-DockerContainer', 'Get-DockerContext', 'New-DockerContext', 'Remove-DockerContext', 'Use-DockerContext', 'Build-DockerImage', 'Copy-DockerImage', 'Export-DockerImage', 'Find-DockerImage', 'Get-DockerImage', 'Import-DockerImage', 'Install-DockerImage', 'Publish-DockerImage', 'Remove-DockerImage', 'Add-DockerNetworkConnection', 'Get-DockerNetwork', 'Get-DockerNetworkConnection', 'New-DockerNetwork', 'Remove-DockerNetwork', 'Remove-DockerNetworkConnection', 'Get-DockerPath', 'Get-DockerVersion', 'Invoke-DockerCommand', 'Set-DockerPath' ) Alias = @( 'ccdc', 'etdc', 'gdc', 'ndc', 'rndc', 'rtdc', 'rudc', 'sadc', 'spdc', 'ssdc', 'wdc', 'gdcx', 'ndcx', 'rdcx', 'udcx', 'bddi', 'cpdi', 'epdi', 'gdi', 'ipdi', 'isdi', 'pbdi', 'rdi', 'adnc', 'gdn', 'gdnc', 'ndn', 'rdn', 'rdnc' ) Variable = @( ) } Export-ModuleMember @ExportModuleMember |