diff --git a/changelogs/fragments/win_chocolatey-bugfixes.yaml b/changelogs/fragments/win_chocolatey-bugfixes.yaml new file mode 100644 index 0000000000..4e5f58a65d --- /dev/null +++ b/changelogs/fragments/win_chocolatey-bugfixes.yaml @@ -0,0 +1,9 @@ +bugfixes: +- win_chocolatey - fix issue where state=downgrade would upgrade a package if no version was set + +minor_changes: +- win_chocolatey - Add support for username and password on source feeds +- win_chocolatey - Add support for installing Chocolatey itself from a source feed +- win_chocolatey - Removed the need to manually escape double quotes in the proxy username and password +- win_chocolatey - Will no longer upgrade Chocolatey in check mode +- win_chocolatey - Added ability to specify multiple packages as a list in 1 module invocation diff --git a/docs/docsite/rst/porting_guides/porting_guide_2.7.rst b/docs/docsite/rst/porting_guides/porting_guide_2.7.rst index ccc01f6697..9ba340345e 100644 --- a/docs/docsite/rst/porting_guides/porting_guide_2.7.rst +++ b/docs/docsite/rst/porting_guides/porting_guide_2.7.rst @@ -100,9 +100,13 @@ The following modules will be removed in Ansible 2.10. Please update your playbo Noteworthy module changes ------------------------- -Check mode is now supported in the ``command`` and ``shell`` modules. However, only when ``creates`` or ``removes`` is -specified. If either of these are specified, the module will check for existence of the file and report the correct -changed status, if they are not included the module will skip like it had done previously. +* Check mode is now supported in the ``command`` and ``shell`` modules. However, only when ``creates`` or ``removes`` is + specified. If either of these are specified, the module will check for existence of the file and report the correct + changed status, if they are not included the module will skip like it had done previously. + +* The ``win_chocolatey`` module originally required the ``proxy_username`` and ``proxy_password`` to + escape any double quotes in the value. This is no longer required and the escaping may cause further + issues. Plugins ======= diff --git a/lib/ansible/modules/windows/win_chocolatey.ps1 b/lib/ansible/modules/windows/win_chocolatey.ps1 index 3262af4636..291b91182a 100644 --- a/lib/ansible/modules/windows/win_chocolatey.ps1 +++ b/lib/ansible/modules/windows/win_chocolatey.ps1 @@ -2,8 +2,11 @@ # Copyright: (c) 2014, Trond Hindenes # Copyright: (c) 2017, Dag Wieers +# Copyright: (c) 2018, Ansible Project # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +#Requires -Module Ansible.ModuleUtils.ArgvParser +#Requires -Module Ansible.ModuleUtils.CommandUtil #Requires -Module Ansible.ModuleUtils.Legacy $ErrorActionPreference = 'Stop' @@ -16,532 +19,562 @@ $params = Parse-Args $args -supports_check_mode $true $check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false $verbosity = Get-AnsibleParam -obj $params -name "_ansible_verbosity" -type "int" -default 0 -$package = Get-AnsibleParam -obj $params -name "name" -type "str" -failifempty $true +$name = Get-AnsibleParam -obj $params -name "name" -type "list" -failifempty $true + +$allow_empty_checksums = Get-AnsibleParam -obj $params -name "allow_empty_checksums" -type "bool" -default $false +$allow_prerelease = Get-AnsibleParam -obj $params -name "allow_prerelease" -type "bool" -default $false +$architecture = Get-AnsibleParam -obj $params -name "architecture" -type "str" -default "default" -validateset "default", "x86" +$install_args = Get-AnsibleParam -obj $params -name "install_args" -type "str" +$ignore_checksums = Get-AnsibleParam -obj $params -name "ignore_checksums" -type "bool" -default $false +$ignore_dependencies = Get-AnsibleParam -obj $params -name "ignore_dependencies" -type "bool" -default $false $force = Get-AnsibleParam -obj $params -name "force" -type "bool" -default $false -$version = Get-AnsibleParam -obj $params -name "version" -type "str" -$source = Get-AnsibleParam -obj $params -name "source" -type "str" -$showlog = Get-AnsibleParam -obj $params -name "showlog" -type "bool" -default $false -$timeout = Get-AnsibleParam -obj $params -name "timeout" -type "int" -default 2700 -aliases "execution_timeout" -$state = Get-AnsibleParam -obj $params -name "state" -type "str" -default "present" -validateset "absent","downgrade","latest","present","reinstalled" -$installargs = Get-AnsibleParam -obj $params -name "install_args" -type "str" -$packageparams = Get-AnsibleParam -obj $params -name "params" -type "str" -$allowemptychecksums = Get-AnsibleParam -obj $params -name "allow_empty_checksums" -type "bool" -default $false -$ignorechecksums = Get-AnsibleParam -obj $params -name "ignore_checksums" -type "bool" -default $false -$ignoredependencies = Get-AnsibleParam -obj $params -name "ignore_dependencies" -type "bool" -default $false -$allowprerelease = Get-AnsibleParam -obj $params -name "allow_prerelease" -type "bool" -default $false -$skipscripts = Get-AnsibleParam -obj $params -name "skip_scripts" -type "bool" -default $false +$package_params = Get-AnsibleParam -obj $params -name "package_params" -type "str" -aliases "params" $proxy_url = Get-AnsibleParam -obj $params -name "proxy_url" -type "str" $proxy_username = Get-AnsibleParam -obj $params -name "proxy_username" -type "str" -$proxy_password = Get-AnsibleParam -obj $params -name "proxy_password" -type "str" -failifempty ($proxy_username -ne $null) -$architecture = Get-AnsibleParam -obj $params -name "architecture" -type "str" -default "default" -validateset "default","x86" +$proxy_password = Get-AnsibleParam -obj $params -name "proxy_password" -type "str" -failifempty ($null -ne $proxy_username) +$skip_scripts = Get-AnsibleParam -obj $params -name "skip_scripts" -type "bool" -default $false +$source = Get-AnsibleParam -obj $params -name "source" -type "str" +$source_username = Get-AnsibleParam -obj $params -name "source_username" -type "str" +$source_password = Get-AnsibleParam -obj $params -name "source_password" -type "str" -failifempty ($null -ne $source_username) +$state = Get-AnsibleParam -obj $params -name "state" -type "str" -default "present" -validateset "absent","downgrade","latest","present","reinstalled" +$timeout = Get-AnsibleParam -obj $params -name "timeout" -type "int" -default 2700 -aliases "execution_timeout" +$validate_certs = Get-AnsibleParam -obj $params -name "validate_certs" -type "bool" -default $true +$version = Get-AnsibleParam -obj $params -name "version" -type "str" $result = @{ - changed = $false + changed = $false rc = 0 } -Function Chocolatey-Install-Upgrade -{ - [CmdletBinding()] +if (-not $validate_certs) { + [System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true } +} - param() +Function Get-CommonChocolateyArguments { + # uses global vars like check_mode and verbosity to control the common args + # run with Chocolatey + $arguments = [System.Collections.ArrayList]@("--yes", "--no-progress") + # global vars that control the arguments + if ($check_mode) { + $arguments.Add("--what-if") > $null + } + if ($verbosity -gt 4) { + $arguments.Add("--debug") > $null + $arguments.Add("--verbose") > $null + } elseif ($verbosity -gt 3) { + $arguments.Add("--verbose") > $null + } else { + $arguments.Add("--limit-output") > $null + } - $ChocoAlreadyInstalled = Get-Command -Name "choco.exe" -ErrorAction SilentlyContinue - if ($ChocoAlreadyInstalled -eq $null) - { + return ,$arguments +} + +Function Get-InstallChocolateyArguments { + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingUserNameAndPassWordParams", "", Justification="We need to use the plaintext pass in the cmdline, also using a SecureString here doesn't make sense considering the source is not secure")] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "", Justification="See above")] + param( + [bool]$allow_downgrade, + [bool]$allow_empty_checksums, + [bool]$allow_prerelease, + [String]$architecture, + [bool]$force, + [bool]$ignore_dependencies, + [String]$install_args, + [String]$package_params, + [String]$proxy_url, + [String]$proxy_username, + [String]$proxy_password, + [bool]$skip_scripts, + [String]$source, + [String]$source_usename, + [String]$source_password, + [int]$timeout, + [String]$version + ) + # returns an ArrayList of common arguments for install/updated a Chocolatey + # package + $arguments = [System.Collections.ArrayList]@("--fail-on-unfound") + $common_args = Get-CommonChocolateyArguments + $arguments.AddRange($common_args) + + if ($allow_downgrade) { + $arguments.Add("--allow-downgrade") > $null + } + if ($allow_empty_checksums) { + $arguments.Add("--allow-empty-checksums") > $null + } + if ($allow_prerelease) { + $arguments.Add("--prerelease") > $null + } + if ($architecture -eq "x86") { + $arguments.Add("--x86") > $null + } + if ($force) { + $arguments.Add("--force") > $null + } + if ($ignore_checksums) { + $arguments.Add("--ignore-checksums") > $null + } + if ($ignore_dependencies) { + $arguments.Add("--ignore-dependencies") > $null + } + if ($install_args) { + $arguments.Add("--install-arguments") > $null + $arguments.add($install_args) > $null + } + if ($package_params) { + $arguments.Add("--package-parameters") > $null + $arguments.Add($package_params) > $null + } + if ($proxy_url) { + $arguments.Add("--proxy") > $null + $arguments.Add($proxy_url) > $null + } + if ($proxy_username) { + $arguments.Add("--proxy-user") > $null + $arguments.Add($proxy_username) > $null + } + if ($proxy_password) { + $arguments.Add("--proxy-password") > $null + $arguments.Add($proxy_password) > $null + } + if ($skip_scripts) { + $arguments.Add("--skip-scripts") > $null + } + if ($source) { + $arguments.Add("--source") > $null + $arguments.Add($source) > $null + } + if ($source_username) { + $arguments.Add("--user") > $null + $arguments.Add($source_username) > $null + $arguments.Add("--password") > $null + $arguments.Add($source_password) > $null + } + if ($null -ne $timeout) { + $arguments.Add("--timeout") > $null + $arguments.Add($timeout) > $null + } + if ($version) { + $arguments.Add("--version") > $null + $arguments.Add($version) > $null + } + + return ,$arguments +} + +Function Install-Chocolatey { + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingUserNameAndPassWordParams", "", Justification="We need to use the plaintext pass in the env vars, also using a SecureString here doesn't make sense considering the source is not secure")] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "", Justification="See above")] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingConvertToSecureStringWithPlainText", "", Justification="See above")] + param( + [String]$proxy_url, + [String]$proxy_username, + [String]$proxy_password, + [String]$source, + [String]$source_username, + [String]$source_password + ) + + $choco_app = Get-Command -Name choco.exe -CommandType Application -ErrorAction SilentlyContinue + if ($null -eq $choco_app) { # We need to install chocolatey # Enable TLS1.1/TLS1.2 if they're available but disabled (eg. .NET 4.5) - $security_protcols = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::SystemDefault + $security_protocols = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::SystemDefault if ([Net.SecurityProtocolType].GetMember("Tls11").Count -gt 0) { - $security_protcols = $security_protcols -bor [Net.SecurityProtocolType]::Tls11 + $security_protocols = $security_protcols -bor [Net.SecurityProtocolType]::Tls11 } if ([Net.SecurityProtocolType].GetMember("Tls12").Count -gt 0) { - $security_protcols = $security_protcols -bor [Net.SecurityProtocolType]::Tls12 + $security_protocols = $security_protcols -bor [Net.SecurityProtocolType]::Tls12 } - [Net.ServicePointManager]::SecurityProtocol = $security_protcols + [Net.ServicePointManager]::SecurityProtocol = $security_protocols - $wc = New-Object System.Net.WebClient; - if ($proxy_url) - { - #We need to configure proxy - $env:chocolateyProxyLocation = $proxy_url - $wp = New-Object System.Net.WebProxy($proxy_url, $true); - $wc.proxy = $wp; + $client = New-Object -TypeName System.Net.WebClient + $environment = @{} + if ($proxy_url) { + # the env values are used in the install.ps1 script when getting + # external dependencies + $environment.chocolateyProxyLocation = $proxy_url + $web_proxy = New-Object -TypeName System.Net.WebProxy -ArgumentList $proxy_url, $true + $client.Proxy = $web_proxy if ($proxy_username -and $proxy_password) { - $env:chocolateyProxyUser = $proxy_username - $env:chocolateyProxyPassword = $proxy_password - $passwd = ConvertTo-SecureString $proxy_password -AsPlainText -Force - $wp.Credentials = New-Object System.Management.Automation.PSCredential($proxy_username, $passwd) + $environment.chocolateyProxyUser = $proxy_username + $environment.chocolateyProxyPassword = $proxy_password + $sec_proxy_password = ConvertTo-SecureString -String $proxy_password -AsPlainText -Force + $web_proxy.Credentials = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $proxy_username, $sec_proxy_password } } - $install_output = $wc.DownloadString("https://chocolatey.org/install.ps1") | powershell - - $result.rc = $LastExitCode - $result.stdout = $install_output | Out-String - if ($result.rc -ne 0) { - Fail-Json -obj $result -message "Chocolatey bootstrap installation failed." + + if ($source) { + # check if the URL already contains the path to install.ps1 + if ($source.EndsWith("install.ps1")) { + $script_url = $source + } else { + # chocolatey server automatically serves a script at + # http://host/install.ps1, we rely on this behaviour when a + # user specifies the choco source URL. If a custom URL or file + # path is desired, they should use win_get_url/win_shell + # manually + # we need to strip the path off the URL and append install.ps1 + $uri_info = [System.Uri]$source + $script_url = "$($uri_info.Scheme)://$($uri_info.Authority)/install.ps1" + } + if ($source_username) { + # while the choco-server does not require creds on install.ps1, + # Net.WebClient will only send the credentials if the initial + # req fails so we will add the creds in case the source URL + # is not choco-server and requires authentication + $sec_source_password = ConvertTo-SecureString -String $source_password -AsPlainText -Force + $client.Credentials = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $source_username, $sec_source_password + } + } else { + $script_url = "https://chocolatey.org/install.ps1" + } + + try { + $install_script = $client.DownloadString($script_url) + } catch { + Fail-Json -obj $result -message "Failed to download Chocolatey script from '$script_url': $($_.Exception.Message)" + } + if (-not $check_mode) { + $res = Run-Command -command "powershell.exe -" -stdin $install_script -environment $environment + if ($res.rc -ne 0) { + $result.rc = $res.rc + $result.stdout = $res.stdout + $result.stderr = $res.stderr + Fail-Json -obj $result -message "Chocolatey bootstrap installation failed." + } + Add-Warning -obj $result -message "Chocolatey was missing from this system, so it was installed during this task run." } $result.changed = $true - Add-Warning -obj $result -message "Chocolatey was missing from this system, so it was installed during this task run." # locate the newly installed choco.exe - $command = Get-Command -Name "choco.exe" -ErrorAction SilentlyContinue - if ($command) - { - $path = $command.Path - } - else - { - $env_value = $env:ChocolateyInstall - if ($env_value) - { - $path = "$env_value\bin\choco.exe" + $choco_app = Get-Command -Name choco.exe -CommandType Application -ErrorAction SilentlyContinue + if ($null -eq $choco_app) { + $choco_path = $env:ChocolateyInstall + if ($null -ne $choco_path) { + $choco_path = "$choco_path\bin\choco.exe" + } else { + $choco_path = "$env:SYSTEMDRIVE\ProgramData\Chocolatey\bin\choco.exe" } - else - { - $path = "$env:SYSTEMDRIVE\ProgramData\Chocolatey\bin\choco.exe" - } - } - if (-not (Test-Path -Path $path)) - { - Fail-Json -obj $result -message "failed to find choco.exe, make sure it is added to the PATH or the env var ChocolateyInstall is set" - } - $script:executable = $path - } - else - { - - $script:executable = "choco.exe" - - if ([Version](choco --version) -lt [Version]'0.10.5') - { - Add-Warning -obj $result -message "Chocolatey was older than v0.10.5, so it was upgraded during this task run." - $script:options = @( "-dv" ) - Choco-Upgrade -package chocolatey -proxy_url $proxy_url -proxy_username $proxy_username -proxy_password $proxy_password + $choco_app = Get-Command -Name $choco_path -CommandType Application -ErrorAction SilentlyContinue } } - - # set the default verbosity options - if ($verbosity -gt 4) { - Add-Warning -obj $result -message "Debug output enabled." - $script:options = @( "-dv", "--no-progress" ) - } elseif ($verbosity -gt 3) { - $script:options = @( "-v", "--no-progress" ) -# } elseif ($verbosity -gt 2) { -# $script:options = @( "--no-progress" ) - } else { - $script:options = @( "-r", "--no-progress" ) + if ($check_mode -and $null -eq $choco_app) { + $result.skipped = $true + $result.msg = "Skipped check mode run on win_chocolatey as choco.exe cannot be found on the system" + Exit-Json -obj $result } + + if (-not (Test-Path -Path $choco_app.Path)) { + Fail-Json -obj $result -message "Failed to find choco.exe, make sure it is added to the PATH or the env var 'ChocolateyInstall' is set" + } + + $actual_version = Get-ChocolateyPackageVersion -choco_path $choco_app.Path -name chocolatey + if ([Version]$actual_version -lt [Version]"0.10.5") { + if ($check_mode) { + $result.skipped = $true + $result.msg = "Skipped check mode run on win_chocolatey as choco.exe is too old, a real run would have upgraded the executable. Actual: '$actual_version', Minimum Version: '0.10.5'" + Exit-Json -obj $result + } + Add-Warning -obj $result -message "Chocolatey was older than v0.10.5 so it was upgraded during this task run." + Update-ChocolateyPackage -choco_path $choco_app.Path -packages @("chocolatey") ` + -proxy_url $proxy_url -proxy_username $proxy_username ` + -proxy_password $proxy_password -source $source ` + -source_username $source_username -source_password $source_password + } + + return $choco_app.Path } - -Function Choco-IsInstalled -{ - [CmdletBinding()] - +Function Get-ChocolateyPackageVersion { param( - [Parameter(Mandatory=$true)] - [string]$package + [Parameter(Mandatory=$true)][String]$choco_path, + [Parameter(Mandatory=$true)][String]$name ) - - if ($package -eq "all") { - return $true + # returns the package version or null if it isn't installed + # all is a special case where we want to say it isn't installed, in choco + # it means runs on all the packages installed + if ($name -eq "all") { + return $null } - $options = @( "--local-only", "--exact", $package ) - - # NOTE: Chocolatey does not use stderr except for help output - Try { - $output = & $script:executable list $options - } Catch { - Fail-Json -obj $result -message "Error checking installation status for package 'package': $($_.Exception.Message)" + $command = Argv-ToString -arguments @($choco_path, "list", "--local-only", "--exact", "--limit-output", $name) + $res = Run-Command -command $command + if ($res.rc -ne 0) { + $result.command = $command + $result.rc = $res.rc + $result.stdout = $res.stdout + $result.stderr = $res.stderr + Fail-Json -obj $result -message "Error checking installation status for the package '$name'" + } + $stdout = $res.stdout.Trim() + $version = $null + if ($stdout) { + # if a match occurs it is in the format of "package|version" we split + # by the last | to get the version in case package contains a pipe char + $pipe_index = $stdout.LastIndexOf("|") + $version = $stdout.Substring($pipe_index + 1) } - if ($LastExitCode -ne 0) { - $result.rc = $LastExitCode - $result.command = "$script:executable list $options" - $result.stdout = $output | Out-String - Fail-Json -obj $result -message "Error checking installation status for $package 'package'" - } - - If ("$output" -match "(\d+) packages installed.") - { - return $matches[1] -gt 0 - } - - return $false + return $version } -Function Choco-Upgrade -{ - [CmdletBinding()] - +Function Update-ChocolateyPackage { + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingUserNameAndPassWordParams", "", Justification="We need to use the plaintext pass in the cmdline, also using a SecureString here doesn't make sense considering the source is not secure")] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "", Justification="See above")] param( - [Parameter(Mandatory=$true)] - [string] $package, - [string] $version, - [bool] $force, - [int] $timeout, - [bool] $skipscripts, - [string] $source, - [string] $installargs, - [string] $packageparams, - [bool] $allowemptychecksums, - [bool] $ignorechecksums, - [bool] $ignoredependencies, - [bool] $allowdowngrade, - [bool] $allowprerelease, - [string] $proxy_url, - [string] $proxy_username, - [string] $proxy_password, - [string] $architecture + [Parameter(Mandatory=$true)][String]$choco_path, + [Parameter(Mandatory=$true)][String[]]$packages, + [bool]$allow_downgrade, + [bool]$allow_empty_checksums, + [bool]$allow_prerelease, + [String]$architecture, + [bool]$force, + [bool]$ignore_checksums, + [bool]$ignore_dependencies, + [String]$install_args, + [String]$package_params, + [String]$proxy_url, + [String]$proxy_username, + [String]$proxy_password, + [bool]$skip_scripts, + [String]$source, + [String]$source_username, + [String]$source_password, + [int]$timeout, + [String]$version ) - if (-not (Choco-IsInstalled $package)) - { - Fail-Json -obj @{} -message "Package '$package' is not installed, you cannot upgrade" - } + $arguments = [System.Collections.ArrayList]@($choco_path, "upgrade") + $arguments.AddRange($packages) + $common_args = Get-InstallChocolateyArguments -allow_downgrade $allow_downgrade ` + -allow_empty_checksums $allow_empty_checksums -allow_prerelease $allow_prerelease ` + -architecture $architecture -force $force -ignore_checksums $ignore_checksums ` + -ignore_dependencies $ignore_dependencies -install_args $install_args ` + -package_params $package_params -proxy_url $proxy_url -proxy_username $proxy_username ` + -proxy_password $proxy_password -skip_scripts $skip_scripts -source $source ` + -source_username $source_username -source_password $source_password -timeout $timeout ` + -version $version + $arguments.AddRange($common_args) - $options = @( "-y", $package, "--timeout", "$timeout", "--failonunfound" ) - - switch ($architecture) { - "x86" { $options += "--x86" ; break} - } - - if ($check_mode) - { - $options += "--whatif" - } - - if ($version) - { - $options += "--version", $version - } - - if ($source) - { - $options += "--source", $source - } - - if ($force) - { - $options += "--force" - } - - if ($installargs) - { - $options += "--installargs", $installargs - } - - if ($packageparams) - { - $options += "--params", $packageparams - } - - if ($allowemptychecksums) - { - $options += "--allow-empty-checksums" - } - - if ($ignorechecksums) - { - $options += "--ignore-checksums" - } - - if ($ignoredependencies) - { - $options += "--ignoredependencies" - } - - if ($skipscripts) - { - $options += "--skip-scripts" - } - - if ($allowdowngrade) - { - $options += "--allow-downgrade" - } - - if ($allowprerelease) - { - $options += "--prerelease" - } - - if ($proxy_url) - { - $options += "--proxy=`"'$proxy_url'`"" - } - - if ($proxy_username) - { - $options += "--proxy-user=`"'$proxy_username'`"" - } - - if ($proxy_password) - { - $options += "--proxy-password=`"'$proxy_password'`"" - } - - # NOTE: Chocolatey does not use stderr except for help output - Try { - $output = & $script:executable upgrade $script:options $options - } Catch { - Fail-Json -obj $result -message "Error upgrading package '$package': $($_.Exception.Message)" - } - - $result.rc = $LastExitCode - - if ($result.rc -notin $successexitcodes) { - $result.command = "$script:executable upgrade $script:options $options" - $result.stdout = $output | Out-String - Fail-Json -obj $result -message "Error upgrading package '$package'" + $command = Argv-ToString -arguments $arguments + $res = Run-Command -command $command + $result.rc = $res.rc + if ($res.rc -notin $successexitcodes) { + $result.command = $command + $result.stdout = $res.stdout + $result.stderr = $res.stderr + Fail-Json -obj $result -message "Error updating package(s) '$($packages -join ", ")'" } if ($verbosity -gt 1) { - $result.stdout = $output | Out-String + $result.stdout = $res.stdout } - if ("$output" -match ' upgraded (\d+)/\d+ package') - { - if ($matches[1] -gt 0) - { + if ($res.stdout -match ' upgraded (\d+)/\d+ package') { + if ($Matches[1] -gt 0) { $result.changed = $true } } + # need to set to false in case the rc is not 0 and a failure didn't actually occur $result.failed = $false } - -Function Choco-Install -{ - [CmdletBinding()] - +Function Install-ChocolateyPackage { + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingUserNameAndPassWordParams", "", Justification="We need to use the plaintext pass in the cmdline, also using a SecureString here doesn't make sense considering the source is not secure")] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "", Justification="See above")] param( - [Parameter(Mandatory=$true)] - [string] $package, - [string] $version, - [bool] $force, - [int] $timeout, - [bool] $skipscripts, - [string] $source, - [string] $installargs, - [string] $packageparams, - [bool] $allowemptychecksums, - [bool] $ignorechecksums, - [bool] $ignoredependencies, - [bool] $allowdowngrade, - [bool] $allowprerelease, - [string] $proxy_url, - [string] $proxy_username, - [string] $proxy_password, - [string] $architecture + [Parameter(Mandatory=$true)][String]$choco_path, + [Parameter(Mandatory=$true)][String[]]$packages, + [bool]$allow_downgrade, + [bool]$allow_empty_checksums, + [bool]$allow_prerelease, + [String]$architecture, + [bool]$force, + [bool]$ignore_checksums, + [bool]$ignore_dependencies, + [String]$install_args, + [String]$package_params, + [String]$proxy_url, + [String]$proxy_username, + [String]$proxy_password, + [bool]$skip_scripts, + [String]$source, + [String]$source_username, + [String]$source_password, + [int]$timeout, + [String]$version ) - if (Choco-IsInstalled $package) - { - if ($state -in ("downgrade", "latest")) - { - Choco-Upgrade -package $package -version $version -force $force -timeout $timeout ` - -skipscripts $skipscripts -source $source -installargs $installargs ` - -packageparams $packageparams -allowemptychecksums $allowemptychecksums ` - -ignorechecksums $ignorechecksums -ignoredependencies $ignoredependencies ` - -allowdowngrade $allowdowngrade -proxy_url $proxy_url ` - -proxy_username $proxy_username -proxy_password $proxy_password ` - -allowprerelease $allowprerelease -architecture $architecture - return - } - elseif (-not $force) - { - return - } - } + $arguments = [System.Collections.ArrayList]@($choco_path, "install") + $arguments.AddRange($packages) + $common_args = Get-InstallChocolateyArguments -allow_downgrade $allow_downgrade ` + -allow_empty_checksums $allow_empty_checksums -allow_prerelease $allow_prerelease ` + -architecture $architecture -force $force -ignore_checksums $ignore_checksums ` + -ignore_dependencies $ignore_dependencies -install_args $install_args ` + -package_params $package_params -proxy_url $proxy_url -proxy_username $proxy_username ` + -proxy_password $proxy_password -skip_scripts $skip_scripts -source $source ` + -source_username $source_username -source_password $source_password -timeout $timeout ` + -version $version + $arguments.AddRange($common_args) - $options = @( "-y", $package, "--timeout", "$timeout", "--failonunfound" ) - - switch ($architecture) { - "x86" { $options += "--x86" ; break} - } - - if ($check_mode) - { - $options += "--whatif" - } - - if ($version) - { - $options += "--version", $version - } - - if ($source) - { - $options += "--source", $source - } - - if ($force) - { - $options += "--force" - } - - if ($installargs) - { - $options += "--installargs", $installargs - } - - if ($packageparams) - { - $options += "--params", $packageparams - } - - if ($allowemptychecksums) - { - $options += "--allow-empty-checksums" - } - - if ($allowprerelease) - { - $options += "--prerelease" - } - - if ($ignorechecksums) - { - $options += "--ignore-checksums" - } - - if ($ignoredependencies) - { - $options += "--ignoredependencies" - } - - if ($skipscripts) - { - $options += "--skip-scripts" - } - - if ($proxy_url) - { - $options += "--proxy=`"'$proxy_url'`"" - } - - if ($proxy_username) - { - $options += "--proxy-user=`"'$proxy_username'`"" - } - - if ($proxy_password) - { - $options += "--proxy-password=`"'$proxy_password'`"" - } - - # NOTE: Chocolatey does not use stderr except for help output - Try { - $output = & $script:executable install $script:options $options - } Catch { - Fail-Json -obj $result -message "Error installing package '$package': $($_.Exception.Message)" - } - - $result.rc = $LastExitCode - - if ($result.rc -notin $successexitcodes) { - $result.command = "$script:executable install $script:options $options" - $result.stdout = $output | Out-String - Fail-Json -obj $result -message "Error installing package '$package'" + $command = Argv-ToString -arguments $arguments + $res = Run-Command -command $command + $result.rc = $res.rc + if ($res.rc -notin $successexitcodes) { + $result.command = $command + $result.stdout = $res.stdout + $result.stderr = $res.stderr + Fail-Json -obj $result -message "Error installing package(s) '$($packages -join ', ')'" } if ($verbosity -gt 1) { - $result.stdout = $output | Out-String + $result.stdout = $res.stdout } $result.changed = $true + # need to set to false in case the rc is not 0 and a failure didn't actually occur $result.failed = $false } -Function Choco-Uninstall -{ - [CmdletBinding()] - +Function Uninstall-ChocolateyPackage { param( - [Parameter(Mandatory=$true)] - [string] $package, - [string] $version, - [bool] $force, - [int] $timeout, - [bool] $skipscripts + [Parameter(Mandatory=$true)][String]$choco_path, + [Parameter(Mandatory=$true)][String[]]$packages, + [bool]$force, + [String]$package_params, + [bool]$skip_scripts, + [int]$timeout, + [String]$version ) - if (-not (Choco-IsInstalled $package)) - { - return + $arguments = [System.Collections.ArrayList]@($choco_path, "uninstall") + $arguments.AddRange($packages) + $common_args = Get-CommonChocolateyArguments + $arguments.AddRange($common_args) + + if ($force) { + $arguments.Add("--force") > $null + } + if ($package_params) { + $arguments.Add("--package-params") > $null + $arguments.Add($package_params) > $null + } + if ($skip_scripts) { + $arguments.Add("--skip-scripts") > $null + } + if ($null -ne $timeout) { + $arguments.Add("--timeout") > $null + $arguments.Add($timeout) > $null + } + if ($version) { + $arguments.Add("--version") > $null + $arguments.Add($version) > $null } - $options = @( "-y", $package, "--timeout", "$timeout" ) - - if ($check_mode) - { - $options += "--whatif" - } - - if ($version) - { - $options += "--version", $version - } - - if ($force) - { - $options += "--force" - } - - if ($packageparams) - { - $options += "--params", $packageparams - } - - if ($skipscripts) - { - $options += "--skip-scripts" - } - - # NOTE: Chocolatey does not use stderr except for help output - Try { - $output = & $script:executable uninstall $script:options $options - } Catch { - Fail-Json -obj $result -message "Error uninstalling package '$package': $($_.Exception.Message)" - } - - $result.rc = $LastExitCode - - if ($result.rc -notin $successexitcodes) { - $result.command = "$script:executable uninstall $script:options $options" - $result.stdout = $output | Out-String - Fail-Json -obj $result -message "Error uninstalling package '$package'" + $command = Argv-ToString -arguments $arguments + $res = Run-Command -command $command + $result.rc = $res.rc + if ($res.rc -notin $successexitcodes) { + $result.command = $command + $result.stdout = $res.stdout + $result.stderr = $res.stderr + Fail-Json -obj $result -message "Error uninstalling package(s) '$($packages -join ", ")'" } if ($verbosity -gt 1) { - $result.stdout = $output | Out-String + $result.stdout = $res.stdout } - $result.changed = $true + # need to set to false in case the rc is not 0 and a failure didn't actually occur $result.failed = $false } -Chocolatey-Install-Upgrade - -if ($state -in ("absent", "reinstalled")) { - - Choco-Uninstall -package $package -version $version -force $force -timeout $timeout ` - -skipscripts $skipscripts +# get the full path to choco.exe, otherwise install/upgrade to at least 0.10.5 +$choco_path = Install-Chocolatey -proxy_url $proxy_url -proxy_username $proxy_username ` + -proxy_password $proxy_password -source $source -source_username $source_username ` + -source_password $source_password +# get the version of all specified packages +$package_info = @{} +foreach ($package in $name) { + $package_version = Get-ChocolateyPackageVersion -choco_path $choco_path -name $package + $package_info.$package = $package_version } -if ($state -in ("downgrade", "latest", "present", "reinstalled")) { +if ($state -in "absent", "reinstalled") { + $installed_packages = ($package_info.GetEnumerator() | Where-Object { $null -ne $_.Value }).Key + if ($null -ne $installed_packages) { + Uninstall-ChocolateyPackage -choco_path $choco_path -packages $installed_packages ` + -force $force -package_params $package_params -skip_scripts $skip_scripts ` + -timeout $timeout -version $version + } - Choco-Install -package $package -version $version -force $force -timeout $timeout ` - -skipscripts $skipscripts -source $source -installargs $installargs ` - -packageparams $packageparams -allowemptychecksums $allowemptychecksums ` - -ignorechecksums $ignorechecksums -ignoredependencies $ignoredependencies ` - -allowdowngrade ($state -eq "downgrade") -proxy_url $proxy_url ` - -proxy_username $proxy_username -proxy_password $proxy_password ` - -allowprerelease $allowprerelease -architecture $architecture + # ensure the package info for the uninstalled versions has been removed + # so state=reinstall will install them in the next step + foreach ($package in $installed_packages) { + $package_info.$package = $null + } +} + +if ($state -in @("downgrade", "latest", "present", "reinstalled")) { + if ($state -eq "present" -and $force) { + # when present and force, we just run the install step with the packages specified + $missing_packages = $name + } else { + # otherwise only install the packages that are not installed + $missing_packages = ($package_info.GetEnumerator() | Where-Object { $null -eq $_.Value }).Key + } + + # if version is specified and installed version does not match, throw error + # ignore this if force or is set + if ($state -eq "present" -and $null -ne $version -and -not $force) { + foreach ($package in $name) { + $package_version = ($package_info.GetEnumerator() | Where-Object { $name -eq $_.Key -and $null -ne $_.Value }).Value + if ($null -ne $package_version -and $package_version -ne $version) { + Fail-Json -obj $result -message "Chocolatey package '$package' is already installed at version '$package_version' but was expecting '$version'. Either change the expected version, set state=latest, set allow_multiple_versions=yes, or set force=yes to continue" + } + } + } + $common_args = @{ + choco_path = $choco_path + allow_downgrade = ($state -eq "downgrade") + allow_empty_checksums = $allow_empty_checksums + allow_prerelease = $allow_prerelease + architecture = $architecture + force = $force + ignore_checksums = $ignore_checksums + ignore_dependencies = $ignore_dependencies + install_args = $install_args + package_params = $package_params + proxy_url = $proxy_url + proxy_username = $proxy_username + proxy_password = $proxy_password + skip_scripts = $skip_scripts + source = $source + source_username = $source_username + source_password = $source_password + timeout = $timeout + version = $version + } + + if ($null -ne $missing_packages) { + Install-ChocolateyPackage -packages $missing_packages @common_args + } + + if ($state -eq "latest" -or ($state -eq "downgrade" -and $null -ne $version)) { + # when in a downgrade/latest situation, we want to run choco upgrade on + # the remaining packages that were already installed, don't run this if + # state=downgrade and a version isn't specified (this will actually + # upgrade a package) + $installed_packages = ($package_info.GetEnumerator() | Where-Object { $null -ne $_.Value }).Key + if ($null -ne $installed_packages) { + Update-ChocolateyPackage -packages $installed_packages @common_args + } + } } Exit-Json -obj $result diff --git a/lib/ansible/modules/windows/win_chocolatey.py b/lib/ansible/modules/windows/win_chocolatey.py index 4e2011d1e8..5b127fe6df 100644 --- a/lib/ansible/modules/windows/win_chocolatey.py +++ b/lib/ansible/modules/windows/win_chocolatey.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # Copyright: (c) 2014, Trond Hindenes +# Copyright: (c) 2018, Ansible Project # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # this is a windows documentation stub. actual code lives in the .ps1 @@ -17,111 +18,184 @@ module: win_chocolatey version_added: "1.9" short_description: Manage packages using chocolatey description: - - Manage packages using Chocolatey (U(http://chocolatey.org/)). - - If Chocolatey is missing from the system, the module will install it. - - List of packages can be found at U(http://chocolatey.org/packages). +- Manage packages using Chocolatey (U(http://chocolatey.org/)). +- If Chocolatey is missing from the system, the module will install it. +- List of packages can be found at U(http://chocolatey.org/packages). requirements: - chocolatey >= 0.10.5 (will be upgraded if older) options: - name: - description: - - Name of the package to be installed. - - This must be a single package name. - required: yes - state: - description: - - State of the package on the system. - choices: - - absent - - downgrade - - latest - - present - - reinstalled - default: present - force: - description: - - Forces install of the package (even if it already exists). - - Using C(force) will cause ansible to always report that a change was made. - type: bool - default: 'no' - version: - description: - - Specific version of the package to be installed. - - Ignored when C(state) is set to C(absent). - source: - description: - - Specify source rather than using default chocolatey repository. - architecture: - description: - - Allows installation of alternative architecture packages, for example, - 32bit on 64bit windows. - version_added: '2.7' - choices: - - default - - x86 - default: default - install_args: - description: - - Arguments to pass to the native installer. - version_added: '2.1' - params: - description: - - Parameters to pass to the package - version_added: '2.1' allow_empty_checksums: description: - - Allow empty checksums to be used. + - Allow empty checksums to be used for downloaded resource from non-secure + locations. + - Use M(win_chocolatey_feature) with the name C(allowEmptyChecksums) to + control this option globally. type: bool default: 'no' version_added: '2.2' + allow_prerelease: + description: + - Allow the installation of pre-release packages. + - If I(state) is C(latest), the latest pre-release package will be + installed. + type: bool + default: 'no' + version_added: '2.6' + architecture: + description: + - Force Chocolatey to install the package of a specific process + architecture. + - When setting C(x86), will ensure Chocolatey installs the x86 package + even when on an x64 bit OS. + choices: + - default + - x86 + default: default + version_added: '2.7' + force: + description: + - Forces the install of a package, even if it already is installed. + - Using I(force) will cause Ansible to always report that a change was + made. + type: bool + default: 'no' + install_args: + description: + - Arguments to pass to the native installer. + - These are arguments that are passed directly to the installer the + Chocolatey package runs, this is generally an advanced option. + type: str + version_added: '2.1' ignore_checksums: description: - - Ignore checksums altogether. + - Ignore the checksums provided by the package. + - Use M(win_chocolatey_feature) with the name C(checksumFiles) to control + this option globally. type: bool default: 'no' version_added: '2.2' ignore_dependencies: description: - - Ignore dependencies, only install/upgrade the package itself. + - Ignore dependencies, only install/upgrade the package itself. type: bool default: 'no' version_added: '2.1' - timeout: + name: description: - - The time to allow chocolatey to finish before timing out. - type: int - default: 2700 - version_added: '2.3' - aliases: [ execution_timeout ] - skip_scripts: + - Name of the package(s) to be installed. + - Set to C(all) to run the action on all the installed packages. + required: yes + type: list + package_params: description: - - Do not run I(chocolateyInstall.ps1) or I(chocolateyUninstall.ps1) scripts. - type: bool - default: 'no' - version_added: '2.4' + - Parameters to pass to the package. + - These are parameters specific to the Chocolatey package and are generally + documented by the package itself. + - Before Ansible 2.7, this option was just I(params). + type: str + version_added: '2.1' + aliases: + - params proxy_url: description: - - Proxy url used to install chocolatey and the package. + - Proxy URL used to install chocolatey and the package. + - Use M(win_chocolatey_config) with the name C(proxy) to control this + option globally. + type: str version_added: '2.4' proxy_username: description: - - Proxy username used to install chocolatey and the package. - - When dealing with a username with double quote characters C("), they - need to be escaped with C(\) beforehand. See examples for more details. + - Proxy username used to install Chocolatey and the package. + - Before Ansible 2.7, users with double quote characters C(") would need to + be escaped with C(\) beforehand. This is no longer necessary. + - Use M(win_chocolatey_config) with the name C(proxyUser) to control this + option globally. + type: str version_added: '2.4' proxy_password: description: - - Proxy password used to install chocolatey and the package. - - See notes in C(proxy_username) when dealing with double quotes in a - password. + - Proxy password used to install Chocolatey and the package. + - This value is exposed as a command argument and any privileged account + can see this value when the module is running Chocolatey, define the + password on the global config level with M(win_chocolatey_config) with + name C(proxyPassword) to avoid this. + type: str version_added: '2.4' - allow_prerelease: + skip_scripts: description: - - Allow install of prerelease packages. - - If state C(state) is C(latest) the highest prerelease package will be installed. + - Do not run I(chocolateyInstall.ps1) or I(chocolateyUninstall.ps1) scripts + when installing a package. type: bool default: 'no' - version_added: '2.6' + version_added: '2.4' + source: + description: + - Specify the source to retrieve the package from. + - Use M(win_chocolatey_source) to manage global sources. + - This value can either be the URL to a Chocolatey feed, a path to a folder + containing C(.nupkg) packages or the name of a source defined by + M(win_chocolatey_source). + - This value is also used when Chocolatey is not installed as the location + of the install.ps1 script and only supports URLs for this case. + type: str + source_username: + description: + - A username to use with I(source) when accessing a feed that requires + authentication. + - It is recommended you define the credentials on a source with + M(win_chocolatey_source) instead of passing it per task. + type: str + version_added: '2.7' + source_password: + description: + - The password for I(source_username). + - This value is exposed as a command argument and any privileged account + can see this value when the module is running Chocolatey, define the + credentials with a source with M(win_chocolatey_source) to avoid this. + type: str + version_added: '2.7' + state: + description: + - State of the package on the system. + - When C(absent), will ensure the package is not installed. + - When C(present), will ensure the package is installed. + - When C(downgrade), will allow Chocolatey to downgrade a package if + I(version) is older than the installed version. + - When C(latest), will ensure the package is installed to the latest + available version. + - When C(reinstalled), will uninstall and reinstall the package. + choices: + - absent + - downgrade + - latest + - present + - reinstalled + default: present + type: str + timeout: + description: + - The time to allow chocolatey to finish before timing out. + type: int + default: 2700 + version_added: '2.3' + aliases: + - execution_timeout + validate_certs: + description: + - Used when downloading the Chocolatey install script if Chocolatey is not + already installed, this does not affect the Chocolatey package install + process. + - When C(no), no SSL certificates will be validated. + - This should only be used on personally controlled sites using self-signed + certificate. + type: bool + default: 'yes' + version_added: '2.7' + version: + description: + - Specific version of the package to be installed. + - Ignored when I(state) is set to C(absent). + type: str notes: - Provide the C(version) parameter value as a string (e.g. C('6.1')), otherwise it is considered to be a floating-point number and depending on the locale could @@ -130,15 +204,22 @@ notes: - When using verbosity 4 (C(-vvvv)) the C(stdout) output will be more verbose. - When using verbosity 5 (C(-vvvvv)) the C(stdout) output will include debug output. - This module will install or upgrade Chocolatey when needed. -- Some packages need an interactive user logon in order to install. You can use (C(become)) to achieve this. -- Even if you are connecting as local Administrator, using (C(become)) to become Administrator will give you an interactive user logon, see examples below. -- Use (M(win_hotfix) to install hotfixes instead of (M(win_chocolatey)) as (M(win_hotfix)) avoids using wusa.exe which cannot be run remotely. +- Some packages, like hotfixes or updates need an interactive user logon in + order to install. You can use (C(become)) to achieve this, see + :doc:`/user_guide/become`. +- Even if you are connecting as local Administrator, using (C(become)) to + become Administrator will give you an interactive user logon, see examples + below. +- If (C(become)) is unavailable, use (M(win_hotfix) to install hotfixes instead + of (M(win_chocolatey)) as (M(win_hotfix)) avoids using wusa.exe which cannot + be run without (C(become)). author: - Trond Hindenes (@trondhindenes) - Peter Mounce (@petemounce) - Pepe Barbe (@elventear) - Adam Keech (@smadam813) - Pierre Templier (@ptemplier) +- Jordan Borean (@jborean93) ''' # TODO: @@ -166,19 +247,37 @@ EXAMPLES = r''' - name: Install notepadplusplus 32 bit version win_chocolatey: name: notepadplusplus - architecture: 'x86' + architecture: x86 - name: Install git from specified repository win_chocolatey: name: git source: https://someserver/api/v2/ +- name: Install git from a pre configured source (win_chocolatey_source) + win_chocolatey: + name: git + source: internal_repo + +- name: ensure Chocolatey itself is installed and use internal repo as source + win_chocolatey: + name: chocolatey + source: http://someserver/chocolatey + - name: Uninstall git win_chocolatey: name: git state: absent - name: Install multiple packages + win_chocolatey: + name: + - procexp + - putty + - windirstat + state: present + +- name: Install multiple packages sequentially win_chocolatey: name: '{{ item }}' state: present @@ -189,12 +288,11 @@ EXAMPLES = r''' - name: uninstall multiple packages win_chocolatey: - name: '{{ item }}' + name: + - procexp + - putty + - windirstat state: absent - with_items: - - procexp - - putty - - windirstat - name: Install curl using proxy win_chocolatey: @@ -203,13 +301,6 @@ EXAMPLES = r''' proxy_username: joe proxy_password: p@ssw0rd -- name: Install curl with proxy credentials that contain quotes - win_chocolatey: - name: curl - proxy_url: http://proxy-server:8080/ - proxy_username: user with \"escaped\" double quotes - proxy_password: pass with \"escaped\" double quotes - - name: Install a package that requires 'become' win_chocolatey: name: officepro2013 diff --git a/test/integration/targets/win_chocolatey/defaults/main.yml b/test/integration/targets/win_chocolatey/defaults/main.yml new file mode 100644 index 0000000000..eb5289d35f --- /dev/null +++ b/test/integration/targets/win_chocolatey/defaults/main.yml @@ -0,0 +1,9 @@ +--- +test_choco_path: '{{ win_output_dir }}\win_chocolatey' +test_choco_source: '{{ test_choco_path }}\packages' +test_choco_source2: '{{ test_choco_path }}\packages2' # used to verify source works with the source name and not just the path +test_choco_package1: ansible +test_choco_package2: ansible-test +test_choco_packages: +- '{{ test_choco_package1 }}' +- '{{ test_choco_package2 }}' diff --git a/test/integration/targets/win_chocolatey/files/package.nuspec b/test/integration/targets/win_chocolatey/files/package.nuspec new file mode 100644 index 0000000000..b5a3e409fe --- /dev/null +++ b/test/integration/targets/win_chocolatey/files/package.nuspec @@ -0,0 +1,13 @@ + + + + --- NAME --- + --- VERSION --- + --- NAME --- + Jordan Borean + Test for win_chocolatey module + + + + + diff --git a/test/integration/targets/win_chocolatey/files/tools/chocolateyUninstall.ps1 b/test/integration/targets/win_chocolatey/files/tools/chocolateyUninstall.ps1 new file mode 100644 index 0000000000..8c49c11fb0 --- /dev/null +++ b/test/integration/targets/win_chocolatey/files/tools/chocolateyUninstall.ps1 @@ -0,0 +1,9 @@ +$ErrorActionPreference = 'Stop' + +$package_name = $env:ChocolateyPackageName +$package_version = $env:ChocolateyPackageVersion +$install_path = "--- PATH ---\$package_name-$package_version.txt" + +if (Test-Path -Path $install_path) { + Remove-Item -Path $install_path -Force > $null +} diff --git a/test/integration/targets/win_chocolatey/files/tools/chocolateyinstall.ps1 b/test/integration/targets/win_chocolatey/files/tools/chocolateyinstall.ps1 new file mode 100644 index 0000000000..6f1df5eeb9 --- /dev/null +++ b/test/integration/targets/win_chocolatey/files/tools/chocolateyinstall.ps1 @@ -0,0 +1,49 @@ +$ErrorActionPreference = 'Stop' + +$package_name = $env:ChocolateyPackageName +$package_version = $env:ChocolateyPackageVersion +$install_path = "--- PATH ---\$package_name-$package_version.txt" +$source = "--- SOURCE ---" # used by the test to determine which source it was installed from + +if ($env:ChocolateyAllowEmptyChecksums) { + $allow_empty_checksums = $true +} else { + $allow_empty_checksums = $false +} +if ($env:ChocolateyIgnoreChecksums) { + $ignore_checksums = $true +} else { + $ignore_checksums = $false +} +if ($env:ChocolateyForce) { + $force = $true +} else { + $force = $false +} +if ($env:ChocolateyForceX86) { + $force_x86 = $true +} else { + $force_x86 = $false +} +#$process_env = Get-EnvironmentVariableNames -Scope Process +#$env_vars = @{} +#foreach ($name in $process_env) { +# $env_vars.$name = Get-EnvironmentVariable -Name $name -Scope Process +#} +$timeout = $env:chocolateyResponseTimeout + +$package_info = @{ + allow_empty_checksums = $allow_empty_checksums + #env_vars = $env_vars + force = $force + force_x86 = $force_x86 + ignore_checksums = $ignore_checksums + install_args = $env:ChocolateyInstallArguments + package_params = Get-PackageParameters + proxy_url = $env:ChocolateyProxyLocation + source = $source + timeout = $timeout +} +$package_json = ConvertTo-Json -InputObject $package_info + +[System.IO.File]::WriteAllText($install_path, $package_json) diff --git a/test/integration/targets/win_chocolatey/tasks/main.yml b/test/integration/targets/win_chocolatey/tasks/main.yml index 9002975aec..e4f99d5353 100644 --- a/test/integration/targets/win_chocolatey/tasks/main.yml +++ b/test/integration/targets/win_chocolatey/tasks/main.yml @@ -1,65 +1,105 @@ -# test code for the win_chocolatey module -# (c) 2017, Dag Wieers - -# This file is part of Ansible -# -# Ansible is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Ansible is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Ansible. If not, see . - -- name: install chocolatey-core.extension +--- +- name: ensure test package is uninstalled win_chocolatey: - name: chocolatey-core.extension - state: present - register: install - -- name: verify install chocolatey-core.extension - assert: - that: - - 'install.changed == true' - - install.rc == 0 - -- name: install chocolatey-core.extension again - win_chocolatey: - name: chocolatey-core.extension - state: present - register: install_again - -- name: verify install chocolatey-core.extension again - assert: - that: - - 'install_again.changed == false' - - install.rc == 0 - -- name: remove chocolatey-core.extension - win_chocolatey: - name: chocolatey-core.extension + name: '{{ test_choco_packages }}' state: absent - register: remove -- name: verify remove chocolatey-core.extension - assert: - that: - - 'remove.changed == true' - - install.rc == 0 +- name: ensure testing dir is cleaned + win_file: + path: '{{ test_choco_path }}' + state: '{{ item }}' + with_items: + - absent + - directory -- name: remove chocolatey-core.extension again - win_chocolatey: - name: chocolatey-core.extension - state: absent - register: remove_again +- name: copy template package files + win_copy: + src: files/ + dest: '{{ test_choco_path }}' -- name: verify remove chocolatey-core.extension again - assert: - that: - - 'remove_again.changed == false' - - install.rc == 0 +# run the setup in 1 shell script to save on test time +- name: set up packages + win_shell: | + $ErrorActionPreference = "Stop" + $root_path = '{{ test_choco_path }}' + $packages_path = '{{ test_choco_source }}' + $packages_path_override = '{{ test_choco_source2 }}' + $packages = @( + @{ name = "ansible"; version = "0.0.1"; override = $false }, + @{ name = "ansible"; version = "0.1.0"; override = $false }, + @{ name = "ansible"; version = "0.1.0"; override = $true }, + @{ name = "ansible-test"; version = "1.0.0"; override = $false }, + @{ name = "ansible-test"; version = "1.0.1-beta1"; override = $false } + ) + $nuspec_src = "$root_path\package.nuspec" + $install_src = "$root_path\tools\chocolateyinstall.ps1" + $uninstall_src = "$root_path\tools\chocolateyUninstall.ps1" + + New-Item -Path $packages_path -ItemType Directory > $null + New-Item -Path $packages_path_override -ItemType Directory > $null + + foreach ($package in $packages) { + $package_dir = "$root_path\$($package.name)-$($package.version)" + New-Item -Path $package_dir -ItemType Directory > $null + New-Item -Path "$package_dir\tools" -ItemType Directory > $null + + if ($package.override) { + $out_path = $packages_path_override + $source_value = "override" + } else { + $out_path = $packages_path + $source_value = "normal" + } + + $nuspec_text = ([System.IO.File]::ReadAllLines($nuspec_src) -join "`r`n") + $nuspec_text = $nuspec_text.Replace('--- NAME ---', $package.name).Replace('--- VERSION ---', $package.version) + + $install_text = ([System.IO.File]::ReadAllLines($install_src) -join "`r`n") + $install_text = $install_text.Replace('--- PATH ---', $root_path).Replace('--- SOURCE ---', $source_value) + + $uninstall_text = ([System.IO.File]::ReadAllLines($uninstall_src) -join "`r`n") + $uninstall_text = $uninstall_text.Replace('--- PATH ---', $root_path) + + $utf8 = New-Object -TypeName System.Text.UTF8Encoding -ArgumentList $false + $utf8_bom = New-Object -TypeName System.Text.UTF8Encoding -ArgumentList $true + [System.IO.File]::WriteAllText("$package_dir\$($package.name).nuspec", $nuspec_text, $utf8) + [System.IO.File]::WriteAllText("$package_dir\tools\chocolateyinstall.ps1", $install_text, $utf8_bom) + [System.IO.File]::WriteAllText("$package_dir\tools\chocolateyUninstall.ps1", $uninstall_text, $utf8_bom) + + &choco.exe pack --out $out_path --no-progress --limit-output "$package_dir\$($package.name).nuspec" + Remove-Item -Path $package_dir -Force -Recurse > $null + } + Remove-Item -Path "$root_path\tools" -Force -Recurse > $null + Remove-Item -Path $nuspec_src > $null + +- name: set up Chocolatey sources + win_chocolatey_source: + name: '{{ item.name }}' + priority: '{{ item.priority }}' + source: '{{ item.src }}' + state: present + with_items: + - name: ansible-test + priority: 1 + src: '{{ test_choco_source }}' + - name: ansible-test-override + priority: 2 + src: '{{ test_choco_source2 }}' + +- block: + - name: run tests + include_tasks: tests.yml + + always: + - name: remove test sources + win_chocolatey_source: + name: '{{ item }}' + state: absent + with_items: + - ansible-test + - ansible-test-override + + - name: remove testing dir + win_file: + path: '{{ test_choco_path }}' + state: absent diff --git a/test/integration/targets/win_chocolatey/tasks/tests.yml b/test/integration/targets/win_chocolatey/tasks/tests.yml new file mode 100644 index 0000000000..b8c58259f0 --- /dev/null +++ b/test/integration/targets/win_chocolatey/tasks/tests.yml @@ -0,0 +1,416 @@ +--- +- name: install package (check mode) + win_chocolatey: + name: '{{ test_choco_package1 }}' + state: present + check_mode: yes + register: install_check + +- name: get result of install package (check mode) + win_command: choco.exe list --local-only --exact --limit-output {{ test_choco_package1|quote }} + register: install_actual_check + +- name: assert install package (check mode) + assert: + that: + - install_check is changed + - install_actual_check.stdout_lines == [] + +- name: install package + win_chocolatey: + name: '{{ test_choco_package1 }}' + state: present + register: install + +- name: get result of install package + win_command: choco.exe list --local-only --exact --limit-output {{ test_choco_package1|quote }} + register: install_actual + +- name: get package info of install package + win_shell: Get-Content -Path '{{ test_choco_path }}\{{ test_choco_package1 }}-0.1.0.txt' -Raw + register: install_actual_info + +- name: assert install package + assert: + that: + - install is changed + - install_actual.stdout_lines == [test_choco_package1 + "|0.1.0"] + - (install_actual_info.stdout|from_json).allow_empty_checksums == False + - (install_actual_info.stdout|from_json).force == False + - (install_actual_info.stdout|from_json).force_x86 == False + - (install_actual_info.stdout|from_json).ignore_checksums == False + - (install_actual_info.stdout|from_json).install_args == None + - (install_actual_info.stdout|from_json).package_params == {} + - (install_actual_info.stdout|from_json).proxy_url == None + - (install_actual_info.stdout|from_json).source == "normal" + - (install_actual_info.stdout|from_json).timeout == "2700000" + +- name: install package (idempotent) + win_chocolatey: + name: '{{ test_choco_package1 }}' + state: present + register: install_again + +- name: assert install package (idempotent) + assert: + that: + - not install_again is changed + +- name: remove package (check mode) + win_chocolatey: + name: '{{ test_choco_package1 }}' + state: absent + check_mode: yes + register: remove_check + +- name: get result of remove package (check mode) + win_command: choco.exe list --local-only --exact --limit-output {{ test_choco_package1|quote }} + register: remove_actual_check + +- name: assert remove package (check mode) + assert: + that: + - remove_check is changed + - remove_actual_check.stdout_lines == [test_choco_package1 + "|0.1.0"] + +- name: remove package + win_chocolatey: + name: '{{ test_choco_package1 }}' + state: absent + register: remove + +- name: get result of remove package + win_command: choco.exe list --local-only --exact --limit-output {{ test_choco_package1|quote }} + register: remove_actual + +- name: check if removed package file still exists + win_stat: + path: '{{ test_choco_path }}\{{ test_choco_package1 }}-0.1.0.txt' + register: remove_actual_info + +- name: assert remove package + assert: + that: + - remove is changed + - remove_actual.stdout_lines == [] + - remove_actual_info.stat.exists == False + +- name: remove package (idempotent) + win_chocolatey: + name: '{{ test_choco_package1 }}' + state: absent + register: remove_again + +- name: assert remove_package (idempotent) + assert: + that: + - not remove_again is changed + +- name: install multiple packages with timeout + win_chocolatey: + name: '{{ test_choco_packages }}' + state: present + timeout: 1000 + register: install_multiple + +- name: get list of installed packages with timeout + win_command: choco.exe list --local-only --limit-output ansible + register: install_multiple_actual + +- name: get info on package 1 + win_shell: Get-Content -Path '{{ test_choco_path }}\{{ test_choco_package1 }}-0.1.0.txt' -Raw + register: install_multiple_package1 + +- name: get info on package 2 + win_shell: Get-Content -Path '{{ test_choco_path }}\{{ test_choco_package2 }}-1.0.0.txt' -Raw + register: install_multiple_package2 + +- name: assert install multiple packages with timeout + assert: + that: + - install_multiple is changed + - install_multiple_actual.stdout_lines == [test_choco_package1 + "|0.1.0", test_choco_package2 + "|1.0.0"] + - (install_multiple_package1.stdout|from_json).allow_empty_checksums == False + - (install_multiple_package1.stdout|from_json).force == False + - (install_multiple_package1.stdout|from_json).force_x86 == False + - (install_multiple_package1.stdout|from_json).ignore_checksums == False + - (install_multiple_package1.stdout|from_json).install_args == None + - (install_multiple_package1.stdout|from_json).package_params == {} + - (install_multiple_package1.stdout|from_json).proxy_url == None + - (install_multiple_package1.stdout|from_json).source == "normal" + - (install_multiple_package1.stdout|from_json).timeout == "1000000" + - (install_multiple_package2.stdout|from_json).allow_empty_checksums == False + - (install_multiple_package2.stdout|from_json).force == False + - (install_multiple_package2.stdout|from_json).force_x86 == False + - (install_multiple_package2.stdout|from_json).ignore_checksums == False + - (install_multiple_package2.stdout|from_json).install_args == None + - (install_multiple_package2.stdout|from_json).package_params == {} + - (install_multiple_package2.stdout|from_json).proxy_url == None + - (install_multiple_package2.stdout|from_json).source == "normal" + - (install_multiple_package2.stdout|from_json).timeout == "1000000" + +- name: install multiple packages (idempotent) + win_chocolatey: + name: '{{ test_choco_packages }}' + state: present + register: install_multiple_again + +- name: assert install multiple packages (idempotent) + assert: + that: + - not install_multiple_again is changed + +- name: remove multiple packages + win_chocolatey: + name: '{{ test_choco_packages }}' + state: absent + register: remove_multiple + +- name: get list of installed packages after removal + win_command: choco.exe list --local-only --limit-output ansible + register: remove_multiple_actual + +- name: get info on package 1 + win_stat: + path: '{{ test_choco_path }}\{{ test_choco_package1 }}-0.1.0.txt' + register: remove_multiple_package1 + +- name: get info on package 2 + win_stat: + path: '{{ test_choco_path }}\{{ test_choco_package2 }}-1.0.0.txt' + register: remove_multiple_package2 + +- name: assert remove multiple packages + assert: + that: + - remove_multiple is changed + - remove_multiple_actual.stdout_lines == [] + - remove_multiple_package1.stat.exists == False + - remove_multiple_package2.stat.exists == False + +- name: remove multiple packages (idempotent) + win_chocolatey: + name: '{{ test_choco_packages }}' + state: absent + register: remove_multiple_again + +- name: assert remove multiple packages (idempotent) + assert: + that: + - not remove_multiple_again is changed + +- name: install package with params + win_chocolatey: + name: '{{ test_choco_package1 }}' + state: present + install_args: /install_arg 1 /install_arg 2 + package_params: /param1 /param2:value + allow_empty_checksums: yes + architecture: x86 + force: yes + ignore_checksums: yes + proxy_url: http://proxyhost + version: 0.0.1 + register: install_params + +- name: get result of install package with params + win_command: choco.exe list --local-only --limit-output --exact {{ test_choco_package1|quote }} + register: install_params_actual + +- name: get info of install package with params + win_shell: Get-Content -Path '{{ test_choco_path }}\{{ test_choco_package1 }}-0.0.1.txt' + register: install_params_info + +- name: assert install package with params + assert: + that: + - install_params is changed + - install_params_actual.stdout_lines == [test_choco_package1 + "|0.0.1"] + - (install_params_info.stdout|from_json).allow_empty_checksums == True + - (install_params_info.stdout|from_json).force == True + - (install_params_info.stdout|from_json).force_x86 == True + - (install_params_info.stdout|from_json).ignore_checksums == True + - (install_params_info.stdout|from_json).install_args == "/install_arg 1 /install_arg 2" + - (install_params_info.stdout|from_json).package_params.keys()|count == 2 + - (install_params_info.stdout|from_json).package_params.param1 == True + - (install_params_info.stdout|from_json).package_params.param2 == "value" + - (install_params_info.stdout|from_json).proxy_url == "http://proxyhost" + - (install_params_info.stdout|from_json).source == "normal" + - (install_params_info.stdout|from_json).timeout == "2700000" + +- name: install package with version (idempotent) + win_chocolatey: + name: '{{ test_choco_package1 }}' + state: present + version: 0.0.1 + register: install_with_version + +- name: assert install package with version (idempotent) + assert: + that: + - not install_with_version is changed + +- name: fail to install side by side package + win_chocolatey: + name: '{{ test_choco_package1 }}' + state: present + version: 0.1.0 + register: fail_multiple_versions + failed_when: fail_multiple_versions.msg != "Chocolatey package '" + test_choco_package1 + "' is already installed at version '0.0.1' but was expecting '0.1.0'. Either change the expected version, set state=latest, set allow_multiple_versions=yes, or set force=yes to continue" + +- name: force the upgrade of an existing version + win_chocolatey: + name: '{{ test_choco_package1 }}' + state: present + version: 0.1.0 + force: yes + register: force_different_version + +- name: get result of force the upgrade of an existing version + win_command: choco.exe list --local-only --limit-output --exact {{ test_choco_package1|quote }} + register: force_different_version_actual + +- name: get result of forced package install file + win_stat: + path: '{{ test_choco_path }}\{{ test_choco_package1 }}-0.1.0.txt' + register: force_different_version_info + +- name: assert force the upgrade of an existing version + assert: + that: + - force_different_version is changed + - force_different_version_actual.stdout_lines == [test_choco_package1 + "|0.1.0"] + - force_different_version_info.stat.exists + +- name: remove package after force clobbered everything + win_chocolatey: + name: '{{ test_choco_package1 }}' + state: absent + ignore_errors: yes # the mock package created doesn't really handle force well + +- name: install package with reference to source name + win_chocolatey: + name: '{{ test_choco_package1 }}' + state: present + source: ansible-test-override + register: install_source_name + +- name: get result of install package with reference to source name + win_command: choco.exe list --local-only --limit-output --exact {{ test_choco_package1|quote }} + register: install_source_name_actual + +- name: get result fo installed package with reference to source name info + win_shell: Get-Content -Path '{{ test_choco_path }}\{{ test_choco_package1 }}-0.1.0.txt' -Raw + register: install_source_name_info + +- name: assert install package with reference to source name + assert: + that: + - install_source_name is changed + - install_source_name_actual.stdout_lines == [test_choco_package1 + "|0.1.0"] + - (install_source_name_info.stdout|from_json).source == "override" + +- name: reinstall package without source override + win_chocolatey: + name: '{{ test_choco_package1 }}' + state: reinstalled + register: reinstalled_package + +- name: get result of reinstalled package without source override + win_shell: Get-Content -Path '{{ test_choco_path }}\{{ test_choco_package1 }}-0.1.0.txt' -Raw + register: reinstalled_package_info + +- name: assert reinstall package without source override + assert: + that: + - reinstalled_package is changed + - (reinstalled_package_info.stdout|from_json).source == "normal" + +- name: downgrade package + win_chocolatey: + name: '{{ test_choco_package1 }}' + state: downgrade + version: 0.0.1 + register: downgraded_package + +- name: get result of downgrade package + win_command: choco.exe list --local-only --limit-output --exact {{ test_choco_package1|quote }} + register: downgraded_package_actual + +- name: assert downgrade package + assert: + that: + - downgraded_package is changed + - downgraded_package_actual.stdout_lines == [test_choco_package1 + "|0.0.1"] + +- name: downgrade package (idempotent) + win_chocolatey: + name: '{{ test_choco_package1 }}' + state: downgrade + version: 0.0.1 + register: downgraded_package_again + +- name: assert downgrade package (idempotent) + assert: + that: + - not downgraded_package_again is changed + +- name: downgrade package without version specified + win_chocolatey: + name: '{{ test_choco_package1 }}' + state: downgrade + register: downgrade_without_version + +- name: get result of downgrade without version + win_command: choco.exe list --local-only --limit-output --exact {{ test_choco_package1|quote }} + register: downgrade_without_version_actual + +- name: assert downgrade package without version specified + assert: + that: + - not downgrade_without_version is changed + - downgrade_without_version_actual.stdout_lines == [test_choco_package1 + "|0.0.1"] + +- name: upgrade package + win_chocolatey: + name: '{{ test_choco_package1 }}' + state: latest + register: upgrade_package + +- name: get result of upgrade package + win_command: choco.exe list --local-only --limit-output --exact {{ test_choco_package1|quote }} + register: upgrade_package_actual + +- name: assert upgrade package + assert: + that: + - upgrade_package is changed + - upgrade_package_actual.stdout_lines == [test_choco_package1 + "|0.1.0"] + +- name: upgrade package (idempotent) + win_chocolatey: + name: '{{ test_choco_package1 }}' + state: latest + register: upgrade_package_again + +- name: assert upgrade package (idempotent) + assert: + that: + - not upgrade_package_again is changed + +- name: install prerelease package + win_chocolatey: + name: '{{ test_choco_package2 }}' + state: present + allow_prerelease: yes + register: install_prerelease + +- name: get result of install prerelease package + win_command: choco.exe list --local-only --limit-output --exact {{ test_choco_package2|quote }} + register: install_prerelease_actual + +- name: assert install prerelease package + assert: + that: + - install_prerelease is changed + - install_prerelease_actual.stdout_lines == [test_choco_package2 + "|1.0.1-beta1"] diff --git a/test/sanity/pslint/ignore.txt b/test/sanity/pslint/ignore.txt index f265fbd3e7..79fc97de9b 100644 --- a/test/sanity/pslint/ignore.txt +++ b/test/sanity/pslint/ignore.txt @@ -13,12 +13,6 @@ lib/ansible/modules/windows/setup.ps1 PSAvoidUsingCmdletAliases lib/ansible/modules/windows/setup.ps1 PSAvoidUsingEmptyCatchBlock lib/ansible/modules/windows/setup.ps1 PSUseDeclaredVarsMoreThanAssignments lib/ansible/modules/windows/win_certificate_store.ps1 PSAvoidUsingPlainTextForPassword -lib/ansible/modules/windows/win_chocolatey.ps1 PSAvoidUsingConvertToSecureStringWithPlainText -lib/ansible/modules/windows/win_chocolatey.ps1 PSAvoidUsingPlainTextForPassword -lib/ansible/modules/windows/win_chocolatey.ps1 PSAvoidUsingUserNameAndPassWordParams -lib/ansible/modules/windows/win_chocolatey.ps1 PSUseApprovedVerbs -lib/ansible/modules/windows/win_chocolatey.ps1 PSUseDeclaredVarsMoreThanAssignments -lib/ansible/modules/windows/win_chocolatey.ps1 PSUseOutputTypeCorrectly lib/ansible/modules/windows/win_copy.ps1 PSUseApprovedVerbs lib/ansible/modules/windows/win_copy.ps1 PSUseDeclaredVarsMoreThanAssignments lib/ansible/modules/windows/win_dns_client.ps1 PSAvoidGlobalVars