1 #!powershell
2 
3 # Copyright: (c) 2020 VMware, Inc. All Rights Reserved.
4 # SPDX-License-Identifier: GPL-3.0-only
5 # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
6 
7 #AnsibleRequires -CSharpUtil Ansible.Basic
8 
9 $spec = @{
10     options = @{
11         type = @{ type = "str"; choices = "reservation", "lease"; default = "reservation" }
12         ip = @{ type = "str" }
13         scope_id = @{ type = "str" }
14         mac = @{ type = "str" }
15         duration = @{ type = "int" }
16         dns_hostname = @{ type = "str"; }
17         dns_regtype = @{ type = "str"; choices = "aptr", "a", "noreg"; default = "aptr" }
18         reservation_name = @{ type = "str"; }
19         description = @{ type = "str"; }
20         state = @{ type = "str"; choices = "absent", "present"; default = "present" }
21     }
22     required_if = @(
23         @("state", "present", @("mac", "ip"), $true),
24         @("state", "absent", @("mac", "ip"), $true)
25     )
26     supports_check_mode = $true
27 }
28 
29 $module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
30 $check_mode = $module.CheckMode
31 
32 $type = $module.Params.type
33 $ip = $module.Params.ip
34 $scope_id = $module.Params.scope_id
35 $mac = $module.Params.mac
36 $duration = $module.Params.duration
37 $dns_hostname = $module.Params.dns_hostname
38 $dns_regtype = $module.Params.dns_regtype
39 $reservation_name = $module.Params.reservation_name
40 $description = $module.Params.description
41 $state = $module.Params.state
42 
Convert-MacAddressnull43 Function Convert-MacAddress {
44     Param(
45         [string]$mac
46     )
47 
48     # Evaluate Length
49     if ($mac.Length -eq 12) {
50         # Insert Dashes
51         $mac = $mac.Insert(2, "-").Insert(5, "-").Insert(8, "-").Insert(11, "-").Insert(14, "-")
52         return $mac
53     }
54     elseif ($mac.Length -eq 17) {
55         # Remove Colons
56         if($mac -like "*:*:*:*:*:*") {
57             return ($mac -replace ':')
58         }
59         # Remove Dashes
60         if ($mac -like "*-*-*-*-*-*") {
61             return ($mac -replace '-')
62         }
63     }
64     else {
65         return $false
66     }
67 }
68 
Compare-DhcpLease()69 Function Compare-DhcpLease {
70     Param(
71         [PSObject]$Original,
72         [PSObject]$Updated
73     )
74 
75     # Compare values that we care about
76     -not (
77         ($Original.AddressState -eq $Updated.AddressState) -and
78         ($Original.IPAddress -eq $Updated.IPAddress) -and
79         ($Original.ScopeId -eq $Updated.ScopeId) -and
80         ($Original.Name -eq $Updated.Name) -and
81         ($Original.Description -eq $Updated.Description)
82     )
83 }
84 
Convert-ReturnValue()85 Function Convert-ReturnValue {
86     Param(
87         $Object
88     )
89 
90     return @{
91         address_state = $Object.AddressState
92         client_id     = $Object.ClientId
93         ip_address    = $Object.IPAddress.IPAddressToString
94         scope_id      = $Object.ScopeId.IPAddressToString
95         name          = $Object.Name
96         description   = $Object.Description
97     }
98 }
99 
100 # Parse Regtype
101 if ($dns_regtype) {
102     Switch ($dns_regtype) {
103         "aptr" { $dns_regtype = "AandPTR"; break }
104         "a" { $dns_regtype = "A"; break }
105         "noreg" { $dns_regtype = "NoRegistration"; break }
106         default { $dns_regtype = "NoRegistration"; break }
107     }
108 }
109 
110 Try {
111     # Import DHCP Server PS Module
112     Import-Module DhcpServer
113 }
114 Catch {
115     # Couldn't load the DhcpServer Module
116     $module.FailJson("The DhcpServer module failed to load properly: $($_.Exception.Message)", $_)
117 }
118 
119 # Determine if there is an existing lease
120 if ($ip) {
121     $current_lease = Get-DhcpServerv4Scope | Get-DhcpServerv4Lease | Where-Object IPAddress -eq $ip
122 }
123 
124 # MacAddress was specified
125 if ($mac) {
126     if ($mac -like "*-*") {
127         $mac_original = $mac
128         $mac = Convert-MacAddress -mac $mac
129     }
130 
131     if ($mac -eq $false) {
132         $module.FailJson("The MAC Address is not properly formatted")
133     }
134     else {
135         $current_lease = Get-DhcpServerv4Scope | Get-DhcpServerv4Lease | Where-Object ClientId -eq $mac_original
136     }
137 }
138 
139 # Did we find a lease/reservation
140 if ($current_lease) {
141     $current_lease_exists = $true
142     $original_lease = $current_lease
143     $module.Diff.before = Convert-ReturnValue -Object $original_lease
144 }
145 else {
146     $current_lease_exists = $false
147 }
148 
149 # If we found a lease, is it a reservation?
150 if ($current_lease_exists -eq $true -and ($current_lease.AddressState -like "*Reservation*")) {
151     $current_lease_reservation = $true
152 }
153 else {
154     $current_lease_reservation = $false
155 }
156 
157 # State: Absent
158 # Ensure the DHCP Lease/Reservation is not present
159 if ($state -eq "absent") {
160     # If the lease doesn't exist, our work here is done
161     if ($current_lease_exists -eq $false) {
162         $module.Result.msg = "The lease doesn't exist."
163     } else {
164         # If the lease exists, we need to destroy it
165         if ($current_lease_reservation -eq $true) {
166             # Try to remove reservation
167             Try {
168                 $current_lease | Remove-DhcpServerv4Reservation -WhatIf:$check_mode
169                 $state_absent_removed = $true
170             }
171             Catch {
172                 $state_absent_removed = $false
173                 $remove_err = $_
174             }
175         }
176         else {
177             # Try to remove lease
178             Try {
179                 $current_lease | Remove-DhcpServerv4Lease -WhatIf:$check_mode
180                 $state_absent_removed = $true
181             }
182             Catch {
183                 $state_absent_removed = $false
184                 $remove_err = $_
185             }
186         }
187 
188         # See if we removed the lease/reservation
189         if ($state_absent_removed) {
190             $module.Result.changed = $true
191         }
192         else {
193             $module.Result.lease = Convert-ReturnValue -Object $current_lease
194             $module.FailJson("Unable to remove lease/reservation: $($remove_err.Exception.Message)", $remove_err)
195         }
196     }
197 }
198 
199 # State: Present
200 # Ensure the DHCP Lease/Reservation is present, and consistent
201 if ($state -eq "present") {
202     # Current lease exists, and is not a reservation
203     if (($current_lease_reservation -eq $false) -and ($current_lease_exists -eq $true)) {
204         if ($type -eq "reservation") {
205             Try {
206                 # Update parameters
207                 $params = @{ }
208 
209                 if ($mac) {
210                     $params.ClientId = $mac
211                 } else {
212                     $params.ClientId = $current_lease.ClientId
213                 }
214 
215                 if ($description) {
216                     $params.Description = $description
217                 } else {
218                     $params.Description = $current_lease.Description
219                 }
220 
221                 if ($reservation_name) {
222                     $params.Name = $reservation_name
223                 } else {
224                     $params.Name = "reservation-" + $params.ClientId
225                 }
226 
227                 # Desired type is reservation
228                 $current_lease | Add-DhcpServerv4Reservation -WhatIf:$check_mode
229 
230                 if(-not $check_mode) {
231                     $current_reservation = Get-DhcpServerv4Lease -ClientId $params.ClientId -ScopeId $current_lease.ScopeId
232                 }
233 
234                 # Update the reservation with new values
235                 $current_reservation | Set-DhcpServerv4Reservation @params -WhatIf:$check_mode
236 
237                 if(-not $check_mode) {
238                     $updated_reservation = Get-DhcpServerv4Lease -ClientId $params.ClientId -ScopeId $current_reservation.ScopeId
239                 }
240 
241                 if(-not $check_mode) {
242                     # Compare Values
243                     $module.Result.changed = Compare-DhcpLease -Original $original_lease -Updated $updated_reservation
244                     $module.Result.lease = Convert-ReturnValue -Object $updated_reservation
245                 } else {
246                     $module.Result.changed = $true
247                 }
248 
249                 $module.ExitJson()
250             }
251             Catch {
252                 $module.FailJson("Could not convert lease to a reservation",$_)
253             }
254         }
255     }
256 
257     # Current lease exists, and is a reservation
258     if (($current_lease_reservation -eq $true) -and ($current_lease_exists -eq $true)) {
259         if ($type -eq "lease") {
260             Try {
261                 # Desired type is a lease, remove the reservation
262                 $current_lease | Remove-DhcpServerv4Reservation -WhatIf:$check_mode
263 
264                 # Build a new lease object with remnants of the reservation
265                 $lease_params = @{
266                     ClientId = $original_lease.ClientId
267                     IPAddress = $original_lease.IPAddress.IPAddressToString
268                     ScopeId = $original_lease.ScopeId.IPAddressToString
269                     HostName = $original_lease.HostName
270                     AddressState = 'Active'
271                 }
272 
273                 # Create new lease
274                 Try {
275                     Add-DhcpServerv4Lease @lease_params -WhatIf:$check_mode
276                 }
277                 Catch {
278                     $module.FailJson("Unable to convert the reservation to a lease",$_)
279                 }
280 
281                 # Get the lease we just created
282                 if(-not $check_mode) {
283                     Try {
284                         $new_lease = Get-DhcpServerv4Lease -ClientId $lease_params.ClientId -ScopeId $lease_params.ScopeId
285                     }
286                     Catch {
287                         $module.FailJson("Unable to retreive the newly created lease",$_)
288                     }
289                 }
290 
291                 if(-not $check_mode) {
292                     $module.Result.lease = Convert-ReturnValue -Object $new_lease
293                 }
294 
295                 $module.Result.changed = $true
296                 $module.ExitJson()
297             }
298             Catch {
299                 $module.FailJson("Could not convert reservation to lease",$_)
300             }
301         }
302 
303         # Already in the desired state
304         if ($type -eq "reservation") {
305 
306             # Update parameters
307             $params = @{ }
308 
309             if ($mac) {
310                 $params.ClientId = $mac
311             } else {
312                 $params.ClientId = $current_lease.ClientId
313             }
314 
315             if ($description) {
316                 $params.Description = $description
317             } else {
318                 $params.Description = $current_lease.Description
319             }
320 
321             if ($reservation_name) {
322                 $params.Name = $reservation_name
323             } else {
324                 # Original lease had a null name so let's generate one
325                 if ($null -eq $original_lease.Name) {
326                     $params.Name = "reservation-" + $original_lease.ClientId
327                 } else {
328                     $params.Name = $original_lease.Name
329                 }
330             }
331 
332             # Update the reservation with new values
333             $current_lease | Set-DhcpServerv4Reservation @params -WhatIf:$check_mode
334 
335             if(-not $check_mode) {
336                 $reservation = Get-DhcpServerv4Lease -ClientId $current_lease.ClientId -ScopeId $current_lease.ScopeId
337                 $module.Result.changed = Compare-DhcpLease -Original $original_lease -Updated $reservation
338                 $module.Result.lease = Convert-ReturnValue -Object $reservation
339             } else {
340                 $module.Result.changed = $true
341             }
342 
343             # Return values
344             $module.ExitJson()
345         }
346     }
347 
348     # Lease Doesn't Exist - Create
349     if ($current_lease_exists -eq $false) {
350         # Required: Scope ID
351         if (-not $scope_id) {
352             $module.Result.changed = $false
353             $module.FailJson("The scope_id parameter is required for state=present when a lease or reservation doesn't already exist")
354         }
355 
356         # Required Parameters
357         $lease_params = @{
358             ClientId = $mac
359             IPAddress = $ip
360             ScopeId = $scope_id
361             AddressState = 'Active'
362             Confirm = $false
363         }
364 
365         if ($duration) {
366             $lease_params.LeaseExpiryTime = (Get-Date).AddDays($duration)
367         }
368 
369         if ($dns_hostname) {
370             $lease_params.HostName = $dns_hostname
371         }
372 
373         if ($dns_regtype) {
374             $lease_params.DnsRR = $dns_regtype
375         }
376 
377         if ($description) {
378             $lease_params.Description = $description
379         }
380 
381         # Create Lease
382         Try {
383             # Create lease based on parameters
384             Add-DhcpServerv4Lease @lease_params -WhatIf:$check_mode
385 
386             # Retreive the lease
387             if(-not $check_mode) {
388                 $new_lease = Get-DhcpServerv4Lease -ClientId $mac -ScopeId $scope_id
389                 $module.Result.lease = Convert-ReturnValue -Object $new_lease
390             }
391 
392             # If lease is the desired type
393             if ($type -eq "lease") {
394                 $module.Result.changed = $true
395                 $module.ExitJson()
396             }
397         }
398         Catch {
399             # Failed to create lease
400             $module.FailJson("Could not create DHCP lease: $($_.Exception.Message)",$_)
401         }
402 
403         # Create Reservation
404         Try {
405             # If reservation is the desired type
406             if ($type -eq "reservation") {
407                 if ($reservation_name) {
408                     $lease_params.Name = $reservation_name
409                 }
410                 else {
411                     $lease_params.Name = "reservation-" + $mac
412                 }
413 
414                 Try {
415                     if($check_mode) {
416                         # In check mode, a lease won't exist for conversion, make one manually
417                         Add-DhcpServerv4Reservation -ScopeId $scope_id -ClientId $mac_original -IPAddress $ip -WhatIf:$check_mode
418                     } else {
419                         # Convert to Reservation
420                         $new_lease | Add-DhcpServerv4Reservation -WhatIf:$check_mode
421                     }
422                 }
423                 Catch {
424                     # Failed to create reservation
425                     $module.FailJson("Could not create DHCP reservation: $($_.Exception.Message)", $_)
426                 }
427 
428                 if(-not $check_mode) {
429                     # Get DHCP reservation object
430                     $new_lease = Get-DhcpServerv4Reservation -ClientId $mac_original -ScopeId $scope_id
431                     $module.Result.lease = Convert-ReturnValue -Object $new_lease
432                 }
433 
434                 $module.Result.changed = $true
435             }
436         }
437         Catch {
438             # Failed to create reservation
439             $module.FailJson("Could not create DHCP reservation: $($_.Exception.Message)", $_)
440         }
441     }
442 }
443 
444 $module.ExitJson()