smart_proxy_ipam 0.0.20 → 0.1.2

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: e7cb97764734cee081d9609f4831b927e82ca0847a551b76823ec4ef3ee0e2cd
4
- data.tar.gz: b999ef4b34cd6e971374120dcc0e972fd9e237bcf8424e017cb2ad09242a690f
3
+ metadata.gz: 9b450f64adc45001ed1f2b969a0291bbb1237fd4077411aca7fe28c54a373cc9
4
+ data.tar.gz: f38780552bb70c0669a2e34758cda24a97f14ad31d147d13d0420f3eda01247b
5
5
  SHA512:
6
- metadata.gz: 0de79823f4095b58d45d888d1b402929365d76299a9b2c7624d006d6b01e8cd4e7b4f42de87c1ab687b0602b62e19f10b806ebb5c39a8d3ebfcc22b5b788ba88
7
- data.tar.gz: 33c07cf01c766f08ebef1545b97512368a983035cdaf81067db71d45b2d7f385f6db5d779d9cc24814dfe44c7861f00e22da3460dd2b1b12254ac18a213cb7a0
6
+ metadata.gz: 932a9e52aa6c6a507a472e52a9fa3bd6e97af23bda10302ac5913ab93557bc69661c49500ad33d3fff5d375b2037cd2cf5e04fa58d0f559ad6473f5940da726c
7
+ data.tar.gz: 292349dbefc9d9abcb4d9b3920e0475d5ed90caf7d460123b389d2dbd7a76a1417721fca1c5a3d03855fd774c78813fcc914bbef0d7e6c9ffa1a095a704b0d45
data/README.md CHANGED
@@ -2,11 +2,9 @@
2
2
 
3
3
  Foreman Smart Proxy plugin for IPAM integration with various IPAM providers.
4
4
 
