netdot-restclient 1.4 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|