smart_proxy_ipam 0.0.18 → 0.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: 58e0c15cc7ee1369b7a1c5b7fa7cfcae0c7c54d75c8fc6486f02c8672447e0f8
4
- data.tar.gz: f7937ba79296964dbfb142c3655e8c0c2c6f97745c10c40fcb3b3eec2b2390d2
3
+ metadata.gz: 6b3ac65296507b0ad85c10e613a774692b1547ef25d39ce59bc5f28db6c28734
4
+ data.tar.gz: 2058474b34f87a243cadc4960c75de19041f532d6c6dc7e2d30a9d63cfc4786d
5
5
  SHA512:
6
- metadata.gz: 68a637fbf28997b2fff0d961f1d8c6b5ffb6dcd8b32824bc25753a913429ba168e94261c3c9706ad1f2edeb786926c3626c91912c4b14976e34013f7e1bb419d
7
- data.tar.gz: 291c8293ee1e81b577ac96e25fbcb497c6438c9a1d6c4411f7e91e073dfbf3d77847115cf0621a9c0c279f0d20bd259eed148e81375551c6fa6f5540c4fa6e9e
6
+ metadata.gz: 25200a0e7c4a88d00b79995863277879ea5f8e479f6b4626426e1b14c2237b43d7cf6c50c11e9f50158c7d8b6ca7dcee48794498931076994a72396434bcc327
7
+ data.tar.gz: b320b8cfbb6f7b2b92944dc2629d28bcfa7d3a1c2626661abdab510bb3866dfb59d75b0640f7e489015066a6c960a727b4039633ac22e0f328d45bc6927fe0ff
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,57 @@
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
+ request['Content-Type'] = 'application/json'
25
+
26
+ Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
27
+ http.request(request)
28
+ end
29
+ end
30
+
31
+ def delete(path, body = nil)
32
+ uri = URI(@api_base + path)
33
+ uri.query = URI.encode_www_form(body) if body
34
+ request = Net::HTTP::Delete.new(uri)
35
+ request[@auth_header] = @token
36
+ request['Accept'] = 'application/json'
37
+ request['Content-Type'] = 'application/json'
38
+
39
+ Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
40
+ http.request(request)
41
+ end
42
+ end
43
+
44
+ def post(path, body = nil)
45
+ uri = URI(@api_base + path)
46
+ request = Net::HTTP::Post.new(uri)
47
+ request.body = body
48
+ request[@auth_header] = @token
49
+ request['Accept'] = 'application/json'
50
+ request['Content-Type'] = 'application/json'
51
+
52
+ Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
53
+ http.request(request)
54
+ end
55
+ end
56
+ end
57
+ 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,130 @@
1
+ require 'yaml'
2
+ require 'json'
3
+ require 'monitor'
4
+ require 'concurrent'
5
+ require 'time'
6
+ require 'smart_proxy_ipam/ipam_helper'
7
+
8
+ module Proxy::Ipam
9
+ # Class for managing temp in-memory cache to prevent same IP's being suggested in race conditions
10
+ class IpCache
11
+ include Proxy::Log
12
+ include Proxy::Ipam::IpamHelper
13
+
14
+ DEFAULT_CLEANUP_INTERVAL = 60
15
+ @@ip_cache = nil
16
+ @@timer_task = nil
17
+
18
+ def initialize(params = {})
19
+ @@m = Monitor.new
20
+ @provider = params[:provider].to_s
21
+ init_cache if @@ip_cache.nil?
22
+ start_cleanup_task if @@timer_task.nil?
23
+ end
24
+
25
+ def set_group(group, value)
26
+ @@ip_cache[group.to_sym] = value
27
+ end
28
+
29
+ def get_group(group)
30
+ @@ip_cache[group.to_sym]
31
+ end
32
+
33
+ def get_cidr(group, cidr)
34
+ @@ip_cache[group.to_sym][cidr.to_sym]
35
+ end
36
+
37
+ def get_ip(group_name, cidr, mac)
38
+ @@ip_cache[group_name.to_sym][cidr.to_sym][mac.to_sym][:ip]
39
+ end
40
+
41
+ def get_cleanup_interval
42
+ DEFAULT_CLEANUP_INTERVAL
43
+ end
44
+
45
+ def ip_exists(ip, cidr, group_name)
46
+ cidr_key = @@ip_cache[group_name.to_sym][cidr.to_sym]&.to_s
47
+ cidr_key.include?(ip.to_s)
48
+ end
49
+
50
+ def add(ip, mac, cidr, group_name)
51
+ logger.debug("Adding IP '#{ip}' to cache for subnet '#{cidr}' in group '#{group_name}' for provider #{@provider}")
52
+ @@m.synchronize do
53
+ mac_addr = mac.nil? || mac.empty? ? SecureRandom.uuid : mac
54
+ group_hash = @@ip_cache[group_name.to_sym]
55
+
56
+ group_hash.each do |key, values|
57
+ if values.keys.include? mac_addr.to_sym
58
+ @@ip_cache[group_name.to_sym][key].delete(mac_addr.to_sym)
59
+ end
60
+ @@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?
61
+ end
62
+
63
+ if group_hash.key?(cidr.to_sym)
64
+ @@ip_cache[group_name.to_sym][cidr.to_sym][mac_addr.to_sym] = {ip: ip.to_s, timestamp: Time.now.to_s}
65
+ else
66
+ @@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}}}})
67
+ end
68
+ end
69
+ end
70
+
71
+ private
72
+
73
+ def start_cleanup_task
74
+ logger.info("Starting ip cache maintenance for provider #{@provider}, used by /next_ip.")
75
+ @@timer_task = Concurrent::TimerTask.new(execution_interval: DEFAULT_CLEANUP_INTERVAL) { init_cache }
76
+ @@timer_task.execute
77
+ end
78
+
79
+ # @@ip_cache structure
80
+ #
81
+ # Groups of subnets are cached under the External IPAM Group name. For example,
82
+ # "IPAM Group Name" would be the section name in phpIPAM. All IP's cached for subnets
83
+ # that do not have an External IPAM group specified, they are cached under the "" key. IP's
84
+ # are cached using one of two possible keys:
85
+ # 1). Mac Address
86
+ # 2). UUID (Used when Mac Address not specified)
87
+ #
88
+ # {
89
+ # "": {
90
+ # "100.55.55.0/24":{
91
+ # "00:0a:95:9d:68:10": {"ip": "100.55.55.1", "timestamp": "2019-09-17 12:03:43 -D400"},
92
+ # "906d8bdc-dcc0-4b59-92cb-665935e21662": {"ip": "100.55.55.2", "timestamp": "2019-09-17 11:43:22 -D400"}
93
+ # },
94
+ # },
95
+ # "IPAM Group Name": {
96
+ # "123.11.33.0/24":{
97
+ # "00:0a:95:9d:68:33": {"ip": "123.11.33.1", "timestamp": "2019-09-17 12:04:43 -0400"},
98
+ # "00:0a:95:9d:68:34": {"ip": "123.11.33.2", "timestamp": "2019-09-17 12:05:48 -0400"},
99
+ # "00:0a:95:9d:68:35": {"ip": "123.11.33.3", "timestamp:: "2019-09-17 12:06:50 -0400"}
100
+ # }
101
+ # },
102
+ # "Another IPAM Group": {
103
+ # "185.45.39.0/24":{
104
+ # "00:0a:95:9d:68:55": {"ip": "185.45.39.1", "timestamp": "2019-09-17 12:04:43 -0400"},
105
+ # "00:0a:95:9d:68:56": {"ip": "185.45.39.2", "timestamp": "2019-09-17 12:05:48 -0400"}
106
+ # }
107
+ # }
108
+ # }
109
+ def init_cache
110
+ @@m.synchronize do
111
+ if @@ip_cache && !@@ip_cache.empty?
112
+ logger.debug("Processing ip cache for provider #{@provider}")
113
+ @@ip_cache.each do |group, subnets|
114
+ subnets.each do |cidr, macs|
115
+ macs.each do |mac, ip|
116
+ if Time.now - Time.parse(ip[:timestamp]) > DEFAULT_CLEANUP_INTERVAL
117
+ @@ip_cache[group][cidr].delete(mac)
118
+ end
119
+ end
120
+ @@ip_cache[group].delete(cidr) if @@ip_cache[group][cidr].nil? || @@ip_cache[group][cidr].empty?
121
+ end
122
+ end
123
+ else
124
+ logger.debug("Clearing ip cache for provider #{@provider}")
125
+ @@ip_cache = {'': {}}
126
+ end
127
+ end
128
+ end
129
+ end
130
+ 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,391 @@
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
+ halt 404, { error: errors[:no_groups] }.to_json if groups.nil?
145
+ groups.to_json
146
+ rescue Proxy::Validations::Error => e
147
+ logger.warn(e.message)
148
+ halt 400, { error: e.to_s }.to_json
149
+ rescue RuntimeError => e
150
+ logger.warn(e.message)
151
+ halt 500, { error: e.message }.to_json
152
+ rescue Errno::ECONNREFUSED, Errno::ECONNRESET
153
+ logger.warn(errors[:no_connection])
154
+ halt 500, { error: errors[:no_connection] }.to_json
155
+ end
156
+ end
157
+
158
+ # Get a group from External IPAM
159
+ #
160
+ # Inputs: 1. group: The name of the External IPAM group
161
+ #
162
+ # Returns:
163
+ # Response if success:
164
+ # ===========================
165
+ # Http Code: 200
166
+ # JSON Response:
167
+ # {"data": {"id": "1", "name": "Group 1", "description": "This is group 1"}}
168
+ #
169
+ # Response if group not found:
170
+ # ===========================
171
+ # Http Code: 404
172
+ # JSON Response:
173
+ # {"error": "Group not Found"}
174
+ #
175
+ # Response if groups are not supported:
176
+ # ===========================
177
+ # Http Code: 500
178
+ # JSON Response:
179
+ # {"error": "Groups are not supported"}
180
+ get '/groups/:group' do
181
+ content_type :json
182
+
183
+ begin
184
+ validate_required_params!([:group], params)
185
+
186
+ group_name = get_request_group(params)
187
+ group = provider.get_ipam_group(group_name)
188
+
189
+ halt 404, { error: errors[:no_group] }.to_json if group.nil?
190
+ group.to_json
191
+ rescue Proxy::Validations::Error => e
192
+ logger.warn(e.message)
193
+ halt 400, { error: e.to_s }.to_json
194
+ rescue RuntimeError => e
195
+ logger.warn(e.message)
196
+ halt 500, { error: e.message }.to_json
197
+ rescue Errno::ECONNREFUSED, Errno::ECONNRESET
198
+ logger.warn(errors[:no_connection])
199
+ halt 500, { error: errors[:no_connection] }.to_json
200
+ end
201
+ end
202
+
203
+ # Get a list of subnets for a given External IPAM group
204
+ #
205
+ # Input: 1. group: The name of the External IPAM group
206
+ #
207
+ # Returns:
208
+ # Response if success:
209
+ # ===========================
210
+ # Http Code: 200
211
+ # JSON Response:
212
+ # {"data":[
213
+ # {"subnet":"10.20.30.0","mask":"29","description":"This is a subnet"},
214
+ # {"subnet":"40.50.60.0","mask":"29","description":"This is another subnet"}
215
+ # ]}
216
+ #
217
+ # Response if no subnets exist in group:
218
+ # ===========================
219
+ # Http Code: 404
220
+ # JSON Response:
221
+ # {"error": "No subnets found in External IPAM group"}
222
+ #
223
+ # Response if groups are not supported:
224
+ # ===========================
225
+ # Http Code: 500
226
+ # JSON Response:
227
+ # {"error": "Groups are not supported"}
228
+ get '/groups/:group/subnets' do
229
+ content_type :json
230
+
231
+ begin
232
+ validate_required_params!([:group], params)
233
+
234
+ group_name = get_request_group(params)
235
+ subnets = provider.get_ipam_subnets(group_name)
236
+
237
+ halt 404, { error: errors[:no_subnets_in_group] }.to_json if subnets.nil?
238
+ subnets.to_json
239
+ rescue Proxy::Validations::Error => e
240
+ logger.warn(e.message)
241
+ halt 400, { error: e.to_s }.to_json
242
+ rescue RuntimeError => e
243
+ logger.warn(e.message)
244
+ halt 500, { error: e.message }.to_json
245
+ rescue Errno::ECONNREFUSED, Errno::ECONNRESET
246
+ logger.warn(errors[:no_connection])
247
+ halt 500, { error: errors[:no_connection] }.to_json
248
+ end
249
+ end
250
+
251
+ # Checks whether an IP address has already been taken in External IPAM
252
+ #
253
+ # Inputs: 1. address: The network address of the IPv4 or IPv6 subnet.
254
+ # 2. prefix: The subnet prefix(e.g. 24)
255
+ # 3. ip: IP address to be queried
256
+ # 4. group(optional): The name of the External IPAM Group, containing the subnet to check
257
+ #
258
+ # Returns:
259
+ # Response if exists:
260
+ # ===========================
261
+ # Http Code: 200
262
+ # Response: true
263
+ #
264
+ # Response if not exists:
265
+ # ===========================
266
+ # Http Code: 200
267
+ # JSON Response: false
268
+ get '/subnet/:address/:prefix/:ip' do
269
+ content_type :json
270
+
271
+ begin
272
+ validate_required_params!([:address, :prefix, :ip], params)
273
+
274
+ ip = validate_ip!(params[:ip])
275
+ cidr = validate_cidr!(params[:address], params[:prefix])
276
+ group_name = get_request_group(params)
277
+ subnet = provider.get_ipam_subnet(cidr, group_name)
278
+
279
+ halt 404, { error: errors[:no_subnet] }.to_json if subnet.nil?
280
+ validate_ip_in_cidr!(ip, cidr)
281
+ ip_exists = provider.ip_exists?(ip, subnet[:id], group_name)
282
+ halt 200, ip_exists.to_json
283
+ rescue Proxy::Validations::Error => e
284
+ logger.warn(e.message)
285
+ halt 400, { error: e.to_s }.to_json
286
+ rescue RuntimeError => e
287
+ logger.warn(e.message)
288
+ halt 500, { error: e.message }.to_json
289
+ rescue Errno::ECONNREFUSED, Errno::ECONNRESET
290
+ logger.warn(errors[:no_connection])
291
+ halt 500, { error: errors[:no_connection] }.to_json
292
+ end
293
+ end
294
+
295
+ # Adds an IP address to the specified subnet for the specified IPAM provider
296
+ #
297
+ # Params: 1. address: The network address of the IPv4 or IPv6 subnet
298
+ # 2. prefix: The subnet prefix(e.g. 24)
299
+ # 3. ip: IP address to be added
300
+ # 4. group(optional): The name of the External IPAM Group, containing the subnet to add ip to
301
+ #
302
+ # Returns:
303
+ # Response if added successfully:
304
+ # ===========================
305
+ # Http Code: 201
306
+ # Response: Empty
307
+ #
308
+ # Response if not added successfully:
309
+ # ===========================
310
+ # Http Code: 500
311
+ # JSON Response:
312
+ # {"error": "Unable to add IP to External IPAM"}
313
+ post '/subnet/:address/:prefix/:ip' do
314
+ content_type :json
315
+
316
+ begin
317
+ validate_required_params!([:address, :ip, :prefix], params)
318
+
319
+ ip = validate_ip!(params[:ip])
320
+ cidr = validate_cidr!(params[:address], params[:prefix])
321
+ group_name = get_request_group(params)
322
+ subnet = provider.get_ipam_subnet(cidr, group_name)
323
+
324
+ halt 404, { error: errors[:no_subnet] }.to_json if subnet.nil?
325
+ add_ip_params = { cidr: cidr, subnet_id: subnet[:id], group_name: group_name }
326
+ validate_ip_in_cidr!(ip, cidr)
327
+
328
+ ip_added = provider.add_ip_to_subnet(ip, add_ip_params) # Returns nil on success
329
+ halt 500, ip_added.to_json unless ip_added.nil?
330
+ halt 201
331
+ rescue Proxy::Validations::Error => e
332
+ logger.warn(e.message)
333
+ halt 400, { error: e.to_s }.to_json
334
+ rescue RuntimeError => e
335
+ logger.warn(e.message)
336
+ halt 500, { error: e.message }.to_json
337
+ rescue Errno::ECONNREFUSED, Errno::ECONNRESET
338
+ logger.warn(errors[:no_connection])
339
+ halt 500, { error: errors[:no_connection] }.to_json
340
+ end
341
+ end
342
+
343
+ # Deletes IP address from a given subnet
344
+ #
345
+ # Params: 1. address: The network address of the IPv4 or IPv6 subnet
346
+ # 2. prefix: The subnet prefix(e.g. 24)
347
+ # 3. ip: IP address to be deleted
348
+ # 4. group(optional): The name of the External IPAM Group, containing the subnet to delete ip from
349
+ #
350
+ # Returns:
351
+ # Response if deleted successfully:
352
+ # ===========================
353
+ # Http Code: 200
354
+ # Response: Empty
355
+ #
356
+ # Response if not added successfully:
357
+ # ===========================
358
+ # Http Code: 500
359
+ # JSON Response:
360
+ # {"error": "Unable to delete IP from External IPAM"}
361
+ delete '/subnet/:address/:prefix/:ip' do
362
+ content_type :json
363
+
364
+ begin
365
+ validate_required_params!([:address, :ip, :prefix], params)
366
+
367
+ ip = validate_ip!(params[:ip])
368
+ cidr = validate_cidr!(params[:address], params[:prefix])
369
+ group_name = get_request_group(params)
370
+ subnet = provider.get_ipam_subnet(cidr, group_name)
371
+
372
+ halt 404, { error: errors[:no_subnet] }.to_json if subnet.nil?
373
+ del_ip_params = { cidr: cidr, subnet_id: subnet[:id], group_name: group_name }
374
+ validate_ip_in_cidr!(ip, cidr)
375
+
376
+ ip_deleted = provider.delete_ip_from_subnet(ip, del_ip_params) # Returns nil on success
377
+ halt 500, ip_deleted.to_json unless ip_deleted.nil?
378
+ halt 200
379
+ rescue Proxy::Validations::Error => e
380
+ logger.warn(e.message)
381
+ halt 400, { error: e.to_s }.to_json
382
+ rescue RuntimeError => e
383
+ logger.warn(e.message)
384
+ halt 500, { error: e.message }.to_json
385
+ rescue Errno::ECONNREFUSED, Errno::ECONNRESET
386
+ logger.warn(errors[:no_connection])
387
+ halt 500, { error: errors[:no_connection] }.to_json
388
+ end
389
+ end
390
+ end
391
+ end