5
- Currently supported Providers:
6
- 1. [phpIPAM](https://phpipam.net/).
7
-
8
- Provides a basic Dashboard for viewing phpIPAM sections, subnets. Also supports obtaining of the next available IPv4 address for a given subnet(via [IPAM Smart Proxy Plugin](https://github.com/grizzthedj/smart_proxy_ipam)).
9
-
5
+ Currently supported Providers:
6
+ 1. [phpIPAM](https://phpipam.net/).
7
+ 2. [NetBox](https://github.com/netbox-community/netbox).
10
8
 
11
9
  ## Installation
12
10
 
@@ -15,14 +13,23 @@ for how to install Foreman plugins
15
13
 
16
14
  ## Usage
17
15
 
18
- Once plugin is installed, you can use phpIPAM to get the next available IP address for a subnet:
16
+ Once plugin is installed, you can use an External IPAM to get the next available IP address in subnets.
17
+
18
+ 1. Create a subnet in Foreman of IPAM type "External IPAM". Click on the `Proxy` tab and associate the subnet with a Smart Proxy that has the `externalipam` feature enabled. _NOTE: This subnet must actually exist in External IPAM. There is no integration with subnet creation at this time._
19
+ 2. Create a host in Foreman. When adding/editing interfaces, select the above created subnet, and the next available IP in the selected subnet will be pulled from phpIPAM, and displayed in the IPv4/IPv6 address field.
20
+
21
+ ### phpIPAM
22
+ 1. Create a User and API Key in phpIPAM, and ensure they are both named exactly the same.
23
+ 2. The "App Security" setting for your API key should be "User token"
24
+ 3. Add the url and User name and password to the configuration at `/etc/foreman-proxy/settings.d/externalipam_phpipam.yml`
19
25
 
20
- 1. Create a subnet in Foreman of IPAM type "External IPAM". Click on the `Proxy` tab and associate the subnet with a Smart Proxy that has the `External IPAM` feature enabled. _NOTE: This subnet must actually exist in phpIPAM. There is no integration with subnet creation at this time._
21
- 2. Create a host in Foreman. When adding/editing interfaces, select the above created subnet, and the next available IP in the selected subnet will be pulled from phpIPAM, and displayed in the IPv4 address field. _NOTE: This is not supported for IPv6._
26
+ ### NetBox
27
+ 1. Obtain an API token via a user profile in Netbox.
28
+ 2. Add the token and the url to your NetBox instance to the configuration in `/etc/foreman-proxy/settings.d/externalipam_netbox.yml`
22
29
 
23
30
  ## Local development
24
31
 
25
- 1. Clone the Foreman repo
32
+ 1. Clone the Foreman repo
26
33
  ```
27
34
  git clone https://github.com/theforeman/foreman.git
28
35
  ```
@@ -30,48 +37,46 @@ git clone https://github.com/theforeman/foreman.git
30
37
  ```
31
38
  git clone https://github.com/theforeman/smart-proxy
32
39
  ```
33
- 3. Fork both the foreman plugin and smart proxy plugin repos, then clone
40
+ 3. Fork the Smart Proxy IPAM plugin repo, then clone
34
41
 
35
- foreman_ipam repo: https://github.com/grizzthedj/foreman_ipam
36
42
  smart_proxy_ipam repo: https://github.com/grizzthedj/smart_proxy_ipam
37
43
 
38
44
  ```
39
- git clone https://github.com/<GITHUB_USER>/foreman_ipam
40
45
  git clone https://github.com/<GITHUB_USER>/smart_proxy_ipam
41
46
  ```
42
- 4. Add the foreman_ipam plugin to `Gemfile.local.rb` in the Foreman bundler.d directory
43
- ```
44
- gem 'foreman_ipam', :path => '/path/to/foreman_ipam'
45
- ```
46
- 5. From Foreman root directory run
47
+ 4. From Foreman root directory run
47
48
  ```
48
49
  bundle install
49
50
  bundle exec rails db:migrate
50
51
  bundle exec rails db:seed # This adds 'External IPAM' feature to Features table
51
52
  bundle exec foreman start
52
53
  ```
53
- 6. Add the smart_proxy_ipam plugin to `Gemfile.local.rb` in Smart Proxy bundler.d directory
54
+ 5. Add the smart_proxy_ipam plugin to `Gemfile.local.rb` in Smart Proxy bundler.d directory
54
55
  ```
55
56
  gem 'smart_proxy_ipam', :path => '/path/to/smart_proxy_ipam'
56
57
  ```
57
- 7. Copy `config/settings.d/externalipam.yml.example` to `config/settings.d/externalipam.yml` and replace values with your phpIPAM URL and credentials.
58
- 8. From Smart Proxy root directory run ...
58
+ 6. Copy `config/settings.d/externalipam.yml.example` to `config/settings.d/externalipam.yml`, and set `enabled` to true, and `use_provider` to `externalipam_phpipam` or `externalipam_netbox`.
59
+ 7. Copy `config/settings.d/externalipam_phpipam.yml.example` to `config/settings.d/externalipam_phpipam.yml` and replace values with your phpIPAM URL and credentials.
60
+ 8. Copy `config/settings.d/externalipam_netbox.yml.example` to `config/settings.d/externalipam_netbox.yml` and replace values with your Netbox URL and API token.
61
+ 9. From Smart Proxy root directory run ...
59
62
  ```
60
63
  bundle install
61
64
  bundle exec smart-proxy start
62
65
  ```
63
- 9. Navigate to Foreman UI at http://localhost:5000
64
- 10. Add a Local Smart Proxy in the Foreman UI(Infrastructure => Smart Proxies)
65
- 11. Ensure that the `External IPAM` feature is present on the proxy(http://localhost:8000/features)
66
- 12. Create a Subnet, and associate the subnet to the `External IPAM` proxy
67
-
66
+ 10. Navigate to Foreman UI at http://localhost:5000
67
+ 11. Add a Local Smart Proxy in the Foreman UI(Infrastructure => Smart Proxies)
68
+ 12. Ensure that the `External IPAM` feature is present on the proxy(http://localhost:8000/features)
69
+ 13. Create a Subnet(IPv4 or IPv6), and associate the subnet with the `External IPAM` proxy. Subnet must exist in phpIPAM.
70
+ 14. Create a Host, and select an External IPAM Subnet to obtain the next available IP from phpIPAM
71
+ NOTE: For IPv6 subnets only, if the subnet has no addresses reserved(i.e. empty), the first address returned is actually the network address(e.g. `fd13:6d20:29dc:cf27::`), which is not a valid IP. This is a bug within phpIPAM itself
72
+
68
73
  ## Contributing
69
74
 
70
75
  Fork and send a Pull Request. Thanks!
71
76
 
72
77
  ## Copyright
73
78
 
74
- Copyright (c) *2019* *Christopher Smith*
79
+ Copyright (c) *2020* *Christopher Smith*
75
80
 
76
81
  This program is free software: you can redistribute it and/or modify
77
82
  it under the terms of the GNU General Public License as published by
@@ -1,2 +1 @@
1
-
2
1
  gem 'smart_proxy_ipam'
@@ -1,2 +1,4 @@
1
1
  require 'smart_proxy_ipam/version'
2
2
  require 'smart_proxy_ipam/ipam'
3
+ require 'smart_proxy_ipam/phpipam/phpipam_plugin'
4
+ require 'smart_proxy_ipam/netbox/netbox_plugin'
@@ -0,0 +1,54 @@
1
+ require 'yaml'
2
+ require 'json'
3
+ require 'net/http'
4
+ require 'uri'
5
+ require 'smart_proxy_ipam/ipam_helper'
6
+
7
+ module Proxy::Ipam
8
+ # Class to handle authentication and HTTP transactions with External IPAM providers
9
+ class ApiResource
10
+ include ::Proxy::Log
11
+ include Proxy::Ipam::IpamHelper
12
+
13
+ def initialize(params = {})
14
+ @api_base = params[:api_base]
15
+ @token = params[:token]
16
+ @auth_header = params[:auth_header] || 'Authorization'
17
+ end
18
+
19
+ def get(path)
20
+ uri = URI(@api_base + path)
21
+ request = Net::HTTP::Get.new(uri)
22
+ request[@auth_header] = @token
23
+ request['Accept'] = 'application/json'
24
+
25
+ Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
26
+ http.request(request)
27
+ end
28
+ end
29
+
30
+ def delete(path)
31
+ uri = URI(@api_base + path)
32
+ request = Net::HTTP::Delete.new(uri)
33
+ request[@auth_header] = @token
34
+ request['Accept'] = 'application/json'
35
+
36
+ Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
37
+ http.request(request)
38
+ end
39
+ end
40
+
41
+ def post(path, body = nil)
42
+ uri = URI(@api_base + path)
43
+ request = Net::HTTP::Post.new(uri)
44
+ request.body = body
45
+ request[@auth_header] = @token
46
+ request['Accept'] = 'application/json'
47
+ request['Content-Type'] = 'application/json'
48
+
49
+ Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
50
+ http.request(request)
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,9 @@
1
+ module Proxy::Ipam
2
+ module DependencyInjection
3
+ include Proxy::DependencyInjection::Accessors
4
+
5
+ def container_instance
6
+ @container_instance ||= ::Proxy::Plugins.instance.find { |p| p[:name] == :externalipam }[:di_container]
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,137 @@
1
+ require 'yaml'
2
+ require 'json'
3
+ require 'monitor'
4
+ require 'concurrent'
5
+ require 'time'
6
+ require 'smart_proxy_ipam/ipam_helper'
7
+ require 'singleton'
8
+
9
+ module Proxy::Ipam
10
+ # Class for managing temp in-memory cache to prevent same IP's being suggested in race conditions
11
+ class IpCache
12
+ include Singleton
13
+ include Proxy::Log
14
+ include Proxy::Ipam::IpamHelper
15
+
16
+ DEFAULT_CLEANUP_INTERVAL = 60
17
+
18
+ def initialize
19
+ @m = Monitor.new
20
+ init_cache
21
+ start_cleanup_task
22
+ end
23
+
24
+ def set_provider(provider)
25
+ @provider = provider
26
+ end
27
+
28
+ def get_provider
29
+ @provider
30
+ end
31
+
32
+ def set_group(group, value)
33
+ @ip_cache[group.to_sym] = value
34
+ end
35
+
36
+ def get_group(group)
37
+ @ip_cache[group.to_sym]
38
+ end
39
+
40
+ def get_cidr(group, cidr)
41
+ @ip_cache[group.to_sym][cidr.to_sym]
42
+ end
43
+
44
+ def get_ip(group_name, cidr, mac)
45
+ @ip_cache[group_name.to_sym][cidr.to_sym][mac.to_sym][:ip]
46
+ end
47
+
48
+ def get_cleanup_interval
49
+ DEFAULT_CLEANUP_INTERVAL
50
+ end
51
+
52
+ def ip_exists(ip, cidr, group_name)
53
+ cidr_key = @ip_cache[group_name.to_sym][cidr.to_sym]&.to_s
54
+ cidr_key.include?(ip.to_s)
55
+ end
56
+
57
+ def add(ip, mac, cidr, group_name)
58
+ logger.debug("Adding IP '#{ip}' to cache for subnet '#{cidr}' in group '#{group_name}' for IPAM provider #{@provider.to_s}")
59
+ @m.synchronize do
60
+ mac_addr = mac.nil? || mac.empty? ? SecureRandom.uuid : mac
61
+ group_hash = @ip_cache[group_name.to_sym]
62
+
63
+ group_hash.each do |key, values|
64
+ if values.keys.include? mac_addr.to_sym
65
+ @ip_cache[group_name.to_sym][key].delete(mac_addr.to_sym)
66
+ end
67
+ @ip_cache[group_name.to_sym].delete(key) if @ip_cache[group_name.to_sym][key].nil? || @ip_cache[group_name.to_sym][key].empty?
68
+ end
69
+
70
+ if group_hash.key?(cidr.to_sym)
71
+ @ip_cache[group_name.to_sym][cidr.to_sym][mac_addr.to_sym] = {ip: ip.to_s, timestamp: Time.now.to_s}
72
+ else
73
+ @ip_cache = @ip_cache.merge({group_name.to_sym => {cidr.to_sym => {mac_addr.to_sym => {ip: ip.to_s, timestamp: Time.now.to_s}}}})
74
+ end
75
+ end
76
+ end
77
+
78
+ private
79
+
80
+ def start_cleanup_task
81
+ logger.info("Starting ip cache maintenance for IPAM provider #{@provider.to_s}, used by /next_ip.")
82
+ @timer_task = Concurrent::TimerTask.new(execution_interval: DEFAULT_CLEANUP_INTERVAL) { init_cache }
83
+ @timer_task.execute
84
+ end
85
+
86
+ # @ip_cache structure
87
+ #
88
+ # Groups of subnets are cached under the External IPAM Group name. For example,
89
+ # "IPAM Group Name" would be the section name in phpIPAM. All IP's cached for subnets
90
+ # that do not have an External IPAM group specified, they are cached under the "" key. IP's
91
+ # are cached using one of two possible keys:
92
+ # 1). Mac Address
93
+ # 2). UUID (Used when Mac Address not specified)
94
+ #
95
+ # {
96
+ # "": {
97
+ # "100.55.55.0/24":{
98
+ # "00:0a:95:9d:68:10": {"ip": "100.55.55.1", "timestamp": "2019-09-17 12:03:43 -D400"},
99
+ # "906d8bdc-dcc0-4b59-92cb-665935e21662": {"ip": "100.55.55.2", "timestamp": "2019-09-17 11:43:22 -D400"}
100
+ # },
101
+ # },
102
+ # "IPAM Group Name": {
103
+ # "123.11.33.0/24":{
104
+ # "00:0a:95:9d:68:33": {"ip": "123.11.33.1", "timestamp": "2019-09-17 12:04:43 -0400"},
105
+ # "00:0a:95:9d:68:34": {"ip": "123.11.33.2", "timestamp": "2019-09-17 12:05:48 -0400"},
106
+ # "00:0a:95:9d:68:35": {"ip": "123.11.33.3", "timestamp:: "2019-09-17 12:06:50 -0400"}
107
+ # }
108
+ # },
109
+ # "Another IPAM Group": {
110
+ # "185.45.39.0/24":{
111
+ # "00:0a:95:9d:68:55": {"ip": "185.45.39.1", "timestamp": "2019-09-17 12:04:43 -0400"},
112
+ # "00:0a:95:9d:68:56": {"ip": "185.45.39.2", "timestamp": "2019-09-17 12:05:48 -0400"}
113
+ # }
114
+ # }
115
+ # }
116
+ def init_cache
117
+ @m.synchronize do
118
+ if @ip_cache && !@ip_cache.empty?
119
+ logger.debug("Processing ip cache for IPAM provider #{@provider.to_s}")
120
+ @ip_cache.each do |group, subnets|
121
+ subnets.each do |cidr, macs|
122
+ macs.each do |mac, ip|
123
+ if Time.now - Time.parse(ip[:timestamp]) > DEFAULT_CLEANUP_INTERVAL
124
+ @ip_cache[group][cidr].delete(mac)
125
+ end
126
+ end
127
+ @ip_cache[group].delete(cidr) if @ip_cache[group][cidr].nil? || @ip_cache[group][cidr].empty?
128
+ end
129
+ end
130
+ else
131
+ logger.debug("Clearing ip cache for IPAM provider #{@provider.to_s}")
132
+ @ip_cache = {'': {}}
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
@@ -1,11 +1,16 @@
1
-
2
1
  module Proxy::Ipam
3
- class NotFound < RuntimeError; end
4
-
5
2
  class Plugin < ::Proxy::Plugin
6
- plugin 'externalipam', Proxy::Ipam::VERSION
3
+ plugin :externalipam, Proxy::Ipam::VERSION
4
+
5
+ uses_provider
6
+ default_settings use_provider: 'externalipam_phpipam'
7
+
8
+ load_classes(proc do
9
+ require 'smart_proxy_ipam/dependency_injection'
10
+ require 'smart_proxy_ipam/ipam_api'
11
+ end)
7
12
 
8
- http_rackup_path File.expand_path('ipam_http_config.ru', File.expand_path('../', __FILE__))
9
- https_rackup_path File.expand_path('ipam_http_config.ru', File.expand_path('../', __FILE__))
13
+ http_rackup_path File.expand_path('ipam_http_config.ru', __dir__)
14
+ https_rackup_path File.expand_path('ipam_http_config.ru', __dir__)
10
15
  end
11
16
  end
@@ -0,0 +1,390 @@
1
+ require 'proxy/validations'
2
+ require 'smart_proxy_ipam/ipam'
3
+ require 'smart_proxy_ipam/ipam_helper'
4
+ require 'smart_proxy_ipam/ipam_validator'
5
+ require 'smart_proxy_ipam/dependency_injection'
6
+
7
+ module Proxy::Ipam
8
+ # Generic API for External IPAM interactions
9
+ class Api < ::Sinatra::Base
10
+ extend Proxy::Ipam::DependencyInjection
11
+
12
+ include ::Proxy::Log
13
+ helpers ::Proxy::Helpers
14
+ include Proxy::Ipam::IpamHelper
15
+ include Proxy::Ipam::IpamValidator
16
+
17
+ inject_attr :externalipam_client, :client
18
+
19
+ # Gets the next available IP address based on a given External IPAM subnet
20
+ #
21
+ # Inputs: 1. address: Network address of the subnet(e.g. 100.55.55.0)
22
+ # 2. prefix: Network prefix(e.g. 24)
23
+ # 3. group(optional): The External IPAM group
24
+ #
25
+ # Returns:
26
+ # Response if success:
27
+ # ======================
28
+ # Http Code: 200
29
+ # JSON Response:
30
+ # "100.55.55.3"
31
+ #
32
+ # Response if missing parameter(e.g. 'mac')
33
+ # ======================
34
+ # Http Code: 400
35
+ # JSON Response:
36
+ # {"error": ["A 'mac' address must be provided(e.g. 00:0a:95:9d:68:10)"]}
37
+ #
38
+ # Response if no free ip's available
39
+ # ======================
40
+ # Http Code: 404
41
+ # JSON Response:
42
+ # {"error": "There are no free IP's in subnet 100.55.55.0/24"}
43
+ get '/subnet/:address/:prefix/next_ip' do
44
+ content_type :json
45
+
46
+ begin
47
+ validate_required_params!([:address, :prefix, :mac], params)
48
+
49
+ mac = validate_mac!(params[:mac])
50
+ cidr = validate_cidr!(params[:address], params[:prefix])
51
+ group_name = get_request_group(params)
52
+
53
+ next_ip = provider.get_next_ip(mac, cidr, group_name)
54
+ halt 404, { error: ERRORS[:no_free_ips] }.to_json if next_ip.nil?
55
+ next_ip.to_json
56
+ rescue Proxy::Validations::Error => e
57
+ logger.warn(e.message)
58
+ halt 400, { error: e.to_s }.to_json
59
+ rescue RuntimeError => e
60
+ logger.warn(e.message)
61
+ halt 500, { error: e.message }.to_json
62
+ rescue Errno::ECONNREFUSED, Errno::ECONNRESET
63
+ logger.warn(ERRORS[:no_connection])
64
+ halt 500, { error: ERRORS[:no_connection] }.to_json
65
+ end
66
+ end
67
+
68
+ # Gets the subnet from External IPAM
69
+ #
70
+ # Inputs: 1. address: Network address of the subnet
71
+ # 2. prefix: Network prefix(e.g. 24)
72
+ # 3. group(optional): The name of the External IPAM group
73
+ #
74
+ # Returns:
75
+ # Response if subnet exists:
76
+ # ===========================
77
+ # Http Code: 200
78
+ # JSON Response:
79
+ # {"data": {
80
+ # "id": "33",
81
+ # "subnet": "10.20.30.0",
82
+ # "description": "Subnet description",
83
+ # "mask": "29"}
84
+ # }
85
+ #
86
+ # Response if subnet does not exist:
87
+ # ===========================
88
+ # Http Code: 404
89
+ # JSON Response:
90
+ # {"error": "No subnets found"}
91
+ get '/subnet/:address/:prefix' do
92
+ content_type :json
93
+
94
+ begin
95
+ validate_required_params!([:address, :prefix], params)
96
+
97
+ cidr = validate_cidr!(params[:address], params[:prefix])
98
+ group_name = get_request_group(params)
99
+ subnet = provider.get_ipam_subnet(cidr, group_name)
100
+
101
+ halt 404, { error: ERRORS[:no_subnet] }.to_json if subnet.nil?
102
+ subnet.to_json
103
+ rescue Proxy::Validations::Error => e
104
+ logger.warn(e.message)
105
+ halt 400, { error: e.to_s }.to_json
106
+ rescue RuntimeError => e
107
+ logger.warn(e.message)
108
+ halt 500, { error: e.message }.to_json
109
+ rescue Errno::ECONNREFUSED, Errno::ECONNRESET
110
+ logger.warn(ERRORS[:no_connection])
111
+ halt 500, { error: ERRORS[:no_connection] }.to_json
112
+ end
113
+ end
114
+
115
+ # Get a list of groups from External IPAM
116
+ #
117
+ # Returns:
118
+ # Response if success:
119
+ # ===========================
120
+ # Http Code: 200
121
+ # JSON Response:
122
+ # {"data": [
123
+ # {"id": "1", "name": "Group 1", "description": "This is group 1"},
124
+ # {"id": "2", "name": "Group 2", "description": "This is group 2"}
125
+ # ]}
126
+ #
127
+ # Response if no groups exist:
128
+ # ===========================
129
+ # Http Code: 404
130
+ # JSON Response:
131
+ # {"error": "Groups are not supported"}
132
+ #
133
+ # Response if groups are not supported:
134
+ # ===========================
135
+ # Http Code: 500
136
+ # JSON Response:
137
+ # {"error": "Groups are not supported"}
138
+ get '/groups' do
139
+ content_type :json
140
+
141
+ begin
142
+ halt 500, { error: ERRORS[:groups_not_supported] }.to_json unless provider.groups_supported?
143
+ groups = provider.get_ipam_groups
144
+ groups.to_json
145
+ rescue Proxy::Validations::Error => e
146
+ logger.warn(e.message)
147
+ halt 400, { error: e.to_s }.to_json
148
+ rescue RuntimeError => e
149
+ logger.warn(e.message)
150
+ halt 500, { error: e.message }.to_json
151
+ rescue Errno::ECONNREFUSED, Errno::ECONNRESET
152
+ logger.warn(ERRORS[:no_connection])
153
+ halt 500, { error: ERRORS[:no_connection] }.to_json
154
+ end
155
+ end
156
+
157
+ # Get a group from External IPAM
158
+ #
159
+ # Inputs: 1. group: The name of the External IPAM group
160
+ #
161
+ # Returns:
162
+ # Response if success:
163
+ # ===========================
164
+ # Http Code: 200
165
+ # JSON Response:
166
+ # {"data": {"id": "1", "name": "Group 1", "description": "This is group 1"}}
167
+ #
168
+ # Response if group not found:
169
+ # ===========================
170
+ # Http Code: 404
171
+ # JSON Response:
172
+ # {"error": "Group not Found"}
173
+ #
174
+ # Response if groups are not supported:
175
+ # ===========================
176
+ # Http Code: 500
177
+ # JSON Response:
178
+ # {"error": "Groups are not supported"}
179
+ get '/groups/:group' do
180
+ content_type :json
181
+
182
+ begin
183
+ validate_required_params!([:group], params)
184
+
185
+ group_name = get_request_group(params)
186
+ group = provider.get_ipam_group(group_name)
187
+
188
+ halt 404, { error: ERRORS[:no_group] }.to_json if group.nil?
189
+ group.to_json
190
+ rescue Proxy::Validations::Error => e
191
+ logger.warn(e.message)
192
+ halt 400, { error: e.to_s }.to_json
193
+ rescue RuntimeError => e
194
+ logger.warn(e.message)
195
+ halt 500, { error: e.message }.to_json
196
+ rescue Errno::ECONNREFUSED, Errno::ECONNRESET
197
+ logger.warn(ERRORS[:no_connection])
198
+ halt 500, { error: ERRORS[:no_connection] }.to_json
199
+ end
200
+ end
201
+
202
+ # Get a list of subnets for a given External IPAM group
203
+ #
204
+ # Input: 1. group: The name of the External IPAM group
205
+ #
206
+ # Returns:
207
+ # Response if success:
208
+ # ===========================
209
+ # Http Code: 200
210
+ # JSON Response:
211
+ # {"data":[
212
+ # {"subnet":"10.20.30.0","mask":"29","description":"This is a subnet"},
213
+ # {"subnet":"40.50.60.0","mask":"29","description":"This is another subnet"}
214
+ # ]}
215
+ #
216
+ # Response if no subnets exist in group:
217
+ # ===========================
218
+ # Http Code: 404
219
+ # JSON Response:
220
+ # {"error": "No subnets found in External IPAM group"}
221
+ #
222
+ # Response if groups are not supported:
223
+ # ===========================
224
+ # Http Code: 500
225
+ # JSON Response:
226
+ # {"error": "Groups are not supported"}
227
+ get '/groups/:group/subnets' do
228
+ content_type :json
229
+
230
+ begin
231
+ validate_required_params!([:group], params)
232
+
233
+ group_name = get_request_group(params)
234
+ subnets = provider.get_ipam_subnets(group_name)
235
+
236
+ halt 404, { error: ERRORS[:no_subnets_in_group] }.to_json if subnets.nil?
237
+ subnets.to_json
238
+ rescue Proxy::Validations::Error => e
239
+ logger.warn(e.message)
240
+ halt 400, { error: e.to_s }.to_json
241
+ rescue RuntimeError => e
242
+ logger.warn(e.message)
243
+ halt 500, { error: e.message }.to_json
244
+ rescue Errno::ECONNREFUSED, Errno::ECONNRESET
245
+ logger.warn(ERRORS[:no_connection])
246
+ halt 500, { error: ERRORS[:no_connection] }.to_json
247
+ end
248
+ end
249
+
250
+ # Checks whether an IP address has already been taken in External IPAM
251
+ #
252
+ # Inputs: 1. address: The network address of the IPv4 or IPv6 subnet.
253
+ # 2. prefix: The subnet prefix(e.g. 24)
254
+ # 3. ip: IP address to be queried
255
+ # 4. group(optional): The name of the External IPAM Group, containing the subnet to check
256
+ #
257
+ # Returns:
258
+ # Response if exists:
259
+ # ===========================
260
+ # Http Code: 200
261
+ # Response: true
262
+ #
263
+ # Response if not exists:
264
+ # ===========================
265
+ # Http Code: 200
266
+ # JSON Response: false
267
+ get '/subnet/:address/:prefix/:ip' do
268
+ content_type :json
269
+
270
+ begin
271
+ validate_required_params!([:address, :prefix, :ip], params)
272
+
273
+ ip = validate_ip!(params[:ip])
274
+ cidr = validate_cidr!(params[:address], params[:prefix])
275
+ group_name = get_request_group(params)
276
+ subnet = provider.get_ipam_subnet(cidr, group_name)
277
+
278
+ halt 404, { error: ERRORS[:no_subnet] }.to_json if subnet.nil?
279
+ validate_ip_in_cidr!(ip, cidr)
280
+ ip_exists = provider.ip_exists?(ip, subnet[:id], group_name)
281
+ halt 200, ip_exists.to_json
282
+ rescue Proxy::Validations::Error => e
283
+ logger.warn(e.message)
284
+ halt 400, { error: e.to_s }.to_json
285
+ rescue RuntimeError => e
286
+ logger.warn(e.message)
287
+ halt 500, { error: e.message }.to_json
288
+ rescue Errno::ECONNREFUSED, Errno::ECONNRESET
289
+ logger.warn(ERRORS[:no_connection])
290
+ halt 500, { error: ERRORS[:no_connection] }.to_json
291
+ end
292
+ end
293
+
294
+ # Adds an IP address to the specified subnet for the specified IPAM provider
295
+ #
296
+ # Params: 1. address: The network address of the IPv4 or IPv6 subnet
297
+ # 2. prefix: The subnet prefix(e.g. 24)
298
+ # 3. ip: IP address to be added
299
+ # 4. group(optional): The name of the External IPAM Group, containing the subnet to add ip to
300
+ #
301
+ # Returns:
302
+ # Response if added successfully:
303
+ # ===========================
304
+ # Http Code: 201
305
+ # Response: Empty
306
+ #
307
+ # Response if not added successfully:
308
+ # ===========================
309
+ # Http Code: 500
310
+ # JSON Response:
311
+ # {"error": "Unable to add IP to External IPAM"}
312
+ post '/subnet/:address/:prefix/:ip' do
313
+ content_type :json
314
+
315
+ begin
316
+ validate_required_params!([:address, :ip, :prefix], params)
317
+
318
+ ip = validate_ip!(params[:ip])
319
+ cidr = validate_cidr!(params[:address], params[:prefix])
320
+ group_name = get_request_group(params)
321
+ subnet = provider.get_ipam_subnet(cidr, group_name)
322
+
323
+ halt 404, { error: ERRORS[:no_subnet] }.to_json if subnet.nil?
324
+ add_ip_params = { cidr: cidr, subnet_id: subnet[:id], group_name: group_name }
325
+ validate_ip_in_cidr!(ip, cidr)
326
+
327
+ ip_added = provider.add_ip_to_subnet(ip, add_ip_params) # Returns nil on success
328
+ halt 500, ip_added.to_json unless ip_added.nil?
329
+ status 201
330
+ rescue Proxy::Validations::Error => e
331
+ logger.warn(e.message)
332
+ halt 400, { error: e.to_s }.to_json
333
+ rescue RuntimeError => e
334
+ logger.warn(e.message)
335
+ halt 500, { error: e.message }.to_json
336
+ rescue Errno::ECONNREFUSED, Errno::ECONNRESET
337
+ logger.warn(ERRORS[:no_connection])
338
+ halt 500, { error: ERRORS[:no_connection] }.to_json
339
+ end
340
+ end
341
+
342
+ # Deletes IP address from a given subnet
343
+ #
344
+ # Params: 1. address: The network address of the IPv4 or IPv6 subnet
345
+ # 2. prefix: The subnet prefix(e.g. 24)
346
+ # 3. ip: IP address to be deleted
347
+ # 4. group(optional): The name of the External IPAM Group, containing the subnet to delete ip from
348
+ #
349
+ # Returns:
350
+ # Response if deleted successfully:
351
+ # ===========================
352
+ # Http Code: 200
353
+ # Response: Empty
354
+ #
355
+ # Response if not added successfully:
356
+ # ===========================
357
+ # Http Code: 500
358
+ # JSON Response:
359
+ # {"error": "Unable to delete IP from External IPAM"}
360
+ delete '/subnet/:address/:prefix/:ip' do
361
+ content_type :json
362
+
363
+ begin
364
+ validate_required_params!([:address, :ip, :prefix], params)
365
+
366
+ ip = validate_ip!(params[:ip])
367
+ cidr = validate_cidr!(params[:address], params[:prefix])
368
+ group_name = get_request_group(params)
369
+ subnet = provider.get_ipam_subnet(cidr, group_name)
370
+
371
+ halt 404, { error: ERRORS[:no_subnet] }.to_json if subnet.nil?
372
+ del_ip_params = { cidr: cidr, subnet_id: subnet[:id], group_name: group_name }
373
+ validate_ip_in_cidr!(ip, cidr)
374
+
375
+ ip_deleted = provider.delete_ip_from_subnet(ip, del_ip_params) # Returns nil on success
376
+ halt 500, ip_deleted.to_json unless ip_deleted.nil?
377
+ halt 204
378
+ rescue Proxy::Validations::Error => e
379
+ logger.warn(e.message)
380
+ halt 400, { error: e.to_s }.to_json
381
+ rescue RuntimeError => e
382
+ logger.warn(e.message)
383
+ halt 500, { error: e.message }.to_json
384
+ rescue Errno::ECONNREFUSED, Errno::ECONNRESET
385
+ logger.warn(ERRORS[:no_connection])
386
+ halt 500, { error: ERRORS[:no_connection] }.to_json
387
+ end
388
+ end
389
+ end
390
+ end