netdot-restclient 1.4 → 2.0.0
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/.rubocop-bbatsov-default.yml +720 -0
- data/.rubocop-bbatsov-disabled.yml +50 -0
- data/.rubocop-bbatsov-enabled.yml +1030 -0
- data/.rubocop.yml +720 -0
- data/README.md +2 -0
- data/Rakefile +7 -1
- data/lib/netdot.rb +5 -4
- data/lib/netdot/host.rb +32 -26
- data/lib/netdot/ipblock.rb +118 -0
- data/lib/netdot/restclient.rb +76 -106
- data/lib/netdot/restclient/version.rb +3 -2
- data/netdot-restclient.gemspec +13 -14
- data/sample/sample.rb +43 -0
- data/spec/host_spec.rb +21 -22
- data/spec/ipblock_spec.rb +108 -0
- data/spec/restclient_spec.rb +34 -46
- data/spec/spec_helper.rb +1 -1
- metadata +22 -42
- data/lib/netdot/subnet.rb +0 -107
- data/sample/host.rb +0 -36
- data/spec/subnet_spec.rb +0 -46
data/README.md
CHANGED
data/Rakefile
CHANGED
@@ -1,2 +1,8 @@
|
|
1
|
-
require
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'yard'
|
2
3
|
|
4
|
+
YARD::Rake::YardocTask.new do |_t|
|
5
|
+
# t.files = ['lib/**/*.rb', OTHER_PATHS] # optional
|
6
|
+
# t.options = ['--any', '--extra', '--opts'] # optional
|
7
|
+
# t.stats_options = ['--list-undoc'] # optional
|
8
|
+
end
|
data/lib/netdot.rb
CHANGED
@@ -1,16 +1,17 @@
|
|
1
1
|
require 'logger'
|
2
2
|
|
3
|
+
# Netdot
|
3
4
|
module Netdot
|
4
|
-
|
5
5
|
class << self
|
6
6
|
attr_accessor :logger
|
7
7
|
end
|
8
8
|
|
9
|
+
# NullLogger
|
9
10
|
class NullLogger < Logger
|
10
|
-
def initialize(*
|
11
|
+
def initialize(*_args)
|
11
12
|
end
|
12
13
|
|
13
|
-
def add(*
|
14
|
+
def add(*_args, &_block)
|
14
15
|
end
|
15
16
|
end
|
16
17
|
|
@@ -19,4 +20,4 @@ end
|
|
19
20
|
|
20
21
|
require 'netdot/restclient'
|
21
22
|
require 'netdot/host'
|
22
|
-
require 'netdot/
|
23
|
+
require 'netdot/ipblock'
|
data/lib/netdot/host.rb
CHANGED
@@ -1,61 +1,68 @@
|
|
1
|
+
# Netdot
|
1
2
|
module Netdot
|
3
|
+
# Manage Host objects.
|
2
4
|
class Host
|
3
5
|
attr_accessor :connection
|
4
6
|
|
7
|
+
# Constructor
|
8
|
+
# @param :connection [Hash] a Netdot::RestClient object
|
5
9
|
def initialize(argv = {})
|
6
10
|
[:connection].each do |k|
|
7
|
-
|
11
|
+
fail ArgumentError, "Missing required argument '#{k}'" unless argv[k]
|
8
12
|
end
|
9
13
|
|
10
|
-
argv.each { |k,v| instance_variable_set("@#{k}", v) }
|
14
|
+
argv.each { |k, v| instance_variable_set("@#{k}", v) }
|
11
15
|
end
|
12
16
|
|
13
|
-
|
14
|
-
#
|
15
|
-
#
|
17
|
+
# Finds all RR and Ipblock records, given a flexible set of arguments.
|
18
|
+
# Handles NOT FOUND exceptions.
|
19
|
+
# @param param [String] a generic parameter
|
20
|
+
# @param value [String] a generic value
|
16
21
|
def find(param, value)
|
17
22
|
begin
|
18
|
-
host = @connection.get("/host?#{param
|
19
|
-
rescue
|
23
|
+
host = @connection.get("/host?#{param}=#{value}")
|
24
|
+
rescue => e
|
20
25
|
# Not Found is ok, otherwise re-raise
|
21
|
-
raise unless
|
26
|
+
raise unless e.message =~ /404/
|
22
27
|
end
|
23
28
|
# Return what we got
|
24
29
|
host
|
25
30
|
end
|
26
31
|
|
27
|
-
|
28
|
-
#
|
32
|
+
# Finds all RR and Ipblock records associated with the specified name.
|
33
|
+
# @param name [String]
|
29
34
|
def find_by_name(name)
|
30
35
|
find(:name, name)
|
31
36
|
end
|
32
37
|
|
33
|
-
|
34
|
-
#
|
38
|
+
# Finds all RR and Ipblock records associated with the specified IP.
|
39
|
+
# @param ip [String]
|
35
40
|
def find_by_ip(ip)
|
36
41
|
find(:address, ip)
|
37
42
|
end
|
38
43
|
|
39
|
-
|
40
|
-
#
|
41
|
-
#
|
44
|
+
# Creates a DNS A record for the specified name and IP.
|
45
|
+
# Will also create PTR record if .arpa zone exists.
|
46
|
+
# @param name [String]
|
47
|
+
# @param ip [String]
|
42
48
|
def create(name, ip)
|
43
|
-
Netdot.logger.debug("Creating new DNS records with name:#{name}
|
44
|
-
|
49
|
+
Netdot.logger.debug("Creating new DNS records with name:#{name}" \
|
50
|
+
" and ip:#{ip}")
|
51
|
+
@connection.post('host', 'name' => name, 'address' => ip)
|
45
52
|
end
|
46
53
|
|
47
|
-
|
48
|
-
#
|
49
|
-
#
|
54
|
+
# Updates the DNS A record for the sepcified name and IP.
|
55
|
+
# Will also create PTR record if .arpa zone exists.
|
56
|
+
# @param name [String]
|
57
|
+
# @param ip [String]
|
50
58
|
def update(name, ip)
|
51
59
|
Netdot.logger.debug("Updating DNS records with name:#{name} and ip:#{ip}")
|
52
60
|
delete(name)
|
53
|
-
#delete_by_ip(ip)
|
54
61
|
create(name, ip)
|
55
62
|
end
|
56
63
|
|
57
|
-
|
58
|
-
#
|
64
|
+
# Deletes the DNS A record for the specified name.
|
65
|
+
# @param name [String]
|
59
66
|
def delete(name)
|
60
67
|
host = find_by_name(name)
|
61
68
|
return unless host
|
@@ -65,12 +72,11 @@ module Netdot
|
|
65
72
|
host['Ipblock'].keys.each do |id|
|
66
73
|
begin
|
67
74
|
@connection.delete("host?ipid=#{id}")
|
68
|
-
rescue
|
75
|
+
rescue => e
|
69
76
|
# Not Found is ok, otherwise re-raise
|
70
|
-
raise unless
|
77
|
+
raise unless e.message =~ /404/
|
71
78
|
end
|
72
79
|
end
|
73
80
|
end
|
74
|
-
|
75
81
|
end
|
76
82
|
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
# Netdot
|
2
|
+
module Netdot
|
3
|
+
# Manage Ipblock objects.
|
4
|
+
class Ipblock
|
5
|
+
attr_accessor :connection
|
6
|
+
|
7
|
+
# Constructor
|
8
|
+
# @param :connection [Hash] a Netdot::RestClient object
|
9
|
+
def initialize(argv = {})
|
10
|
+
[:connection].each do |k|
|
11
|
+
fail ArgumentError, "Missing required argument '#{k}'" unless argv[k]
|
12
|
+
end
|
13
|
+
|
14
|
+
argv.each { |k, v| instance_variable_set("@#{k}", v) }
|
15
|
+
end
|
16
|
+
|
17
|
+
# Gets the next available Ipblock in the specified container.
|
18
|
+
# @param container [String] address
|
19
|
+
# @param prefix [Fixnum]
|
20
|
+
# @param description [String] (optional)
|
21
|
+
# @return [String] new Ipblock id, or nil
|
22
|
+
def allocate(container, prefix = 24, description = nil)
|
23
|
+
# Netdot currently only supports /24 prefixes
|
24
|
+
fail ArgumentError,
|
25
|
+
"Prefix size #{prefix} is not currently supported (must be 24)" \
|
26
|
+
unless prefix == 24
|
27
|
+
|
28
|
+
# Search for container and get its ID
|
29
|
+
cont_id = find_by_addr(container)
|
30
|
+
|
31
|
+
# Get container's children blocks
|
32
|
+
begin
|
33
|
+
resp = @connection.get("Ipblock?parent=#{cont_id}")
|
34
|
+
rescue => e
|
35
|
+
# Not Found is ok, otherwise re-raise
|
36
|
+
raise unless e.message =~ /404/
|
37
|
+
end
|
38
|
+
|
39
|
+
# store existing Ipblocks in hash (if any)
|
40
|
+
ipblocks = {}
|
41
|
+
if resp
|
42
|
+
resp.values.each do |b|
|
43
|
+
b.each do |_k, v|
|
44
|
+
address = v['address']
|
45
|
+
ipblocks[address] = 1
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Iterate over all possible Ipblocks
|
51
|
+
# This assumes that Ipblocks are /24
|
52
|
+
spref = container.split('/')[0]
|
53
|
+
spref.gsub!(/(\d+\.\d+)\..*/, '\1')
|
54
|
+
|
55
|
+
(1..255).each do |n|
|
56
|
+
spref.dup
|
57
|
+
saddr = spref + ".#{n}.0"
|
58
|
+
if !ipblocks.empty? && ipblocks.key?(saddr)
|
59
|
+
next # Ipblock exists
|
60
|
+
end
|
61
|
+
|
62
|
+
# Create Ipblock
|
63
|
+
args = {
|
64
|
+
'address' => saddr,
|
65
|
+
'prefix' => prefix.to_s,
|
66
|
+
'status' => 'Subnet'
|
67
|
+
}
|
68
|
+
args['description'] = description unless description.nil?
|
69
|
+
resp = @connection.post('Ipblock', args)
|
70
|
+
return resp['address'] + '/' + resp['prefix']
|
71
|
+
end
|
72
|
+
|
73
|
+
fail "Could not allocate Ipblock in #{container}"
|
74
|
+
end
|
75
|
+
|
76
|
+
# Deletes an Ipblock (and optionally, all its children), for the specified
|
77
|
+
# address or CIDR.
|
78
|
+
# @param ipblock [String] address or CIDR
|
79
|
+
# @return (Truth)
|
80
|
+
def delete(ipblock, recursive = false)
|
81
|
+
if recursive
|
82
|
+
resp = @connection.get("host?subnet=#{ipblock}")
|
83
|
+
unless resp.empty?
|
84
|
+
resp['Ipblock'].keys.each do |id|
|
85
|
+
@connection.delete("Ipblock/#{id}")
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
sid = find_by_addr(ipblock)
|
91
|
+
@connection.delete("Ipblock/#{sid}")
|
92
|
+
end
|
93
|
+
|
94
|
+
# Gets the matching Ipblock id for the specified address (in CIDR format).
|
95
|
+
# @param cidr [String]
|
96
|
+
# @return [Ipblock]
|
97
|
+
def find_by_addr(cidr)
|
98
|
+
(address, prefix) = cidr.split('/')
|
99
|
+
prefix ||= '24'
|
100
|
+
begin
|
101
|
+
@connection.get("Ipblock?address=#{address}&prefix=#{prefix}")[
|
102
|
+
'Ipblock'].keys[0]
|
103
|
+
rescue => e
|
104
|
+
raise unless e.message =~ /404/
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Gets an array of matching Ipblock ids for the specified description
|
109
|
+
# (name).
|
110
|
+
# @param descr [String]
|
111
|
+
# @return [Array<Ipblock>]
|
112
|
+
def find_by_descr(descr)
|
113
|
+
@connection.get("Ipblock?description=#{descr}")['Ipblock']
|
114
|
+
rescue => e
|
115
|
+
raise unless e.message =~ /404/
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
data/lib/netdot/restclient.rb
CHANGED
@@ -2,92 +2,83 @@ require 'netdot/restclient/version'
|
|
2
2
|
|
3
3
|
require 'httpclient'
|
4
4
|
|
5
|
+
# Netdot
|
5
6
|
module Netdot
|
6
|
-
|
7
|
+
# Manage RestClient objects.
|
8
|
+
class RestClient
|
7
9
|
attr_accessor :format, :base_url, :ua, :xs
|
8
10
|
|
9
11
|
# Constructor and login method
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
# ssl_ca_dir - Path to SSL CA cert directory
|
23
|
-
# cookie_file - Cookie filename
|
24
|
-
#
|
25
|
-
# Returns:
|
26
|
-
# Netdot::RestClient object
|
27
|
-
# Example:
|
28
|
-
# Netdot::Restclient.new(args)
|
29
|
-
#
|
12
|
+
# @param :server [String] Netdot server URL
|
13
|
+
# @param :username [String] Netdot Username
|
14
|
+
# @param :password [String] Netdot password
|
15
|
+
# @param :retries [String] (optional) Number of attempts
|
16
|
+
# @param :timeout [String] (optional) Timeout in seconds
|
17
|
+
# @param :format [String] (optional) Content format <xml>
|
18
|
+
# @param :ssl_version [String] (optional) Specify version of SSL;
|
19
|
+
# see HTTPClient
|
20
|
+
# @param :ssl_verify [String] (optional) Verify server cert (default: yes)
|
21
|
+
# @param :ssl_ca_file [String] (optional) Path to SSL CA cert file
|
22
|
+
# @param :ssl_ca_dir [String] (optional) Path to SSL CA cert directory
|
23
|
+
# @param :cookie_file [String] (optional) Cookie filename
|
30
24
|
def initialize(argv = {})
|
31
|
-
|
32
25
|
[:server, :username, :password].each do |k|
|
33
|
-
|
26
|
+
fail ArgumentError, "Missing required argument '#{k}'" unless argv[k]
|
34
27
|
end
|
35
|
-
|
36
|
-
argv.each { |k,v| instance_variable_set("@#{k}", v) }
|
37
|
-
|
38
|
-
@timeout
|
39
|
-
@retries
|
40
|
-
@format
|
28
|
+
|
29
|
+
argv.each { |k, v| instance_variable_set("@#{k}", v) }
|
30
|
+
|
31
|
+
@timeout ||= 10
|
32
|
+
@retries ||= 3
|
33
|
+
@format ||= 'xml'
|
41
34
|
@cookie_file ||= './cookie.dat'
|
42
|
-
defined?(@ssl_verify)
|
35
|
+
defined?(@ssl_verify) || @ssl_verify = true
|
43
36
|
|
44
|
-
if (
|
37
|
+
if (@format == 'xml')
|
45
38
|
begin
|
46
39
|
require 'xmlsimple'
|
47
|
-
rescue LoadError
|
48
|
-
raise LoadError,
|
40
|
+
rescue LoadError
|
41
|
+
raise LoadError,
|
42
|
+
"Cannot load XML library. Try running 'gem install xml-simple'"
|
49
43
|
end
|
50
|
-
xs = XmlSimple.new(
|
44
|
+
xs = XmlSimple.new('ForceArray' => true, 'KeyAttr' => 'id')
|
51
45
|
@xs = xs
|
52
46
|
else
|
53
|
-
|
47
|
+
fail ArgumentError, 'Only XML formatting supported at this time'
|
54
48
|
end
|
55
49
|
|
56
|
-
ua = HTTPClient.new(:
|
50
|
+
ua = HTTPClient.new(agent_name: "Netdot::RestClient/#{version}")
|
57
51
|
ua.set_cookie_store("#{@cookie_file}")
|
58
52
|
|
59
53
|
# SSL stuff
|
60
|
-
if
|
61
|
-
if
|
54
|
+
if @ssl_verify
|
55
|
+
if @ssl_ca_dir
|
62
56
|
# We are told to add a certs path
|
63
57
|
# We'll want to clear the default cert store first
|
64
58
|
ua.ssl_config.clear_cert_store
|
65
59
|
ua.ssl_config.set_trust_ca(@ssl_ca_dir)
|
66
|
-
elsif
|
67
|
-
ua.ssl_config.set_trust_ca(@ssl_ca_file)
|
60
|
+
elsif @ssl_ca_file
|
61
|
+
ua.ssl_config.set_trust_ca(@ssl_ca_file)
|
68
62
|
end
|
69
63
|
else
|
70
64
|
ua.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
71
65
|
end
|
72
66
|
|
73
67
|
# If version given, set it
|
74
|
-
|
75
|
-
ua.ssl_config.ssl_version = @ssl_version
|
76
|
-
end
|
68
|
+
ua.ssl_config.ssl_version = @ssl_version if @ssl_version
|
77
69
|
|
78
70
|
login_url = @server + '/NetdotLogin'
|
79
|
-
|
71
|
+
|
80
72
|
resp = nil
|
81
73
|
|
82
74
|
@retries.times do
|
83
|
-
resp = ua.post login_url,
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
if ( resp.status == 302 )
|
75
|
+
resp = ua.post login_url,
|
76
|
+
'destination' => 'index.html',
|
77
|
+
'credential_0' => @username,
|
78
|
+
'credential_1' => @password,
|
79
|
+
'permanent_session' => 1
|
80
|
+
|
81
|
+
if (resp.status == 302)
|
91
82
|
ua.save_cookie_store
|
92
83
|
@ua = ua
|
93
84
|
@base_url = @server + '/rest'
|
@@ -97,82 +88,61 @@ module Netdot
|
|
97
88
|
end
|
98
89
|
end
|
99
90
|
|
100
|
-
|
101
|
-
|
102
|
-
end
|
103
|
-
|
91
|
+
return if (resp.status == 302)
|
92
|
+
fail "Could not log into #{@server}. Status Code: '#{resp.status}'"
|
104
93
|
end
|
105
|
-
|
106
94
|
|
107
|
-
#
|
108
|
-
#
|
95
|
+
# Builds the Extra headers.
|
109
96
|
def extheader
|
110
|
-
{ 'Accept' => 'text/' +
|
97
|
+
{ 'Accept' => 'text/' + format + '; version=1.0' }
|
111
98
|
end
|
112
99
|
|
113
|
-
#
|
114
|
-
#
|
100
|
+
# Builds a URL for the specified resource.
|
101
|
+
# @param [String] resource a URI
|
115
102
|
def build_url(resource)
|
116
|
-
|
103
|
+
base_url + '/' + resource
|
117
104
|
end
|
118
105
|
|
119
|
-
#
|
120
|
-
#
|
121
|
-
#
|
122
|
-
# resource - A URI
|
123
|
-
# Returns:
|
124
|
-
# hash when successful
|
125
|
-
# exception when not
|
106
|
+
# Gets a resource.
|
107
|
+
# @param [String] resource a URI
|
108
|
+
# @return [Hash] hash when successful; exception when not
|
126
109
|
def get(resource)
|
127
|
-
url =
|
128
|
-
resp =
|
129
|
-
if (
|
130
|
-
|
110
|
+
url = build_url(resource)
|
111
|
+
resp = ua.get(url, nil, extheader)
|
112
|
+
if (resp.status == 200)
|
113
|
+
xs.xml_in(resp.content)
|
131
114
|
else
|
132
|
-
|
115
|
+
fail "Could not get #{url}: #{resp.status}"
|
133
116
|
end
|
134
117
|
end
|
135
118
|
|
136
|
-
|
137
|
-
#
|
138
|
-
#
|
139
|
-
#
|
140
|
-
#
|
141
|
-
# data - Hash with key/values
|
142
|
-
# Returns:
|
143
|
-
# new or modified record hash when successful
|
144
|
-
# exception when not
|
119
|
+
# Updates or creates a resource.
|
120
|
+
# @param [String] resource a URI
|
121
|
+
# @param [Hash] data
|
122
|
+
# @return [Hash] new or modified record hash when successful; exception
|
123
|
+
# when not
|
145
124
|
def post(resource, data)
|
146
|
-
url =
|
147
|
-
|
148
|
-
resp =
|
149
|
-
if (
|
150
|
-
|
125
|
+
url = build_url(resource)
|
126
|
+
fail ArgumentError, 'Data must be hash' unless data.is_a?(Hash)
|
127
|
+
resp = ua.post(url, data, extheader)
|
128
|
+
if (resp.status == 200)
|
129
|
+
xs.xml_in(resp.content)
|
151
130
|
else
|
152
|
-
|
131
|
+
fail "Could not post to #{url}: #{resp.status}"
|
153
132
|
end
|
154
133
|
end
|
155
134
|
|
156
|
-
|
157
|
-
#
|
158
|
-
#
|
159
|
-
# Arguments:
|
160
|
-
# resource - A URI
|
161
|
-
#
|
162
|
-
# Returns:
|
163
|
-
# true when successful
|
164
|
-
# exception when not
|
165
|
-
#
|
135
|
+
# Deletes a resource.
|
136
|
+
# @param [String] resource a URI
|
137
|
+
# @return [Truth] true when successful; exception when not
|
166
138
|
def delete(resource)
|
167
|
-
url =
|
168
|
-
resp =
|
169
|
-
if (
|
139
|
+
url = build_url(resource)
|
140
|
+
resp = ua.delete(url, nil, extheader)
|
141
|
+
if (resp.status == 200)
|
170
142
|
return true
|
171
143
|
else
|
172
|
-
|
144
|
+
fail "Could not delete #{url}: #{resp.status}"
|
173
145
|
end
|
174
|
-
|
175
146
|
end
|
176
|
-
|
177
147
|
end
|
178
148
|
end
|