From 724501e9afc586f1a207d23fca3a72535ce4c738 Mon Sep 17 00:00:00 2001 From: Pepe Barbe Date: Sun, 7 Jun 2015 13:18:33 -0500 Subject: [PATCH] Refactor win_chocolatey module * Refactor code to be more robust. Run main logic inside a try {} catch {} block. If there is any error, bail out and log all the command output automatically. * Rely on error code generated by chocolatey instead of scraping text output to determine success/failure. * Add support for unattended installs: (`-y` flag is a requirement by chocolatey) * Before (un)installing, check existence of files. * Use functions to abstract logic * The great rewrite of 0.9.9, the `choco` interface has changed, check if chocolatey is installed and an older version. If so upgrade to latest. * Allow upgrading packages that are already installed * Use verbose logging for chocolate actions * Adding functionality to specify a source for a chocolatey repository. (@smadam813) * Removing pre-determined sources and adding specified source url in it's place. (@smadam813) Contains contributions from: * Adam Keech (@smadam813) --- windows/win_chocolatey.ps1 | 339 ++++++++++++++++++++++--------------- windows/win_chocolatey.py | 43 ++--- 2 files changed, 218 insertions(+), 164 deletions(-) diff --git a/windows/win_chocolatey.ps1 b/windows/win_chocolatey.ps1 index de42434da76..4a033d23157 100644 --- a/windows/win_chocolatey.ps1 +++ b/windows/win_chocolatey.ps1 @@ -16,25 +16,11 @@ # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . +$ErrorActionPreference = "Stop" + # WANT_JSON # POWERSHELL_COMMON -function Write-Log -{ - param - ( - [parameter(mandatory=$false)] - [System.String] - $message - ) - - $date = get-date -format 'yyyy-MM-dd hh:mm:ss.zz' - - Write-Host "$date | $message" - - Out-File -InputObject "$date $message" -FilePath $global:LoggingFile -Append -} - $params = Parse-Args $args; $result = New-Object PSObject; Set-Attr $result "changed" $false; @@ -48,21 +34,22 @@ Else Fail-Json $result "missing required argument: name" } -if(($params.logPath).length -gt 0) +If ($params.force) { - $global:LoggingFile = $params.logPath + $force = $params.force | ConvertTo-Bool } -else +Else { - $global:LoggingFile = "c:\ansible-playbook.log" + $force = $false } -If ($params.force) + +If ($params.upgrade) { - $force = $params.force | ConvertTo-Bool + $upgrade = $params.upgrade | ConvertTo-Bool } Else { - $force = $false + $upgrade = $false } If ($params.version) @@ -74,6 +61,15 @@ Else $version = $null } +If ($params.source) +{ + $source = $params.source.ToString().ToLower() +} +Else +{ + $source = $null +} + If ($params.showlog) { $showlog = $params.showlog | ConvertTo-Bool @@ -96,157 +92,230 @@ Else $state = "present" } -$ChocoAlreadyInstalled = get-command choco -ErrorAction 0 -if ($ChocoAlreadyInstalled -eq $null) +Function Chocolatey-Install-Upgrade { - #We need to install chocolatey - $install_choco_result = iex ((new-object net.webclient).DownloadString("https://chocolatey.org/install.ps1")) - $result.changed = $true - $executable = "C:\ProgramData\chocolatey\bin\choco.exe" -} -Else -{ - $executable = "choco.exe" -} + [CmdletBinding()] -If ($params.source) -{ - $source = $params.source.ToString().ToLower() - If (($source -ne "chocolatey") -and ($source -ne "webpi") -and ($source -ne "windowsfeatures") -and ($source -ne "ruby") -and (!$source.startsWith("http://", "CurrentCultureIgnoreCase")) -and (!$source.startsWith("https://", "CurrentCultureIgnoreCase"))) + param() + + $ChocoAlreadyInstalled = get-command choco -ErrorAction 0 + if ($ChocoAlreadyInstalled -eq $null) + { + #We need to install chocolatey + iex ((new-object net.webclient).DownloadString("https://chocolatey.org/install.ps1")) + $result.changed = $true + $script:executable = "C:\ProgramData\chocolatey\bin\choco.exe" + } + else { - Fail-Json $result "source is $source - must be one of chocolatey, ruby, webpi, windowsfeatures or a custom source url." + $script:executable = "choco.exe" + + if ((choco --version) -lt '0.9.9') + { + Choco-Upgrade chocolatey + } } } -Elseif (!$params.source) + + +Function Choco-IsInstalled { - $source = "chocolatey" + [CmdletBinding()] + + param( + [Parameter(Mandatory=$true, Position=1)] + [string]$package + ) + + $cmd = "$executable list --local-only $package" + $results = invoke-expression $cmd + + if ($LastExitCode -ne 0) + { + Set-Attr $result "choco_error_cmd" $cmd + Set-Attr $result "choco_error_log" "$results" + + Throw "Error checking installation status for $package" + } + + If ("$results" -match " $package .* (\d+) packages installed.") + { + return $matches[1] -gt 0 + } + + $false } -if ($source -eq "webpi") +Function Choco-Upgrade { - # check whether 'webpi' installation source is available; if it isn't, install it - $webpi_check_cmd = "$executable list webpicmd -localonly" - $webpi_check_result = invoke-expression $webpi_check_cmd - Set-Attr $result "chocolatey_bootstrap_webpi_check_cmd" $webpi_check_cmd - Set-Attr $result "chocolatey_bootstrap_webpi_check_log" $webpi_check_result - if ( - ( - ($webpi_check_result.GetType().Name -eq "String") -and - ($webpi_check_result -match "No packages found") - ) -or - ($webpi_check_result -contains "No packages found.") + [CmdletBinding()] + + param( + [Parameter(Mandatory=$true, Position=1)] + [string]$package, + [Parameter(Mandatory=$false, Position=2)] + [string]$version, + [Parameter(Mandatory=$false, Position=3)] + [string]$source, + [Parameter(Mandatory=$false, Position=4)] + [bool]$force ) + + if (-not (Choco-IsInstalled $package)) { - #lessmsi is a webpicmd dependency, but dependency resolution fails unless it's installed separately - $lessmsi_install_cmd = "$executable install lessmsi" - $lessmsi_install_result = invoke-expression $lessmsi_install_cmd - Set-Attr $result "chocolatey_bootstrap_lessmsi_install_cmd" $lessmsi_install_cmd - Set-Attr $result "chocolatey_bootstrap_lessmsi_install_log" $lessmsi_install_result + throw "$package is not installed, you cannot upgrade" + } - $webpi_install_cmd = "$executable install webpicmd" - $webpi_install_result = invoke-expression $webpi_install_cmd - Set-Attr $result "chocolatey_bootstrap_webpi_install_cmd" $webpi_install_cmd - Set-Attr $result "chocolatey_bootstrap_webpi_install_log" $webpi_install_result + $cmd = "$executable upgrade -dv -y $package" - if (($webpi_install_result | select-string "already installed").length -gt 0) - { - #no change - } - elseif (($webpi_install_result | select-string "webpicmd has finished successfully").length -gt 0) + if ($version) + { + $cmd += " -version $version" + } + + if ($source) + { + $cmd += " -source $source" + } + + if ($force) + { + $cmd += " -force" + } + + $results = invoke-expression $cmd + + if ($LastExitCode -ne 0) + { + Set-Attr $result "choco_error_cmd" $cmd + Set-Attr $result "choco_error_log" "$results" + Throw "Error installing $package" + } + + if ("$results" -match ' upgraded (\d+)/\d+ package\(s\)\. ') + { + if ($matches[1] -gt 0) { $result.changed = $true } - Else - { - Fail-Json $result "WebPI install error: $webpi_install_result" - } } } -$expression = $executable -if ($state -eq "present") -{ - $expression += " install $package" -} -Elseif ($state -eq "absent") -{ - $expression += " uninstall $package" -} -if ($force) + +Function Choco-Install { - if ($state -eq "present") + [CmdletBinding()] + + param( + [Parameter(Mandatory=$true, Position=1)] + [string]$package, + [Parameter(Mandatory=$false, Position=2)] + [string]$version, + [Parameter(Mandatory=$false, Position=3)] + [string]$source, + [Parameter(Mandatory=$false, Position=4)] + [bool]$force, + [Parameter(Mandatory=$false, Position=5)] + [bool]$upgrade + ) + + if (Choco-IsInstalled $package) { - $expression += " -force" + if ($upgrade) + { + Choco-Upgrade -package $package -version $version -source $source -force $force + } + + return } -} -if ($version) -{ - $expression += " -version $version" -} -if ($source -eq "chocolatey") -{ - $expression += " -source https://chocolatey.org/api/v2/" -} -elseif (($source -eq "windowsfeatures") -or ($source -eq "webpi") -or ($source -eq "ruby")) -{ - $expression += " -source $source" -} -elseif(($source -ne $Null) -and ($source -ne "")) -{ - $expression += " -source $source" -} -Set-Attr $result "chocolatey command" $expression -$op_result = invoke-expression $expression -if ($state -eq "present") -{ - if ( - (($op_result | select-string "already installed").length -gt 0) -or - # webpi has different text output, and that doesn't include the package name but instead the human-friendly name - (($op_result | select-string "No products to be installed").length -gt 0) - ) + $cmd = "$executable install -dv -y $package" + + if ($version) { - #no change + $cmd += " -version $version" } - elseif ( - (($op_result | select-string "has finished successfully").length -gt 0) -or - # webpi has different text output, and that doesn't include the package name but instead the human-friendly name - (($op_result | select-string "Install of Products: SUCCESS").length -gt 0) -or - (($op_result | select-string "gem installed").length -gt 0) -or - (($op_result | select-string "gems installed").length -gt 0) - ) + + if ($source) { - $result.changed = $true + $cmd += " -source $source" + } + + if ($force) + { + $cmd += " -force" } - Else + + $results = invoke-expression $cmd + + if ($LastExitCode -ne 0) { - Fail-Json $result "Install error: $op_result" + Set-Attr $result "choco_error_cmd" $cmd + Set-Attr $result "choco_error_log" "$results" + Throw "Error installing $package" } + + $result.changed = $true } -Elseif ($state -eq "absent") + +Function Choco-Uninstall { - $op_result = invoke-expression "$executable uninstall $package" - # HACK: Misleading - 'Uninstalling from folder' appears in output even when package is not installed, hence order of checks this way - if ( - (($op_result | select-string "not installed").length -gt 0) -or - (($op_result | select-string "Cannot find path").length -gt 0) + [CmdletBinding()] + + param( + [Parameter(Mandatory=$true, Position=1)] + [string]$package, + [Parameter(Mandatory=$false, Position=2)] + [string]$version, + [Parameter(Mandatory=$false, Position=3)] + [bool]$force ) + + if (-not (Choco-IsInstalled $package)) { - #no change + return } - elseif (($op_result | select-string "Uninstalling from folder").length -gt 0) + + $cmd = "$executable uninstall -dv -y $package" + + if ($version) { - $result.changed = $true + $cmd += " -version $version" } - else + + if ($force) { - Fail-Json $result "Uninstall error: $op_result" + $cmd += " -force" } + + $results = invoke-expression $cmd + + if ($LastExitCode -ne 0) + { + Set-Attr $result "choco_error_cmd" $cmd + Set-Attr $result "choco_error_log" "$results" + Throw "Error uninstalling $package" + } + + $result.changed = $true } +Try +{ + Chocolatey-Install-Upgrade + + if ($state -eq "present") + { + Choco-Install -package $package -version $version -source $source ` + -force $force -upgrade $upgrade + } + else + { + Choco-Uninstall -package $package -version $version -force $force + } -if ($showlog) + Exit-Json $result; +} +Catch { - Set-Attr $result "chocolatey_log" $op_result + Fail-Json $result $_.Exception.Message } -Set-Attr $result "chocolatey_success" "true" -Exit-Json $result; diff --git a/windows/win_chocolatey.py b/windows/win_chocolatey.py index 63ec1ecd214..fe00f2e0f6a 100644 --- a/windows/win_chocolatey.py +++ b/windows/win_chocolatey.py @@ -53,42 +53,29 @@ options: - no default: no aliases: [] - version: + upgrade: description: - - Specific version of the package to be installed - - Ignored when state == 'absent' - required: false - default: null - aliases: [] - showlog: - description: - - Outputs the chocolatey log inside a chocolatey_log property. + - If package is already installed it, try to upgrade to the latest version or to the specified version required: false choices: - yes - no default: no aliases: [] - source: + version: description: - - Which source to install from - require: false - choices: - - chocolatey - - ruby - - webpi - - windowsfeatures - default: chocolatey + - Specific version of the package to be installed + - Ignored when state == 'absent' + required: false + default: null aliases: [] - logPath: + source: description: - - Where to log command output to + - Specify source rather than using default chocolatey repository require: false - default: c:\\ansible-playbook.log + default: null aliases: [] -author: - - '"Trond Hindenes (@trondhindenes)" ' - - '"Peter Mounce (@petemounce)" ' +author: Trond Hindenes, Peter Mounce, Pepe Barbe, Adam Keech ''' # TODO: @@ -111,10 +98,8 @@ EXAMPLES = ''' name: git state: absent - # Install Application Request Routing v3 from webpi - # Logically, this requires that you install IIS first (see win_feature) - # To find a list of packages available via webpi source, `choco list -source webpi` + # Install git from specified repository win_chocolatey: - name: ARRv3 - source: webpi + name: git + source: https://someserver/api/v2/ '''