ganddyn 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b0feada86e7855617874d6d329144969022a4c9c
4
+ data.tar.gz: 7191df63d2bea9643bc2ccdf768d2a72fc9dcc91
5
+ SHA512:
6
+ metadata.gz: 116d9fe483f702b7c70e690be5801ee37dd821b4008663cc2d8cb79991d62d71dba3dedd4d5ee1f93735ab5740c7d6d59b984d91b34343208ea90fc45de4b6d7
7
+ data.tar.gz: 66a5d1db13fe7dd1c29bdcee78fd03f3139f3c65065e1405e5bc560c7f0f6f6a3632db5217cefd6496f07e79e7cb22b9686c0da3839b5450330e79cffd99564a
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/.travis.yml ADDED
@@ -0,0 +1,8 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.1
4
+ - 2.1.0
5
+ - 2.0.0
6
+ - 1.9.3
7
+ - 1.9.2
8
+ - 1.8.7
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in ganddyn.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Guillaume Virlet
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,37 @@
1
+ Ganddyn [![Build Status](https://travis-ci.org/doc75/ganddyn.svg?branch=master)](https://travis-ci.org/doc75/ganddyn)
2
+ =======
3
+
4
+ This gem allows to update your GANDI DNS zone with the current external IPv4 of your machine.
5
+ It duplicate current zone information in the last inactive version of the zone (or a newly
6
+ created one if only one version exist). It updates the IPv4 for the name requested and activate
7
+ this version.
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ gem 'ganddyn'
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install ganddyn
22
+
23
+ ## Usage
24
+
25
+ $ gannddyn hostname.domain.com GANDIAPIKEY /path/to/file/storing/last/ip/updated
26
+
27
+ **Warning**:
28
+ - This is an early version not fully tested, use it at your own risk.
29
+ - When used on multiple machines for the same domain, it might miss some update in case of concurrent update
30
+
31
+ ## Contributing
32
+
33
+ 1. Fork it
34
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
35
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
36
+ 4. Push to the branch (`git push origin my-new-feature`)
37
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rspec/core/rake_task'
4
+ RSpec::Core::RakeTask.new(:spec)
5
+ task :default => :spec
data/bin/ganddyn ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH << './lib'
3
+ require 'ganddyn'
4
+
5
+ raise ArgumentError, 'Usage: ganddyn host.domain.com gandi_api_key yaml_file' if ARGV.size != 3
6
+
7
+ begin
8
+ cli = Ganddyn::Client.new({:hostname => ARGV[0], :api_key => ARGV[1], :config_file => ARGV[2]})
9
+ cli.update
10
+ retval = 0
11
+ rescue Exception => e
12
+ puts e
13
+ puts e.backtrace
14
+ retval = 1
15
+ end
16
+
17
+ exit retval
data/ganddyn.gemspec ADDED
@@ -0,0 +1,32 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'ganddyn/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'ganddyn'
8
+ spec.version = Ganddyn::VERSION
9
+ spec.authors = ['Guillaume Virlet']
10
+ spec.email = ['github@virlet.org']
11
+ spec.description = %q{This gem allows to update your GANDI DNS zone with the current external IPv4 of your machine.
12
+ It duplicate current zone information in the last inactive version of the zone (or a newly
13
+ created one if only one version exist). It updates the IPv4 for the name requested and activate
14
+ this version.}
15
+ spec.summary = %q{Update GANDI DNS zone IPv4}
16
+ spec.homepage = 'https://github.com/doc75/ganddyn'
17
+ spec.license = 'MIT'
18
+
19
+ spec.files = `git ls-files`.split($/)
20
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
21
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
22
+ spec.require_paths = ['lib']
23
+
24
+ spec.add_development_dependency 'bundler', '~> 1.3'
25
+ spec.add_development_dependency 'rake', '~>10.3.1'
26
+ spec.add_development_dependency 'rspec', '~>2.14.1'
27
+ spec.add_development_dependency 'webmock', '~>1.17.4'
28
+
29
+ spec.add_dependency 'gandi', '~>2.0.10'
30
+ spec.add_dependency 'highline', '~>1.6.21'
31
+ spec.add_dependency 'certified', '~> 0.1.2'
32
+ end
@@ -0,0 +1,297 @@
1
+ require 'gandi'
2
+
3
+ require 'fileutils'
4
+ require 'yaml'
5
+ require 'highline'
6
+
7
+ if RUBY_PLATFORM =~ /mingw32/
8
+ require 'certified'
9
+ end
10
+
11
+ module Ganddyn
12
+ class Client
13
+
14
+ TTL = 300
15
+
16
+ def initialize opts
17
+ raise ArgumentError, 'opts is not a Hash' unless opts.is_a? Hash
18
+ raise ArgumentError, 'opts does not contain key :hostname' unless opts.has_key? :hostname
19
+ raise ArgumentError, 'opts does not contain key :api_key' unless opts.has_key? :api_key
20
+ raise ArgumentError, 'opts does not contain key :config_file' unless opts.has_key? :config_file
21
+
22
+ @debug = false
23
+
24
+ @api_key = opts[:api_key]
25
+ @config_file = opts[:config_file]
26
+
27
+ tab = opts[:hostname].split('.')
28
+ @name = tab[0...-2].join('.')
29
+ @domain = tab[-2..-1].join('.')
30
+
31
+ @terminal = opts.has_key?(:terminal) ? opts[:terminal] : HighLine.new
32
+
33
+ begin
34
+ last_ips_gandi = YAML.load_file @config_file
35
+ print_debug last_ips_gandi.inspect, 2
36
+ rescue => e
37
+ print_debug 'Cannot load config file', 1
38
+ last_ips_gandi = false
39
+ end
40
+
41
+ if last_ips_gandi
42
+ print_debug "last_ips_gandi exist", 2
43
+ else
44
+ print_debug "last_ips_gandi is nil or false", 2
45
+ end
46
+ print_debug last_ips_gandi.inspect, 2
47
+ @last_ipv4 = (last_ips_gandi && last_ips_gandi[:ipv4].size > 0) ? last_ips_gandi[:ipv4] : nil
48
+
49
+ print_debug "Last IPv4 (form config file):#{@last_ipv4}", 1
50
+
51
+ @cur_ipv4 = nil
52
+ @api = nil
53
+ @zone_id = nil
54
+
55
+ end
56
+
57
+ # return value:
58
+ # true => update done
59
+ # false => update not needed
60
+ # nil => no network found
61
+ def update
62
+ return nil unless ipv4_available?
63
+
64
+ last_ipv4 = @last_ipv4 ? @last_ipv4 : get_gandi_ipv4
65
+
66
+ update_config = false
67
+
68
+ if @last_ipv4.nil?
69
+ update_config = true
70
+ else
71
+ update_config = true if @last_ipv4.empty?
72
+ update_config = true if last_ipv4 != @last_ipv4
73
+ end
74
+
75
+ print_debug "Last IPv4 (form config file or Gandi):#{last_ipv4}", 1
76
+
77
+ # TODO: save it in YAML file to reduce Gandi queries
78
+ ip_resolv = IpResolver.new
79
+ cur_ipv4 = ipv4_available? ? ip_resolv.get_ipv4 : ''
80
+
81
+ if !cur_ipv4.empty? && cur_ipv4 != last_ipv4
82
+ to_update = cur_ipv4
83
+ update_config = true
84
+ else
85
+ to_update = ''
86
+ end
87
+
88
+ print_debug to_update.inspect, 2
89
+ retval = update_ips to_update
90
+ if retval and update_config
91
+ retval = update_config_file cur_ipv4
92
+ end
93
+ retval
94
+ end
95
+
96
+ private
97
+ # return value:
98
+ # nil => no network available for IPv4
99
+ # '' => no previous IPv4 stored in Ganddi DNS
100
+ # otherwise return IPv4 address as a string
101
+ def get_gandi_ipv4
102
+ get_record('A') if ipv4_available?
103
+ end
104
+
105
+ def get_ping_option
106
+ if RUBY_PLATFORM =~ /mingw32/
107
+ return '-n', '> NUL'
108
+ else
109
+ return '-c', '> /dev/null 2>&1'
110
+ end
111
+ end
112
+
113
+ def ipv4_available?
114
+ return @ipv4_avail unless @ipv4_avail.nil?
115
+
116
+ opt, out = get_ping_option
117
+ @ipv4_avail = system("ping #{opt} 1 8.8.8.8 #{out}")
118
+ if @ipv4_avail
119
+ print_info 'IPv4 network available'
120
+ else
121
+ print_info 'IPv4 network not available'
122
+ end
123
+ @ipv4_avail
124
+ end
125
+
126
+ def get_record type
127
+ raise ArgumentError, %Q{type is not 'A'} unless type == 'A'
128
+ res = gandi_api.domain.zone.record.list(get_zone_id, 0, {:name => @name, :type => type})
129
+ print_debug res.inspect, 2
130
+ if res.size == 0
131
+ print_debug "record not found: '#{@name}'", 1
132
+ ''
133
+ else
134
+ print_debug "record found: '#{@name}' => #{res[0].value}", 1
135
+ res[0].value
136
+ end
137
+ end
138
+
139
+ def update_ips update
140
+
141
+ raise(ArgumentError, 'update is not a String') unless update.is_a? String
142
+
143
+ if update.size == 0
144
+ print_info 'no update needed'
145
+ return true
146
+ end
147
+
148
+ cur_vers, all_vers = get_zone_version
149
+ if all_vers.size == 1
150
+ # create a new version and add it to list of all versions
151
+ new_vers = create_new_zone_version
152
+ else
153
+ all_vers = all_vers - [cur_vers]
154
+ # we take the last version of existing versions (my choice ;-)
155
+ new_vers = all_vers[-1]
156
+ clone_zone_version( cur_vers, new_vers )
157
+ end
158
+
159
+ retval = false
160
+ if update_ipv4(new_vers, update)
161
+ if activate_updated_version new_vers
162
+ retval = true
163
+ print_info 'update done'
164
+ else
165
+ retval = false
166
+ print_info 'update FAILED'
167
+ end
168
+ end
169
+ retval
170
+ end
171
+
172
+ def activate_updated_version version
173
+ res = gandi_api.domain.zone.version.set(get_zone_id, version)
174
+ if res
175
+ print_debug 'activation of zone version successful', 1
176
+ else
177
+ print_debug 'activation of zone version failed', 1
178
+ end
179
+ res
180
+ end
181
+
182
+ def update_ipv4 zone_version, ip
183
+ update_record zone_version, ip, 'A'
184
+ end
185
+
186
+ def update_record zone_version, ip, type
187
+ return false if ip.empty?
188
+ retval = false
189
+ records = gandi_api.domain.zone.record.list( get_zone_id,
190
+ zone_version,
191
+ {:name => @name, :type => type} )
192
+ if records.size == 0
193
+ res = gandi_api.domain.zone.record.add( get_zone_id,
194
+ zone_version,
195
+ { :name => @name,
196
+ :type => type,
197
+ :value => ip,
198
+ :ttl => TTL } )
199
+ retval = true
200
+ else
201
+ records.each do |rec|
202
+ # only update if ip is different
203
+ if rec.value != ip
204
+ res = gandi_api.domain.zone.record.update( get_zone_id,
205
+ zone_version,
206
+ { :id => rec.id },
207
+ { :name => rec.name,
208
+ :type => rec.type,
209
+ :value => ip,
210
+ :ttl => TTL } )
211
+ retval = true
212
+ end
213
+ end
214
+ end
215
+ retval
216
+ end
217
+
218
+ def clone_zone_version src, dest
219
+ src_list = gandi_api.domain.zone.record.list(get_zone_id, src)
220
+ print_debug src_list.inspect, 2
221
+
222
+ dest_list = gandi_api.domain.zone.record.list(get_zone_id, dest)
223
+ print_debug dest_list.inspect, 2
224
+
225
+ dest_list.each do |elt|
226
+
227
+ curs = src_list.select { |e| e.name == elt.name && e.type == elt.type }
228
+
229
+ if curs.size > 0
230
+ print_debug "\n#{curs.inspect}", 2
231
+ # normally with the same name and same type we should have only 1 record
232
+ cur = curs[0]
233
+
234
+ if cur.ttl != elt.ttl || cur.value != elt.value
235
+ print_debug "Updating record (name: #{elt.name} - type: #{elt.type}) to ttl: #{cur.ttl} - value: #{cur.value}", 1
236
+ res = gandi_api.domain.zone.record.update( get_zone_id, dest, { :id => elt.id }, {:name => cur.name, :type => cur.type, :value => cur.value, :ttl => cur.ttl} )
237
+ print_debug res.inspect, 2
238
+ else
239
+ print_debug "record (name: #{elt.name} - type: #{elt.type}) already up-to-date", 1
240
+ end
241
+
242
+ src_list = src_list - [cur]
243
+
244
+ else
245
+ # it does not exist in current zone, let's delete this record
246
+ print_debug "\nDeleting record (name: #{elt.name} - type: #{elt.type})", 1
247
+ res = gandi_api.domain.zone.record.delete( get_zone_id, dest, { :id => elt.id } )
248
+
249
+ if res == 1
250
+ print_debug "Deletion OK", 2
251
+ else
252
+ print_debug "ERROR: deletion of #{res} records", 2
253
+ end
254
+ end
255
+ end
256
+
257
+ src_list.each do |cur|
258
+ print_debug "Creating record (name: #{cur.name} - type: #{cur.type} - ttl: #{cur.ttl} - value: #{cur.value}", 1
259
+ res = gandi_api.domain.zone.record.add( get_zone_id, dest, {:name => cur.name, :type => cur.type, :value => cur.value, :ttl => cur.ttl} )
260
+ end
261
+ end
262
+
263
+ def update_config_file ip
264
+ if File.open(@config_file, 'w+') { |f| f.write({:ipv4 => ip}.to_yaml) }
265
+ true
266
+ else
267
+ false
268
+ end
269
+ end
270
+
271
+ def get_zone_version
272
+ get_zone_id
273
+ infos = gandi_api.domain.zone.info(@zone_id)
274
+ return infos.version, infos.versions
275
+ end
276
+
277
+ def create_new_zone_version
278
+ gandi_api.domain.zone.version.new_version(@zone_id)
279
+ end
280
+
281
+ def get_zone_id
282
+ @zone_id ||= gandi_api.domain.info(@domain).zone_id
283
+ end
284
+
285
+ def gandi_api
286
+ @api ||= Gandi::Session.new(@api_key)
287
+ end
288
+
289
+ def print_info str
290
+ @terminal.say str
291
+ end
292
+
293
+ def print_debug str, level = 1
294
+ @terminal.say str if @debug and @debug >= level
295
+ end
296
+ end
297
+ end
@@ -0,0 +1,44 @@
1
+ require 'net/http'
2
+
3
+ module Ganddyn
4
+
5
+ class IpResolver
6
+
7
+ # urls: hash or ipv4 and ipv6 url services
8
+ # { :ipv4 => 'http://ipv4_url', :ipv6 => 'http://ipv6_url'}
9
+ def initialize urls = {}
10
+ raise ArgumentError, 'urls is not a Hash' unless urls.is_a? Hash
11
+
12
+ @ipv4_url = urls.has_key?(:ipv4) ? urls[:ipv4] : "http://v4.ipv6-test.com/api/myip.php"
13
+ @ipv6_url = urls.has_key?(:ipv6) ? urls[:ipv6] : "http://v6.ipv6-test.com/api/myip.php"
14
+ end
15
+
16
+ def get_ipv4
17
+ get_url @ipv4_url
18
+ end
19
+
20
+ def get_ipv6
21
+ get_url @ipv6_url
22
+ end
23
+
24
+ private
25
+ def get_url( iUrl )
26
+ retval = nil
27
+ uri = URI(iUrl)
28
+ begin
29
+ res = Net::HTTP.get_response(uri)
30
+ if res.code == '200'
31
+ retval = res.body
32
+ end
33
+ rescue SocketError => e
34
+ # normally it happens if the server name is invalid
35
+ # or if no network is available
36
+ # let's return nil
37
+ rescue Exception => e
38
+ # normally it happen if the machine has no ipv6 support
39
+ # let's return nil
40
+ end
41
+ retval
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,3 @@
1
+ module Ganddyn
2
+ VERSION = "0.0.1"
3
+ end
data/lib/ganddyn.rb ADDED
@@ -0,0 +1,5 @@
1
+ # Add the directory containing this file to the start of the load path if it isn't there already.
2
+ #$:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ require 'ganddyn/ipresolver'
5
+ require 'ganddyn/client'
@@ -0,0 +1,559 @@
1
+ require 'spec_helper'
2
+
3
+ require 'ganddyn'
4
+
5
+ describe Ganddyn::Client do
6
+ before :each do
7
+ @input = StringIO.new
8
+ @output = StringIO.new
9
+ @terminal = HighLine.new(@input, @output)
10
+
11
+ @ipv4 = '12.34.56.78'
12
+
13
+ @cur_ver = 123
14
+ @other_ver = 456
15
+ @vers = [@cur_ver, @other_ver]
16
+
17
+ @mash = Hashie::Mash.new({:version => @cur_ver, :versions => [@cur_ver, @other_ver]})
18
+ @domain = 'domain.com'
19
+ @name = 'rspec'
20
+
21
+ @host = { :hostname => 'rspec.domain.com' }
22
+ @key = { :api_key => 'FAKEAPIKEY' }
23
+ @cfg = { :config_file => '/etc/ganddyn.yaml' }
24
+ @param = @host.merge(@key).merge(@cfg)
25
+ allow(YAML).to receive(:load_file).and_return({:ipv4 => @ipv4})
26
+
27
+ @api = double('api_gandi')
28
+ allow(@api).to receive(:domain).and_return(@api)
29
+ allow(@api).to receive(:zone).and_return(@api)
30
+ allow(@api).to receive(:info).and_return(@mash)
31
+
32
+ allow(@api).to receive(:version).and_return(@api)
33
+ allow(@api).to receive(:new_version).and_return(3)
34
+
35
+ allow(@api).to receive(:record).and_return(@api)
36
+ allow(@api).to receive(:add).and_return([])
37
+ allow(@api).to receive(:delete).and_return(1)
38
+ allow(@api).to receive(:update).and_return([])
39
+ allow(@api).to receive(:list).and_return([])
40
+
41
+ allow(@api).to receive(:version).and_return(@api)
42
+ allow(@api).to receive(:set).and_return(true)
43
+
44
+ allow_any_instance_of(Ganddyn::Client).to receive(:update_config_file).and_return(true)
45
+ end
46
+
47
+ describe '#initialize' do
48
+ before :each do
49
+ end
50
+ it 'takes 1 parameters' do
51
+ Ganddyn::Client.new( @param ).should be_a Ganddyn::Client
52
+ end
53
+
54
+ it 'raises an error if parameter opts is not a Hash' do
55
+ expect { Ganddyn::Client.new('rspec.domain.com') }.to raise_error(ArgumentError, /opts is not a Hash/)
56
+ end
57
+
58
+ it 'raises an error if parameter opts does not contain :hostname key' do
59
+ expect { Ganddyn::Client.new( {:host_name => 'rspec.domain.com'}.merge(@key).merge(@cfg)) }.to raise_error(ArgumentError, /opts does not contain key :hostname/)
60
+ end
61
+
62
+ it 'raises an error if parameter opts does not contain :api_key key' do
63
+ expect { Ganddyn::Client.new({:apikey => 'FAKEAPIKEY'}.merge(@host).merge(@cfg)) }.to raise_error(ArgumentError, /opts does not contain key :api_key/)
64
+ end
65
+
66
+ it 'raises an error if parameter opts does not contain :config_file key' do
67
+ expect { Ganddyn::Client.new({ :configfile => '/etc/ganddyn.yaml'}.merge(@host).merge(@key)) }.to raise_error(ArgumentError, /opts does not contain key :config_file/)
68
+ end
69
+
70
+ it 'loads the config file' do
71
+ expect(YAML).to receive(:load_file).with(@cfg[:config_file]).and_return({:ipv4 => @ipv4})
72
+ Ganddyn::Client.new( @param )
73
+ end
74
+ end
75
+
76
+ describe '#update' do
77
+ before :each do
78
+ allow_any_instance_of(Ganddyn::IpResolver).to receive(:get_ipv4).and_return('11.22.33.44')
79
+ @gand = Ganddyn::Client.new( @param.merge({:terminal => @terminal}) )
80
+ allow(@gand).to receive(:gandi_api).and_return(@api)
81
+ allow(@gand).to receive(:system).and_return(true)
82
+ end
83
+
84
+ context 'when yaml config file is not found' do
85
+ before :each do
86
+ expect(YAML).to receive(:load_file).and_return(false)
87
+ @gand = Ganddyn::Client.new( @param.merge({:terminal => @terminal}) )
88
+ allow(@gand).to receive(:gandi_api).and_return(@api)
89
+ allow(@gand).to receive(:system).and_return(true)
90
+ allow(@gand).to receive(:get_record)
91
+ end
92
+
93
+ it 'asks gandi for ipv4' do
94
+ expect(@gand).to receive(:get_record).with('A').and_return(@ipv4)
95
+ expect(@gand.update).to be_true
96
+ end
97
+
98
+ end
99
+
100
+ context 'when yaml config file exist' do
101
+ it 'does not ask Gandi' do
102
+ allow(@gand).to receive(:get_record)
103
+ expect(@gand.update).to be_true
104
+ expect(@gand).to_not have_received(:get_record)
105
+ end
106
+ end
107
+
108
+ context 'when no network' do
109
+ before :each do
110
+ allow(@gand).to receive(:system).and_return(false)
111
+ end
112
+
113
+ it 'does not ask for IPv4 information' do
114
+ expect_any_instance_of(Ganddyn::IpResolver).to_not receive(:get_ipv4)
115
+ expect(@gand.update).to be_nil
116
+ end
117
+ end
118
+
119
+ context 'when no ip to update' do
120
+ before :each do
121
+ allow_any_instance_of(Ganddyn::IpResolver).to receive(:get_ipv4).and_return('')
122
+ end
123
+
124
+ it 'returns true' do
125
+ expect(@gand.update).to be_true
126
+ end
127
+
128
+ it 'does not modify the yaml config file' do
129
+ expect(@gand).to_not receive(:update_config_file)
130
+ @gand.update
131
+ end
132
+ end
133
+
134
+ context 'when ip to update' do
135
+ it 'updates the ip' do
136
+ expect(@gand.update).to be_true
137
+ end
138
+
139
+ it 'updates the yaml config file content' do
140
+ expect(@gand).to receive(:update_config_file).and_return(true)
141
+ @gand.update
142
+ end
143
+ end
144
+ end
145
+
146
+ end
147
+
148
+ describe_internally Ganddyn::Client do
149
+ before :each do
150
+ @input = StringIO.new
151
+ @output = StringIO.new
152
+ @terminal = HighLine.new(@input, @output)
153
+
154
+ @ipv4 = '12.34.56.78'
155
+
156
+ @cur_ver = 123
157
+ @other_ver = 456
158
+ @vers = [@cur_ver, @other_ver]
159
+
160
+ @mash = Hashie::Mash.new({:version => @cur_ver, :versions => @vers})
161
+ @domain = 'domain.com'
162
+ @name = 'rspec'
163
+
164
+ @host = { :hostname => 'rspec.domain.com' }
165
+ @key = { :api_key => 'FAKEAPIKEY' }
166
+ @cfg = { :config_file => '/etc/ganddyn.yaml' }
167
+ @param = @host.merge(@key).merge(@cfg)
168
+ allow(YAML).to receive(:load_file).and_return({:ipv4 => @ipv4})
169
+
170
+ @api = double('api_gandi')
171
+ allow(@api).to receive(:domain).and_return(@api)
172
+ allow(@api).to receive(:zone).and_return(@api)
173
+ allow(@api).to receive(:info).and_return(@mash)
174
+
175
+ allow(@api).to receive(:version).and_return(@api)
176
+ allow(@api).to receive(:new_version).and_return(3)
177
+
178
+ allow(@api).to receive(:record).and_return(@api)
179
+ allow(@api).to receive(:add).and_return([])
180
+ allow(@api).to receive(:delete).and_return(1)
181
+ allow(@api).to receive(:update).and_return([])
182
+ allow(@api).to receive(:list).and_return([])
183
+
184
+ allow(@api).to receive(:version).and_return(@api)
185
+ allow(@api).to receive(:set).and_return(true)
186
+
187
+ @gand = Ganddyn::Client.new( @param.merge({:terminal => @terminal}) )
188
+ allow(@gand).to receive(:gandi_api).and_return(@api)
189
+ allow(@gand).to receive(:system).and_return(true)
190
+
191
+ allow_any_instance_of(Ganddyn::Client).to receive(:update_config_file).and_return(true)
192
+ end
193
+
194
+ describe '#get_zone_id' do
195
+ it 'returns the current zone id' do
196
+ @api.should_receive(:info).with(@domain).and_return(Hashie::Mash.new({:zone_id => 666}))
197
+ @gand.get_zone_id.should == 666
198
+ end
199
+
200
+ it 'calls gandi api only once' do
201
+ @api.should_receive(:info).once.with(@domain).and_return(Hashie::Mash.new({:zone_id => 666}))
202
+ @gand.get_zone_id.should == 666
203
+ @gand.get_zone_id.should == 666
204
+ end
205
+ end
206
+
207
+ describe '#create_new_zone_version' do
208
+ pending 'not really necessary'
209
+ end
210
+
211
+ describe '#get_zone_version' do
212
+ it 'returns 2 values' do
213
+ version, versions = @gand.get_zone_version
214
+ version.should == @cur_ver
215
+ versions.should == @vers
216
+ end
217
+ end
218
+
219
+ describe '#get_record' do
220
+ before :each do
221
+ @type = 'A'
222
+ @filter = { :name => @name, :type => @type }
223
+ @res = Hashie::Mash.new({:value => @ipv4})
224
+ end
225
+
226
+ it 'takes 1 parameter as argument' do
227
+ @api.should_receive(:list).with(anything(), 0, @filter).and_return([@res])
228
+ @gand.get_record('A')
229
+ end
230
+
231
+ context 'when Gandi returns a record' do
232
+ before :each do
233
+ expect(@api).to receive(:list).with(anything(), 0, @filter).and_return([@res])
234
+ end
235
+
236
+ it 'returns the IPv4 address' do
237
+ @gand.get_record('A').should == @ipv4
238
+ end
239
+
240
+ # it 'output record found with value' do
241
+ # @gand.get_record('A')
242
+ # expect(@output.string).to match /record found: '#{@name}' => #{@ipv4}/
243
+ # end
244
+ end
245
+
246
+ context 'when Gandi returns no record' do
247
+ before :each do
248
+ expect(@api).to receive(:list).with(anything(), 0, @filter).and_return([])
249
+ end
250
+
251
+ it 'returns an empty string if Gandi returns no record' do
252
+ expect(@gand.get_record('A')).to eq ''
253
+ end
254
+
255
+ # it 'output "record not found"' do
256
+ # @gand.get_record('A')
257
+ # expect(@output.string).to match /record not found: '#{@name}'/
258
+ # end
259
+ end
260
+
261
+ it 'raises an error if type is not A' do
262
+ expect { @gand.get_record('AA') }.to raise_error(ArgumentError, /type is not 'A'/)
263
+ end
264
+ end
265
+
266
+ describe '#get_gandi_ipv4' do
267
+ before :each do
268
+ @type = 'A'
269
+ @filter = { :name => @name, :type => @type }
270
+ @res = Hashie::Mash.new({:value => @ipv4})
271
+ end
272
+
273
+ it 'retrieve the record A from Gandi DNS' do
274
+ expect(@api).to receive(:list).with(anything(), 0, @filter).and_return([@res])
275
+ expect(@gand.get_gandi_ipv4).to eq @ipv4
276
+ end
277
+
278
+ it 'returns nil if IPv4 network is not available' do
279
+ allow(@gand).to receive(:get_record).and_return('1.2.3.4')
280
+ expect(@gand).to receive(:ipv4_available?).and_return(false)
281
+ expect(@gand.get_gandi_ipv4).to be_nil
282
+ expect(@gand).to_not have_received(:get_record)
283
+ end
284
+ end
285
+
286
+ describe '#update_record' do
287
+ before :each do
288
+ @type = 'A'
289
+ @rec = { :name => @name, :type => @type, :value => @ipv4, :ttl => Ganddyn::Client::TTL }
290
+
291
+ end
292
+
293
+ it 'takes 3 arguments' do
294
+ expect(@gand.update_record( @cur_ver, @ipv4, 'A')).to be_true
295
+ end
296
+
297
+ context 'when input ip is empty' do
298
+ it 'returns false' do
299
+ expect(@gand.update_record( @cur_ver, '', 'A')).to be_false
300
+ end
301
+ end
302
+
303
+ context 'when record does not exist yet' do
304
+ it 'adds a new record' do
305
+ expect(@gand.update_record( @cur_ver, @ipv4, 'A')).to be_true
306
+ expect(@api).to have_received(:add).with(anything, @cur_ver, @rec )
307
+ expect(@api).to_not have_received(:update)
308
+ end
309
+ end
310
+
311
+ context 'when record already exist' do
312
+ before :each do
313
+ @filter = { :name => @name, :type => @type }
314
+ @res = Hashie::Mash.new({:value => @ipv4})
315
+ expect(@api).to receive(:list).with(anything(), @cur_ver, @filter).and_return([@res])
316
+ end
317
+
318
+ context 'and ip is different' do
319
+ it 'updates the record' do
320
+ @gand.update_record( @cur_ver, "#{@ipv4}1", 'A')
321
+ expect(@api).to_not have_received(:add)
322
+ expect(@api).to have_received(:update).once
323
+ end
324
+
325
+ it 'returns true' do
326
+ expect(@gand.update_record( @cur_ver, "#{@ipv4}1", 'A')).to be_true
327
+ end
328
+ end
329
+
330
+ context 'and ip is the same' do
331
+ it 'does not update the record' do
332
+ @gand.update_record( @cur_ver, @ipv4, 'A')
333
+ expect(@api).to_not have_received(:add)
334
+ expect(@api).to_not have_received(:update)
335
+ end
336
+
337
+ it 'returns false' do
338
+ expect(@gand.update_record( @cur_ver, @ipv4, 'A')).to be_false
339
+ end
340
+ end
341
+ end
342
+ end
343
+
344
+ describe '#clone_zone_version' do
345
+ before :each do
346
+ @src = 111
347
+ @src_ret = [ 123, 456 ].map do |id|
348
+ Hashie::Mash.new({ :id => id,
349
+ :name => "name#{id}",
350
+ :type => "type#{id}",
351
+ :value => "value#{id}",
352
+ :ttl => "ttl#{id}" })
353
+ end
354
+ @dest = 222
355
+ @dest_ret = [ 123, 789 ].map do |id|
356
+ Hashie::Mash.new({ :id => id,
357
+ :name => "name#{id}",
358
+ :type => "type#{id}",
359
+ :value => "value#{id}-dest",
360
+ :ttl => "ttl#{id}-dest" })
361
+ end
362
+ allow(@api).to receive(:list).with(anything(), @src).and_return(@src_ret)
363
+ allow(@api).to receive(:list).with(anything(), @dest).and_return(@dest_ret)
364
+ end
365
+
366
+ it 'get the information from source zone' do
367
+ expect(@api).to receive(:list).with(anything(), @src).and_return(@src_ret)
368
+ @gand.clone_zone_version @src, @dest
369
+ end
370
+
371
+ it 'get the information from destination zone' do
372
+ expect(@api).to receive(:list).with(anything(), @dest).and_return(@dest_ret)
373
+ @gand.clone_zone_version @src, @dest
374
+ end
375
+
376
+ it 'deletes the record existing in destination zone but not in source zone' do
377
+ expect(@api).to receive(:delete).with(anything(), @dest, {:id => 789})
378
+ @gand.clone_zone_version @src, @dest
379
+ end
380
+
381
+ it 'update the record existing in both zone' do
382
+ ret = @src_ret[0].to_hash.inject({}) { |acc,(k,v)| acc[k.to_sym] = v if k != 'id'; acc }
383
+ expect(@api).to receive(:update).with(anything(), @dest, {:id => 123}, ret)
384
+ @gand.clone_zone_version @src, @dest
385
+ end
386
+
387
+ it 'create the record existing only in source zone' do
388
+ ret = @src_ret[1].to_hash.inject({}) { |acc,(k,v)| acc[k.to_sym] = v if k != 'id'; acc }
389
+ expect(@api).to receive(:add).with(anything(), @dest, ret)
390
+ @gand.clone_zone_version @src, @dest
391
+ end
392
+ end
393
+
394
+ describe '#activate_updated_version' do
395
+ context 'when activation succeeded' do
396
+ it 'returns true' do
397
+ expect(@gand.activate_updated_version(2)).to be_true
398
+ end
399
+
400
+ # it 'output activation of zone version successful' do
401
+ # @gand.activate_updated_version(2)
402
+ # expect(@output.string).to match /activation of zone version successful/
403
+ # end
404
+ end
405
+
406
+ context 'when activation failed' do
407
+ before :each do
408
+ expect(@api).to receive(:set).with(anything(), 9).and_return(false)
409
+ end
410
+ it 'returns false' do
411
+ expect(@gand.activate_updated_version(9)).to be_false
412
+ end
413
+
414
+ # it 'output activation of zone version failed' do
415
+ # @gand.activate_updated_version(9)
416
+ # expect(@output.string).to match /activation of zone version failed/
417
+ # end
418
+
419
+ end
420
+ end
421
+
422
+ describe '#update_ipv4' do
423
+ # pending 'not necessary'
424
+ end
425
+
426
+
427
+ describe '#update_ips' do
428
+ it 'take 1 input as parameter' do
429
+ expect(@gand.update_ips('')).to be_true
430
+ end
431
+
432
+ context 'when input is not a String' do
433
+ it 'raise an error' do
434
+ expect{ @gand.update_ips(['1.2.3.4']) }.to raise_error(ArgumentError, /update is not a String/)
435
+ end
436
+ end
437
+
438
+ context 'when no ip to update' do
439
+ it 'returns true without doing anything' do
440
+ allow(@gand).to receive(:get_zone_version)
441
+ allow(@gand).to receive(:create_new_zone_version)
442
+ allow(@gand).to receive(:clone_zone_version)
443
+
444
+ expect(@gand.update_ips('')).to be_true
445
+
446
+ expect(@gand).to_not have_received(:get_zone_version)
447
+ expect(@gand).to_not have_received(:create_new_zone_version)
448
+ expect(@gand).to_not have_received(:clone_zone_version)
449
+ end
450
+
451
+ it 'output no update needed' do
452
+ @gand.update_ips('')
453
+ expect(@output.string).to match /no update needed/
454
+ end
455
+ end
456
+
457
+ context 'when ipv4 to update' do
458
+
459
+ it 'updates ipv4 record' do
460
+ expect(@gand).to receive(:update_ipv4).and_return(true)
461
+ @gand.update_ips '1.2.3.4'
462
+ end
463
+
464
+ context 'when activation of new zone version is OK' do
465
+ it 'returns true' do
466
+ expect(@gand.update_ips('1.2.3.4')).to be_true
467
+ end
468
+
469
+ it 'output update done' do
470
+ @gand.update_ips('1.2.3.4')
471
+ expect(@output.string).to match /update done/
472
+ end
473
+ end
474
+
475
+ context 'when activation of new zone version is KO' do
476
+ before :each do
477
+ expect(@gand).to receive(:get_zone_version).and_return([666, [666]])
478
+ expect(@api).to receive(:set).with(anything(), 3).and_return(false)
479
+ end
480
+
481
+ it 'returns false' do
482
+ expect(@gand.update_ips('1.2.3.4')).to be_false
483
+ end
484
+
485
+ it 'output update FAILED' do
486
+ @gand.update_ips('1.2.3.4')
487
+ expect(@output.string).to match /update FAILED/
488
+ end
489
+ end
490
+
491
+ it 'activate the new zone version' do
492
+ expect(@gand).to receive(:activate_updated_version).and_return(true)
493
+ @gand.update_ips '1.2.3.4'
494
+ end
495
+ end
496
+
497
+ context 'when only 1 zone version exists' do
498
+ it 'creates a new version for the zone from current active version' do
499
+ allow(@gand).to receive(:clone_zone_version)
500
+ mash = Hashie::Mash.new({:version => 1, :versions => [1]})
501
+ allow(@api).to receive(:info).and_return(mash)
502
+ expect(@api).to receive(:new_version).and_return(99)
503
+
504
+ @gand.update_ips('1.2.3.4')
505
+
506
+ expect(@gand).to_not receive(:clone_zone_version)
507
+ end
508
+ end
509
+
510
+ context 'when more than 1 zone version exist' do
511
+ it 'clone the last non active version of the zone' do
512
+ expect(@gand).to receive(:clone_zone_version)
513
+ @gand.update_ips('1.2.3.4')
514
+ expect(@api).to_not receive(:new_version)
515
+ end
516
+ end
517
+ end
518
+
519
+ describe '#ipv4_available' do
520
+ context 'when network is not available' do
521
+ before :each do
522
+ allow(@gand).to receive(:system).and_return(false)
523
+ end
524
+
525
+ it 'returns false' do
526
+ expect(@gand.ipv4_available?).to be false
527
+ end
528
+
529
+ it 'output IPv4 network not available' do
530
+ @gand.ipv4_available?
531
+ expect(@output.string).to match /IPv4 network not available/
532
+ end
533
+ end
534
+
535
+ context 'when network is available' do
536
+ it 'returns true' do
537
+ expect(@gand.ipv4_available?).to be true
538
+ end
539
+
540
+ it 'output IPv4 network available' do
541
+ @gand.ipv4_available?
542
+ expect(@output.string).to match /IPv4 network available/
543
+ end
544
+ end
545
+
546
+ context 'on windows platform' do
547
+ it 'redirect output to > NUL' do
548
+ pending
549
+ end
550
+ end
551
+
552
+ context 'on Linux platform' do
553
+ it 'redirect output to > /dev/null 2>&1' do
554
+ pending
555
+ end
556
+ end
557
+ end
558
+
559
+ end
@@ -0,0 +1,81 @@
1
+ require 'spec_helper'
2
+
3
+ require 'ganddyn'
4
+
5
+ describe Ganddyn::IpResolver do
6
+
7
+ before :each do
8
+ @ip = Ganddyn::IpResolver.new( { :ipv4 => 'http://v4.rspec.domain.com', :ipv6 => 'http://v6.rspec.domain.com' } )
9
+ end
10
+
11
+ context '#intialize' do
12
+ it 'takes 0 parameter' do
13
+ Ganddyn::IpResolver.new.should be_a Ganddyn::IpResolver
14
+ end
15
+
16
+ it 'take 1 parameter' do
17
+ Ganddyn::IpResolver.new({}).should be_a Ganddyn::IpResolver
18
+ end
19
+
20
+ it 'raises an error if parameter is not Hash' do
21
+ expect { Ganddyn::IpResolver.new([]) }.to raise_error(ArgumentError, /urls is not a Hash/)
22
+ end
23
+ end
24
+
25
+ context '#get_ipv4' do
26
+ it 'returns the ipv4' do
27
+ stub_request(:get, 'v4.rspec.domain.com').to_return(:body => '1.2.3.4', :status => 200, :headers => { 'Content-Length' => 3 })
28
+ @ip.get_ipv4.should == '1.2.3.4'
29
+ end
30
+
31
+ it 'returns empty string if client has no ipv4' do
32
+ stub_request(:get, 'v4.rspec.domain.com').to_raise(Exception)
33
+ @ip.get_ipv4.should be_nil
34
+ end
35
+
36
+ it 'returns nil if server is not reachable' do
37
+ stub_request(:get, 'v4.rspec.domain.com').to_raise(SocketError)
38
+ @ip.get_ipv4.should be_nil
39
+ end
40
+
41
+ it 'returns nil if url is incorrect' do
42
+ stub_request(:get, 'v4.rspec.domain.com').to_return(:body => '<html><head>
43
+ <title>404 Not Found</title>
44
+ </head><body>
45
+ <h1>Not Found</h1>
46
+ <p>The requested URL xxx was not found on this server.</p>
47
+ <hr>
48
+ </body></html>', :status => 404, :headers => { 'Content-Length' => 3 })
49
+ @ip.get_ipv4.should be_nil
50
+ end
51
+ end
52
+
53
+ context '#get_ipv6' do
54
+ it 'returns the ipv6' do
55
+ stub_request(:get, 'v6.rspec.domain.com').to_return(:body => '3b12:d24:1e6a:8e81:b6a2:6f67:5627:bf44', :status => 200, :headers => { 'Content-Length' => 3 })
56
+ @ip.get_ipv6.should == '3b12:d24:1e6a:8e81:b6a2:6f67:5627:bf44'
57
+ end
58
+
59
+ it 'returns nil if client has no ipv6' do
60
+ stub_request(:get, 'v6.rspec.domain.com').to_raise(Exception)
61
+ @ip.get_ipv6.should be_nil
62
+
63
+ end
64
+
65
+ it 'returns nil if server is not reachable' do
66
+ stub_request(:get, 'v6.rspec.domain.com').to_raise(SocketError)
67
+ @ip.get_ipv6.should be_nil
68
+ end
69
+
70
+ it 'returns nil if url is incorrect' do
71
+ stub_request(:get, 'v6.rspec.domain.com').to_return(:body => '<html><head>
72
+ <title>404 Not Found</title>
73
+ </head><body>
74
+ <h1>Not Found</h1>
75
+ <p>The requested URL xxx was not found on this server.</p>
76
+ <hr>
77
+ </body></html>', :status => 404, :headers => { 'Content-Length' => 3 })
78
+ @ip.get_ipv6.should be_nil
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,35 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # Require this file using `require "spec_helper"` to ensure that it is only
4
+ # loaded once.
5
+ #
6
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
+ RSpec.configure do |config|
8
+ config.treat_symbols_as_metadata_keys_with_true_values = true
9
+ config.run_all_when_everything_filtered = true
10
+ config.filter_run :focus
11
+
12
+ # Run specs in random order to surface order dependencies. If you find an
13
+ # order dependency and want to debug it, you can fix the order by providing
14
+ # the seed, which is printed after each run.
15
+ # --seed 1234
16
+ config.order = 'random'
17
+ end
18
+
19
+ require 'webmock/rspec'
20
+
21
+ # enable to test private method of a class
22
+ def describe_internally *args, &block
23
+ example = describe *args, &block
24
+ cur_class = args[0]
25
+ if cur_class.is_a? Class
26
+ saved_private_instance_methods = cur_class.private_instance_methods
27
+ example.before do
28
+ cur_class.class_eval { public *saved_private_instance_methods }
29
+ end
30
+ example.after do
31
+ cur_class.class_eval { private *saved_private_instance_methods }
32
+ end
33
+ end
34
+ end
35
+
metadata ADDED
@@ -0,0 +1,166 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ganddyn
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Guillaume Virlet
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-05-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: 10.3.1
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: 10.3.1
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: 2.14.1
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: 2.14.1
55
+ - !ruby/object:Gem::Dependency
56
+ name: webmock
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 1.17.4
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: 1.17.4
69
+ - !ruby/object:Gem::Dependency
70
+ name: gandi
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ version: 2.0.10
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ~>
81
+ - !ruby/object:Gem::Version
82
+ version: 2.0.10
83
+ - !ruby/object:Gem::Dependency
84
+ name: highline
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ~>
88
+ - !ruby/object:Gem::Version
89
+ version: 1.6.21
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ~>
95
+ - !ruby/object:Gem::Version
96
+ version: 1.6.21
97
+ - !ruby/object:Gem::Dependency
98
+ name: certified
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ~>
102
+ - !ruby/object:Gem::Version
103
+ version: 0.1.2
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ~>
109
+ - !ruby/object:Gem::Version
110
+ version: 0.1.2
111
+ description: |-
112
+ This gem allows to update your GANDI DNS zone with the current external IPv4 of your machine.
113
+ It duplicate current zone information in the last inactive version of the zone (or a newly
114
+ created one if only one version exist). It updates the IPv4 for the name requested and activate
115
+ this version.
116
+ email:
117
+ - github@virlet.org
118
+ executables:
119
+ - ganddyn
120
+ extensions: []
121
+ extra_rdoc_files: []
122
+ files:
123
+ - .gitignore
124
+ - .rspec
125
+ - .travis.yml
126
+ - Gemfile
127
+ - LICENSE.txt
128
+ - README.md
129
+ - Rakefile
130
+ - bin/ganddyn
131
+ - ganddyn.gemspec
132
+ - lib/ganddyn.rb
133
+ - lib/ganddyn/client.rb
134
+ - lib/ganddyn/ipresolver.rb
135
+ - lib/ganddyn/version.rb
136
+ - spec/lib/ganddyn/client_spec.rb
137
+ - spec/lib/ganddyn/ipresolver_spec.rb
138
+ - spec/spec_helper.rb
139
+ homepage: https://github.com/doc75/ganddyn
140
+ licenses:
141
+ - MIT
142
+ metadata: {}
143
+ post_install_message:
144
+ rdoc_options: []
145
+ require_paths:
146
+ - lib
147
+ required_ruby_version: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - '>='
150
+ - !ruby/object:Gem::Version
151
+ version: '0'
152
+ required_rubygems_version: !ruby/object:Gem::Requirement
153
+ requirements:
154
+ - - '>='
155
+ - !ruby/object:Gem::Version
156
+ version: '0'
157
+ requirements: []
158
+ rubyforge_project:
159
+ rubygems_version: 2.1.9
160
+ signing_key:
161
+ specification_version: 4
162
+ summary: Update GANDI DNS zone IPv4
163
+ test_files:
164
+ - spec/lib/ganddyn/client_spec.rb
165
+ - spec/lib/ganddyn/ipresolver_spec.rb
166
+ - spec/spec_helper.rb