dynect4r 0.2.1
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/LICENSE +15 -0
- data/README.rdoc +56 -0
- data/bin/dynect4r-client +173 -0
- data/lib/dynect4r.rb +221 -0
- metadata +100 -0
data/LICENSE
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
Copyright (c) 2010 Michael Conigliaro <mike [at] conigliaro [dot] org>
|
2
|
+
|
3
|
+
This program is free software; you can redistribute it and/or modify
|
4
|
+
it under the terms of the GNU General Public License as published by
|
5
|
+
the Free Software Foundation; either version 2 of the License, or
|
6
|
+
(at your option) any later version.
|
7
|
+
|
8
|
+
This program is distributed in the hope that it will be useful,
|
9
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
10
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
11
|
+
GNU General Public License for more details.
|
12
|
+
|
13
|
+
You should have received a copy of the GNU General Public License
|
14
|
+
along with this program; if not, write to the Free Software
|
15
|
+
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
data/README.rdoc
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
= dynect4r
|
2
|
+
|
3
|
+
dynect4r is a Ruby library and command line client for the Dynect REST API (version 2).
|
4
|
+
|
5
|
+
== Installation
|
6
|
+
|
7
|
+
gem install dynect4r
|
8
|
+
|
9
|
+
== Using this library in your own project
|
10
|
+
|
11
|
+
require 'dynect4r'
|
12
|
+
client = Dynect::Client.new(:customer_name => 'example',
|
13
|
+
:user_name => 'example',
|
14
|
+
:password => 'example')
|
15
|
+
response = client.rest_call(:get, 'Zone/example.org')
|
16
|
+
pp response
|
17
|
+
|
18
|
+
== Using the built-in command line client
|
19
|
+
|
20
|
+
1. Create a file called <b>dynect4r.secret</b> containing your Dynect customer name, username and password (i.e. on one line separated by whitespace). Note that this file is assumed to be in the current directory by default.
|
21
|
+
2. See examples below:
|
22
|
+
|
23
|
+
=== General usage
|
24
|
+
|
25
|
+
dynect4r-client [options] [rdata][, ...]
|
26
|
+
|
27
|
+
- Multiple sets of rdata can be specified by separating them with commas.
|
28
|
+
- Records can be deleted by not specifying rdata.
|
29
|
+
|
30
|
+
=== Examples
|
31
|
+
|
32
|
+
==== Create an A record
|
33
|
+
|
34
|
+
dynect4r-client -n test.example.org 1.1.1.1
|
35
|
+
|
36
|
+
==== Create round-robin A records
|
37
|
+
|
38
|
+
dynect4r-client -n test.example.org 1.1.1.1,2.2.2.2,3.3.3.3
|
39
|
+
|
40
|
+
==== Create a CNAME record
|
41
|
+
|
42
|
+
dynect4r-client -n test.example.org -t CNAME test.example.org.
|
43
|
+
|
44
|
+
==== Create an SRV record
|
45
|
+
|
46
|
+
dynect4r-client -n srv.example.org -t SRV 0 10 20 target.example.org.
|
47
|
+
|
48
|
+
=== Help and Troubleshooting
|
49
|
+
|
50
|
+
See the <b>--help</b> command line option:
|
51
|
+
|
52
|
+
dynect4r-client --help
|
53
|
+
|
54
|
+
Use debug logging:
|
55
|
+
|
56
|
+
dynect4r-client -v debug
|
data/bin/dynect4r-client
ADDED
@@ -0,0 +1,173 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'dynect4r'
|
4
|
+
require 'optparse'
|
5
|
+
require 'pp'
|
6
|
+
require 'set'
|
7
|
+
|
8
|
+
# set default command line options
|
9
|
+
options = {
|
10
|
+
:cred_file => './dynect4r.secret',
|
11
|
+
:customer => nil,
|
12
|
+
:username => nil,
|
13
|
+
:password => nil,
|
14
|
+
:zone => nil,
|
15
|
+
:node => Socket.gethostbyname(Socket.gethostname).first,
|
16
|
+
:ttl => 86400,
|
17
|
+
:type => 'A',
|
18
|
+
:rdata => nil,
|
19
|
+
:log_level => 'info',
|
20
|
+
:dry_run => false,
|
21
|
+
:cancel_on_error => false
|
22
|
+
}
|
23
|
+
|
24
|
+
# parse command line options
|
25
|
+
OptionParser.new do |opts|
|
26
|
+
opts.banner = "Usage: #{$0} [options] [rdata][, ...]\n" \
|
27
|
+
+ "Example: #{$0} -n srv.example.org -t SRV 0 10 20 target.example.org"
|
28
|
+
|
29
|
+
opts.on('-c', '--credentials-file VALUE', 'Path to file containing API customer/username/password (default: %s)' % options[:cred_file]) do |opt|
|
30
|
+
options[:cred_file] = opt
|
31
|
+
end
|
32
|
+
|
33
|
+
opts.on('-z', '--zone VALUE', 'DNS Zone (default: Auto-detect)') do |opt|
|
34
|
+
options[:zone] = opt
|
35
|
+
end
|
36
|
+
|
37
|
+
opts.on('-n', '--node VALUE', 'Node name (default: %s)' % options[:node]) do |opt|
|
38
|
+
options[:node] = opt
|
39
|
+
end
|
40
|
+
|
41
|
+
opts.on('-s', '--ttl VALUE', 'Time to Live (default: %s)' % options[:ttl]) do |opt|
|
42
|
+
options[:ttl] = opt
|
43
|
+
end
|
44
|
+
|
45
|
+
opts.on('-t', '--type VALUE', 'Record type (default: %s)' % options[:type]) do |opt|
|
46
|
+
options[:type] = opt.upcase
|
47
|
+
end
|
48
|
+
|
49
|
+
opts.on('-v', '--verbosity VALUE', 'Log verbosity (default: %s)' % options[:log_level]) do |opt|
|
50
|
+
options[:log_level] = opt
|
51
|
+
end
|
52
|
+
|
53
|
+
opts.on('--dry-run', "Perform a trial run without making changes (default: %s)" % options[:dry_run]) do |opt|
|
54
|
+
options[:dry_run] = opt
|
55
|
+
end
|
56
|
+
|
57
|
+
opts.on('--cancel-on-error', "All changes will be canceled if any error occurs (default: %s)" % options[:cancel_on_error]) do |opt|
|
58
|
+
options[:cancel_on_error] = opt
|
59
|
+
end
|
60
|
+
|
61
|
+
end.parse!
|
62
|
+
options[:rdata] = ARGV.join(' ').split(',').collect { |obj| obj.strip() }
|
63
|
+
|
64
|
+
# instantiate logger
|
65
|
+
log = Dynect::Logger.new(STDOUT)
|
66
|
+
log.level = eval('Dynect::Logger::' + options[:log_level].upcase)
|
67
|
+
RestClient.log = log
|
68
|
+
|
69
|
+
# validate command line options
|
70
|
+
begin
|
71
|
+
(options[:customer_name], options[:user_name], options[:password]) = File.open(options[:cred_file]).readline().strip().split()
|
72
|
+
rescue Errno::ENOENT
|
73
|
+
log.error('Credentials file does not exist: %s' % options[:cred_file])
|
74
|
+
Process.exit(1)
|
75
|
+
end
|
76
|
+
if !options[:zone]
|
77
|
+
options[:zone] = options[:node][(options[:node].index('.') + 1)..-1]
|
78
|
+
end
|
79
|
+
|
80
|
+
# track number of changes and errors
|
81
|
+
changes = 0
|
82
|
+
errors = 0
|
83
|
+
|
84
|
+
# instantiate dynect client and log in
|
85
|
+
log.info('Starting session')
|
86
|
+
begin
|
87
|
+
c = Dynect::Client.new(:customer_name => options[:customer_name],
|
88
|
+
:user_name => options[:user_name],
|
89
|
+
:password => options[:password])
|
90
|
+
rescue Dynect::DynectError
|
91
|
+
log.error($!.message)
|
92
|
+
Process.exit(1)
|
93
|
+
end
|
94
|
+
|
95
|
+
# create set of existing records
|
96
|
+
begin
|
97
|
+
existing_rdata = {}
|
98
|
+
response = c.rest_call(:get, [Dynect::rtype_to_resource(options[:type]), options[:zone], options[:node]])
|
99
|
+
response[:data].each do |url|
|
100
|
+
rdata = c.rest_call(:get, url)[:data][:rdata].inject({}) { |memo,(k,v)| memo[k.to_s] = v.to_s; memo }
|
101
|
+
existing_rdata[rdata] = url
|
102
|
+
log.info('Found record (Zone="%s", Node="%s" TTL="%s", Type="%s", RData="%s")' %
|
103
|
+
[options[:zone], options[:node], options[:ttl], options[:type], rdata.to_json])
|
104
|
+
end
|
105
|
+
rescue Dynect::DynectError
|
106
|
+
log.error('Query for records failed - %s' % $!.message)
|
107
|
+
Process.exit(1)
|
108
|
+
end
|
109
|
+
|
110
|
+
# create set of new records
|
111
|
+
new_rdata = Set.new
|
112
|
+
options[:rdata].each do |rdata|
|
113
|
+
new_rdata << Dynect::args_for_rtype(options[:type], rdata)
|
114
|
+
end
|
115
|
+
|
116
|
+
# delete records
|
117
|
+
(existing_rdata.keys.to_set - new_rdata).each do |rdata|
|
118
|
+
log.warn('%sDeleting record (Zone="%s", Node="%s" TTL="%s", Type="%s", RData="%s")' %
|
119
|
+
[options[:dry_run] ? '(NOT) ' : '', options[:zone], options[:node], options[:ttl], options[:type], rdata.to_json])
|
120
|
+
begin
|
121
|
+
if not options[:dry_run]
|
122
|
+
c.rest_call(:delete, existing_rdata[rdata])
|
123
|
+
end
|
124
|
+
changes += 1
|
125
|
+
rescue Dynect::DynectError
|
126
|
+
errors += 1
|
127
|
+
log.error('Failed to delete record - %s' % $!.message)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# add new records
|
132
|
+
(new_rdata - existing_rdata.keys.to_set).each do |rdata|
|
133
|
+
log.warn('%sCreating record (Zone="%s", Node="%s" TTL="%s", Type="%s", RData="%s")' %
|
134
|
+
[options[:dry_run] ? '(NOT) ' : '', options[:zone], options[:node], options[:ttl], options[:type], rdata.to_json])
|
135
|
+
begin
|
136
|
+
if not options[:dry_run]
|
137
|
+
response = c.rest_call(:post, [Dynect::rtype_to_resource(options[:type]), options[:zone], options[:node]], { 'rdata' => rdata, 'ttl' => options[:ttl] })
|
138
|
+
end
|
139
|
+
changes += 1
|
140
|
+
rescue Dynect::DynectError
|
141
|
+
errors += 1
|
142
|
+
log.error('Failed to add record - %s' % $!.message)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
# publish changes
|
147
|
+
if changes > 0
|
148
|
+
begin
|
149
|
+
if options[:cancel_on_error] and errors > 0
|
150
|
+
log.warn('%sCanceling changes' % [options[:dry_run] ? '(NOT) ' : ''])
|
151
|
+
if not options[:dry_run]
|
152
|
+
c.rest_call(:delete, [ 'ZoneChanges', options[:zone]])
|
153
|
+
end
|
154
|
+
else
|
155
|
+
log.info('%sPublishing changes' % [options[:dry_run] ? '(NOT) ' : ''])
|
156
|
+
if not options[:dry_run]
|
157
|
+
c.rest_call(:put, [ 'Zone', options[:zone]], { 'publish' => 'true' })
|
158
|
+
end
|
159
|
+
end
|
160
|
+
rescue Dynect::DynectError
|
161
|
+
log.error($!.message)
|
162
|
+
end
|
163
|
+
else
|
164
|
+
log.info('No changes made')
|
165
|
+
end
|
166
|
+
|
167
|
+
# terminate session
|
168
|
+
log.info('Terminating session')
|
169
|
+
begin
|
170
|
+
c.rest_call(:delete, 'Session')
|
171
|
+
rescue Dynect::DynectError
|
172
|
+
log.error($!.message)
|
173
|
+
end
|
data/lib/dynect4r.rb
ADDED
@@ -0,0 +1,221 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
gem 'json', '>= 1.4.3'
|
3
|
+
|
4
|
+
require 'json'
|
5
|
+
require 'logger'
|
6
|
+
require 'rest_client'
|
7
|
+
|
8
|
+
module Dynect
|
9
|
+
|
10
|
+
class Client
|
11
|
+
|
12
|
+
# log in, set auth token
|
13
|
+
def initialize(params)
|
14
|
+
@base_url = 'https://api2.dynect.net'
|
15
|
+
@headers = { :content_type => :json, :accept => :json }
|
16
|
+
response = rest_call(:post, 'Session', params)
|
17
|
+
@headers['Auth-Token'] = response[:data][:token]
|
18
|
+
end
|
19
|
+
|
20
|
+
# do a rest call
|
21
|
+
def rest_call(action, resource, arguments = nil)
|
22
|
+
|
23
|
+
# set up retry loop
|
24
|
+
max_tries = 12
|
25
|
+
for try_counter in (1..max_tries)
|
26
|
+
|
27
|
+
# pause between retries
|
28
|
+
if try_counter > 1
|
29
|
+
sleep(5)
|
30
|
+
end
|
31
|
+
|
32
|
+
resource_url = resource_to_url(resource)
|
33
|
+
|
34
|
+
# do rest call
|
35
|
+
begin
|
36
|
+
response = case action
|
37
|
+
when :post, :put
|
38
|
+
RestClient.send(action, resource_url, arguments.to_json, @headers) do |res,req|
|
39
|
+
Dynect::Response.new(res)
|
40
|
+
end
|
41
|
+
else
|
42
|
+
RestClient.send(action, resource_url, @headers) do |res,req|
|
43
|
+
Dynect::Response.new(res)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# if we got this far, then it's safe to break out of the retry loop
|
48
|
+
break
|
49
|
+
|
50
|
+
# on redirect, rewrite rest call params and retry
|
51
|
+
rescue RedirectError
|
52
|
+
if try_counter < max_tries
|
53
|
+
action = :get
|
54
|
+
resource = $!.message
|
55
|
+
arguments = nil
|
56
|
+
else
|
57
|
+
raise OperationTimedOut, "Maximum number of tries (%d) exceeded on resource: %s" % [max_tries, resource]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
# return a response object
|
64
|
+
response
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
# convert the given resource into a proper url
|
70
|
+
def resource_to_url(resource)
|
71
|
+
|
72
|
+
# convert into an array
|
73
|
+
if resource.is_a? String
|
74
|
+
resource = resource.split('/')
|
75
|
+
end
|
76
|
+
|
77
|
+
# remove empty elements
|
78
|
+
resource.delete('')
|
79
|
+
|
80
|
+
# make sure first element is 'REST'
|
81
|
+
if resource[0] != 'REST'
|
82
|
+
resource.unshift('REST')
|
83
|
+
end
|
84
|
+
|
85
|
+
# prepend base url and convert back to string
|
86
|
+
"%s/%s/" % [@base_url, resource.join('/')]
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
class Response
|
92
|
+
|
93
|
+
def initialize(response)
|
94
|
+
|
95
|
+
# parse response
|
96
|
+
begin
|
97
|
+
@hash = JSON.parse(response, :symbolize_names => true)
|
98
|
+
rescue JSON::ParserError
|
99
|
+
if response =~ /REST\/Job\/[0-9]+/
|
100
|
+
raise RedirectError, response
|
101
|
+
else
|
102
|
+
raise
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# raise error based on error code
|
107
|
+
if @hash.has_key?(:msgs)
|
108
|
+
@hash[:msgs].each do |msg|
|
109
|
+
case msg[:ERR_CD]
|
110
|
+
when 'ILLEGAL_OPERATION'
|
111
|
+
raise IllegalOperationError, msg[:INFO]
|
112
|
+
when 'INTERNAL_ERROR'
|
113
|
+
raise InternalErrorError, msg[:INFO]
|
114
|
+
when 'INVALID_DATA'
|
115
|
+
raise InvalidDataError, msg[:INFO]
|
116
|
+
when 'INVALID_REQUEST'
|
117
|
+
raise InvalidRequestError, msg[:INFO]
|
118
|
+
when 'INVALID_VERSION'
|
119
|
+
raise InvalidVersionError, msg[:INFO]
|
120
|
+
when 'MISSING_DATA'
|
121
|
+
raise MissingDataError, msg[:INFO]
|
122
|
+
when 'NOT_FOUND'
|
123
|
+
raise NotFoundError, msg[:INFO]
|
124
|
+
when 'OPERATION_FAILED'
|
125
|
+
raise OperationFailedError, msg[:INFO]
|
126
|
+
when 'PERMISSION_DENIED'
|
127
|
+
raise PermissionDeniedError, msg[:INFO]
|
128
|
+
when 'SERVICE_UNAVAILABLE'
|
129
|
+
raise ServiceUnavailableError, msg[:INFO]
|
130
|
+
when 'TARGET_EXISTS'
|
131
|
+
raise TargetExistsError, msg[:INFO]
|
132
|
+
when 'UNKNOWN_ERROR'
|
133
|
+
raise UnknownErrorError, msg[:INFO]
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def [](key)
|
140
|
+
@hash[key]
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
|
145
|
+
# exceptions generated by class
|
146
|
+
class DynectError < StandardError; end
|
147
|
+
class RedirectError < DynectError; end
|
148
|
+
class OperationTimedOut < DynectError; end
|
149
|
+
|
150
|
+
# exceptions generated by api
|
151
|
+
class IllegalOperationError < DynectError; end
|
152
|
+
class InternalErrorError < DynectError; end
|
153
|
+
class InvalidDataError < DynectError; end
|
154
|
+
class InvalidRequestError < DynectError; end
|
155
|
+
class InvalidVersionError < DynectError; end
|
156
|
+
class MissingDataError < DynectError; end
|
157
|
+
class NotFoundError < DynectError; end
|
158
|
+
class OperationFailedError < DynectError; end
|
159
|
+
class PermissionDeniedError < DynectError; end
|
160
|
+
class ServiceUnavailableError < DynectError; end
|
161
|
+
class TargetExistsError < DynectError; end
|
162
|
+
class UnknownErrorError < DynectError; end
|
163
|
+
|
164
|
+
class Logger < Logger
|
165
|
+
|
166
|
+
# override << operator to control rest_client logging
|
167
|
+
# see http://github.com/archiloque/rest-client/issues/issue/34/
|
168
|
+
def << (msg)
|
169
|
+
debug(msg.strip)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
class << self
|
174
|
+
|
175
|
+
# return the appropriate rest resource for the given rtype
|
176
|
+
def rtype_to_resource(rtype)
|
177
|
+
rtype.upcase + 'Record'
|
178
|
+
end
|
179
|
+
|
180
|
+
# return a hash of arguments for the specified rtype
|
181
|
+
def args_for_rtype(rtype, rdata)
|
182
|
+
|
183
|
+
arg_array = case rtype
|
184
|
+
when 'A', 'AAAA'
|
185
|
+
['address']
|
186
|
+
when 'CNAME'
|
187
|
+
['cname']
|
188
|
+
when 'DNSKEY', 'KEY'
|
189
|
+
['flags', 'protocol', 'algorithm', 'public_key']
|
190
|
+
when 'DS'
|
191
|
+
['keytag', 'algorithm', 'digtype', 'digest']
|
192
|
+
when 'LOC'
|
193
|
+
['version', 'size', 'horiz_pre', 'vert_pre' 'latitude', 'longitude', 'altitude']
|
194
|
+
when 'MX'
|
195
|
+
['preference', 'exchange']
|
196
|
+
when 'NS'
|
197
|
+
['nsdname']
|
198
|
+
when 'PTR'
|
199
|
+
['ptrdname']
|
200
|
+
when 'RP'
|
201
|
+
['mbox', 'txtdname']
|
202
|
+
when 'SOA'
|
203
|
+
['rname']
|
204
|
+
when 'SRV'
|
205
|
+
['priority', 'weight', 'port', 'target']
|
206
|
+
when 'TXT'
|
207
|
+
['txtdata']
|
208
|
+
else
|
209
|
+
[]
|
210
|
+
end
|
211
|
+
|
212
|
+
if rtype == 'TXT'
|
213
|
+
rdata = { arg_array[0] => rdata }
|
214
|
+
else
|
215
|
+
rdata.split.inject({}) { |memo,obj| memo[arg_array[memo.length]] = obj; memo }
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
end
|
220
|
+
|
221
|
+
end
|
metadata
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dynect4r
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 21
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 2
|
9
|
+
- 1
|
10
|
+
version: 0.2.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Michael T. Conigliaro
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-07-08 00:00:00 -06:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: json
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 1
|
30
|
+
segments:
|
31
|
+
- 1
|
32
|
+
- 4
|
33
|
+
- 3
|
34
|
+
version: 1.4.3
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: rest-client
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 3
|
46
|
+
segments:
|
47
|
+
- 0
|
48
|
+
version: "0"
|
49
|
+
type: :runtime
|
50
|
+
version_requirements: *id002
|
51
|
+
description: dynect4r is a Ruby library and command line client for the Dynect REST API (version 2)
|
52
|
+
email:
|
53
|
+
- mike [at] conigliaro [dot] org
|
54
|
+
executables:
|
55
|
+
- dynect4r-client
|
56
|
+
extensions: []
|
57
|
+
|
58
|
+
extra_rdoc_files: []
|
59
|
+
|
60
|
+
files:
|
61
|
+
- LICENSE
|
62
|
+
- README.rdoc
|
63
|
+
- lib/dynect4r.rb
|
64
|
+
- bin/dynect4r-client
|
65
|
+
has_rdoc: true
|
66
|
+
homepage: http://github.com/mconigliaro/dynect4r
|
67
|
+
licenses: []
|
68
|
+
|
69
|
+
post_install_message:
|
70
|
+
rdoc_options: []
|
71
|
+
|
72
|
+
require_paths:
|
73
|
+
- lib
|
74
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
75
|
+
none: false
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
hash: 3
|
80
|
+
segments:
|
81
|
+
- 0
|
82
|
+
version: "0"
|
83
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
84
|
+
none: false
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
hash: 3
|
89
|
+
segments:
|
90
|
+
- 0
|
91
|
+
version: "0"
|
92
|
+
requirements: []
|
93
|
+
|
94
|
+
rubyforge_project: dynect4r
|
95
|
+
rubygems_version: 1.3.7
|
96
|
+
signing_key:
|
97
|
+
specification_version: 3
|
98
|
+
summary: Ruby library and command line client for the Dynect REST API (version 2)
|
99
|
+
test_files: []
|
100
|
+
|