smart_proxy_ipam 0.0.19 → 0.1.1

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: 557b60d8a66e595502b01b96675bcfd85c1c6e40df80fd824d1aa57e7a11d29f
4
- data.tar.gz: e1488f94cfa129ccbd3da7006c535bc4632b4206511a25cd7c81778c27bf7dcc
3
+ metadata.gz: 0754301cca6caabaf785d9c4a7753d8543289fa9754339c12f706d23610577d2
4
+ data.tar.gz: ad1c8b77662f0ff667ff1c2127bfb9af6221dfe67cd7877968c6425e20fc4074
5
5
  SHA512:
6
- metadata.gz: 4f9b9a41c0367c7aaa4abd902a2db04faf8026a9c61d28b001b5b57ef94169a6b219ea799fb2819a7209a081d304f393098b452e72fbcefb67c98a3aa845e779
7
- data.tar.gz: ca812b143249eb666d02c2af202576024e0b2993332b7563b897950b40b7ef4ba0944b844b7eea9aa83c3c7074c55a3baa392aa59835c5b40c0ed3e7d7a56180
6
+ metadata.gz: 74ba390d81e5be049f0ab15c1586babb91feddbde52c513cf65c35cc140a7471d3f531d578da899b406c09bdebbf8f6039a15e8105bf8ee7bafd67bb5ec3e4a9
7
+ data.tar.gz: a6fe51e572df5e809546fea30864b5bdf153e7eb297bc0a6ae909eda79c181fc028f6b7c2421c1745f62ef20e94bbf55f2cfcd1f5a86270c1ed372db22c3722d
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