smart_proxy_dhcp_kea_api 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/lib/smart_proxy_dhcp_kea_api/dhcp_kea_api_main.rb +50 -3
- data/lib/smart_proxy_dhcp_kea_api/dhcp_kea_api_plugin.rb +2 -0
- data/lib/smart_proxy_dhcp_kea_api/dhcp_kea_api_subnet_service.rb +2 -0
- data/lib/smart_proxy_dhcp_kea_api/dhcp_kea_api_version.rb +1 -1
- data/lib/smart_proxy_dhcp_kea_api/kea_api_client.rb +29 -0
- data/lib/smart_proxy_dhcp_kea_api/plugin_configuration.rb +3 -0
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 60b69b716ae58881effcacb442e0357e0d92a2b30b4c5be29e24439e07c9e047
|
4
|
+
data.tar.gz: c698f7bdb99f3965eb1f7eb02afe908559b29c2145e3c8870b87038c3552c101
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bd83ca3c705b98af40b57b11b9d9d10386f43277a07884d537eda07f28ef433e3939aeb5d7ab90d351e0f1f429560ea642a9a8ce0b4a98eb60867cb037c6b1e5
|
7
|
+
data.tar.gz: e965386db272058aa1504d089cc69e3e39ada4610a2fe96ff4e2f1a2c5ee7632577430f781e0bffc16239f7243d94f5acb536ef0e6ebcefd0558541b080a018d
|
data/README.md
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# Foreman Smart Proxy DHCP Kea API
|
2
|
-
|
2
|
+
[](https://badge.fury.io/rb/smart_proxy_dhcp_kea_api)
|
3
3
|
[](https://www.gnu.org/licenses/gpl-3.0)
|
4
4
|
|
5
5
|
A Foreman Smart Proxy plugin to provide DHCP management by interacting with the ISC Kea API. This provider allows Foreman to view subnets and leases and to create and delete host reservations directly via Kea's JSON-RPC interface.
|
@@ -29,8 +29,36 @@ module Proxy
|
|
29
29
|
# `add_record` to perform initial validation and IP selection, then building
|
30
30
|
# the Kea-specific payload and sending it via the API client.
|
31
31
|
#
|
32
|
-
# @param options [Hash] A hash
|
32
|
+
# @param options [Hash] A hash containing the details for the new reservation.
|
33
|
+
# @option options [String] 'mac' The hardware address of the host.
|
34
|
+
# @option options [String] 'ip' The IP address to reserve.
|
35
|
+
# @option options [String] 'hostname' The hostname for the reservation.
|
36
|
+
# @option options [Proxy::DHCP::Subnet] :subnet The subnet object this reservation belongs to.
|
37
|
+
# @option options [String, nil] 'nextServer' (optional) The IP of the TFTP boot server.
|
38
|
+
# @option options [String, nil] 'filename' (optional) The boot filename (e.g., 'pxelinux.0').
|
33
39
|
# @return [Proxy::DHCP::Reservation] The created reservation object.
|
40
|
+
#
|
41
|
+
# @example Add a new DHCP reservation for a host
|
42
|
+
# # Assume @provider is an instance of Proxy::DHCP::KeaApi::Provider
|
43
|
+
# # and 'subnet' is a valid Proxy::DHCP::Subnet object for '192.168.1.0/24'
|
44
|
+
# options = {
|
45
|
+
# 'mac' => 'aa:bb:cc:dd:ee:ff',
|
46
|
+
# 'hostname' => 'test-host.example.com',
|
47
|
+
# 'ip' => '192.168.1.15',
|
48
|
+
# subnet: subnet,
|
49
|
+
# 'nextServer' => 'tftp.example.com',
|
50
|
+
# 'filename' => 'pxelinux.0',
|
51
|
+
# 'routers' => ['192.168.1.1'],
|
52
|
+
# 'ntp_servers' => ['192.168.1.254']
|
53
|
+
# }
|
54
|
+
#
|
55
|
+
# reservation = @provider.add_record(options)
|
56
|
+
#
|
57
|
+
# reservation.mac #=> "aa:bb:cc:dd:ee:ff"
|
58
|
+
# reservation.ip #=> "192.168.1.15"
|
59
|
+
# reservation.name #=> "test-host.example.com"
|
60
|
+
# reservation.subnet #=> #<Proxy::DHCP::Subnet ...>
|
61
|
+
#
|
34
62
|
def add_record(options = {})
|
35
63
|
logger.debug "DHCP options received from Foreman: #{options.inspect}"
|
36
64
|
record = super
|
@@ -51,10 +79,29 @@ module Proxy
|
|
51
79
|
# Deletes a DHCP reservation from Kea.
|
52
80
|
#
|
53
81
|
# @param record [Proxy::DHCP::Reservation] The reservation object to be deleted.
|
54
|
-
#
|
82
|
+
# This object should be retrieved from the subnet service first.
|
83
|
+
# @return [Proxy::DHCP::Reservation] The object that was successfully deleted.
|
84
|
+
# @raise [Proxy::DHCP::Error] if the corresponding Kea subnet-id cannot be found or the API call fails.
|
85
|
+
#
|
86
|
+
# @example Delete a known DHCP reservation
|
87
|
+
# # Assume @provider is an instance of Proxy::DHCP::KeaApi::Provider
|
88
|
+
# mac_to_delete = 'aa:bb:cc:dd:ee:ff'
|
89
|
+
#
|
90
|
+
# # First, find the reservation object
|
91
|
+
# record_to_delete = @provider.get_record('mac' => mac_to_delete)
|
92
|
+
#
|
93
|
+
# # Now, pass the entire object to del_record
|
94
|
+
# if record_to_delete
|
95
|
+
# result = @provider.del_record(record_to_delete)
|
96
|
+
# #=> #<Proxy::DHCP::Reservation ...>
|
97
|
+
# end
|
98
|
+
#
|
55
99
|
def del_record(record)
|
56
100
|
logger.debug "Deleting record; #{record.inspect}"
|
57
|
-
|
101
|
+
unless record.is_a?(::Proxy::DHCP::Reservation)
|
102
|
+
logger.warn "Attempted to delete a record for MAC '#{record.mac}' but it is not a Reservation (actual type: #{record.class.name}). No action taken."
|
103
|
+
return record
|
104
|
+
end
|
58
105
|
|
59
106
|
subnet_id = @subnet_service.kea_id_map[record.subnet.network]
|
60
107
|
raise Proxy::DHCP::Error, "Unable to find Kea subnet-id for network #{record.subnet.network}" unless subnet_id
|
@@ -10,6 +10,8 @@ module Proxy
|
|
10
10
|
# as the entry point for the Foreman Smart Proxy to load and configure the
|
11
11
|
# plugin. It defines the plugin's name, version, dependencies on other
|
12
12
|
# modules, default settings, and hooks into the dependency injection framework.
|
13
|
+
#
|
14
|
+
# @see Proxy::DHCP::KeaApi::PluginConfiguration For how dependencies are loaded and wired.
|
13
15
|
class Plugin < ::Proxy::Provider
|
14
16
|
# Registers the provider with the Smart Proxy, giving it a unique name
|
15
17
|
# (`:dhcp_kea_api`) and sourcing the version from the VERSION constant.
|
@@ -60,6 +60,7 @@ module Proxy
|
|
60
60
|
|
61
61
|
# Fetches all subnets and their associated reservations from the Kea API.
|
62
62
|
# This single `config-get` call is the most efficient way to get all static configuration.
|
63
|
+
# @see Proxy::DHCP::KeaApi::Client#post_command
|
63
64
|
def load_subnets_and_reservations_from_kea
|
64
65
|
config = @client.post_command('dhcp4', 'config-get')
|
65
66
|
subnets_data = config&.dig('Dhcp4', 'subnet4')
|
@@ -79,6 +80,7 @@ module Proxy
|
|
79
80
|
end
|
80
81
|
|
81
82
|
# Fetches all active leases from the Kea API for the subnets currently in the cache.
|
83
|
+
# @see Proxy::DHCP::KeaApi::Client#post_command
|
82
84
|
def load_leases_from_kea
|
83
85
|
# This guard is necessary because the `lease4-get-all` command requires
|
84
86
|
# a list of subnet IDs to query. If no subnets were loaded, we can't get leases.
|
@@ -19,6 +19,16 @@ module Proxy
|
|
19
19
|
# @param open_timeout [Integer] Time in seconds to wait for the initial TCP connection to be established (defaults to 5).
|
20
20
|
# @param read_timeout [Integer] Time in seconds to wait for a response from the server after the connection is made (defaults to 10).
|
21
21
|
# @raise [ArgumentError] if the URL is blank, malformed, or not a valid HTTP/S URL.
|
22
|
+
#
|
23
|
+
# @example Basic Initialization
|
24
|
+
# client = Proxy::DHCP::KeaApi::Client.new(url: 'https://kea.example.com:8443')
|
25
|
+
#
|
26
|
+
# @example Initialization with Custom Timeouts
|
27
|
+
# client = Proxy::DHCP::KeaApi::Client.new(
|
28
|
+
# url: 'http://127.0.0.1:8000',
|
29
|
+
# open_timeout: 2,
|
30
|
+
# read_timeout: 5
|
31
|
+
# )
|
22
32
|
def initialize(url:, open_timeout: 5, read_timeout: 10)
|
23
33
|
raise ArgumentError, 'Kea API URL cannot be nil or empty' if url.to_s.empty?
|
24
34
|
|
@@ -42,6 +52,23 @@ module Proxy
|
|
42
52
|
#
|
43
53
|
# @return [Hash] The 'arguments' hash from the Kea API response on success.
|
44
54
|
# @raise [Proxy::DHCP::Error] if the API returns an error or if there's a communication issue.
|
55
|
+
#
|
56
|
+
# @example Get the current DHCPv4 configuration
|
57
|
+
# client = Proxy::DHCP::KeaApi::Client.new(url: 'http://localhost:8000')
|
58
|
+
# config_response = client.post_command('dhcp4', 'config-get')
|
59
|
+
# # => {"Dhcp4"=>{"subnet4"=>[{"id"=>1, "subnet"=>"192.168.1.0/24", ...}]}}
|
60
|
+
#
|
61
|
+
# @example Add a DHCPv4 reservation
|
62
|
+
# client = Proxy::DHCP::KeaApi::Client.new(url: 'http://localhost:8000')
|
63
|
+
# add_response = client.post_command('dhcp4', 'reservation-add', {
|
64
|
+
# reservation: {
|
65
|
+
# 'subnet-id': 1,
|
66
|
+
# 'ip-address': '192.168.1.100',
|
67
|
+
# 'hw-address': '00:11:22:33:44:55',
|
68
|
+
# hostname: 'my-new-host'
|
69
|
+
# }
|
70
|
+
# })
|
71
|
+
# # => {"text"=>"Reservation added successfully."}
|
45
72
|
def post_command(service, command, arguments = {})
|
46
73
|
header = { 'Content-Type' => 'application/json' }
|
47
74
|
payload = {
|
@@ -82,6 +109,7 @@ module Proxy
|
|
82
109
|
#
|
83
110
|
# @return [Hash] The 'arguments' hash from the response on success.
|
84
111
|
# @raise [Proxy::DHCP::Error] if the response indicates a failure or is malformed.
|
112
|
+
# @private
|
85
113
|
def handle_response(response, command)
|
86
114
|
body = JSON.parse(response.body)
|
87
115
|
logger.debug "Received response from Kea: #{body.inspect}"
|
@@ -105,6 +133,7 @@ module Proxy
|
|
105
133
|
# @param command [String] The original command sent, needed for special case handling.
|
106
134
|
#
|
107
135
|
# @return [Boolean] `true` if the response is considered a success, `false` otherwise.
|
136
|
+
# @private
|
108
137
|
def response_successful?(result, command)
|
109
138
|
# Universal success is result code 0.
|
110
139
|
return true if result['result'].zero?
|
@@ -38,6 +38,7 @@ module Proxy
|
|
38
38
|
|
39
39
|
# The custom client for communicating with the Kea API. This is registered as a singleton
|
40
40
|
# so that a single, persistent HTTP connection pool is used for all API calls.
|
41
|
+
# @see Proxy::DHCP::KeaApi::Client#initialize
|
41
42
|
container.singleton_dependency :kea_client, (lambda do
|
42
43
|
::Proxy::DHCP::KeaApi::Client.new(
|
43
44
|
url: settings[:kea_api_url],
|
@@ -50,6 +51,7 @@ module Proxy
|
|
50
51
|
# This is a singleton because we want one central, authoritative cache that all
|
51
52
|
# requests can share. It depends on the Kea client to fetch data and the memory
|
52
53
|
# stores to cache it.
|
54
|
+
# @see Proxy::DHCP::KeaApi::SubnetService#initialize
|
53
55
|
container.singleton_dependency :subnet_service, (lambda do
|
54
56
|
memory_store = container.get_dependency(:memory_store)
|
55
57
|
::Proxy::DHCP::KeaApi::SubnetService.new(
|
@@ -65,6 +67,7 @@ module Proxy
|
|
65
67
|
# The main provider class that ties everything together. This is the entry point
|
66
68
|
# for handling DHCP requests from Foreman. It depends on the subnet service,
|
67
69
|
# the API client, and the IP blacklist service to do its job.
|
70
|
+
# @see Proxy::DHCP::KeaApi::Provider#initialize
|
68
71
|
container.dependency :dhcp_provider, (lambda do
|
69
72
|
::Proxy::DHCP::KeaApi::Provider.new(
|
70
73
|
container.get_dependency(:subnet_service),
|