smart_proxy_ipam 0.0.22 → 0.1.4

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: 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