netdot-restclient 1.3 → 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.
- data/lib/netdot.rb +22 -0
- data/lib/netdot/host.rb +76 -0
- data/lib/netdot/restclient/version.rb +1 -1
- data/lib/netdot/subnet.rb +107 -0
- data/spec/host_spec.rb +60 -0
- data/spec/restclient_spec.rb +63 -37
- data/spec/spec_helper.rb +15 -0
- data/spec/subnet_spec.rb +46 -0
- metadata +11 -2
data/lib/netdot.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module Netdot
|
4
|
+
|
5
|
+
class << self
|
6
|
+
attr_accessor :logger
|
7
|
+
end
|
8
|
+
|
9
|
+
class NullLogger < Logger
|
10
|
+
def initialize(*args)
|
11
|
+
end
|
12
|
+
|
13
|
+
def add(*args, &block)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
Netdot.logger = NullLogger.new
|
18
|
+
end
|
19
|
+
|
20
|
+
require 'netdot/restclient'
|
21
|
+
require 'netdot/host'
|
22
|
+
require 'netdot/subnet'
|
data/lib/netdot/host.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
module Netdot
|
2
|
+
class Host
|
3
|
+
attr_accessor :connection
|
4
|
+
|
5
|
+
def initialize(argv = {})
|
6
|
+
[:connection].each do |k|
|
7
|
+
raise ArgumentError, "Missing required argument '#{k}'" unless argv[k]
|
8
|
+
end
|
9
|
+
|
10
|
+
argv.each { |k,v| instance_variable_set("@#{k}", v) }
|
11
|
+
end
|
12
|
+
|
13
|
+
##############################################################
|
14
|
+
# Find RR and Ipblock records with flexible arguments
|
15
|
+
# Handle exceptions
|
16
|
+
def find(param, value)
|
17
|
+
begin
|
18
|
+
host = @connection.get("/host?#{param.to_s}=#{value}");
|
19
|
+
rescue Exception => e
|
20
|
+
# Not Found is ok, otherwise re-raise
|
21
|
+
raise unless (e.message =~ /404/)
|
22
|
+
end
|
23
|
+
# Return what we got
|
24
|
+
host
|
25
|
+
end
|
26
|
+
|
27
|
+
##############################################################
|
28
|
+
# Find RR and Ipblock records associated with given name
|
29
|
+
def find_by_name(name)
|
30
|
+
find(:name, name)
|
31
|
+
end
|
32
|
+
|
33
|
+
##############################################################
|
34
|
+
# Find RR and Ipblock records associated with this IP
|
35
|
+
def find_by_ip(ip)
|
36
|
+
find(:address, ip)
|
37
|
+
end
|
38
|
+
|
39
|
+
##############################################################
|
40
|
+
# Create A record for given name and IP
|
41
|
+
# Will also create PTR record if .arpa zone exists
|
42
|
+
def create(name, ip)
|
43
|
+
Netdot.logger.debug("Creating new DNS records with name:#{name} and ip:#{ip}")
|
44
|
+
@connection.post('host', {'name' => name, 'address' => ip})
|
45
|
+
end
|
46
|
+
|
47
|
+
##############################################################
|
48
|
+
# Update A record for given name and IP
|
49
|
+
# Will also create PTR record if .arpa zone exists
|
50
|
+
def update(name, ip)
|
51
|
+
Netdot.logger.debug("Updating DNS records with name:#{name} and ip:#{ip}")
|
52
|
+
delete(name)
|
53
|
+
#delete_by_ip(ip)
|
54
|
+
create(name, ip)
|
55
|
+
end
|
56
|
+
|
57
|
+
##############################################################
|
58
|
+
# Delete A record for given name
|
59
|
+
def delete(name)
|
60
|
+
host = find_by_name(name)
|
61
|
+
return unless host
|
62
|
+
|
63
|
+
# remove any associated IP addresses
|
64
|
+
Netdot.logger.debug("Removing IP records for #{name}")
|
65
|
+
host['Ipblock'].keys.each do |id|
|
66
|
+
begin
|
67
|
+
@connection.delete("host?ipid=#{id}")
|
68
|
+
rescue Exception => e
|
69
|
+
# Not Found is ok, otherwise re-raise
|
70
|
+
raise unless (e.message =~ /404/)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module Netdot
|
2
|
+
class Subnet
|
3
|
+
attr_accessor :connection
|
4
|
+
|
5
|
+
def initialize(argv = {})
|
6
|
+
[:connection].each do |k|
|
7
|
+
raise ArgumentError, "Missing required argument '#{k}'" unless argv[k]
|
8
|
+
end
|
9
|
+
|
10
|
+
argv.each { |k,v| instance_variable_set("@#{k}", v) }
|
11
|
+
end
|
12
|
+
|
13
|
+
# Get next available subnet in given container
|
14
|
+
#
|
15
|
+
# Arguments:
|
16
|
+
# IP container block (string)
|
17
|
+
# CIDR subnet size (optional integer)
|
18
|
+
# description (optional string)
|
19
|
+
# Returns:
|
20
|
+
# New subnet ID when successful
|
21
|
+
#
|
22
|
+
def allocate(container, prefix=24, description=nil)
|
23
|
+
|
24
|
+
# Netdot currently only supports /24 prefixes
|
25
|
+
raise ArgumentError, "Prefix size #{prefix} is not currently supported (must be 24)" unless prefix==24
|
26
|
+
|
27
|
+
# Search for container and get its ID
|
28
|
+
cont_id = get_ipblock_id(container)
|
29
|
+
|
30
|
+
# Get container's children blocks
|
31
|
+
begin
|
32
|
+
resp = @connection.get("Ipblock?parent=#{cont_id}")
|
33
|
+
rescue Exception => e
|
34
|
+
# Not Found is ok, otherwise re-raise
|
35
|
+
raise unless (e.message =~ /404/)
|
36
|
+
end
|
37
|
+
|
38
|
+
# store existing subnets in hash (if any)
|
39
|
+
subnets = Hash.new
|
40
|
+
if ( resp )
|
41
|
+
resp.values.each do |b|
|
42
|
+
b.each do |k,v|
|
43
|
+
address = v['address']
|
44
|
+
subnets[address] = 1
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Iterate over all possible subnets
|
50
|
+
# This assumes that subnets are /24
|
51
|
+
spref = container.split('/')[0]
|
52
|
+
spref.gsub!(/(\d+\.\d+)\..*/, '\1')
|
53
|
+
|
54
|
+
(1..255).each do |n|
|
55
|
+
saddr = spref.dup
|
56
|
+
saddr = spref + ".#{n}.0"
|
57
|
+
if !subnets.empty? && subnets.key?(saddr)
|
58
|
+
next # subnet exists
|
59
|
+
end
|
60
|
+
|
61
|
+
# Create subnet
|
62
|
+
args = { 'address' => saddr, 'prefix' => prefix.to_s, 'status' => 'Subnet' }
|
63
|
+
args['description'] = description unless description.nil?
|
64
|
+
resp = @connection.post("Ipblock", args)
|
65
|
+
return resp['address'] + '/' + resp['prefix']
|
66
|
+
end
|
67
|
+
|
68
|
+
raise "Could not allocate subnet in #{container}"
|
69
|
+
end
|
70
|
+
|
71
|
+
######################################################################
|
72
|
+
# Delete subnet and all its records
|
73
|
+
#
|
74
|
+
# Arguments:
|
75
|
+
# subnet address (CIDR)
|
76
|
+
# Returns:
|
77
|
+
# True if successful
|
78
|
+
#
|
79
|
+
def delete(subnet)
|
80
|
+
resp = @connection.get("host?subnet=#{subnet}")
|
81
|
+
|
82
|
+
if ! resp.empty?
|
83
|
+
resp['Ipblock'].keys.each do |id|
|
84
|
+
@connection.delete("Ipblock/#{id}")
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
sid = get_ipblock_id(subnet)
|
89
|
+
@connection.delete("Ipblock/#{sid}")
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
######################################################################
|
94
|
+
# Get Ipblock ID given its address in CIDR format
|
95
|
+
#
|
96
|
+
# Arguments:
|
97
|
+
# subnet address (CIDR)
|
98
|
+
# Returns:
|
99
|
+
# ID (number)
|
100
|
+
#
|
101
|
+
def get_ipblock_id(cidr)
|
102
|
+
(address, prefix) = cidr.split('/')
|
103
|
+
resp = @connection.get("Ipblock?address=#{address}&prefix=#{prefix}")
|
104
|
+
resp['Ipblock'].keys[0]
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
data/spec/host_spec.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Netdot::Host do
|
4
|
+
|
5
|
+
before :all do
|
6
|
+
@netdot = connect
|
7
|
+
@host = Netdot::Host.new(:connection => @netdot) if @host.nil?
|
8
|
+
subnet = Netdot::Subnet.new(:connection => @netdot)
|
9
|
+
subnet_id = subnet.allocate('10.0.0.0/8', 24, 'test')
|
10
|
+
@cidr = NetAddr::CIDR.create(subnet_id)
|
11
|
+
end
|
12
|
+
|
13
|
+
context 'when creating a new Host instance' do
|
14
|
+
it 'creates a new instance' do
|
15
|
+
expect {
|
16
|
+
host = Netdot::Host.new(:connection => @netdot)
|
17
|
+
}.not_to raise_error
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'raises an exception for invalid arguments' do
|
21
|
+
expect {
|
22
|
+
host = Netdot::Host.new
|
23
|
+
}.to raise_error(ArgumentError)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'creating a new host' do
|
28
|
+
it 'creates a new host allocation' do
|
29
|
+
h = @host.create('test0-01-yyy', @cidr.nth(1))
|
30
|
+
expect(h.key? 'name').to be_truthy
|
31
|
+
expect(h['name']).to match('test0-01-yyy')
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'updates an existing allocation' do
|
35
|
+
h = @host.update('test0-01-zzz', @cidr.nth(1))
|
36
|
+
expect(h.key? 'name').to be_truthy
|
37
|
+
expect(h['name']).to match('test0-01-zzz')
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context 'finding existing hosts' do
|
42
|
+
it 'finds a host by name' do
|
43
|
+
h = @host.find_by_name('test0-01-zzz')
|
44
|
+
expect(h).to be_instance_of(Hash)
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'finds a host by ip' do
|
48
|
+
h = @host.find_by_ip(@cidr.nth(1))
|
49
|
+
expect(h).to be_instance_of(Hash)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context 'deleting a host' do
|
54
|
+
it 'deletes a host' do
|
55
|
+
expect {
|
56
|
+
@host.delete('test-01-zzz')
|
57
|
+
}.not_to raise_error
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/spec/restclient_spec.rb
CHANGED
@@ -1,37 +1,64 @@
|
|
1
|
-
require '
|
1
|
+
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Netdot::RestClient do
|
4
|
+
|
4
5
|
before :all do
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
6
|
+
@netdot = connect
|
7
|
+
end
|
8
|
+
|
9
|
+
context 'when connecting' do
|
10
|
+
|
11
|
+
it 'raises an exception for incomplete arguments' do
|
12
|
+
args = {
|
13
|
+
username: 'admin',
|
14
|
+
server: 'http://localhost/netdot'
|
15
|
+
}
|
16
|
+
expect {
|
17
|
+
Netdot::RestClient.new(args)
|
18
|
+
}.to raise_error(ArgumentError)
|
19
|
+
end
|
17
20
|
|
18
|
-
|
21
|
+
it 'raises an exception for an invalid password' do
|
22
|
+
args = {
|
23
|
+
server: ENV['SERVER'] || 'http://localhost/netdot',
|
24
|
+
username: ENV['USERNAME'] || 'admin',
|
25
|
+
password: 'this-is-not-the-password'
|
26
|
+
}
|
27
|
+
expect {
|
28
|
+
Netdot::RestClient.new(args)
|
29
|
+
}.to raise_error
|
30
|
+
end
|
19
31
|
|
20
32
|
# The following two assume that local server has SSL
|
21
33
|
# enabled with a self-signed cert
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
34
|
+
it 'raises an exception if SSL verification fails' do
|
35
|
+
args = {
|
36
|
+
server: ENV['SERVER'] || 'https://localhost/netdot',
|
37
|
+
username: ENV['USERNAME'] || 'admin',
|
38
|
+
password: ENV['PASSWORD'] || 'admin'
|
39
|
+
}
|
40
|
+
expect {
|
41
|
+
Netdot::RestClient.new(args)
|
42
|
+
}.to raise_error
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'does not raise an exception if SSL verification is disabled' do
|
46
|
+
args = {
|
47
|
+
server: ENV['SERVER'] || 'https://localhost/netdot',
|
48
|
+
username: ENV['USERNAME'] || 'admin',
|
49
|
+
password: ENV['PASSWORD'] || 'admin',
|
50
|
+
ssl_verify: false
|
51
|
+
}
|
52
|
+
|
53
|
+
netdot = Netdot::RestClient.new(args)
|
54
|
+
expect(netdot).to be_an_instance_of(Netdot::RestClient)
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'connects to the API' do
|
58
|
+
n = connect
|
59
|
+
expect(n).to be_an_instance_of(Netdot::RestClient)
|
60
|
+
end
|
61
|
+
|
35
62
|
end
|
36
63
|
|
37
64
|
context 'when getting' do
|
@@ -44,7 +71,7 @@ describe Netdot::RestClient do
|
|
44
71
|
|
45
72
|
it 'valid resource as hash' do
|
46
73
|
resp = @netdot.get('Entity/1')
|
47
|
-
resp.
|
74
|
+
expect(resp).to be_an_instance_of(Hash)
|
48
75
|
end
|
49
76
|
|
50
77
|
it 'record by id' do
|
@@ -57,18 +84,17 @@ describe Netdot::RestClient do
|
|
57
84
|
expect(resp['Entity']['1']['name']).to eq('Unknown')
|
58
85
|
end
|
59
86
|
|
60
|
-
|
61
87
|
end
|
62
88
|
|
63
|
-
|
89
|
+
context 'when posting' do
|
64
90
|
|
65
|
-
|
91
|
+
it 'fails to update invalid record' do
|
66
92
|
expect {
|
67
93
|
resp = @netdot.post('Foobar/1', {'key' => 'value'} )
|
68
94
|
}.to raise_error
|
69
|
-
|
95
|
+
end
|
70
96
|
|
71
|
-
|
97
|
+
it 'creates new record' do
|
72
98
|
resp = @netdot.post('Person',
|
73
99
|
{ 'firstname' => 'Joe',
|
74
100
|
'lastname' => 'Plumber',
|
@@ -77,9 +103,9 @@ describe Netdot::RestClient do
|
|
77
103
|
expect(resp['firstname']).to eq ('Joe')
|
78
104
|
expect(resp['lastname']).to eq ('Plumber')
|
79
105
|
|
80
|
-
|
106
|
+
end
|
81
107
|
|
82
|
-
|
108
|
+
it 'fails to create duplicate record' do
|
83
109
|
expect {
|
84
110
|
resp = @netdot.post('Person',
|
85
111
|
{ 'firstname' => 'Joe',
|
@@ -87,7 +113,7 @@ describe Netdot::RestClient do
|
|
87
113
|
'username' => 'joetubes'
|
88
114
|
})
|
89
115
|
}.to raise_error
|
90
|
-
|
116
|
+
end
|
91
117
|
|
92
118
|
end
|
93
119
|
|
@@ -103,7 +129,7 @@ describe Netdot::RestClient do
|
|
103
129
|
resp = @netdot.get('Person?lastname=Plumber')
|
104
130
|
person_id = resp['Person'].keys[0]
|
105
131
|
resp = @netdot.delete("Person/#{person_id}")
|
106
|
-
expect(resp).to
|
132
|
+
expect(resp).to be_truthy
|
107
133
|
end
|
108
134
|
|
109
135
|
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'netdot'
|
2
|
+
require 'netaddr'
|
3
|
+
|
4
|
+
@netdot = nil
|
5
|
+
|
6
|
+
def connect
|
7
|
+
args = {
|
8
|
+
server: ENV['SERVER'] || 'http://localhost/netdot',
|
9
|
+
username: ENV['USERNAME'] || 'admin',
|
10
|
+
password: ENV['PASSWORD'] || 'admin',
|
11
|
+
ssl_verify: false
|
12
|
+
}
|
13
|
+
@netdot = Netdot::RestClient.new(args) if @netdot.nil?
|
14
|
+
return @netdot
|
15
|
+
end
|
data/spec/subnet_spec.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Netdot::Subnet do
|
4
|
+
|
5
|
+
before :all do
|
6
|
+
@netdot = connect
|
7
|
+
@subnet = Netdot::Subnet.new(:connection => @netdot) if @subnet.nil?
|
8
|
+
end
|
9
|
+
|
10
|
+
context 'when creating a new Subnet instance' do
|
11
|
+
it 'creates a new instance' do
|
12
|
+
expect {
|
13
|
+
subnet = Netdot::Subnet.new(:connection => @netdot)
|
14
|
+
}.not_to raise_error
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'raises an exception for invalid arguments' do
|
18
|
+
expect {
|
19
|
+
subnet = Netdot::Subnet.new
|
20
|
+
}.to raise_error(ArgumentError)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'creating a new subnet' do
|
25
|
+
it 'allocates a new subnet' do
|
26
|
+
subnet_id = @subnet.allocate('10.0.0.0/8', 24, 'test')
|
27
|
+
expect(subnet_id).to match('10.0.*.0/24')
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'throws an exception if the prefix is not /24' do
|
31
|
+
expect {
|
32
|
+
subnet_id = @subnet.allocate('10.0.0.0/8', 26)
|
33
|
+
}.to raise_error(ArgumentError)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'deleting a subnet' do
|
38
|
+
let(:subnet_id) do
|
39
|
+
@subnet.allocate('10.0.0.0/8', 24)
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'deletes a subnet' do
|
43
|
+
expect(@subnet.delete(subnet_id)).to be_truthy
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: netdot-restclient
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '1.
|
4
|
+
version: '1.4'
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2014-09-
|
12
|
+
date: 2014-09-30 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -103,11 +103,17 @@ files:
|
|
103
103
|
- LICENSE.txt
|
104
104
|
- README.md
|
105
105
|
- Rakefile
|
106
|
+
- lib/netdot.rb
|
107
|
+
- lib/netdot/host.rb
|
106
108
|
- lib/netdot/restclient.rb
|
107
109
|
- lib/netdot/restclient/version.rb
|
110
|
+
- lib/netdot/subnet.rb
|
108
111
|
- netdot-restclient.gemspec
|
109
112
|
- sample/host.rb
|
113
|
+
- spec/host_spec.rb
|
110
114
|
- spec/restclient_spec.rb
|
115
|
+
- spec/spec_helper.rb
|
116
|
+
- spec/subnet_spec.rb
|
111
117
|
homepage: ''
|
112
118
|
licenses:
|
113
119
|
- Apache-2.0
|
@@ -134,4 +140,7 @@ signing_key:
|
|
134
140
|
specification_version: 3
|
135
141
|
summary: RESTful API client for Netdot
|
136
142
|
test_files:
|
143
|
+
- spec/host_spec.rb
|
137
144
|
- spec/restclient_spec.rb
|
145
|
+
- spec/spec_helper.rb
|
146
|
+
- spec/subnet_spec.rb
|