smart_proxy_ipam 0.0.22 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0f0b5b073c4b57b12c9ca83fd64a21422ec79a2ad2f9e2207a024e3d7a36ea27
4
- data.tar.gz: dcee04479c5e2862e24b8195c4b22bf614869e865b35ca55516cb8c37e75b946
3
+ metadata.gz: 6b3ac65296507b0ad85c10e613a774692b1547ef25d39ce59bc5f28db6c28734
4
+ data.tar.gz: 2058474b34f87a243cadc4960c75de19041f532d6c6dc7e2d30a9d63cfc4786d
5
5
  SHA512:
6
- metadata.gz: b9b297ff0d4baa62b0d5423ec74b203aa114802f461eb4ab8a6cdb575d80bbc45b9695d5b7f1da9e7e069a2a48cf9bd06c700ca3c40e407f989d79e66ae8c19c
7
- data.tar.gz: 4b4368f41022d1d9c778a9767d558f8a4c782165631dfddc06bb63c24f4b15a78fb0e28c6d189e81b6c5a0d95dfcec4391641bf169c1fecc0b15ff0cfe5b0539
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