smart_proxy_ipam 0.0.22 → 0.1.4

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: e010cf0ea2091a880899f37aef589b272ced50d132c67794b916609194a13865
4
+ data.tar.gz: 4551dc45e2357c13cb080e76794fa8ed8546fa9bcf4da2236b003ab7a52d18ee
5
5
  SHA512:
6
- metadata.gz: b9b297ff0d4baa62b0d5423ec74b203aa114802f461eb4ab8a6cdb575d80bbc45b9695d5b7f1da9e7e069a2a48cf9bd06c700ca3c40e407f989d79e66ae8c19c
7
- data.tar.gz: 4b4368f41022d1d9c778a9767d558f8a4c782165631dfddc06bb63c24f4b15a78fb0e28c6d189e81b6c5a0d95dfcec4391641bf169c1fecc0b15ff0cfe5b0539
6
+ metadata.gz: ff9fd209a2e8f4a6ff345a6e19cdb8ddd9abef20bc6d95f9e9f97f5e6187d67f6bc0197768bff4e131dbe51670d917c845912cd45379f58e11fd9025a11c47bf
7
+ data.tar.gz: 4d5800bfe4c2c7705d652c9f589b7f90feeaf3203f3b540c755acfd1525d134e05b9035ab727fffdd0b1dadaf56f41ca2b9eceaddaca053a136b2a93ef5afeee
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_name(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,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], params)
48
+
49
+ mac_param = params[:mac]
50
+ mac = validate_mac!(params[:mac]) unless mac_param.nil? || mac_param.empty?
51
+ cidr = validate_cidr!(params[:address], params[:prefix])
52
+ group_name = get_request_group(params)
53
+
54
+ next_ip = provider.get_next_ip(mac, cidr, group_name)
55
+ halt 404, { error: ERRORS[:no_free_ips] }.to_json if next_ip.nil?
56
+ next_ip.to_json
57
+ rescue Proxy::Validations::Error => e
58
+ logger.warn(e.message)
59
+ halt 400, { error: e.to_s }.to_json
60
+ rescue RuntimeError => e
61
+ logger.warn(e.message)
62
+ halt 500, { error: e.message }.to_json
63
+ rescue Errno::ECONNREFUSED, Errno::ECONNRESET
64
+ logger.warn(ERRORS[:no_connection])
65
+ halt 500, { error: ERRORS[:no_connection] }.to_json
66
+ end
67
+ end
68
+
69
+ # Gets the subnet from External IPAM
70
+ #
71
+ # Inputs: 1. address: Network address of the subnet
72
+ # 2. prefix: Network prefix(e.g. 24)
73
+ # 3. group(optional): The name of the External IPAM group
74
+ #
75
+ # Returns:
76
+ # Response if subnet exists:
77
+ # ===========================
78
+ # Http Code: 200
79
+ # JSON Response:
80
+ # {"data": {
81
+ # "id": "33",
82
+ # "subnet": "10.20.30.0",
83
+ # "description": "Subnet description",
84
+ # "mask": "29"}
85
+ # }
86
+ #
87
+ # Response if subnet does not exist:
88
+ # ===========================
89
+ # Http Code: 404
90
+ # JSON Response:
91
+ # {"error": "No subnets found"}
92
+ get '/subnet/:address/:prefix' do
93
+ content_type :json
94
+
95
+ begin
96
+ validate_required_params!([:address, :prefix], params)
97
+
98
+ cidr = validate_cidr!(params[:address], params[:prefix])
99
+ group_name = get_request_group(params)
100
+ subnet = provider.get_ipam_subnet(cidr, group_name)
101
+
102
+ halt 404, { error: ERRORS[:no_subnet] }.to_json if subnet.nil?
103
+ subnet.to_json
104
+ rescue Proxy::Validations::Error => e
105
+ logger.warn(e.message)
106
+ halt 400, { error: e.to_s }.to_json
107
+ rescue RuntimeError => e
108
+ logger.warn(e.message)
109
+ halt 500, { error: e.message }.to_json
110
+ rescue Errno::ECONNREFUSED, Errno::ECONNRESET
111
+ logger.warn(ERRORS[:no_connection])
112
+ halt 500, { error: ERRORS[:no_connection] }.to_json
113
+ end
114
+ end
115
+
116
+ # Get a list of groups from External IPAM
117
+ #
118
+ # Returns:
119
+ # Response if success:
120
+ # ===========================
121
+ # Http Code: 200
122
+ # JSON Response:
123
+ # {"data": [
124
+ # {"id": "1", "name": "Group 1", "description": "This is group 1"},
125
+ # {"id": "2", "name": "Group 2", "description": "This is group 2"}
126
+ # ]}
127
+ #
128
+ # Response if no groups exist:
129
+ # ===========================
130
+ # Http Code: 404
131
+ # JSON Response:
132
+ # {"error": "Groups are not supported"}
133
+ #
134
+ # Response if groups are not supported:
135
+ # ===========================
136
+ # Http Code: 500
137
+ # JSON Response:
138
+ # {"error": "Groups are not supported"}
139
+ get '/groups' do
140
+ content_type :json
141
+
142
+ begin
143
+ halt 500, { error: ERRORS[:groups_not_supported] }.to_json unless provider.groups_supported?
144
+ groups = provider.get_ipam_groups
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
+ status 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 204
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