1 #!powershell
2
3 # Copyright: (c) 2017, Ansible Project
4 # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
5
6 #Requires -Module Ansible.ModuleUtils.Legacy
7 #Requires -Module Ansible.ModuleUtils.FileUtil
8
9 $ErrorActionPreference = "Stop"
10
11 $params = Parse-Args -arguments $args -supports_check_mode $true
12
13 $connect_timeout = Get-AnsibleParam -obj $params -name "connect_timeout" -type "int" -default 5
14 $delay = Get-AnsibleParam -obj $params -name "delay" -type "int"
15 $exclude_hosts = Get-AnsibleParam -obj $params -name "exclude_hosts" -type "list"
16 $hostname = Get-AnsibleParam -obj $params -name "host" -type "str" -default "127.0.0.1"
17 $path = Get-AnsibleParam -obj $params -name "path" -type "path"
18 $port = Get-AnsibleParam -obj $params -name "port" -type "int"
19 $regex = Get-AnsibleParam -obj $params -name "regex" -type "str" -aliases "search_regex","regexp"
20 $sleep = Get-AnsibleParam -obj $params -name "sleep" -type "int" -default 1
21 $state = Get-AnsibleParam -obj $params -name "state" -type "str" -default "started" -validateset "present","started","stopped","absent","drained"
22 $timeout = Get-AnsibleParam -obj $params -name "timeout" -type "int" -default 300
23
24 $result = @{
25 changed = $false
26 elapsed = 0
27 }
28
29 # validate the input with the various options
30 if ($null -ne $port -and $null -ne $path) {
31 Fail-Json $result "port and path parameter can not both be passed to win_wait_for"
32 }
33 if ($null -ne $exclude_hosts -and $state -ne "drained") {
34 Fail-Json $result "exclude_hosts should only be with state=drained"
35 }
36 if ($null -ne $path) {
37 if ($state -in @("stopped","drained")) {
38 Fail-Json $result "state=$state should only be used for checking a port in the win_wait_for module"
39 }
40
41 if ($null -ne $exclude_hosts) {
42 Fail-Json $result "exclude_hosts should only be used when checking a port and state=drained in the win_wait_for module"
43 }
44 }
45
46 if ($null -ne $port) {
47 if ($null -ne $regex) {
48 Fail-Json $result "regex should by used when checking a string in a file in the win_wait_for module"
49 }
50
51 if ($null -ne $exclude_hosts -and $state -ne "drained") {
52 Fail-Json $result "exclude_hosts should be used when state=drained in the win_wait_for module"
53 }
54 }
55
Test-Port($hostname, $port)56 Function Test-Port($hostname, $port) {
57 $timeout = $connect_timeout * 1000
58 $socket = New-Object -TypeName System.Net.Sockets.TcpClient
59 $connect = $socket.BeginConnect($hostname, $port, $null, $null)
60 $wait = $connect.AsyncWaitHandle.WaitOne($timeout, $false)
61
62 if ($wait) {
63 try {
64 $socket.EndConnect($connect) | Out-Null
65 $valid = $true
66 } catch {
67 $valid = $false
68 }
69 } else {
70 $valid = $false
71 }
72
73 $socket.Close()
74 $socket.Dispose()
75
76 $valid
77 }
78
Get-PortConnections($hostname, $port)79 Function Get-PortConnections($hostname, $port) {
80 $connections = @()
81
82 $conn_info = [Net.NetworkInformation.IPGlobalProperties]::GetIPGlobalProperties()
83 if ($hostname -eq "0.0.0.0") {
84 $active_connections = $conn_info.GetActiveTcpConnections() | Where-Object { $_.LocalEndPoint.Port -eq $port }
85 } else {
86 $active_connections = $conn_info.GetActiveTcpConnections() | Where-Object { $_.LocalEndPoint.Address -eq $hostname -and $_.LocalEndPoint.Port -eq $port }
87 }
88
89 if ($null -ne $active_connections) {
90 foreach ($active_connection in $active_connections) {
91 $connections += $active_connection.RemoteEndPoint.Address
92 }
93 }
94
95 $connections
96 }
97
98 $module_start = Get-Date
99
100 if ($null -ne $delay) {
101 Start-Sleep -Seconds $delay
102 }
103
104 $attempts = 0
105 if ($null -eq $path -and $null -eq $port -and $state -ne "drained") {
106 Start-Sleep -Seconds $timeout
107 } elseif ($null -ne $path) {
108 if ($state -in @("present", "started")) {
109 # check if the file exists or string exists in file
110 $start_time = Get-Date
111 $complete = $false
112 while (((Get-Date) - $start_time).TotalSeconds -lt $timeout) {
113 $attempts += 1
114 if (Test-AnsiblePath -Path $path) {
115 if ($null -eq $regex) {
116 $complete = $true
117 break
118 } else {
119 $file_contents = Get-Content -Path $path -Raw
120 if ($file_contents -match $regex) {
121 $complete = $true
122 break
123 }
124 }
125 }
126 Start-Sleep -Seconds $sleep
127 }
128
129 if ($complete -eq $false) {
130 $result.elapsed = ((Get-Date) - $module_start).TotalSeconds
131 $result.wait_attempts = $attempts
132 if ($null -eq $regex) {
133 Fail-Json $result "timeout while waiting for file $path to be present"
134 } else {
135 Fail-Json $result "timeout while waiting for string regex $regex in file $path to match"
136 }
137 }
138 } elseif ($state -in @("absent")) {
139 # check if the file is deleted or string doesn't exist in file
140 $start_time = Get-Date
141 $complete = $false
142 while (((Get-Date) - $start_time).TotalSeconds -lt $timeout) {
143 $attempts += 1
144 if (Test-AnsiblePath -Path $path) {
145 if ($null -ne $regex) {
146 $file_contents = Get-Content -Path $path -Raw
147 if ($file_contents -notmatch $regex) {
148 $complete = $true
149 break
150 }
151 }
152 } else {
153 $complete = $true
154 break
155 }
156
157 Start-Sleep -Seconds $sleep
158 }
159
160 if ($complete -eq $false) {
161 $result.elapsed = ((Get-Date) - $module_start).TotalSeconds
162 $result.wait_attempts = $attempts
163 if ($null -eq $regex) {
164 Fail-Json $result "timeout while waiting for file $path to be absent"
165 } else {
166 Fail-Json $result "timeout while waiting for string regex $regex in file $path to not match"
167 }
168 }
169 }
170 } elseif ($null -ne $port) {
171 if ($state -in @("started","present")) {
172 # check that the port is online and is listening
173 $start_time = Get-Date
174 $complete = $false
175 while (((Get-Date) - $start_time).TotalSeconds -lt $timeout) {
176 $attempts += 1
177 $port_result = Test-Port -hostname $hostname -port $port
178 if ($port_result -eq $true) {
179 $complete = $true
180 break
181 }
182
183 Start-Sleep -Seconds $sleep
184 }
185
186 if ($complete -eq $false) {
187 $result.elapsed = ((Get-Date) - $module_start).TotalSeconds
188 $result.wait_attempts = $attempts
189 Fail-Json $result "timeout while waiting for $($hostname):$port to start listening"
190 }
191 } elseif ($state -in @("stopped","absent")) {
192 # check that the port is offline and is not listening
193 $start_time = Get-Date
194 $complete = $false
195 while (((Get-Date) - $start_time).TotalSeconds -lt $timeout) {
196 $attempts += 1
197 $port_result = Test-Port -hostname $hostname -port $port
198 if ($port_result -eq $false) {
199 $complete = $true
200 break
201 }
202
203 Start-Sleep -Seconds $sleep
204 }
205
206 if ($complete -eq $false) {
207 $result.elapsed = ((Get-Date) - $module_start).TotalSeconds
208 $result.wait_attempts = $attempts
209 Fail-Json $result "timeout while waiting for $($hostname):$port to stop listening"
210 }
211 } elseif ($state -eq "drained") {
212 # check that the local port is online but has no active connections
213 $start_time = Get-Date
214 $complete = $false
215 while (((Get-Date) - $start_time).TotalSeconds -lt $timeout) {
216 $attempts += 1
217 $active_connections = Get-PortConnections -hostname $hostname -port $port
218 if ($null -eq $active_connections) {
219 $complete = $true
220 break
221 } elseif ($active_connections.Count -eq 0) {
222 # no connections on port
223 $complete = $true
224 break
225 } else {
226 # there are listeners, check if we should ignore any hosts
227 if ($null -ne $exclude_hosts) {
228 $connection_info = $active_connections
229 foreach ($exclude_host in $exclude_hosts) {
230 try {
231 $exclude_ips = [System.Net.Dns]::GetHostAddresses($exclude_host) | ForEach-Object { Write-Output $_.IPAddressToString }
232 $connection_info = $connection_info | Where-Object { $_ -notin $exclude_ips }
233 } catch { # ignore invalid hostnames
234 Add-Warning -obj $result -message "Invalid hostname specified $exclude_host"
235 }
236 }
237
238 if ($connection_info.Count -eq 0) {
239 $complete = $true
240 break
241 }
242 }
243 }
244
245 Start-Sleep -Seconds $sleep
246 }
247
248 if ($complete -eq $false) {
249 $result.elapsed = ((Get-Date) - $module_start).TotalSeconds
250 $result.wait_attempts = $attempts
251 Fail-Json $result "timeout while waiting for $($hostname):$port to drain"
252 }
253 }
254 }
255
256 $result.elapsed = ((Get-Date) - $module_start).TotalSeconds
257 $result.wait_attempts = $attempts
258
259 Exit-Json $result
260