summaryrefslogtreecommitdiffstats
path: root/lib/ansible/executor/powershell/async_watchdog.ps1
blob: c2138e35914326c166f319e9dff73c846425099f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# (c) 2018 Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

param(
    [Parameter(Mandatory = $true)][System.Collections.IDictionary]$Payload
)

# help with debugging errors as we don't have visibility of this running process
trap {
    $watchdog_path = "$($env:TEMP)\ansible-async-watchdog-error-$(Get-Date -Format "yyyy-MM-ddTHH-mm-ss.ffffZ").txt"
    $error_msg = "Error while running the async exec wrapper`r`n$(Format-AnsibleException -ErrorRecord $_)"
    Set-Content -Path $watchdog_path -Value $error_msg
    break
}

$ErrorActionPreference = "Stop"

Write-AnsibleLog "INFO - starting async_watchdog" "async_watchdog"

# pop 0th action as entrypoint
$payload.actions = $payload.actions[1..99]

$actions = $Payload.actions
$entrypoint = $payload.($actions[0])
$entrypoint = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($entrypoint))

$resultfile_path = $payload.async_results_path
$max_exec_time_sec = $payload.async_timeout_sec

Write-AnsibleLog "INFO - deserializing existing result file args at: '$resultfile_path'" "async_watchdog"
if (-not (Test-Path -Path $resultfile_path)) {
    $msg = "result file at '$resultfile_path' does not exist"
    Write-AnsibleLog "ERROR - $msg" "async_watchdog"
    throw $msg
}
$result_json = Get-Content -Path $resultfile_path -Raw
Write-AnsibleLog "INFO - result file json is: $result_json" "async_watchdog"
$result = ConvertFrom-AnsibleJson -InputObject $result_json

Write-AnsibleLog "INFO - creating async runspace" "async_watchdog"
$rs = [RunspaceFactory]::CreateRunspace()
$rs.Open()

Write-AnsibleLog "INFO - creating async PowerShell pipeline" "async_watchdog"
$ps = [PowerShell]::Create()
$ps.Runspace = $rs

# these functions are set in exec_wrapper
Write-AnsibleLog "INFO - adding global functions to PowerShell pipeline script" "async_watchdog"
$ps.AddScript($script:common_functions).AddStatement() > $null
$ps.AddScript($script:wrapper_functions).AddStatement() > $null
$function_params = @{
    Name = "common_functions"
    Value = $script:common_functions
    Scope = "script"
}
$ps.AddCommand("Set-Variable").AddParameters($function_params).AddStatement() > $null

Write-AnsibleLog "INFO - adding $($actions[0]) to PowerShell pipeline script" "async_watchdog"
$ps.AddScript($entrypoint).AddArgument($payload) > $null

Write-AnsibleLog "INFO - async job start, calling BeginInvoke()" "async_watchdog"
$job_async_result = $ps.BeginInvoke()

Write-AnsibleLog "INFO - waiting '$max_exec_time_sec' seconds for async job to complete" "async_watchdog"
$job_async_result.AsyncWaitHandle.WaitOne($max_exec_time_sec * 1000) > $null
$result.finished = 1

if ($job_async_result.IsCompleted) {
    Write-AnsibleLog "INFO - async job completed, calling EndInvoke()" "async_watchdog"

    $job_output = $ps.EndInvoke($job_async_result)
    $job_error = $ps.Streams.Error

    Write-AnsibleLog "INFO - raw module stdout:`r`n$($job_output | Out-String)" "async_watchdog"
    if ($job_error) {
        Write-AnsibleLog "WARN - raw module stderr:`r`n$($job_error | Out-String)" "async_watchdog"
    }

    # write success/output/error to result object
    # TODO: cleanse leading/trailing junk
    try {
        Write-AnsibleLog "INFO - deserializing Ansible stdout" "async_watchdog"
        $module_result = ConvertFrom-AnsibleJson -InputObject $job_output
        # TODO: check for conflicting keys
        $result = $result + $module_result
    }
    catch {
        $result.failed = $true
        $result.msg = "failed to parse module output: $($_.Exception.Message)"
        # return output back to Ansible to help with debugging errors
        $result.stdout = $job_output | Out-String
        $result.stderr = $job_error | Out-String
    }

    $result_json = ConvertTo-Json -InputObject $result -Depth 99 -Compress
    Set-Content -Path $resultfile_path -Value $result_json

    Write-AnsibleLog "INFO - wrote output to $resultfile_path" "async_watchdog"
}
else {
    Write-AnsibleLog "ERROR - reached timeout on async job, stopping job" "async_watchdog"
    $ps.BeginStop($null, $null)  > $null # best effort stop

    # write timeout to result object
    $result.failed = $true
    $result.msg = "timed out waiting for module completion"
    $result_json = ConvertTo-Json -InputObject $result -Depth 99 -Compress
    Set-Content -Path $resultfile_path -Value $result_json

    Write-AnsibleLog "INFO - wrote timeout to '$resultfile_path'" "async_watchdog"
}

# in the case of a hung pipeline, this will cause the process to stay alive until it's un-hung...
#$rs.Close() | Out-Null

Write-AnsibleLog "INFO - ending async_watchdog" "async_watchdog"