From 63aa762777e60f158b41783abb25b0c08818b213 Mon Sep 17 00:00:00 2001 From: viktor-kaydalov Date: Tue, 1 Dec 2020 15:53:18 +0200 Subject: [PATCH 01/10] use Convert-ToDockerHostPath for docker tests --- Source/Private/Convert-ToDockerHostPath.ps1 | 24 +++++++++++++++++++++ Source/Public/Invoke-DockerTests.ps1 | 6 ++++-- 2 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 Source/Private/Convert-ToDockerHostPath.ps1 diff --git a/Source/Private/Convert-ToDockerHostPath.ps1 b/Source/Private/Convert-ToDockerHostPath.ps1 new file mode 100644 index 00000000..c8927bb2 --- /dev/null +++ b/Source/Private/Convert-ToDockerHostPath.ps1 @@ -0,0 +1,24 @@ +function Convert-ToDockerHostPath { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [String] + $Path + ) + + $pathOnDockerHost = $Path + if ( (Invoke-DockerCommand 'ps').StdOut | Select-String $(hostname) ) { + # executed inside docker container $(hostname) + $dockerCommand = "inspect -f ""{{ range .Mounts }}{{ .Source }}:{{ .Destination }}{{ println }} {{ end }}"" $(hostname)" + $mounts = (Invoke-DockerCommand $dockerCommand ).StdOut.trim() | Where-Object { $_ -NotMatch "/var/lib/docker" -and $_ -NotMatch "docker.sock" -and $_ -ne '' } + if ($mounts) { + $mounts | ForEach-Object { + if ($_.split(':')[0] -ne $_.split(':')[1]) { + # Replace $($_.split(':')[1]) with $($_.split(':')[0]) + $pathOnDockerHost = $pathOnDockerHost.Replace($_.split(':')[1],$_.split(':')[0]) + } + } + } + } + return $pathOnDockerHost +} diff --git a/Source/Public/Invoke-DockerTests.ps1 b/Source/Public/Invoke-DockerTests.ps1 index 23c9b096..f5d81cbf 100644 --- a/Source/Public/Invoke-DockerTests.ps1 +++ b/Source/Public/Invoke-DockerTests.ps1 @@ -31,10 +31,12 @@ function Invoke-DockerTests { } $here = Format-AsAbsolutePath (Get-Location) + $hereOnDockerHost = Convert-ToDockerHostPath $here $absoluteTestReportDir = Format-AsAbsolutePath ($TestReportDir) if (!(Test-Path $absoluteTestReportDir -PathType Container)) { New-Item $absoluteTestReportDir -ItemType Directory -Force | Out-Null } + $absoluteTestReportDirOnDockerHost = Convert-ToDockerHostPath $absoluteTestReportDir $osType = Find-DockerOSType $dockerSocket = Find-DockerSocket -OsType $osType if ($osType -ieq 'windows') { @@ -45,8 +47,8 @@ function Invoke-DockerTests { $report = '/report' } $structureCommand = "run -i" + ` - " -v `"${here}:${configs}`"" + ` - " -v `"${absoluteTestReportDir}:${report}`"" + ` + " -v `"${hereOnDockerHost}:${configs}`"" + ` + " -v `"${absoluteTestReportDirOnDockerHost}:${report}`"" + ` " -v `"${dockerSocket}:${dockerSocket}`"" + ` " 3shape/containerized-structure-test:latest test -i ${ImageName} --test-report ${report}/${TestReportName}" From c17eb7692dac9861a47ea9b76862d1c5703b3044 Mon Sep 17 00:00:00 2001 From: viktor-kaydalov Date: Mon, 7 Dec 2020 10:52:20 +0200 Subject: [PATCH 02/10] remove noise from log --- Source/Private/Convert-ToDockerHostPath.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Private/Convert-ToDockerHostPath.ps1 b/Source/Private/Convert-ToDockerHostPath.ps1 index c8927bb2..e48d3be6 100644 --- a/Source/Private/Convert-ToDockerHostPath.ps1 +++ b/Source/Private/Convert-ToDockerHostPath.ps1 @@ -7,10 +7,10 @@ function Convert-ToDockerHostPath { ) $pathOnDockerHost = $Path - if ( (Invoke-DockerCommand 'ps').StdOut | Select-String $(hostname) ) { + if ( (Invoke-DockerCommand 'ps' -Quiet).StdOut | Select-String $(hostname) ) { # executed inside docker container $(hostname) $dockerCommand = "inspect -f ""{{ range .Mounts }}{{ .Source }}:{{ .Destination }}{{ println }} {{ end }}"" $(hostname)" - $mounts = (Invoke-DockerCommand $dockerCommand ).StdOut.trim() | Where-Object { $_ -NotMatch "/var/lib/docker" -and $_ -NotMatch "docker.sock" -and $_ -ne '' } + $mounts = (Invoke-DockerCommand $dockerCommand -Quiet).StdOut.trim() | Where-Object { $_ -NotMatch "/var/lib/docker" -and $_ -NotMatch "docker.sock" -and $_ -ne '' } if ($mounts) { $mounts | ForEach-Object { if ($_.split(':')[0] -ne $_.split(':')[1]) { From f1db4e9df73013b854a3dd466bde745d305e89e3 Mon Sep 17 00:00:00 2001 From: Khang Yeen Lee Date: Wed, 9 Dec 2020 14:18:45 +0100 Subject: [PATCH 03/10] Added test code. --- .../Convert-ToDockerHostPath.Tests.ps1 | 170 ++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 Test-Source/Convert-ToDockerHostPath.Tests.ps1 diff --git a/Test-Source/Convert-ToDockerHostPath.Tests.ps1 b/Test-Source/Convert-ToDockerHostPath.Tests.ps1 new file mode 100644 index 00000000..db013cc7 --- /dev/null +++ b/Test-Source/Convert-ToDockerHostPath.Tests.ps1 @@ -0,0 +1,170 @@ +Import-Module -Force (Get-ChildItem -Path $PSScriptRoot/../Source -Recurse -Include *.psm1 -File).FullName +Import-Module -Global -Force $PSScriptRoot/Docker-CI.Tests.psm1 + +# CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES SIZE +# fcac150e3686 jenkins.agent:bionic "/bin/bash" 2 minutes ago Up 2 minutes zealous_driscoll 0B (virtual 1.33GB) +# /home/devops/workspace:/home/jenkins/workspace +# /var/run/docker.sock:/var/run/docker.sock +# /var/lib/docker/volumes/5c15f2a0ff099e4a6d59a863a9dfa4580e5615798987200bed2e98acd26fd3f8/_data:/home/jenkins/.jenkins +# /var/lib/docker/volumes/b29f5bbedd3a667ee45f0dde717a2c9b8092af81e0b9da92f9499f5c10a6c12e/_data:/home/jenkins/agent + +# CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +# a28e1ca69345 jenkins.agent:bionic "jenkins-agent" 41 minutes ago Up 41 minutes jenkins.agent +# /home/devops/workspace:/home/jenkins/workspace +# /var/run/docker.sock:/var/run/docker.sock +# /var/lib/docker/volumes/1a370219fb850732be17f2e0b56599b55541d6831894addd4f1d7c12c1c437cb/_data:/home/jenkins/.jenkins +# /var/lib/docker/volumes/362d0e59897a73814d2d63db9108f2a5c7f62b4709b692317ae120f6b5c3d6d9/_data:/home/jenkins/agent + +Describe 'Pull docker images' { + + BeforeEach { + Initialize-MockReg + Mock -CommandName "Invoke-Command" $Global:CodeThatReturnsExitCodeZero -Verifiable -ModuleName $Global:moduleName + } + + AfterEach { + Assert-MockCalled -CommandName "Invoke-Command" -ModuleName $Global:moduleName + } + + Context 'Docker pulls docker images' { + + It 'pulls public docker image by image name only' { + Invoke-DockerPull -ImageName 'ubuntu' + $result = GetMockValue -Key $Global:InvokeCommandArgsReturnValueKeyName + Write-Debug $result + $result | Should -BeLikeExactly "pull ubuntu:latest" + } + + It 'pulls public docker image by image name and tag' { + Invoke-DockerPull -ImageName 'ubuntu' -Tag 'bionic' + $result = GetMockValue -Key $Global:InvokeCommandArgsReturnValueKeyName + Write-Debug $result + $result | Should -BeLikeExactly "pull ubuntu:bionic" + } + + It 'pulls public docker image by registry and image name' { + Invoke-DockerPull -Registry 'not.docker.hub' -ImageName 'ubuntu' + $result = GetMockValue -Key $Global:InvokeCommandArgsReturnValueKeyName + Write-Debug $result + $result | Should -BeLikeExactly "pull not.docker.hub/ubuntu:latest" + } + + It 'pulls explicit public docker image with $null registry value and image name' { + Invoke-DockerPull -Registry $null -ImageName 'ubuntu' + $result = GetMockValue -Key $Global:InvokeCommandArgsReturnValueKeyName + Write-Debug $result + $result | Should -BeLikeExactly "pull ubuntu:latest" + } + + It 'pulls explicit public docker image with whitespace registry value and image name' { + Invoke-DockerPull -Registry ' ' -ImageName 'ubuntu' + $result = GetMockValue -Key $Global:InvokeCommandArgsReturnValueKeyName + Write-Debug $result + $result | Should -BeLikeExactly "pull ubuntu:latest" + } + + It 'pulls explicit public docker image with empty registry value, image name and tag' { + Invoke-DockerPull -Registry '' -ImageName 'ubuntu' -Tag 'bionic' + $result = GetMockValue -Key $Global:InvokeCommandArgsReturnValueKeyName + Write-Debug $result + $result | Should -BeLikeExactly "pull ubuntu:bionic" + } + + It 'pulls public docker image by image name and digest' { + Invoke-DockerPull -ImageName 'ubuntu' -Digest 'sha256:a7b8b7b33e44b123d7f997bd4d3d0a59fafc63e203d17efedf09ff3f6f516152' + $result = GetMockValue -Key $Global:InvokeCommandArgsReturnValueKeyName + Write-Debug $result + $result | Should -BeLikeExactly "pull ubuntu@sha256:a7b8b7b33e44b123d7f997bd4d3d0a59fafc63e203d17efedf09ff3f6f516152" + } + + It 'pulls public docker image by image name, with both tag and digest; and fails' { + $theCode = { Invoke-DockerPull -ImageName 'ubuntu' -Tag 'bionic' -Digest 'sha256:f5c0a8d225a4b7556db2b26753a7f4c4de3b090c1a8852983885b80694ca9840' } + $theCode | Should -Throw -ExceptionType ([System.Management.Automation.ParameterBindingException]) -PassThru + } + + It 'pulls public docker image by image name with invalid digest, missing sha256: prefix; and fails' { + $theCode = { Invoke-DockerPull -ImageName 'ubuntu' -Digest 'f5c0a8d225a4b7556db2b26753a7f4c4de3b090c1a8852983885b80694ca9840' } + $theCode | Should -Throw -ExceptionType ([System.Management.Automation.RuntimeException]) -PassThru + } + + It 'pulls public docker image by image name with invalid digest, wrong digest length; and fails' { + $theCode = { Invoke-DockerPull -ImageName 'ubuntu' -Digest 'sha256:f5c0a8d225a4b7556db2b26753a7f4c4d' } + $theCode | Should -Throw -ExceptionType ([System.Management.Automation.RuntimeException]) -PassThru + } + + It 'does not allow colons in imagename, force use of tag' { + $theCode = { Invoke-DockerPull -ImageName 'ubuntu:bionic' } + $theCode | Should -Throw -ExceptionType ([System.ArgumentException]) -PassThru + } + + It 'does not allow at signs in imagename, force use of tag' { + $theCode = { Invoke-DockerPull -ImageName 'ubuntu@sha256:f5c0a8d225a4b7556db2b26753a7f4c4d' } + $theCode | Should -Throw -ExceptionType ([System.ArgumentException]) -PassThru + } + + It 'cannot pull the requested docker image, throws exception on non-zero exit code' { + Mock -CommandName "Invoke-Command" $Global:CodeThatReturnsExitCodeOne -Verifiable -ModuleName $Global:ModuleName + $theCode = { Invoke-DockerPull -ImageName 'mcr.microsoft.com/ubuntu' } + $theCode | Should -Throw -ExceptionType ([System.Exception]) -PassThru + } + } + + Context 'Pipeline execution' { + + BeforeEach { + Initialize-MockReg + Mock -CommandName "Invoke-Command" $Global:CodeThatReturnsExitCodeZero -Verifiable -ModuleName $Global:ModuleName + } + + AfterEach { + Assert-MockCalled -CommandName "Invoke-Command" -ModuleName $Global:ModuleName + } + BeforeAll { + $pipedInput = { + $input = [PSCustomObject]@{ + "ImageName" = "myimage"; + "Registry" = "localhost"; + "Tag" = "v1.0.2" + } + return $input + } + } + + It 'can consume arguments from pipeline' { + & $pipedInput | Invoke-DockerPull + } + + It 'returns the expected pscustomobject' { + $result = & $pipedInput | Invoke-DockerPull + + $result.ImageName | Should -Be 'myimage' + $result.Registry | Should -Be 'localhost/' + $result.Tag | Should -Be 'v1.0.2' + $result.CommandResult | Should -Not -BeNullOrEmpty + } + } + + Context 'Verbosity of execution' { + + It 'outputs results if Quiet is disabled' { + $tempFile = New-TemporaryFile + Mock -CommandName "Invoke-Command" $Global:CodeThatReturnsExitCodeZero -Verifiable -ModuleName $Global:ModuleName + + Invoke-DockerPull -ImageName 'ubuntu' -Quiet:$false 6> $tempFile + + $result = Get-Content $tempFile + $result | Should -Be @('Hello', 'World') + } + + It 'suppresses results if Quiet is enabled' { + $tempFile = New-TemporaryFile + Mock -CommandName "Invoke-Command" $Global:CodeThatReturnsExitCodeZero -Verifiable -ModuleName $Global:ModuleName + + Invoke-DockerPull -ImageName 'ubuntu' -Quiet:$true 6> $tempFile + + $result = Get-Content $tempFile + $result | Should -BeNullOrEmpty + } + + } +} From d73ba5ce832fd97edf4852e72668c19cde03c7ae Mon Sep 17 00:00:00 2001 From: Viktor Kaydalov Date: Mon, 14 Dec 2020 22:23:24 +0200 Subject: [PATCH 04/10] add tests for Convert-ToDockerHostPath --- Source/Private/Convert-ToDockerHostPath.ps1 | 10 +- .../Convert-ToDockerHostPath.Tests.ps1 | 210 +++++------------- Test-Source/Docker-CI.Tests.psm1 | 40 ++++ 3 files changed, 100 insertions(+), 160 deletions(-) diff --git a/Source/Private/Convert-ToDockerHostPath.ps1 b/Source/Private/Convert-ToDockerHostPath.ps1 index e48d3be6..ca37ff55 100644 --- a/Source/Private/Convert-ToDockerHostPath.ps1 +++ b/Source/Private/Convert-ToDockerHostPath.ps1 @@ -9,13 +9,13 @@ function Convert-ToDockerHostPath { $pathOnDockerHost = $Path if ( (Invoke-DockerCommand 'ps' -Quiet).StdOut | Select-String $(hostname) ) { # executed inside docker container $(hostname) - $dockerCommand = "inspect -f ""{{ range .Mounts }}{{ .Source }}:{{ .Destination }}{{ println }} {{ end }}"" $(hostname)" - $mounts = (Invoke-DockerCommand $dockerCommand -Quiet).StdOut.trim() | Where-Object { $_ -NotMatch "/var/lib/docker" -and $_ -NotMatch "docker.sock" -and $_ -ne '' } + $dockerCommand = "inspect -f ""{{ range .Mounts }}{{ .Source }}={{ .Destination }}{{ println }} {{ end }}"" $(hostname)" + $mounts = (Invoke-DockerCommand $dockerCommand -Quiet).StdOut.trim() | Where-Object { $_ -NotMatch "/var/lib/docker" -and $_ -NotMatch "docker.sock" -and $_ -NotMatch "\\pipe\\" -and $_ -ne '' } if ($mounts) { $mounts | ForEach-Object { - if ($_.split(':')[0] -ne $_.split(':')[1]) { - # Replace $($_.split(':')[1]) with $($_.split(':')[0]) - $pathOnDockerHost = $pathOnDockerHost.Replace($_.split(':')[1],$_.split(':')[0]) + if ($_.split('=')[0] -ne $_.split('=')[1]) { + # Replace container path with host path + $pathOnDockerHost = $pathOnDockerHost.Replace($_.split('=')[1],$_.split('=')[0]) } } } diff --git a/Test-Source/Convert-ToDockerHostPath.Tests.ps1 b/Test-Source/Convert-ToDockerHostPath.Tests.ps1 index db013cc7..5047426d 100644 --- a/Test-Source/Convert-ToDockerHostPath.Tests.ps1 +++ b/Test-Source/Convert-ToDockerHostPath.Tests.ps1 @@ -1,170 +1,70 @@ Import-Module -Force (Get-ChildItem -Path $PSScriptRoot/../Source -Recurse -Include *.psm1 -File).FullName -Import-Module -Global -Force $PSScriptRoot/Docker-CI.Tests.psm1 - -# CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES SIZE -# fcac150e3686 jenkins.agent:bionic "/bin/bash" 2 minutes ago Up 2 minutes zealous_driscoll 0B (virtual 1.33GB) -# /home/devops/workspace:/home/jenkins/workspace -# /var/run/docker.sock:/var/run/docker.sock -# /var/lib/docker/volumes/5c15f2a0ff099e4a6d59a863a9dfa4580e5615798987200bed2e98acd26fd3f8/_data:/home/jenkins/.jenkins -# /var/lib/docker/volumes/b29f5bbedd3a667ee45f0dde717a2c9b8092af81e0b9da92f9499f5c10a6c12e/_data:/home/jenkins/agent - -# CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES -# a28e1ca69345 jenkins.agent:bionic "jenkins-agent" 41 minutes ago Up 41 minutes jenkins.agent -# /home/devops/workspace:/home/jenkins/workspace -# /var/run/docker.sock:/var/run/docker.sock -# /var/lib/docker/volumes/1a370219fb850732be17f2e0b56599b55541d6831894addd4f1d7c12c1c437cb/_data:/home/jenkins/.jenkins -# /var/lib/docker/volumes/362d0e59897a73814d2d63db9108f2a5c7f62b4709b692317ae120f6b5c3d6d9/_data:/home/jenkins/agent - -Describe 'Pull docker images' { - - BeforeEach { - Initialize-MockReg - Mock -CommandName "Invoke-Command" $Global:CodeThatReturnsExitCodeZero -Verifiable -ModuleName $Global:moduleName - } - - AfterEach { - Assert-MockCalled -CommandName "Invoke-Command" -ModuleName $Global:moduleName - } - - Context 'Docker pulls docker images' { - - It 'pulls public docker image by image name only' { - Invoke-DockerPull -ImageName 'ubuntu' - $result = GetMockValue -Key $Global:InvokeCommandArgsReturnValueKeyName - Write-Debug $result - $result | Should -BeLikeExactly "pull ubuntu:latest" - } - - It 'pulls public docker image by image name and tag' { - Invoke-DockerPull -ImageName 'ubuntu' -Tag 'bionic' - $result = GetMockValue -Key $Global:InvokeCommandArgsReturnValueKeyName - Write-Debug $result - $result | Should -BeLikeExactly "pull ubuntu:bionic" - } - - It 'pulls public docker image by registry and image name' { - Invoke-DockerPull -Registry 'not.docker.hub' -ImageName 'ubuntu' - $result = GetMockValue -Key $Global:InvokeCommandArgsReturnValueKeyName - Write-Debug $result - $result | Should -BeLikeExactly "pull not.docker.hub/ubuntu:latest" - } - - It 'pulls explicit public docker image with $null registry value and image name' { - Invoke-DockerPull -Registry $null -ImageName 'ubuntu' - $result = GetMockValue -Key $Global:InvokeCommandArgsReturnValueKeyName - Write-Debug $result - $result | Should -BeLikeExactly "pull ubuntu:latest" - } - - It 'pulls explicit public docker image with whitespace registry value and image name' { - Invoke-DockerPull -Registry ' ' -ImageName 'ubuntu' - $result = GetMockValue -Key $Global:InvokeCommandArgsReturnValueKeyName - Write-Debug $result - $result | Should -BeLikeExactly "pull ubuntu:latest" - } - - It 'pulls explicit public docker image with empty registry value, image name and tag' { - Invoke-DockerPull -Registry '' -ImageName 'ubuntu' -Tag 'bionic' - $result = GetMockValue -Key $Global:InvokeCommandArgsReturnValueKeyName - Write-Debug $result - $result | Should -BeLikeExactly "pull ubuntu:bionic" - } - - It 'pulls public docker image by image name and digest' { - Invoke-DockerPull -ImageName 'ubuntu' -Digest 'sha256:a7b8b7b33e44b123d7f997bd4d3d0a59fafc63e203d17efedf09ff3f6f516152' - $result = GetMockValue -Key $Global:InvokeCommandArgsReturnValueKeyName - Write-Debug $result - $result | Should -BeLikeExactly "pull ubuntu@sha256:a7b8b7b33e44b123d7f997bd4d3d0a59fafc63e203d17efedf09ff3f6f516152" - } - - It 'pulls public docker image by image name, with both tag and digest; and fails' { - $theCode = { Invoke-DockerPull -ImageName 'ubuntu' -Tag 'bionic' -Digest 'sha256:f5c0a8d225a4b7556db2b26753a7f4c4de3b090c1a8852983885b80694ca9840' } - $theCode | Should -Throw -ExceptionType ([System.Management.Automation.ParameterBindingException]) -PassThru - } - - It 'pulls public docker image by image name with invalid digest, missing sha256: prefix; and fails' { - $theCode = { Invoke-DockerPull -ImageName 'ubuntu' -Digest 'f5c0a8d225a4b7556db2b26753a7f4c4de3b090c1a8852983885b80694ca9840' } - $theCode | Should -Throw -ExceptionType ([System.Management.Automation.RuntimeException]) -PassThru - } +. "$PSScriptRoot\..\Source\Private\Convert-ToDockerHostPath.ps1" - It 'pulls public docker image by image name with invalid digest, wrong digest length; and fails' { - $theCode = { Invoke-DockerPull -ImageName 'ubuntu' -Digest 'sha256:f5c0a8d225a4b7556db2b26753a7f4c4d' } - $theCode | Should -Throw -ExceptionType ([System.Management.Automation.RuntimeException]) -PassThru - } +Import-Module -Global -Force $PSScriptRoot/Docker-CI.Tests.psm1 - It 'does not allow colons in imagename, force use of tag' { - $theCode = { Invoke-DockerPull -ImageName 'ubuntu:bionic' } - $theCode | Should -Throw -ExceptionType ([System.ArgumentException]) -PassThru - } +Describe 'Convert absolute path to path on docker host with Convert-ToDockerHostPath' { - It 'does not allow at signs in imagename, force use of tag' { - $theCode = { Invoke-DockerPull -ImageName 'ubuntu@sha256:f5c0a8d225a4b7556db2b26753a7f4c4d' } - $theCode | Should -Throw -ExceptionType ([System.ArgumentException]) -PassThru - } + $moduleName = (Get-ChildItem -Path $PSScriptRoot/../Source -Recurse -Include *.psm1 -File).BaseName - It 'cannot pull the requested docker image, throws exception on non-zero exit code' { - Mock -CommandName "Invoke-Command" $Global:CodeThatReturnsExitCodeOne -Verifiable -ModuleName $Global:ModuleName - $theCode = { Invoke-DockerPull -ImageName 'mcr.microsoft.com/ubuntu' } - $theCode | Should -Throw -ExceptionType ([System.Exception]) -PassThru - } - } + InModuleScope $moduleName { + + Context 'Verify we can mock' { - Context 'Pipeline execution' { - - BeforeEach { - Initialize-MockReg - Mock -CommandName "Invoke-Command" $Global:CodeThatReturnsExitCodeZero -Verifiable -ModuleName $Global:ModuleName - } - - AfterEach { - Assert-MockCalled -CommandName "Invoke-Command" -ModuleName $Global:ModuleName - } - BeforeAll { - $pipedInput = { - $input = [PSCustomObject]@{ - "ImageName" = "myimage"; - "Registry" = "localhost"; - "Tag" = "v1.0.2" - } - return $input + It 'Hostname can be mocked' { + Mock -CommandName "hostname" -MockWith {return $Global:DockerContainerHostname} -Verifiable + $result = hostname + $result | Should -Be $Global:DockerContainerHostname + Assert-MockCalled -CommandName "hostname" -Times 1 + } + It 'Invoke-DockerCommand can be mocked (ps)' { + Mock -CommandName "Invoke-DockerCommand" -MockWith $Global:DockerPsMockCode -ParameterFilter { $CommandArgs.StartsWith('ps') } -Verifiable + $result = Invoke-DockerCommand 'ps' + $result.Output | Should -Be $Global:DockerPsOutput + Assert-MockCalled -CommandName "Invoke-DockerCommand" -ParameterFilter { $CommandArgs.StartsWith('ps') } -Times 1 + } + It 'Invoke-DockerCommand can be mocked (inspect)' { + Mock -CommandName "Invoke-DockerCommand" -MockWith $Global:DockerInspectMockCode -ParameterFilter { $CommandArgs.StartsWith('inspect -f') } -Verifiable + $result = Invoke-DockerCommand 'inspect -f' + $result.Output | Should -Be $Global:DockerInspectOutput + Assert-MockCalled -CommandName "Invoke-DockerCommand" -ParameterFilter { $CommandArgs.StartsWith('inspect -f') } -Times 1 } } - It 'can consume arguments from pipeline' { - & $pipedInput | Invoke-DockerPull - } - - It 'returns the expected pscustomobject' { - $result = & $pipedInput | Invoke-DockerPull - - $result.ImageName | Should -Be 'myimage' - $result.Registry | Should -Be 'localhost/' - $result.Tag | Should -Be 'v1.0.2' - $result.CommandResult | Should -Not -BeNullOrEmpty - } - } - - Context 'Verbosity of execution' { - - It 'outputs results if Quiet is disabled' { - $tempFile = New-TemporaryFile - Mock -CommandName "Invoke-Command" $Global:CodeThatReturnsExitCodeZero -Verifiable -ModuleName $Global:ModuleName + Context 'When run NOT inside docker container' { - Invoke-DockerPull -ImageName 'ubuntu' -Quiet:$false 6> $tempFile - - $result = Get-Content $tempFile - $result | Should -Be @('Hello', 'World') + It 'returns the provided path' { + Mock -CommandName "Invoke-DockerCommand" -MockWith $Global:DockerPsMockCode -ParameterFilter { $CommandArgs.StartsWith('ps') } -Verifiable + Mock -CommandName "hostname" -MockWith {return 'someHostname'} -Verifiable + $result = Convert-ToDockerHostPath $Global:WorkspaceAbsolutePath + $result | Should -Be $Global:WorkspaceAbsolutePath + Assert-MockCalled -CommandName "Invoke-DockerCommand" -ParameterFilter { $CommandArgs.StartsWith('ps') } -Times 1 + Assert-MockCalled -CommandName "hostname" -Times 1 + } } - It 'suppresses results if Quiet is enabled' { - $tempFile = New-TemporaryFile - Mock -CommandName "Invoke-Command" $Global:CodeThatReturnsExitCodeZero -Verifiable -ModuleName $Global:ModuleName - - Invoke-DockerPull -ImageName 'ubuntu' -Quiet:$true 6> $tempFile + Context 'When run inside docker container' { - $result = Get-Content $tempFile - $result | Should -BeNullOrEmpty + It 'returns the path on docker host if folder belongs to mapped volume' { + Mock -CommandName "Invoke-DockerCommand" -MockWith $Global:DockerPsMockCode -ParameterFilter { $CommandArgs.StartsWith('ps') } -Verifiable + Mock -CommandName "hostname" -MockWith {return $Global:DockerContainerHostname} -Verifiable + Mock -CommandName "Invoke-DockerCommand" -MockWith $Global:DockerInspectMockCode -ParameterFilter { $CommandArgs.StartsWith('inspect -f') } -Verifiable + $result = Convert-ToDockerHostPath $Global:WorkspaceAbsolutePath + $result | Should -Be $Global:DockerHostAbsolutePath + Assert-MockCalled -CommandName "Invoke-DockerCommand" -ParameterFilter { $CommandArgs.StartsWith('ps') } -Times 1 + Assert-MockCalled -CommandName "hostname" -Times 1 + Assert-MockCalled -CommandName "Invoke-DockerCommand" -ParameterFilter { $CommandArgs.StartsWith('inspect -f') } -Times 1 + } + It 'returns the provided path otherwise' { + Mock -CommandName "Invoke-DockerCommand" -MockWith $Global:DockerPsMockCode -ParameterFilter { $CommandArgs.StartsWith('ps') } -Verifiable + Mock -CommandName "hostname" -MockWith {return $Global:DockerContainerHostname} -Verifiable + Mock -CommandName "Invoke-DockerCommand" -MockWith {$result = [CommandResult]::new(); $result.StdOut = ''; $result.ExitCode = 0; return $result} -ParameterFilter { $CommandArgs.StartsWith('inspect -f') } -Verifiable #-ModuleName $Global:ModuleName + $result = Convert-ToDockerHostPath $Global:WorkspaceAbsolutePath + $result | Should -Be $Global:WorkspaceAbsolutePath + Assert-MockCalled -CommandName "Invoke-DockerCommand" -ParameterFilter { $CommandArgs.StartsWith('ps') } -Times 2 + Assert-MockCalled -CommandName "hostname" -Times 2 + Assert-MockCalled -CommandName "Invoke-DockerCommand" -ParameterFilter { $CommandArgs.StartsWith('inspect -f') } -Times 2 + } } - } -} +} \ No newline at end of file diff --git a/Test-Source/Docker-CI.Tests.psm1 b/Test-Source/Docker-CI.Tests.psm1 index fa546799..1d3ad048 100644 --- a/Test-Source/Docker-CI.Tests.psm1 +++ b/Test-Source/Docker-CI.Tests.psm1 @@ -54,6 +54,46 @@ Set-GlobalVar -Variable CodeThatReturnsExitCodeOne -Value { return $result } +Set-GlobalVar -Variable DockerPsOutput -Value @( + 'CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES', + 'a28e1ca69345 jenkins.agent:bionic "jenkins-agent" 41 minutes ago Up 41 minutes jenkins.agent' +) +Set-GlobalVar -Variable DockerPsMockCode -Value { + $result = [CommandResult]::new() + $result.Output = $Global:DockerPsOutput + $result.StdOut = $Global:DockerPsOutput + $result.ExitCode = 0 + return $result +} + +Set-GlobalVar -Variable DockerInspectOutput -Value @( + ' /home/devops/workspace=/home/jenkins/workspace', + ' /var/run/docker.sock=/var/run/docker.sock', + ' /var/lib/docker/volumes/5c15f2a0ff099e4a6d59a863a9dfa4580e5615798987200bed2e98acd26fd3f8/_data=/home/jenkins/.jenkins', + ' /var/lib/docker/volumes/b29f5bbedd3a667ee45f0dde717a2c9b8092af81e0b9da92f9499f5c10a6c12e/_data=/home/jenkins/agent', + ' c:\devops\workspace=c:\jenkins\workspace', + ' \\.\pipe\docker_engine=\\.\pipe\docker_engine' +) + +Set-GlobalVar -Variable DockerInspectMockCode -Value { + $result = [CommandResult]::new() + $result.Output = $Global:DockerInspectOutput + $result.StdOut = $Global:DockerInspectOutput + $result.ExitCode = 0 + return $result +} + +Set-GlobalVar -Variable DockerContainerHostname -Value 'a28e1ca69345' # must be part of DockerPsMockCode + +if ($IsWindows) { + Set-GlobalVar -Variable WorkspaceAbsolutePath -Value 'c:\jenkins\workspace\mybuild' + Set-GlobalVar -Variable DockerHostAbsolutePath -Value 'c:\devops\workspace\mybuild' +} elseif ($IsLinux) { + Set-GlobalVar -Variable WorkspaceAbsolutePath -Value '/home/jenkins/workspace/mybuild' + Set-GlobalVar -Variable DockerHostAbsolutePath -Value '/home/devops/workspace/mybuild' +} + + . "$PSScriptRoot\..\Source\Private\Invoke-Command.ps1" . "$PSScriptRoot\..\Source\Private\Invoke-DockerCommand.ps1" . "$PSScriptRoot\..\Source\Private\Assert-ExitCodeOk.ps1" From 6f0871a4a6732fee2e6b383ad9bfd80f78e3aad7 Mon Sep 17 00:00:00 2001 From: Khang Yeen Lee Date: Tue, 15 Dec 2020 02:32:53 +0100 Subject: [PATCH 05/10] WIP --- Source/Private/Convert-ToDockerHostPath.ps1 | 12 +++-- .../Convert-ToDockerHostPath.Tests.ps1 | 46 ++++++++++--------- Test-Source/Docker-CI.Tests.psm1 | 6 ++- 3 files changed, 38 insertions(+), 26 deletions(-) diff --git a/Source/Private/Convert-ToDockerHostPath.ps1 b/Source/Private/Convert-ToDockerHostPath.ps1 index ca37ff55..b91ad7d9 100644 --- a/Source/Private/Convert-ToDockerHostPath.ps1 +++ b/Source/Private/Convert-ToDockerHostPath.ps1 @@ -7,15 +7,19 @@ function Convert-ToDockerHostPath { ) $pathOnDockerHost = $Path - if ( (Invoke-DockerCommand 'ps' -Quiet).StdOut | Select-String $(hostname) ) { + $commandResultPS = Invoke-DockerCommand 'ps' -Quiet -ErrorAction Stop + + if ( ($commandResultPS).StdOut | Select-String $(hostname) ) { # executed inside docker container $(hostname) $dockerCommand = "inspect -f ""{{ range .Mounts }}{{ .Source }}={{ .Destination }}{{ println }} {{ end }}"" $(hostname)" - $mounts = (Invoke-DockerCommand $dockerCommand -Quiet).StdOut.trim() | Where-Object { $_ -NotMatch "/var/lib/docker" -and $_ -NotMatch "docker.sock" -and $_ -NotMatch "\\pipe\\" -and $_ -ne '' } - if ($mounts) { + $commandResultInspect = Invoke-DockerCommand $dockerCommand -Quiet -ErrorAction Stop + $mounts = ($commandResultInspect).StdOut.trim() | Where-Object { $_ -NotMatch "/var/lib/docker" -and $_ -NotMatch "docker.sock" -and $_ -NotMatch "\\pipe\\" -and $_ -ne '' } + + if ($mounts.Length -gt 0) { $mounts | ForEach-Object { if ($_.split('=')[0] -ne $_.split('=')[1]) { # Replace container path with host path - $pathOnDockerHost = $pathOnDockerHost.Replace($_.split('=')[1],$_.split('=')[0]) + $pathOnDockerHost = $pathOnDockerHost.Replace($_.split('=')[1], $_.split('=')[0]) } } } diff --git a/Test-Source/Convert-ToDockerHostPath.Tests.ps1 b/Test-Source/Convert-ToDockerHostPath.Tests.ps1 index 5047426d..6d15f74c 100644 --- a/Test-Source/Convert-ToDockerHostPath.Tests.ps1 +++ b/Test-Source/Convert-ToDockerHostPath.Tests.ps1 @@ -1,28 +1,31 @@ Import-Module -Force (Get-ChildItem -Path $PSScriptRoot/../Source -Recurse -Include *.psm1 -File).FullName -. "$PSScriptRoot\..\Source\Private\Convert-ToDockerHostPath.ps1" - Import-Module -Global -Force $PSScriptRoot/Docker-CI.Tests.psm1 +. "$PSScriptRoot\..\Source\Private\Convert-ToDockerHostPath.ps1" Describe 'Convert absolute path to path on docker host with Convert-ToDockerHostPath' { - $moduleName = (Get-ChildItem -Path $PSScriptRoot/../Source -Recurse -Include *.psm1 -File).BaseName + BeforeEach { + Initialize-MockReg + } + + InModuleScope $Global:ModuleName { - InModuleScope $moduleName { - Context 'Verify we can mock' { It 'Hostname can be mocked' { - Mock -CommandName "hostname" -MockWith {return $Global:DockerContainerHostname} -Verifiable + Mock -CommandName "hostname" -MockWith { return $Global:DockerContainerHostname } -Verifiable $result = hostname $result | Should -Be $Global:DockerContainerHostname Assert-MockCalled -CommandName "hostname" -Times 1 } + It 'Invoke-DockerCommand can be mocked (ps)' { Mock -CommandName "Invoke-DockerCommand" -MockWith $Global:DockerPsMockCode -ParameterFilter { $CommandArgs.StartsWith('ps') } -Verifiable $result = Invoke-DockerCommand 'ps' $result.Output | Should -Be $Global:DockerPsOutput Assert-MockCalled -CommandName "Invoke-DockerCommand" -ParameterFilter { $CommandArgs.StartsWith('ps') } -Times 1 } + It 'Invoke-DockerCommand can be mocked (inspect)' { Mock -CommandName "Invoke-DockerCommand" -MockWith $Global:DockerInspectMockCode -ParameterFilter { $CommandArgs.StartsWith('inspect -f') } -Verifiable $result = Invoke-DockerCommand 'inspect -f' @@ -33,12 +36,12 @@ Describe 'Convert absolute path to path on docker host with Convert-ToDockerHost Context 'When run NOT inside docker container' { - It 'returns the provided path' { - Mock -CommandName "Invoke-DockerCommand" -MockWith $Global:DockerPsMockCode -ParameterFilter { $CommandArgs.StartsWith('ps') } -Verifiable - Mock -CommandName "hostname" -MockWith {return 'someHostname'} -Verifiable + It 'returns unchanged provided path' { + Mock -CommandName "Invoke-Command" -MockWith $Global:DockerPsMockCode -ParameterFilter { $CommandArgs.StartsWith('ps') } -Verifiable + Mock -CommandName "hostname" -MockWith { return 'someHostname' } -Verifiable $result = Convert-ToDockerHostPath $Global:WorkspaceAbsolutePath $result | Should -Be $Global:WorkspaceAbsolutePath - Assert-MockCalled -CommandName "Invoke-DockerCommand" -ParameterFilter { $CommandArgs.StartsWith('ps') } -Times 1 + Assert-MockCalled -CommandName "Invoke-Command" -ParameterFilter { $CommandArgs.StartsWith('ps') } -Times 1 Assert-MockCalled -CommandName "hostname" -Times 1 } } @@ -46,25 +49,26 @@ Describe 'Convert absolute path to path on docker host with Convert-ToDockerHost Context 'When run inside docker container' { It 'returns the path on docker host if folder belongs to mapped volume' { - Mock -CommandName "Invoke-DockerCommand" -MockWith $Global:DockerPsMockCode -ParameterFilter { $CommandArgs.StartsWith('ps') } -Verifiable - Mock -CommandName "hostname" -MockWith {return $Global:DockerContainerHostname} -Verifiable - Mock -CommandName "Invoke-DockerCommand" -MockWith $Global:DockerInspectMockCode -ParameterFilter { $CommandArgs.StartsWith('inspect -f') } -Verifiable + Mock -CommandName "Invoke-Command" -MockWith $Global:DockerPsMockCode -ParameterFilter { $CommandArgs.StartsWith('ps') } -Verifiable + Mock -CommandName "hostname" -MockWith { return $Global:DockerContainerHostname } -Verifiable + Mock -CommandName "Invoke-Command" -MockWith $Global:DockerInspectMockCode -ParameterFilter { $CommandArgs.StartsWith('inspect -f') } -Verifiable $result = Convert-ToDockerHostPath $Global:WorkspaceAbsolutePath $result | Should -Be $Global:DockerHostAbsolutePath - Assert-MockCalled -CommandName "Invoke-DockerCommand" -ParameterFilter { $CommandArgs.StartsWith('ps') } -Times 1 + Assert-MockCalled -CommandName "Invoke-Command" -ParameterFilter { $CommandArgs.StartsWith('ps') } -Times 1 Assert-MockCalled -CommandName "hostname" -Times 1 - Assert-MockCalled -CommandName "Invoke-DockerCommand" -ParameterFilter { $CommandArgs.StartsWith('inspect -f') } -Times 1 + Assert-MockCalled -CommandName "Invoke-Command" -ParameterFilter { $CommandArgs.StartsWith('inspect -f') } -Times 1 } + It 'returns the provided path otherwise' { - Mock -CommandName "Invoke-DockerCommand" -MockWith $Global:DockerPsMockCode -ParameterFilter { $CommandArgs.StartsWith('ps') } -Verifiable - Mock -CommandName "hostname" -MockWith {return $Global:DockerContainerHostname} -Verifiable - Mock -CommandName "Invoke-DockerCommand" -MockWith {$result = [CommandResult]::new(); $result.StdOut = ''; $result.ExitCode = 0; return $result} -ParameterFilter { $CommandArgs.StartsWith('inspect -f') } -Verifiable #-ModuleName $Global:ModuleName + Mock -CommandName "Invoke-Command" -MockWith $Global:DockerPsMockCode -ParameterFilter { $CommandArgs.StartsWith('ps') } -Verifiable + Mock -CommandName "hostname" -MockWith { return $Global:DockerContainerHostname } -Verifiable + Mock -CommandName "Invoke-Command" -MockWith { $result = [CommandResult]::new(); $result.StdOut = ''; $result.ExitCode = 0; return $result } -ParameterFilter { $CommandArgs.StartsWith('inspect -f') } -Verifiable #-ModuleName $Global:ModuleName $result = Convert-ToDockerHostPath $Global:WorkspaceAbsolutePath $result | Should -Be $Global:WorkspaceAbsolutePath - Assert-MockCalled -CommandName "Invoke-DockerCommand" -ParameterFilter { $CommandArgs.StartsWith('ps') } -Times 2 + Assert-MockCalled -CommandName "Invoke-Command" -ParameterFilter { $CommandArgs.StartsWith('ps') } -Times 2 Assert-MockCalled -CommandName "hostname" -Times 2 - Assert-MockCalled -CommandName "Invoke-DockerCommand" -ParameterFilter { $CommandArgs.StartsWith('inspect -f') } -Times 2 + Assert-MockCalled -CommandName "Invoke-Command" -ParameterFilter { $CommandArgs.StartsWith('inspect -f') } -Times 2 } } } -} \ No newline at end of file +} diff --git a/Test-Source/Docker-CI.Tests.psm1 b/Test-Source/Docker-CI.Tests.psm1 index 1d3ad048..8a6a1a7a 100644 --- a/Test-Source/Docker-CI.Tests.psm1 +++ b/Test-Source/Docker-CI.Tests.psm1 @@ -58,7 +58,10 @@ Set-GlobalVar -Variable DockerPsOutput -Value @( 'CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES', 'a28e1ca69345 jenkins.agent:bionic "jenkins-agent" 41 minutes ago Up 41 minutes jenkins.agent' ) + Set-GlobalVar -Variable DockerPsMockCode -Value { + StoreMockValue -Key $Global:InvokeCommandReturnValueKeyName -Value $Command + StoreMockValue -Key $Global:InvokeCommandArgsReturnValueKeyName -Value $CommandArgs $result = [CommandResult]::new() $result.Output = $Global:DockerPsOutput $result.StdOut = $Global:DockerPsOutput @@ -76,6 +79,8 @@ Set-GlobalVar -Variable DockerInspectOutput -Value @( ) Set-GlobalVar -Variable DockerInspectMockCode -Value { + StoreMockValue -Key $Global:InvokeCommandReturnValueKeyName -Value $Command + StoreMockValue -Key $Global:InvokeCommandArgsReturnValueKeyName -Value $CommandArgs $result = [CommandResult]::new() $result.Output = $Global:DockerInspectOutput $result.StdOut = $Global:DockerInspectOutput @@ -93,7 +98,6 @@ if ($IsWindows) { Set-GlobalVar -Variable DockerHostAbsolutePath -Value '/home/devops/workspace/mybuild' } - . "$PSScriptRoot\..\Source\Private\Invoke-Command.ps1" . "$PSScriptRoot\..\Source\Private\Invoke-DockerCommand.ps1" . "$PSScriptRoot\..\Source\Private\Assert-ExitCodeOk.ps1" From a0e44c026e90431951a128a3f540a477d1aec9af Mon Sep 17 00:00:00 2001 From: Khang Yeen Lee Date: Tue, 15 Dec 2020 20:03:35 +0100 Subject: [PATCH 06/10] Have both Windows and Linux tests. --- .../Convert-ToDockerHostPath.Tests.ps1 | 30 ++++++++++++- Test-Source/Docker-CI.Tests.psm1 | 42 +++++++++++++++---- 2 files changed, 62 insertions(+), 10 deletions(-) diff --git a/Test-Source/Convert-ToDockerHostPath.Tests.ps1 b/Test-Source/Convert-ToDockerHostPath.Tests.ps1 index 6d15f74c..ddd7e32a 100644 --- a/Test-Source/Convert-ToDockerHostPath.Tests.ps1 +++ b/Test-Source/Convert-ToDockerHostPath.Tests.ps1 @@ -48,7 +48,7 @@ Describe 'Convert absolute path to path on docker host with Convert-ToDockerHost Context 'When run inside docker container' { - It 'returns the path on docker host if folder belongs to mapped volume' { + It 'returns the path on docker host if folder belongs to mapped volume generic' { Mock -CommandName "Invoke-Command" -MockWith $Global:DockerPsMockCode -ParameterFilter { $CommandArgs.StartsWith('ps') } -Verifiable Mock -CommandName "hostname" -MockWith { return $Global:DockerContainerHostname } -Verifiable Mock -CommandName "Invoke-Command" -MockWith $Global:DockerInspectMockCode -ParameterFilter { $CommandArgs.StartsWith('inspect -f') } -Verifiable @@ -59,10 +59,36 @@ Describe 'Convert absolute path to path on docker host with Convert-ToDockerHost Assert-MockCalled -CommandName "Invoke-Command" -ParameterFilter { $CommandArgs.StartsWith('inspect -f') } -Times 1 } + if ($IsWindows) { + It 'returns the path on docker host if folder belongs to mapped volume on Windows' { + Mock -CommandName "Invoke-Command" -MockWith $Global:DockerPsMockCode -ParameterFilter { $CommandArgs.StartsWith('ps') } -Verifiable + Mock -CommandName "hostname" -MockWith { return $Global:DockerContainerHostname } -Verifiable + Mock -CommandName "Invoke-Command" -MockWith $Global:DockerInspectMockCodeWindows -ParameterFilter { $CommandArgs.StartsWith('inspect -f') } -Verifiable + $result = Convert-ToDockerHostPath $Global:WorkspaceAbsolutePath + $result | Should -Be $Global:DockerHostAbsolutePath + Assert-MockCalled -CommandName "Invoke-Command" -ParameterFilter { $CommandArgs.StartsWith('ps') } -Times 1 + Assert-MockCalled -CommandName "hostname" -Times 1 + Assert-MockCalled -CommandName "Invoke-Command" -ParameterFilter { $CommandArgs.StartsWith('inspect -f') } -Times 1 + } + } + + if ($IsLinux) { + It 'returns the path on docker host if folder belongs to mapped volume on Linux' { + Mock -CommandName "Invoke-Command" -MockWith $Global:DockerPsMockCode -ParameterFilter { $CommandArgs.StartsWith('ps') } -Verifiable + Mock -CommandName "hostname" -MockWith { return $Global:DockerContainerHostname } -Verifiable + Mock -CommandName "Invoke-Command" -MockWith $Global:DockerInspectMockCodeLinux -ParameterFilter { $CommandArgs.StartsWith('inspect -f') } -Verifiable + $result = Convert-ToDockerHostPath $Global:WorkspaceAbsolutePath + $result | Should -Be $Global:DockerHostAbsolutePath + Assert-MockCalled -CommandName "Invoke-Command" -ParameterFilter { $CommandArgs.StartsWith('ps') } -Times 1 + Assert-MockCalled -CommandName "hostname" -Times 1 + Assert-MockCalled -CommandName "Invoke-Command" -ParameterFilter { $CommandArgs.StartsWith('inspect -f') } -Times 1 + } + } + It 'returns the provided path otherwise' { Mock -CommandName "Invoke-Command" -MockWith $Global:DockerPsMockCode -ParameterFilter { $CommandArgs.StartsWith('ps') } -Verifiable Mock -CommandName "hostname" -MockWith { return $Global:DockerContainerHostname } -Verifiable - Mock -CommandName "Invoke-Command" -MockWith { $result = [CommandResult]::new(); $result.StdOut = ''; $result.ExitCode = 0; return $result } -ParameterFilter { $CommandArgs.StartsWith('inspect -f') } -Verifiable #-ModuleName $Global:ModuleName + Mock -CommandName "Invoke-Command" -MockWith $Global:CodeThatReturnsExitCodeZeroAndEmptyStdOut -ParameterFilter { $CommandArgs.StartsWith('inspect -f') } -Verifiable $result = Convert-ToDockerHostPath $Global:WorkspaceAbsolutePath $result | Should -Be $Global:WorkspaceAbsolutePath Assert-MockCalled -CommandName "Invoke-Command" -ParameterFilter { $CommandArgs.StartsWith('ps') } -Times 2 diff --git a/Test-Source/Docker-CI.Tests.psm1 b/Test-Source/Docker-CI.Tests.psm1 index 8a6a1a7a..19d7f64d 100644 --- a/Test-Source/Docker-CI.Tests.psm1 +++ b/Test-Source/Docker-CI.Tests.psm1 @@ -54,14 +54,19 @@ Set-GlobalVar -Variable CodeThatReturnsExitCodeOne -Value { return $result } +Set-GlobalVar -Variable CodeThatReturnsExitCodeZeroAndEmptyStdOut -Value { + $result = [CommandResult]::new() + $result.StdOut = '' + $result.ExitCode = 0 + return $result +} + Set-GlobalVar -Variable DockerPsOutput -Value @( 'CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES', 'a28e1ca69345 jenkins.agent:bionic "jenkins-agent" 41 minutes ago Up 41 minutes jenkins.agent' ) Set-GlobalVar -Variable DockerPsMockCode -Value { - StoreMockValue -Key $Global:InvokeCommandReturnValueKeyName -Value $Command - StoreMockValue -Key $Global:InvokeCommandArgsReturnValueKeyName -Value $CommandArgs $result = [CommandResult]::new() $result.Output = $Global:DockerPsOutput $result.StdOut = $Global:DockerPsOutput @@ -69,18 +74,21 @@ Set-GlobalVar -Variable DockerPsMockCode -Value { return $result } -Set-GlobalVar -Variable DockerInspectOutput -Value @( +Set-GlobalVar -Variable DockerInspectOutputWindows -Value @( + ' c:\devops\workspace=c:\jenkins\workspace', + ' \\.\pipe\docker_engine=\\.\pipe\docker_engine' +) + +Set-GlobalVar -Variable DockerInspectOutputLinux -Value @( ' /home/devops/workspace=/home/jenkins/workspace', ' /var/run/docker.sock=/var/run/docker.sock', ' /var/lib/docker/volumes/5c15f2a0ff099e4a6d59a863a9dfa4580e5615798987200bed2e98acd26fd3f8/_data=/home/jenkins/.jenkins', - ' /var/lib/docker/volumes/b29f5bbedd3a667ee45f0dde717a2c9b8092af81e0b9da92f9499f5c10a6c12e/_data=/home/jenkins/agent', - ' c:\devops\workspace=c:\jenkins\workspace', - ' \\.\pipe\docker_engine=\\.\pipe\docker_engine' + ' /var/lib/docker/volumes/b29f5bbedd3a667ee45f0dde717a2c9b8092af81e0b9da92f9499f5c10a6c12e/_data=/home/jenkins/agent' ) +Set-GlobalVar -Variable DockerInspectOutput -Value @( $Global:DockerInspectOutputLinux, $Global:DockerInspectOutputWindows ) + Set-GlobalVar -Variable DockerInspectMockCode -Value { - StoreMockValue -Key $Global:InvokeCommandReturnValueKeyName -Value $Command - StoreMockValue -Key $Global:InvokeCommandArgsReturnValueKeyName -Value $CommandArgs $result = [CommandResult]::new() $result.Output = $Global:DockerInspectOutput $result.StdOut = $Global:DockerInspectOutput @@ -93,9 +101,27 @@ Set-GlobalVar -Variable DockerContainerHostname -Value 'a28e1ca69345' # must be if ($IsWindows) { Set-GlobalVar -Variable WorkspaceAbsolutePath -Value 'c:\jenkins\workspace\mybuild' Set-GlobalVar -Variable DockerHostAbsolutePath -Value 'c:\devops\workspace\mybuild' + + Set-GlobalVar -Variable DockerInspectMockCodeWindows -Value { + $result = [CommandResult]::new() + $result.Output = $Global:DockerInspectOutputWindows + $result.StdOut = $Global:DockerInspectOutputWindows + $result.ExitCode = 0 + return $result + } + } elseif ($IsLinux) { Set-GlobalVar -Variable WorkspaceAbsolutePath -Value '/home/jenkins/workspace/mybuild' Set-GlobalVar -Variable DockerHostAbsolutePath -Value '/home/devops/workspace/mybuild' + + Set-GlobalVar -Variable DockerInspectMockCodeLinux -Value { + $result = [CommandResult]::new() + $result.Output = $Global:DockerInspectOutputLinux + $result.StdOut = $Global:DockerInspectOutputLinux + $result.ExitCode = 0 + return $result + } + } . "$PSScriptRoot\..\Source\Private\Invoke-Command.ps1" From 9f16f62efc0f64ddffcdbde398e8daf39b6882bf Mon Sep 17 00:00:00 2001 From: Khang Yeen Lee Date: Tue, 15 Dec 2020 20:19:55 +0100 Subject: [PATCH 07/10] Breaks command when docker command fails. --- Source/Private/Convert-ToDockerHostPath.ps1 | 3 +++ .../Convert-ToDockerHostPath.Tests.ps1 | 21 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/Source/Private/Convert-ToDockerHostPath.ps1 b/Source/Private/Convert-ToDockerHostPath.ps1 index b91ad7d9..0edb68c1 100644 --- a/Source/Private/Convert-ToDockerHostPath.ps1 +++ b/Source/Private/Convert-ToDockerHostPath.ps1 @@ -8,11 +8,14 @@ function Convert-ToDockerHostPath { $pathOnDockerHost = $Path $commandResultPS = Invoke-DockerCommand 'ps' -Quiet -ErrorAction Stop + Assert-ExitCodeOK $commandResultPS if ( ($commandResultPS).StdOut | Select-String $(hostname) ) { # executed inside docker container $(hostname) $dockerCommand = "inspect -f ""{{ range .Mounts }}{{ .Source }}={{ .Destination }}{{ println }} {{ end }}"" $(hostname)" $commandResultInspect = Invoke-DockerCommand $dockerCommand -Quiet -ErrorAction Stop + Assert-ExitCodeOK $commandResultInspect + $mounts = ($commandResultInspect).StdOut.trim() | Where-Object { $_ -NotMatch "/var/lib/docker" -and $_ -NotMatch "docker.sock" -and $_ -NotMatch "\\pipe\\" -and $_ -ne '' } if ($mounts.Length -gt 0) { diff --git a/Test-Source/Convert-ToDockerHostPath.Tests.ps1 b/Test-Source/Convert-ToDockerHostPath.Tests.ps1 index ddd7e32a..bc5f3599 100644 --- a/Test-Source/Convert-ToDockerHostPath.Tests.ps1 +++ b/Test-Source/Convert-ToDockerHostPath.Tests.ps1 @@ -96,5 +96,26 @@ Describe 'Convert absolute path to path on docker host with Convert-ToDockerHost Assert-MockCalled -CommandName "Invoke-Command" -ParameterFilter { $CommandArgs.StartsWith('inspect -f') } -Times 2 } } + + Context 'When docker ps or docker inspect command fails' { + + It 'cannot proceed further when docker ps command fails, throws exception on non-zero exit code' { + Mock -CommandName "Invoke-Command" -MockWith $Global:CodeThatReturnsExitCodeOne -ParameterFilter { $CommandArgs.StartsWith('ps') } -Verifiable + $theCode = { Convert-ToDockerHostPath $Global:WorkspaceAbsolutePath } + $theCode | Should -Throw -ExceptionType ([System.Exception]) -PassThru + Assert-MockCalled -CommandName "Invoke-Command" -ParameterFilter { $CommandArgs.StartsWith('ps') } -Times 1 + } + + It 'cannot proceed further when docker inspect command fails, throws exception on non-zero exit code' { + Mock -CommandName "Invoke-Command" -MockWith $Global:DockerPsMockCode -ParameterFilter { $CommandArgs.StartsWith('ps') } -Verifiable + Mock -CommandName "hostname" -MockWith { return $Global:DockerContainerHostname } -Verifiable + Mock -CommandName "Invoke-Command" -MockWith $Global:CodeThatReturnsExitCodeOne -ParameterFilter { $CommandArgs.StartsWith('inspect -f') } -Verifiable + $theCode = { Convert-ToDockerHostPath $Global:WorkspaceAbsolutePath } + $theCode | Should -Throw -ExceptionType ([System.Exception]) -PassThru + Assert-MockCalled -CommandName "Invoke-Command" -ParameterFilter { $CommandArgs.StartsWith('ps') } -Times 1 + Assert-MockCalled -CommandName "hostname" -Times 2 + Assert-MockCalled -CommandName "Invoke-Command" -ParameterFilter { $CommandArgs.StartsWith('inspect -f') } -Times 1 + } + } } } From 70c0a803ed5932dfa4578368947db15454e6429c Mon Sep 17 00:00:00 2001 From: Khang Yeen Lee Date: Tue, 15 Dec 2020 20:44:20 +0100 Subject: [PATCH 08/10] Fixed test data. --- Test-Source/Docker-CI.Tests.psm1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Test-Source/Docker-CI.Tests.psm1 b/Test-Source/Docker-CI.Tests.psm1 index 19d7f64d..e78acb44 100644 --- a/Test-Source/Docker-CI.Tests.psm1 +++ b/Test-Source/Docker-CI.Tests.psm1 @@ -86,7 +86,7 @@ Set-GlobalVar -Variable DockerInspectOutputLinux -Value @( ' /var/lib/docker/volumes/b29f5bbedd3a667ee45f0dde717a2c9b8092af81e0b9da92f9499f5c10a6c12e/_data=/home/jenkins/agent' ) -Set-GlobalVar -Variable DockerInspectOutput -Value @( $Global:DockerInspectOutputLinux, $Global:DockerInspectOutputWindows ) +Set-GlobalVar -Variable DockerInspectOutput -Value @( $Global:DockerInspectOutputLinux + $Global:DockerInspectOutputWindows ) Set-GlobalVar -Variable DockerInspectMockCode -Value { $result = [CommandResult]::new() From 777d14aeac0c03244197f0ee883d707204f3916e Mon Sep 17 00:00:00 2001 From: Khang Yeen Lee Date: Tue, 15 Dec 2020 21:41:06 +0100 Subject: [PATCH 09/10] Added 2 tests in Invoke-DockerTests. --- Source/Private/Convert-ToDockerHostPath.ps1 | 4 +-- Test-Source/Invoke-DockerTests.Tests.ps1 | 28 ++++++++++++++++++++- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/Source/Private/Convert-ToDockerHostPath.ps1 b/Source/Private/Convert-ToDockerHostPath.ps1 index 0edb68c1..dd7ad4d7 100644 --- a/Source/Private/Convert-ToDockerHostPath.ps1 +++ b/Source/Private/Convert-ToDockerHostPath.ps1 @@ -7,13 +7,13 @@ function Convert-ToDockerHostPath { ) $pathOnDockerHost = $Path - $commandResultPS = Invoke-DockerCommand 'ps' -Quiet -ErrorAction Stop + $commandResultPS = Invoke-DockerCommand 'ps' -Quiet Assert-ExitCodeOK $commandResultPS if ( ($commandResultPS).StdOut | Select-String $(hostname) ) { # executed inside docker container $(hostname) $dockerCommand = "inspect -f ""{{ range .Mounts }}{{ .Source }}={{ .Destination }}{{ println }} {{ end }}"" $(hostname)" - $commandResultInspect = Invoke-DockerCommand $dockerCommand -Quiet -ErrorAction Stop + $commandResultInspect = Invoke-DockerCommand $dockerCommand -Quiet Assert-ExitCodeOK $commandResultInspect $mounts = ($commandResultInspect).StdOut.trim() | Where-Object { $_ -NotMatch "/var/lib/docker" -and $_ -NotMatch "docker.sock" -and $_ -NotMatch "\\pipe\\" -and $_ -ne '' } diff --git a/Test-Source/Invoke-DockerTests.Tests.ps1 b/Test-Source/Invoke-DockerTests.Tests.ps1 index 543fdfef..77be68b3 100644 --- a/Test-Source/Invoke-DockerTests.Tests.ps1 +++ b/Test-Source/Invoke-DockerTests.Tests.ps1 @@ -101,7 +101,7 @@ Describe 'Run docker tests using Google Structure' { $testResult.Results.Length | Should -Be 2 } - It 'Can pick up tests in a nested folder structure' { + It 'can pick up tests in a nested folder structure' { $allPassingTestsDir = Join-Path $Global:StructureTestsPassDir $Global:DockerOsType $result = Invoke-DockerTests -ImageName $imageToTest -ConfigPath $allPassingTestsDir $commandResult = $result.CommandResult @@ -168,6 +168,32 @@ Describe 'Run docker tests using Google Structure' { $theCode = { Invoke-DockerTests -ImageName $imageToTest -ConfigPath (New-RandomFolder) } $theCode | Should -Throw -ExceptionType 'System.ArgumentException' } + + InModuleScope $Global:ModuleName { + + It 'throws an exception if Convert-ToDockerHostPath fails on docker ps' { + $structureCommandConfig = Join-Path $Global:StructureTestsFailDir $Global:DockerOsType 'testshell.yml' + $configs = @($structureCommandConfig) + Mock -CommandName "Invoke-Command" -MockWith $Global:CodeThatReturnsExitCodeOne -ParameterFilter { $CommandArgs.StartsWith('ps') } -Verifiable + $theCode = { Invoke-DockerTests -ImageName $imageToTest -ConfigPath $configs } + $theCode | Should -Throw -ExceptionType ([System.Exception]) -PassThru + Assert-MockCalled -CommandName "Invoke-Command" -ParameterFilter { $CommandArgs.StartsWith('ps') } -Times 1 + } + + It 'throws an exception if Convert-ToDockerHostPath fails on docker inspect' { + $structureCommandConfig = Join-Path $Global:StructureTestsFailDir $Global:DockerOsType 'testshell.yml' + $configs = @($structureCommandConfig) + Mock -CommandName "Invoke-Command" -MockWith $Global:DockerPsMockCode -ParameterFilter { $CommandArgs.StartsWith('ps') } -Verifiable + Mock -CommandName "hostname" -MockWith { return $Global:DockerContainerHostname } -Verifiable + Mock -CommandName "Invoke-Command" -MockWith $Global:CodeThatReturnsExitCodeOne -ParameterFilter { $CommandArgs.StartsWith('inspect -f') } -Verifiable + + $theCode = { Invoke-DockerTests -ImageName $imageToTest -ConfigPath $configs } + $theCode | Should -Throw -ExceptionType ([System.Exception]) -PassThru + Assert-MockCalled -CommandName "Invoke-Command" -ParameterFilter { $CommandArgs.StartsWith('ps') } -Times 1 + Assert-MockCalled -CommandName "hostname" -Times 2 + Assert-MockCalled -CommandName "Invoke-Command" -ParameterFilter { $CommandArgs.StartsWith('inspect -f') } -Times 1 + } + } } Context 'Pipeline execution' { From fde733efa6b536f25811e2147a9ec4a385d4357f Mon Sep 17 00:00:00 2001 From: Khang Yeen Lee Date: Wed, 16 Dec 2020 22:26:04 +0100 Subject: [PATCH 10/10] Windows only test. Sigh. --- Test-Source/Invoke-DockerTests.Tests.ps1 | 43 +++++++++++++----------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/Test-Source/Invoke-DockerTests.Tests.ps1 b/Test-Source/Invoke-DockerTests.Tests.ps1 index 77be68b3..d1492f51 100644 --- a/Test-Source/Invoke-DockerTests.Tests.ps1 +++ b/Test-Source/Invoke-DockerTests.Tests.ps1 @@ -171,27 +171,30 @@ Describe 'Run docker tests using Google Structure' { InModuleScope $Global:ModuleName { - It 'throws an exception if Convert-ToDockerHostPath fails on docker ps' { - $structureCommandConfig = Join-Path $Global:StructureTestsFailDir $Global:DockerOsType 'testshell.yml' - $configs = @($structureCommandConfig) - Mock -CommandName "Invoke-Command" -MockWith $Global:CodeThatReturnsExitCodeOne -ParameterFilter { $CommandArgs.StartsWith('ps') } -Verifiable - $theCode = { Invoke-DockerTests -ImageName $imageToTest -ConfigPath $configs } - $theCode | Should -Throw -ExceptionType ([System.Exception]) -PassThru - Assert-MockCalled -CommandName "Invoke-Command" -ParameterFilter { $CommandArgs.StartsWith('ps') } -Times 1 - } + if ($IsWindows) { + + It 'throws an exception if Convert-ToDockerHostPath fails on docker ps' { + $structureCommandConfig = Join-Path $Global:StructureTestsFailDir $Global:DockerOsType 'testshell.yml' + $configs = @($structureCommandConfig) + Mock -CommandName "Invoke-Command" -MockWith $Global:CodeThatReturnsExitCodeOne -ParameterFilter { $CommandArgs.StartsWith('ps') } -Verifiable + $theCode = { Invoke-DockerTests -ImageName $imageToTest -ConfigPath $configs } + $theCode | Should -Throw -ExceptionType ([System.Exception]) -PassThru + Assert-MockCalled -CommandName "Invoke-Command" -ParameterFilter { $CommandArgs.StartsWith('ps') } -Times 1 + } - It 'throws an exception if Convert-ToDockerHostPath fails on docker inspect' { - $structureCommandConfig = Join-Path $Global:StructureTestsFailDir $Global:DockerOsType 'testshell.yml' - $configs = @($structureCommandConfig) - Mock -CommandName "Invoke-Command" -MockWith $Global:DockerPsMockCode -ParameterFilter { $CommandArgs.StartsWith('ps') } -Verifiable - Mock -CommandName "hostname" -MockWith { return $Global:DockerContainerHostname } -Verifiable - Mock -CommandName "Invoke-Command" -MockWith $Global:CodeThatReturnsExitCodeOne -ParameterFilter { $CommandArgs.StartsWith('inspect -f') } -Verifiable - - $theCode = { Invoke-DockerTests -ImageName $imageToTest -ConfigPath $configs } - $theCode | Should -Throw -ExceptionType ([System.Exception]) -PassThru - Assert-MockCalled -CommandName "Invoke-Command" -ParameterFilter { $CommandArgs.StartsWith('ps') } -Times 1 - Assert-MockCalled -CommandName "hostname" -Times 2 - Assert-MockCalled -CommandName "Invoke-Command" -ParameterFilter { $CommandArgs.StartsWith('inspect -f') } -Times 1 + It 'throws an exception if Convert-ToDockerHostPath fails on docker inspect' { + $structureCommandConfig = Join-Path $Global:StructureTestsFailDir $Global:DockerOsType 'testshell.yml' + $configs = @($structureCommandConfig) + Mock -CommandName "Invoke-Command" -MockWith $Global:DockerPsMockCode -ParameterFilter { $CommandArgs.StartsWith('ps') } -Verifiable + Mock -CommandName "hostname" -MockWith { return $Global:DockerContainerHostname } -Verifiable + Mock -CommandName "Invoke-Command" -MockWith $Global:CodeThatReturnsExitCodeOne -ParameterFilter { $CommandArgs.StartsWith('inspect -f') } -Verifiable + + $theCode = { Invoke-DockerTests -ImageName $imageToTest -ConfigPath $configs } + $theCode | Should -Throw -ExceptionType ([System.Exception]) -PassThru + Assert-MockCalled -CommandName "Invoke-Command" -ParameterFilter { $CommandArgs.StartsWith('ps') } -Times 1 + Assert-MockCalled -CommandName "hostname" -Times 2 + Assert-MockCalled -CommandName "Invoke-Command" -ParameterFilter { $CommandArgs.StartsWith('inspect -f') } -Times 1 + } } } }