smart_proxy_dhcp_kea_api 1.0.0 → 1.1.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 142ceab2eeffbc270cb77ef8193a1eabdfe44f824fb60d11f71a942b726f054b
4
- data.tar.gz: 85edbf8f08b9a9c81f1ce07ec97c2b9dff7038149ee8d2f79787c9be3fe38cdf
3
+ metadata.gz: 42b1eec7524d54aa43e9f202c85cb88a92fbfcdf3dbf91a42df43e20d000ac1c
4
+ data.tar.gz: 66ae862538c0fc90345eb7a32e487ef63ef536c37cac572a07adfff9d39bb978
5
5
  SHA512:
6
- metadata.gz: 410a191c0459dc5a854bc61ddf08fcba06519d458e337fd2b5eae3bc2afa68bf09fc8c1ffb1888725dbed5b5f5d688f8bdc7d421651ac05d377af264aa5f8391
7
- data.tar.gz: ef8f1adacf0b3c33405f493325d6138d69f617c3d6287bb8c6ce2acbfb31581bc9a0e2b5160c321b5b95fc69d000f365794a9fdce62cc2351350386bdebf1500
6
+ metadata.gz: 5cf2dfcf2d1b5b52efa98f274b9abd4d777b5b62182f048401040a01baf3f7c32e70cf38921d7e596090a993b684ac19748257e8e2d67d9f35479ef0cc6f823c
7
+ data.tar.gz: 1c54018b7438505c0d8de97337a0363435498c29682e93d7699ffacb2933e7f4af57037c52bbe180b6273066b2c48e6712b06bf3484a83902715e585931d656b
data/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  # Foreman Smart Proxy DHCP Kea API
2
-
2
+ [![Gem Version](https://badge.fury.io/rb/smart_proxy_dhcp_kea_api.svg)](https://badge.fury.io/rb/smart_proxy_dhcp_kea_api)
3
3
  [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](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.
data/lib/.DS_Store ADDED
Binary file
@@ -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 of options for the new record, typically including :mac, :ip, and :name.
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
- # @return [Proxy::DHCP::Reservation] The deleted reservation object.
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
- return record if record.is_a? ::Proxy::DHCP::Lease
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.
@@ -21,8 +23,12 @@ module Proxy
21
23
 
22
24
  # Defines the default settings for this provider. These values are used if
23
25
  # they are not explicitly overridden in a user's settings file
26
+ # The `kea_api_username` and `kea_api_password` can be used to enable
27
+ # HTTP Basic Authentication if required by the Kea control agent.
24
28
  # (e.g. `/etc/foreman-proxy/settings.d/dhcp_kea_api.yml`).
25
29
  default_settings kea_api_url: 'http://127.0.0.1:8000/',
30
+ kea_api_username: nil,
31
+ kea_api_password: nil,
26
32
  blacklist_duration_minutes: 5,
27
33
  open_timeout: 5,
28
34
  read_timeout: 10
@@ -31,7 +31,7 @@ module Proxy
31
31
  # @param reservations_by_ip [Proxy::MemoryStore] A memory store for reservations, passed to the parent class.
32
32
  # @param reservations_by_mac [Proxy::MemoryStore] A memory store for reservations, passed to the parent class.
33
33
  # @param reservations_by_name [Proxy::MemoryStore] A memory store for reservations, passed to the parent class.
34
-
34
+ # @return [void]
35
35
  # rubocop:disable Metrics/ParameterLists
36
36
  def initialize(client, leases_by_ip, leases_by_mac, reservations_by_ip, reservations_by_mac, reservations_by_name)
37
37
  @client = client
@@ -47,6 +47,8 @@ module Proxy
47
47
  #
48
48
  # @return [true] on success.
49
49
  # @raise [Proxy::DHCP::Error] if any part of the loading process fails.
50
+ # @see #load_subnets_and_reservations_from_kea
51
+ # @see #load_leases_from_kea
50
52
  # rubocop:disable Naming/PredicateMethod
51
53
  def load!
52
54
  subnets.clear
@@ -60,6 +62,10 @@ module Proxy
60
62
 
61
63
  # Fetches all subnets and their associated reservations from the Kea API.
62
64
  # This single `config-get` call is the most efficient way to get all static configuration.
65
+ # @return [void]
66
+ # @raise [Proxy::DHCP::Error] if the API call fails.
67
+ # @raise [IPAddr::InvalidAddressError] if a subnet address from Kea is invalid.
68
+ # @see Proxy::DHCP::KeaApi::Client#post_command
63
69
  def load_subnets_and_reservations_from_kea
64
70
  config = @client.post_command('dhcp4', 'config-get')
65
71
  subnets_data = config&.dig('Dhcp4', 'subnet4')
@@ -79,6 +85,9 @@ module Proxy
79
85
  end
80
86
 
81
87
  # Fetches all active leases from the Kea API for the subnets currently in the cache.
88
+ # @return [void]
89
+ # @raise [Proxy::DHCP::Error] if the API call fails.
90
+ # @see Proxy::DHCP::KeaApi::Client#post_command
82
91
  def load_leases_from_kea
83
92
  # This guard is necessary because the `lease4-get-all` command requires
84
93
  # a list of subnet IDs to query. If no subnets were loaded, we can't get leases.
@@ -112,6 +121,8 @@ module Proxy
112
121
  # Foreman Subnet and Reservation objects, and adds them to the cache.
113
122
  #
114
123
  # @param subnet_data [Hash] The hash representing a single subnet from Kea's `config-get` response.
124
+ # @return [void]
125
+ # @raise [IPAddr::InvalidAddressError] if the subnet string is not a valid IP address.
115
126
  def process_subnet(subnet_data)
116
127
  ip_object = IPAddr.new(subnet_data['subnet'])
117
128
  subnet_addr = ip_object.to_s
@@ -140,6 +151,7 @@ module Proxy
140
151
  #
141
152
  # @param subnet_data [Hash] The hash representing a single subnet.
142
153
  # @return [Array<String>, nil] An array of router IP addresses, or nil if none are found.
154
+ # @note The router data in Kea is a single comma-separated string which must be split into an array.
143
155
  def extract_routers(subnet_data)
144
156
  # The router data is a comma-separated string which must be split into an array.
145
157
  subnet_data['option-data']&.find { |opt| opt['name'] == 'routers' }&.[]('data')&.split(',')
@@ -149,6 +161,7 @@ module Proxy
149
161
  #
150
162
  # @param subnet_data [Hash] The hash representing a single subnet.
151
163
  # @return [Array<String>, nil] A two-element array containing the start and end of the range, or nil.
164
+ # @note The pool range is a hyphen-separated string (e.g. "10.0.0.10-10.0.0.20").
152
165
  def extract_range(subnet_data)
153
166
  # The pool range is a hyphen-separated string (e.g. "10.0.0.10-10.0.0.20").
154
167
  pool_string = subnet_data.dig('pools', 0, 'pool')
@@ -159,6 +172,7 @@ module Proxy
159
172
  #
160
173
  # @param res_data [Hash] The hash representing a single reservation.
161
174
  # @param subnet [Proxy::DHCP::Subnet] The subnet object this reservation belongs to.
175
+ # @return [void]
162
176
  def process_reservation(res_data, subnet)
163
177
  record = ::Proxy::DHCP::Reservation.new(res_data['hostname'], res_data['ip-address'], res_data['hw-address'], subnet)
164
178
  # Add the reservation to the parent class's cache, making it searchable.
@@ -3,7 +3,8 @@
3
3
  module Proxy
4
4
  module DHCP
5
5
  module KeaApi
6
- VERSION = '1.0.0'
6
+ # The current version of the smart_proxy_dhcp_kea_api gem.
7
+ VERSION = '1.1.0'
7
8
  end
8
9
  end
9
10
  end
@@ -16,10 +16,24 @@ module Proxy
16
16
  # Initialises a new Kea API client.
17
17
  #
18
18
  # @param url [String] The base URL of the Kea API endpoint (e.g. 'http://127.0.0.1:8000/').
19
+ # @param username [String, nil] The username for HTTP Basic Authentication.
20
+ # @param password [String, nil] The password for HTTP Basic Authentication.
19
21
  # @param open_timeout [Integer] Time in seconds to wait for the initial TCP connection to be established (defaults to 5).
20
22
  # @param read_timeout [Integer] Time in seconds to wait for a response from the server after the connection is made (defaults to 10).
21
23
  # @raise [ArgumentError] if the URL is blank, malformed, or not a valid HTTP/S URL.
22
- def initialize(url:, open_timeout: 5, read_timeout: 10)
24
+ #
25
+ # @example Basic Initialization
26
+ # client = Proxy::DHCP::KeaApi::Client.new(url: 'https://kea.example.com:8443')
27
+ #
28
+ # @example Initialization with Custom Timeouts
29
+ # client = Proxy::DHCP::KeaApi::Client.new(
30
+ # url: 'http://127.0.0.1:8000',
31
+ # username: 'myuser',
32
+ # password: 'mypassword'
33
+ # open_timeout: 2,
34
+ # read_timeout: 5
35
+ # )
36
+ def initialize(url:, username: nil, password: nil, open_timeout: 5, read_timeout: 10)
23
37
  raise ArgumentError, 'Kea API URL cannot be nil or empty' if url.to_s.empty?
24
38
 
25
39
  @uri = URI.parse(url)
@@ -28,6 +42,8 @@ module Proxy
28
42
 
29
43
  raise ArgumentError, "Invalid Kea API URL: '#{url}' is missing a host" unless @uri.host
30
44
 
45
+ @username = username
46
+ @password = password
31
47
  @open_timeout = open_timeout
32
48
  @read_timeout = read_timeout
33
49
  logger.info "Initializing Kea API client for URL: #{@uri} with timeouts (open: #{@open_timeout}s, read: #{@read_timeout}s)"
@@ -39,9 +55,30 @@ module Proxy
39
55
  # @param service [String] The Kea service to target (e.g. 'dhcp4').
40
56
  # @param command [String] The command to execute (e.g. 'config-get', 'reservation-add').
41
57
  # @param arguments [Hash] A hash of arguments required by the command. Defaults to an empty hash.
42
- #
43
58
  # @return [Hash] The 'arguments' hash from the Kea API response on success.
44
59
  # @raise [Proxy::DHCP::Error] if the API returns an error or if there's a communication issue.
60
+ # This can be caused by underlying errors like `Net::ReadTimeout`, `Net::OpenTimeout`,
61
+ # `Errno::ECONNREFUSED`, or `JSON::ParserError`.
62
+ #
63
+ # @example Get the current DHCPv4 configuration
64
+ # client = Proxy::DHCP::KeaApi::Client.new(url: 'http://localhost:8000')
65
+ # config_response = client.post_command('dhcp4', 'config-get')
66
+ # # => {"Dhcp4"=>{"subnet4"=>[{"id"=>1, "subnet"=>"192.168.1.0/24", ...}]}}
67
+ #
68
+ # @example Add a DHCPv4 reservation
69
+ # client = Proxy::DHCP::KeaApi::Client.new(url: 'http://localhost:8000')
70
+ # add_response = client.post_command('dhcp4', 'reservation-add', {
71
+ # reservation: {
72
+ # 'subnet-id': 1,
73
+ # 'ip-address': '192.168.1.100',
74
+ # 'hw-address': '00:11:22:33:44:55',
75
+ # hostname: 'my-new-host'
76
+ # }
77
+ # })
78
+ # # => {"text"=>"Reservation added successfully."}
79
+ #
80
+ # @see https://kea.readthedocs.io/en/latest/api.html General Kea Management API documentation.
81
+ # @see https://kea.readthedocs.io/en/latest/api.html#ref-reservation-add For the `reservation-add` command.
45
82
  def post_command(service, command, arguments = {})
46
83
  header = { 'Content-Type' => 'application/json' }
47
84
  payload = {
@@ -60,16 +97,16 @@ module Proxy
60
97
  http.read_timeout = @read_timeout
61
98
  request = Net::HTTP::Post.new(@uri.request_uri, header)
62
99
  request.body = payload.to_json
100
+ request.basic_auth(@username, @password) if @username && @password
63
101
 
64
102
  logger.debug "Sending command to Kea: #{payload.inspect}"
65
103
  response = http.request(request)
66
104
 
67
105
  handle_response(response, command)
68
- # This rescue block catches all standard communication errors (e.g. connection refused,
69
- # timeouts, DNS failures) and wraps them in a Foreman-specific error type. This ensures
70
- # consistent error handling and reporting up to the Foreman UI.
71
- rescue StandardError => e
72
- logger.error "Failed to send command to Kea API: #{e.message}"
106
+ # This rescue block catches specific, expected network and parsing errors,
107
+ # wrapping them in a Foreman-specific error type for consistent handling.
108
+ rescue Net::ReadTimeout, Net::OpenTimeout, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, JSON::ParserError => e
109
+ logger.error "Failed to send command to Kea API: #{e.class.name} - #{e.message}"
73
110
  raise Proxy::DHCP::Error, "Kea API communication error: #{e.message}"
74
111
  end
75
112
 
@@ -79,9 +116,10 @@ module Proxy
79
116
  #
80
117
  # @param response [Net::HTTPResponse] The raw response object from the HTTP request.
81
118
  # @param command [String] The original command that was sent, used for context-specific handling.
82
- #
83
119
  # @return [Hash] The 'arguments' hash from the response on success.
84
- # @raise [Proxy::DHCP::Error] if the response indicates a failure or is malformed.
120
+ # @raise [Proxy::DHCP::Error] if the response indicates a failure, is malformed, or is empty.
121
+ # @raise [JSON::ParserError] if the response body is not valid JSON.
122
+ # @private
85
123
  def handle_response(response, command)
86
124
  body = JSON.parse(response.body)
87
125
  logger.debug "Received response from Kea: #{body.inspect}"
@@ -91,7 +129,7 @@ module Proxy
91
129
 
92
130
  # If the response is successful, return its arguments. Otherwise, raise an error.
93
131
  if response_successful?(result, command)
94
- # Provide a fallback of '{}' to prevent returning nil if 'arguments' key is missing.
132
+ # Provide a fallback of '{}' to prevent returning nil if the 'arguments' key is missing.
95
133
  result['arguments'] || {}
96
134
  else
97
135
  error_message = result['text'] || 'Unknown error from Kea API'
@@ -103,15 +141,16 @@ module Proxy
103
141
  #
104
142
  # @param result [Hash] The parsed result hash from the Kea response body.
105
143
  # @param command [String] The original command sent, needed for special case handling.
106
- #
107
144
  # @return [Boolean] `true` if the response is considered a success, `false` otherwise.
145
+ #
146
+ # @see https://kea.readthedocs.io/en/stable/api.html For documentation on Kea API result codes.
147
+ # @private
108
148
  def response_successful?(result, command)
109
149
  # Universal success is result code 0.
110
150
  return true if result['result'].zero?
111
- # Special case: 'lease4-get-all' is successful even with result code 3 (no leases found).
112
- return true if command == 'lease4-get-all' && result['result'] == 3
113
151
 
114
- false
152
+ # Special case: 'lease4-get-all' is successful even with result code 3 (no leases found).
153
+ command == 'lease4-get-all' && result['result'] == 3
115
154
  end
116
155
  end
117
156
  end
@@ -38,9 +38,12 @@ 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],
45
+ username: settings[:kea_api_username],
46
+ password: settings[:kea_api_password],
44
47
  open_timeout: settings[:open_timeout],
45
48
  read_timeout: settings[:read_timeout]
46
49
  )
@@ -50,6 +53,7 @@ module Proxy
50
53
  # This is a singleton because we want one central, authoritative cache that all
51
54
  # requests can share. It depends on the Kea client to fetch data and the memory
52
55
  # stores to cache it.
56
+ # @see Proxy::DHCP::KeaApi::SubnetService#initialize
53
57
  container.singleton_dependency :subnet_service, (lambda do
54
58
  memory_store = container.get_dependency(:memory_store)
55
59
  ::Proxy::DHCP::KeaApi::SubnetService.new(
@@ -65,6 +69,7 @@ module Proxy
65
69
  # The main provider class that ties everything together. This is the entry point
66
70
  # for handling DHCP requests from Foreman. It depends on the subnet service,
67
71
  # the API client, and the IP blacklist service to do its job.
72
+ # @see Proxy::DHCP::KeaApi::Provider#initialize
68
73
  container.dependency :dhcp_provider, (lambda do
69
74
  ::Proxy::DHCP::KeaApi::Provider.new(
70
75
  container.get_dependency(:subnet_service),
@@ -1,10 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # The top-level namespace for the Foreman Smart Proxy application.
4
+ # All core Smart Proxy modules and plugins are defined within this namespace.
3
5
  module Proxy
6
+ # The namespace for DHCP-related functionality within the Foreman Smart Proxy.
7
+ # All DHCP providers and their associated services are defined here.
4
8
  module DHCP
5
9
  # The top-level namespace for this DHCP provider. All classes, modules,
6
10
  # and services related to the Kea API integration will be defined within
7
11
  # this KeaApi module to prevent naming conflicts with other plugins.
12
+ # @see Proxy::DHCP::KeaApi::Client
13
+ # @see Proxy::DHCP::KeaApi::SubnetService
8
14
  module KeaApi; end
9
15
  end
10
16
  end
@@ -14,11 +14,25 @@ Gem::Specification.new do |s|
14
14
  s.description = 'Provides DHCP management for Foreman via the ISC Kea API, requiring the host_cmds and lease_cmds hooks.'
15
15
  s.license = 'GPL-3.0-only'
16
16
  s.required_ruby_version = '>= 3.0', '< 4'
17
+ s.metadata = {
18
+ 'bug_tracker_uri' => 'https://gitlab.surrey.ac.uk/sm0049/smart-proxy-dhcp-kea-api/-/issues',
19
+ 'homepage_uri' => 'https://gitlab.surrey.ac.uk/sm0049/smart-proxy-dhcp-kea-api',
20
+ 'rubygems_mfa_required' => 'true'
21
+ }
17
22
 
18
23
  s.files = Dir.glob('{lib}/**/*', File::FNM_DOTMATCH).reject { |f| File.directory?(f) }
19
24
  s.files += %w[LICENSE README.md smart_proxy_dhcp_kea_api.gemspec]
20
25
 
21
26
  s.require_paths = ['lib']
22
27
 
23
- s.metadata['rubygems_mfa_required'] = 'true'
28
+ s.add_development_dependency "bundler"
29
+ s.add_development_dependency "concurrent-ruby"
30
+ s.add_development_dependency "rack-test"
31
+ s.add_development_dependency "rake"
32
+ s.add_development_dependency "rspec"
33
+ s.add_development_dependency "rubocop"
34
+ s.add_development_dependency "rubocop-rspec"
35
+ s.add_development_dependency "yard"
36
+ s.add_development_dependency "yard-rspec"
37
+ s.add_development_dependency "webmock"
24
38
  end
metadata CHANGED
@@ -1,15 +1,155 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smart_proxy_dhcp_kea_api
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam McCarthy
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-07-17 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2025-07-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: concurrent-ruby
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rack-test
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rubocop-rspec
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: yard
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: yard-rspec
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: webmock
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
13
153
  description: Provides DHCP management for Foreman via the ISC Kea API, requiring the
14
154
  host_cmds and lease_cmds hooks.
15
155
  email:
@@ -19,6 +159,7 @@ extra_rdoc_files: []
19
159
  files:
20
160
  - LICENSE
21
161
  - README.md
162
+ - lib/.DS_Store
22
163
  - lib/smart_proxy_dhcp_kea_api.rb
23
164
  - lib/smart_proxy_dhcp_kea_api/dhcp_kea_api_main.rb
24
165
  - lib/smart_proxy_dhcp_kea_api/dhcp_kea_api_plugin.rb
@@ -31,6 +172,8 @@ homepage: https://gitlab.surrey.ac.uk/sm0049/smart-proxy-dhcp-kea-api
31
172
  licenses:
32
173
  - GPL-3.0-only
33
174
  metadata:
175
+ bug_tracker_uri: https://gitlab.surrey.ac.uk/sm0049/smart-proxy-dhcp-kea-api/-/issues
176
+ homepage_uri: https://gitlab.surrey.ac.uk/sm0049/smart-proxy-dhcp-kea-api
34
177
  rubygems_mfa_required: 'true'
35
178
  post_install_message:
36
179
  rdoc_options: []