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 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'
@@ -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
@@ -1,6 +1,6 @@
1
1
  module Netdot
2
2
  class RestClient
3
- VERSION = "1.3"
3
+ VERSION = "1.4"
4
4
 
5
5
  def version
6
6
  VERSION
@@ -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
@@ -1,37 +1,64 @@
1
- require 'netdot/restclient'
1
+ require 'spec_helper'
2
2
 
3
3
  describe Netdot::RestClient do
4
+
4
5
  before :all do
5
- args = {
6
- username: 'admin',
7
- server: 'http://localhost/netdot'
8
- }
9
- expect {
10
- Netdot::RestClient.new(args)
11
- }.to raise_error(ArgumentError)
12
-
13
- args[:password] = 'this-is-not-the-password'
14
- expect {
15
- Netdot::RestClient.new(args)
16
- }.to raise_error
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
- args[:password] = 'admin'
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
- args[:server] = 'https://localhost/netdot'
23
- expect {
24
- Netdot::RestClient.new(args)
25
- }.to raise_error
26
-
27
- args[:ssl_verify] = false
28
- @netdot = Netdot::RestClient.new(args)
29
- expect(@netdot).to be_an_instance_of(Netdot::RestClient)
30
-
31
- args[:server] = 'http://localhost/netdot'
32
- @netdot = Netdot::RestClient.new(args)
33
- expect(@netdot).to be_an_instance_of(Netdot::RestClient)
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.should be_an_instance_of(Hash)
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
- context 'when posting' do
89
+ context 'when posting' do
64
90
 
65
- it 'fails to update invalid record' do
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
- end
95
+ end
70
96
 
71
- it 'creates new record' do
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
- end
106
+ end
81
107
 
82
- it 'fails to create duplicate record' do
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
- end
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 be_true
132
+ expect(resp).to be_truthy
107
133
  end
108
134
 
109
135
  end
@@ -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
@@ -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.3'
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-02 00:00:00.000000000 Z
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