gehirn_dns 1.0.0.pre
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.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.rspec +2 -0
- data/.rubocop.yml +25 -0
- data/.travis.yml +5 -0
- data/Gemfile +9 -0
- data/LICENSE.md +25 -0
- data/README.md +151 -0
- data/Rakefile +6 -0
- data/bin/setup +8 -0
- data/gehirn_dns.gemspec +29 -0
- data/lib/gehirn_dns.rb +9 -0
- data/lib/gehirn_dns/client.rb +117 -0
- data/lib/gehirn_dns/error.rb +40 -0
- data/lib/gehirn_dns/resource.rb +71 -0
- data/lib/gehirn_dns/resource/preset.rb +19 -0
- data/lib/gehirn_dns/resource/record.rb +83 -0
- data/lib/gehirn_dns/resource/record_set.rb +151 -0
- data/lib/gehirn_dns/resource/version.rb +73 -0
- data/lib/gehirn_dns/resource/zone.rb +71 -0
- data/lib/gehirn_dns/version.rb +5 -0
- metadata +119 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: 58c59e72437e8283188c2c51075d062b11e4b58a
|
|
4
|
+
data.tar.gz: 270c708b1baa5ea61359797db016b9318351e1b0
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 1b6af906fe22e06b6aa6c7b04cf21c47e769cc1b274cc41d5192a5fadb4a54d87d2b4796e7de53ba553a28bbe9749b91d76550250d0b73adad9bb92fdb672171
|
|
7
|
+
data.tar.gz: 4ef110de89b2b7a047d84ac0480e0f9189d7e2e218e09bf4089bfdf26a308a1dc5b6d5cdac467028c3667579d11c7d0366a5976c72b1e978ca60741b2c3c8ca2
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
AsciiComments:
|
|
2
|
+
Enabled: false
|
|
3
|
+
Documentation:
|
|
4
|
+
Enabled: false
|
|
5
|
+
LineLength:
|
|
6
|
+
Enabled: false
|
|
7
|
+
PercentLiteralDelimiters:
|
|
8
|
+
Enabled: false
|
|
9
|
+
ClassLength:
|
|
10
|
+
Max: 200
|
|
11
|
+
MethodLength:
|
|
12
|
+
Max: 20
|
|
13
|
+
Metrics/AbcSize:
|
|
14
|
+
Enabled: false
|
|
15
|
+
Style/ClassAndModuleChildren:
|
|
16
|
+
Enabled: false
|
|
17
|
+
AllCops:
|
|
18
|
+
Exclude:
|
|
19
|
+
- 'Rakefile'
|
|
20
|
+
- 'bin/rake'
|
|
21
|
+
- 'vendor/**/*'
|
|
22
|
+
- 'doc/**/*'
|
|
23
|
+
- 'coverage/**/*'
|
|
24
|
+
- 'tmp/**/*'
|
|
25
|
+
TargetRubyVersion: 2.4
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
BSD 2-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2017, kyontan
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
* Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
17
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
18
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
19
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
20
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
21
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
22
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
23
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
24
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
25
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# `gehirndns-ruby`
|
|
2
|
+
|
|
3
|
+
An API Client of [Gehirn DNS](https://www.gehirn.jp/gis/dns.html) for Ruby
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add this line to your application's Gemfile:
|
|
8
|
+
(*This way can't use now, I'll publish this to rubygems on nearby 2017-08-01)
|
|
9
|
+
|
|
10
|
+
```ruby
|
|
11
|
+
gem 'gehirn_dns'
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
And then execute:
|
|
15
|
+
|
|
16
|
+
$ bundle
|
|
17
|
+
|
|
18
|
+
Or install it yourself as:
|
|
19
|
+
|
|
20
|
+
$ gem install gehirn_dns
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
# Create client instance
|
|
26
|
+
client = GehirnDns::Client.new(token: "nice_token", secret: "mikakunin")
|
|
27
|
+
|
|
28
|
+
# Get all managing zones
|
|
29
|
+
client.zones
|
|
30
|
+
|
|
31
|
+
# Get specify zone
|
|
32
|
+
zone = client.zone(name: "example.jp")
|
|
33
|
+
|
|
34
|
+
# Get all record sets (A, AAAA, TXT, ...) of current version
|
|
35
|
+
# zone.current_record_sets
|
|
36
|
+
|
|
37
|
+
# Get all versions
|
|
38
|
+
# zone.versions
|
|
39
|
+
|
|
40
|
+
# Get current specify record set (name, type are optional)
|
|
41
|
+
current_record_set = zone.current_record_set(name: "miku.example.jp.", type: :A)
|
|
42
|
+
# => #<GehirnDns::RecordSet
|
|
43
|
+
# @alias_to=nil,
|
|
44
|
+
# @editable=false,
|
|
45
|
+
# @enable_alias=false,
|
|
46
|
+
# @id=...,
|
|
47
|
+
# @name="miku.example.jp.",
|
|
48
|
+
# @records=[#<GehirnDns::Record @address=... >],
|
|
49
|
+
# @ttl=3600,
|
|
50
|
+
# @type=:A,
|
|
51
|
+
# @version=...>
|
|
52
|
+
|
|
53
|
+
current_record_set.records
|
|
54
|
+
# => [#<GehirnDns::Record
|
|
55
|
+
# @address="10.39.39.39",
|
|
56
|
+
# @record_set=
|
|
57
|
+
# #<GehirnDns::RecordSet
|
|
58
|
+
# @alias_to=nil,
|
|
59
|
+
# @base_path=...,
|
|
60
|
+
# @client=...,
|
|
61
|
+
# @editable=false,
|
|
62
|
+
# @enable_alias=false,
|
|
63
|
+
# @id=...,
|
|
64
|
+
# @name="miku.example.jp.",
|
|
65
|
+
# @records=[...],
|
|
66
|
+
# @ttl=3600,
|
|
67
|
+
# @type=:A, ...>>,
|
|
68
|
+
# ...]
|
|
69
|
+
|
|
70
|
+
# It's possible to edit DNS record directly! (if the record is editable: latest version or not migrated yet)
|
|
71
|
+
# current_record_set.records.first.address = "10.0.0.22"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# you can get all versions (already sorted by the time):
|
|
75
|
+
# zone.versions
|
|
76
|
+
|
|
77
|
+
# Let's begin add record set, and migrate!
|
|
78
|
+
new_version = zone.current_version.clone(name: "Add A record to megu.example.jp.")
|
|
79
|
+
|
|
80
|
+
# You can set :A, :AAAA, :CNAME, :MX, :NS, :SRV, :TXT, and following attributes are to be set to records
|
|
81
|
+
# A, AAAA: address
|
|
82
|
+
# CNAME: cname
|
|
83
|
+
# MX: prio exchange
|
|
84
|
+
# NS: nsdname
|
|
85
|
+
# SRV: target port weight
|
|
86
|
+
# TXT: data
|
|
87
|
+
|
|
88
|
+
new_record_set = GehirnDns::RecordSet.new(name: "megu.example.jp.", ttl: 300, type: :A)
|
|
89
|
+
# => #<GehirnDns::RecordSet
|
|
90
|
+
# @alias_to=nil,
|
|
91
|
+
# @editable=true,
|
|
92
|
+
# @enable_alias=false,
|
|
93
|
+
# @id=nil,
|
|
94
|
+
# @name="megu.example.jp.",
|
|
95
|
+
# @records=[],
|
|
96
|
+
# @ttl=300,
|
|
97
|
+
# @type=:A, ...>
|
|
98
|
+
|
|
99
|
+
# of course, you can edit as:
|
|
100
|
+
# new_record_set.name = "megu.example.jp."
|
|
101
|
+
# new_record_set.ttl = 300
|
|
102
|
+
# new_record_set.type = :A
|
|
103
|
+
|
|
104
|
+
new_record_set << GehirnDns::Record.new(address: '10.22.39.22')
|
|
105
|
+
|
|
106
|
+
# Add second record (DNS Round-robin)
|
|
107
|
+
new_record_set << GehirnDns::Record.new(address: '10.22.39.23')
|
|
108
|
+
|
|
109
|
+
# If you want to alias existing domain:
|
|
110
|
+
# new_record_set.alias_to = "example.jp."
|
|
111
|
+
|
|
112
|
+
# Add record set to new version
|
|
113
|
+
new_version << new_record_set
|
|
114
|
+
|
|
115
|
+
# Ship it!
|
|
116
|
+
# (applied_at is to be enough later to gradually decrease TTL by Gehirn DNS, or denied)
|
|
117
|
+
new_version.migrate(name: "Add megu.example.jp!", applied_at: Time.now + 600)
|
|
118
|
+
# => #<GehirnDns::Preset:0x007fd7d73a2df0
|
|
119
|
+
# @applied_at=2017-07-24 14:xx:yy UTC,
|
|
120
|
+
# @completed_at=2017-07-24 14:xx:yy UTC,
|
|
121
|
+
# @created_at=2017-07-24 14:xx:yy UTC,
|
|
122
|
+
# @id=...,
|
|
123
|
+
# @is_completed=false,
|
|
124
|
+
# @name="Add megu.example.jp!",
|
|
125
|
+
# @next_version_id=nil,
|
|
126
|
+
# @prev_version_id=...>
|
|
127
|
+
|
|
128
|
+
# Or you can apply just now!
|
|
129
|
+
# new_version.migrate!
|
|
130
|
+
|
|
131
|
+
# You can refer migrations by:
|
|
132
|
+
migration = zone.migrations.last
|
|
133
|
+
# migration.next_migration
|
|
134
|
+
# migration.prev_migration
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Development
|
|
138
|
+
|
|
139
|
+
From bundler auto generated doc:
|
|
140
|
+
|
|
141
|
+
> After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
142
|
+
>
|
|
143
|
+
> To install this gem onto your local machine, run `bundle exec rake install`.
|
|
144
|
+
|
|
145
|
+
## Contributing
|
|
146
|
+
|
|
147
|
+
Any questions, bug reports, patches are welcome on GitHub at [kyontan/gehirndns_ruby](https://github.com/kyontan/gehirndns_ruby)
|
|
148
|
+
|
|
149
|
+
## LICENCE
|
|
150
|
+
|
|
151
|
+
Refer LICENCE.md. Also, this library is licenced as [](https://github.com/MakeNowJust/sushi-ware)
|
data/Rakefile
ADDED
data/bin/setup
ADDED
data/gehirn_dns.gemspec
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
5
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
6
|
+
require 'gehirn_dns/version'
|
|
7
|
+
|
|
8
|
+
Gem::Specification.new do |spec|
|
|
9
|
+
spec.name = 'gehirn_dns'
|
|
10
|
+
spec.version = GehirnDns::VERSION
|
|
11
|
+
spec.authors = ['kyontan']
|
|
12
|
+
spec.email = ['kyontan@monora.me']
|
|
13
|
+
|
|
14
|
+
spec.summary = 'The Gehirn DNS API client for Ruby'
|
|
15
|
+
spec.description = 'The Gehirn DNS API client for Ruby'
|
|
16
|
+
spec.homepage = 'https://github.com/kyontan/gehirn_dns'
|
|
17
|
+
|
|
18
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
|
19
|
+
f.match(%r{^(test|spec|features)/})
|
|
20
|
+
end
|
|
21
|
+
spec.bindir = 'exe'
|
|
22
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
23
|
+
spec.require_paths = ['lib']
|
|
24
|
+
|
|
25
|
+
spec.add_development_dependency 'bundler', '~> 1.14'
|
|
26
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
|
27
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
|
28
|
+
spec.add_development_dependency 'webmock'
|
|
29
|
+
end
|
data/lib/gehirn_dns.rb
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'net/http'
|
|
4
|
+
require 'json'
|
|
5
|
+
|
|
6
|
+
require 'gehirn_dns/resource'
|
|
7
|
+
require 'gehirn_dns/resource/preset'
|
|
8
|
+
require 'gehirn_dns/resource/record'
|
|
9
|
+
require 'gehirn_dns/resource/record_set'
|
|
10
|
+
require 'gehirn_dns/resource/version'
|
|
11
|
+
require 'gehirn_dns/resource/zone'
|
|
12
|
+
require 'gehirn_dns/version'
|
|
13
|
+
|
|
14
|
+
module GehirnDns
|
|
15
|
+
class Client
|
|
16
|
+
attr_accessor :base_uri, :token, :secret
|
|
17
|
+
|
|
18
|
+
DEFAULT_USER_AGENT = "gehirndns-ruby/#{::GehirnDns::VERSION}"
|
|
19
|
+
|
|
20
|
+
def initialize(options = {})
|
|
21
|
+
@base_uri = ::URI.parse(options[:base_uri] || ENV.fetch('GEHIRN_DNS_BASE_URL', DEFAULT_BASE_URL))
|
|
22
|
+
@token = options[:token] || ENV.fetch('GEHIRN_DNS_API_TOKEN')
|
|
23
|
+
@secret = options[:secret] || ENV.fetch('GEHIRN_DNS_API_SECRET')
|
|
24
|
+
@user_agent = options[:user_agent] || ENV.fetch('GEHIRN_DNS_USER_AGENT', DEFAULT_USER_AGENT)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def get(path)
|
|
28
|
+
execute :get, path
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def post(path, body)
|
|
32
|
+
execute :post, path, body
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def put(path, body)
|
|
36
|
+
execute :put, path, body
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def delete(path)
|
|
40
|
+
execute :delete, path
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def base_uri=(base_uri)
|
|
44
|
+
@base_uri = ::URI.parse(base_uri)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def inspect
|
|
48
|
+
%Q(#<#{self.class}:#{object_id} @base_uri=#{@base_uri.inspect}, @secret=<HIDDEN>, @token=<HIDDEN>, @user_agent=#{@user_agent.inspect}>)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def execute(method, path, body = nil)
|
|
54
|
+
response = request(method, path, body)
|
|
55
|
+
|
|
56
|
+
body = if response.header['Content-Type']&.start_with? 'application/json'
|
|
57
|
+
JSON.parse(response.body, symbolize_names: true)
|
|
58
|
+
else
|
|
59
|
+
response.body
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
case response.code.to_i
|
|
63
|
+
when 200..299
|
|
64
|
+
body
|
|
65
|
+
when 401
|
|
66
|
+
raise UnauthorizedError, 'Expects API key has expired or not valid'
|
|
67
|
+
when 403
|
|
68
|
+
raise ForbiddenError, "Expects API key doesn't have a permission to request"
|
|
69
|
+
when 404
|
|
70
|
+
raise NotFoundError.new(path, body)
|
|
71
|
+
when 408
|
|
72
|
+
raise ReuqestTimeoutError.new(path, body)
|
|
73
|
+
when 500..599
|
|
74
|
+
raise RequestError.new(path, body)
|
|
75
|
+
else
|
|
76
|
+
raise RequestError.new(path, body)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def request_class_for(method)
|
|
81
|
+
case method.downcase.to_sym
|
|
82
|
+
when :get
|
|
83
|
+
::Net::HTTP::Get
|
|
84
|
+
when :post
|
|
85
|
+
::Net::HTTP::Post
|
|
86
|
+
when :put
|
|
87
|
+
::Net::HTTP::Put
|
|
88
|
+
when :delete
|
|
89
|
+
::Net::HTTP::Delete
|
|
90
|
+
else
|
|
91
|
+
raise ArgumentError, "method: #{method} isn't allowed."
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def http
|
|
96
|
+
http = ::Net::HTTP.new(@base_uri.host, @base_uri.port)
|
|
97
|
+
http.use_ssl = true
|
|
98
|
+
http
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def request(method, path, body = nil)
|
|
102
|
+
body = body.to_json if body.is_a? Hash
|
|
103
|
+
|
|
104
|
+
request_path = Pathname(@base_uri.path) + path.to_s
|
|
105
|
+
|
|
106
|
+
request = request_class_for(method).new(request_path.to_s)
|
|
107
|
+
|
|
108
|
+
if body
|
|
109
|
+
request.content_type = 'application/json'
|
|
110
|
+
request.body = body
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
request.basic_auth(@token, @secret)
|
|
114
|
+
http.request(request)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GehirnDns
|
|
4
|
+
class Error < StandardError
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
class ValidationError < Error
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
class UnrequestableError < Error
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
class RequestError < Error
|
|
14
|
+
attr_reader :path, :body
|
|
15
|
+
|
|
16
|
+
def initialize(path, body = nil)
|
|
17
|
+
@path = path
|
|
18
|
+
@body = body
|
|
19
|
+
|
|
20
|
+
message = "path: #{@path}"
|
|
21
|
+
message += ", response: #{@body}" if @body
|
|
22
|
+
super(message)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
class UnauthorizedError < RequestError
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
class ForbiddenError < RequestError
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
class NotFoundError < RequestError
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
class RequestToDeletedError < RequestError
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
class RequestTimeoutError < RequestError
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'time'
|
|
4
|
+
require 'pathname'
|
|
5
|
+
|
|
6
|
+
module GehirnDns
|
|
7
|
+
class Resource
|
|
8
|
+
attr_reader :client
|
|
9
|
+
|
|
10
|
+
def initialize(attrs = {}, client:, base_path: '')
|
|
11
|
+
attrs.each do |key, value|
|
|
12
|
+
if key.to_s.end_with? '_at'
|
|
13
|
+
# rubocop:disable Lint/HandleExceptions
|
|
14
|
+
begin
|
|
15
|
+
value = Time.parse(value)
|
|
16
|
+
rescue ArgumentError
|
|
17
|
+
# do nothing
|
|
18
|
+
end
|
|
19
|
+
# rubocop:enable Lint/HandleExceptions
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
instance_variable_set(:"@#{key}", value)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
@client = client
|
|
26
|
+
@base_path = Pathname(base_path)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
protected
|
|
30
|
+
|
|
31
|
+
def plulal_name
|
|
32
|
+
self.class.to_s.split('::').last.downcase + 's'
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def resource_path
|
|
36
|
+
@base_path + plulal_name + @id.to_s
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def http_get(*args)
|
|
40
|
+
execute(:get, *args)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def http_post(*args)
|
|
44
|
+
execute(:post, *args)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def http_put(*args)
|
|
48
|
+
execute(:put, *args)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def http_delete(*args)
|
|
52
|
+
response = execute(:delete, *args)
|
|
53
|
+
mark_as_deleted!
|
|
54
|
+
response
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def mark_as_deleted!
|
|
60
|
+
@deleted = true
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def execute(method, path, *args)
|
|
64
|
+
path = resource_path + path unless path.start_with? '/'
|
|
65
|
+
|
|
66
|
+
raise RequestToDeletedError, path if @deleted
|
|
67
|
+
|
|
68
|
+
@client.send(method, path, *args)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GehirnDns
|
|
4
|
+
class Preset < Resource
|
|
5
|
+
attr_reader :id, :name, :is_completed, :created_at, :applied_at, :completed_at, :next_version_id, :prev_version_id
|
|
6
|
+
|
|
7
|
+
def next_version
|
|
8
|
+
version = http_get "../../versions/#{@next_version_id}"
|
|
9
|
+
path = resource_path + '../../'
|
|
10
|
+
Version.new(version, client: @client, base_path: path)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def prev_version
|
|
14
|
+
version = http_get "../../versions/#{@prev_version_id}"
|
|
15
|
+
path = resource_path + '../../'
|
|
16
|
+
Version.new(version, client: @client, base_path: path)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GehirnDns
|
|
4
|
+
class Record
|
|
5
|
+
attr_reader :id
|
|
6
|
+
attr_accessor :record_set
|
|
7
|
+
|
|
8
|
+
RECORD_TYPES = %i(A AAAA CNAME MX NS SRV TXT).freeze
|
|
9
|
+
RECORD_FIELDS = {
|
|
10
|
+
A: %i(address),
|
|
11
|
+
AAAA: %i(address),
|
|
12
|
+
CNAME: %i(cname),
|
|
13
|
+
MX: %i(prio exchange),
|
|
14
|
+
NS: %i(nsdname),
|
|
15
|
+
SRV: %i(target port weight),
|
|
16
|
+
TXT: %i(data),
|
|
17
|
+
}.freeze
|
|
18
|
+
|
|
19
|
+
def initialize(record, record_set: nil)
|
|
20
|
+
@record_set = record_set
|
|
21
|
+
|
|
22
|
+
attribute_names(type: record_set&.type).each do |key|
|
|
23
|
+
instance_variable_set(:"@#{key}", record[key])
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
redefine_attributes
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def record_set=(record_set)
|
|
30
|
+
@record_set = record_set
|
|
31
|
+
|
|
32
|
+
redefine_attributes
|
|
33
|
+
|
|
34
|
+
@record_set << self if @record_set
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def to_h
|
|
38
|
+
attributes
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def attributes(type: @record_set&.type)
|
|
42
|
+
Hash[attribute_names(type: type).map{|attr| [attr, instance_variable_get(:"@#{attr}")] }]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def attribute_names(type: @record_set&.type)
|
|
46
|
+
return RECORD_FIELDS.values.flatten unless RECORD_FIELDS.has_key? type
|
|
47
|
+
RECORD_FIELDS[type]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def delete
|
|
51
|
+
@record_set.delete_record(self)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def all_attriubute_names
|
|
57
|
+
RECORD_FIELDS.values.flatten.uniq
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def redefine_attributes(type: @record_set&.type)
|
|
61
|
+
required_attrs = attribute_names(type: type)
|
|
62
|
+
|
|
63
|
+
all_attriubute_names.each do |attr|
|
|
64
|
+
inst_var_sym = :"@#{attr}"
|
|
65
|
+
setter_sym = "#{attr}="
|
|
66
|
+
|
|
67
|
+
if required_attrs.include? attr
|
|
68
|
+
define_singleton_method(attr) { instance_variable_get(inst_var_sym) }
|
|
69
|
+
|
|
70
|
+
define_singleton_method(setter_sym) do |value|
|
|
71
|
+
instance_variable_set(inst_var_sym, value)
|
|
72
|
+
@record_set&.update
|
|
73
|
+
end
|
|
74
|
+
else
|
|
75
|
+
remove_instance_variable(inst_var_sym) if instance_variable_defined?(inst_var_sym)
|
|
76
|
+
singleton_class.class_eval do
|
|
77
|
+
undef_method attr, setter_sym if respond_to?(setter_sym)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GehirnDns
|
|
4
|
+
class RecordSet < Resource
|
|
5
|
+
attr_reader :id, :type, :enable_alias, :editable, :version
|
|
6
|
+
attr_accessor :name, :alias_to, :ttl, :records
|
|
7
|
+
|
|
8
|
+
include Enumerable
|
|
9
|
+
|
|
10
|
+
def initialize(record_set, editable: true, version: nil, client: nil, base_path: '')
|
|
11
|
+
@id = record_set[:id]
|
|
12
|
+
@name = record_set[:name]
|
|
13
|
+
@type = record_set[:type]&.upcase&.to_sym
|
|
14
|
+
@ttl = record_set[:ttl]
|
|
15
|
+
@enable_alias = record_set[:enable_alias] || false
|
|
16
|
+
@editable = editable # API can ignore it
|
|
17
|
+
|
|
18
|
+
singleton_class.class_eval { attr_writer :id, :type, :enable_alias } unless @id
|
|
19
|
+
|
|
20
|
+
@version = version
|
|
21
|
+
|
|
22
|
+
if @enable_alias
|
|
23
|
+
@alias_to = record_set[:alias_to]
|
|
24
|
+
@records = []
|
|
25
|
+
else
|
|
26
|
+
@alias_to = nil
|
|
27
|
+
@records = (record_set[:records] || []).map { |r| Record.new(r, record_set: self) }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
super(client: client, base_path: base_path)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def each
|
|
34
|
+
@record_set.each { |r| yield r }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def name=(name)
|
|
38
|
+
@name = name
|
|
39
|
+
update
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def alias_to=(alias_to)
|
|
43
|
+
@alias_to = alias_to
|
|
44
|
+
@enable_alias = true
|
|
45
|
+
@records = []
|
|
46
|
+
update
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def ttl=(ttl)
|
|
50
|
+
raise StandardError, "alias record can't set ttl" if @enable_alias
|
|
51
|
+
@ttl = ttl
|
|
52
|
+
update
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def version=(version)
|
|
56
|
+
raise StandardError, "Can't edit version already has" if @id
|
|
57
|
+
|
|
58
|
+
@version = version
|
|
59
|
+
@client = version.client
|
|
60
|
+
@base_path = version.resource_path
|
|
61
|
+
|
|
62
|
+
begin
|
|
63
|
+
response = http_post '../records', to_h
|
|
64
|
+
@id = response[:id]
|
|
65
|
+
@name = response[:name]
|
|
66
|
+
rescue => e # failed to add record set, revert
|
|
67
|
+
@version = nil
|
|
68
|
+
@client = nil
|
|
69
|
+
@base_path = nil
|
|
70
|
+
raise e
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
self
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# append record
|
|
77
|
+
def <<(record)
|
|
78
|
+
return self if @records.include?(record) || self.equal?(record.record_set)
|
|
79
|
+
|
|
80
|
+
record = case record
|
|
81
|
+
when Hash
|
|
82
|
+
Record.new(record, record_set: self)
|
|
83
|
+
when Record
|
|
84
|
+
if record.record_set.nil?
|
|
85
|
+
record.record_set = self
|
|
86
|
+
elsif record.record_set != self
|
|
87
|
+
raise ArgumentError, "record is already member of a RecordSet"
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
record
|
|
91
|
+
else
|
|
92
|
+
raise ArgumentError
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
@enable_alias = false
|
|
96
|
+
@alias_to = nil
|
|
97
|
+
@records << record
|
|
98
|
+
|
|
99
|
+
begin
|
|
100
|
+
update
|
|
101
|
+
rescue => e # failed to add record, revert
|
|
102
|
+
@records.delete(record)
|
|
103
|
+
record.record_set = nil
|
|
104
|
+
raise e
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def records=(records)
|
|
109
|
+
@records = records.map { |r| Record.new(r, record_set: self) }
|
|
110
|
+
@enable_alias = false
|
|
111
|
+
|
|
112
|
+
update
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def to_h
|
|
116
|
+
{
|
|
117
|
+
id: @id,
|
|
118
|
+
type: @type,
|
|
119
|
+
enable_alias: @enable_alias,
|
|
120
|
+
name: @name,
|
|
121
|
+
ttl: @ttl,
|
|
122
|
+
records: @enable_alias ? nil : @records.map(&:to_h),
|
|
123
|
+
alias_to: @enable_alias ? @alias_to : nil,
|
|
124
|
+
}.compact
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def update
|
|
128
|
+
response = http_put '.', to_h if @client && @version
|
|
129
|
+
@name = response[:name]
|
|
130
|
+
self
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def delete
|
|
134
|
+
raise UnrequestableError, "record set doen't have a version" unless @client && @version
|
|
135
|
+
http_delete '.'
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def delete_record(record)
|
|
139
|
+
raise ArgumentError, "record isn't member of record set" unless @records.include? record || self.equal?(record.record_set)
|
|
140
|
+
raise ValidationError, "Can't delete only record of record set" if @records.size == 1
|
|
141
|
+
@records.delete(record)
|
|
142
|
+
update
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
protected
|
|
146
|
+
|
|
147
|
+
def plulal_name
|
|
148
|
+
'records'
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GehirnDns
|
|
4
|
+
class Version < Resource
|
|
5
|
+
attr_reader :id, :name, :editable, :created_at, :last_modified_at, :zone
|
|
6
|
+
|
|
7
|
+
def initialize(attrs = {}, zone:, client: nil, base_path: '')
|
|
8
|
+
@zone = zone
|
|
9
|
+
|
|
10
|
+
super(attrs, client: client, base_path: base_path)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# TODO: not fqdn matching
|
|
14
|
+
def record_sets(name: nil, type: nil)
|
|
15
|
+
type = type.upcase.to_sym if type
|
|
16
|
+
|
|
17
|
+
respnose = http_get 'records'
|
|
18
|
+
respnose \
|
|
19
|
+
.map { |record_set| RecordSet.new(record_set, editable: @editable, version: self, client: @client, base_path: resource_path) } \
|
|
20
|
+
.select { |record_set| (name.nil? || record_set.name == name) && (type.nil? || record_set.type == type) }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def record_set(id: nil, name: nil, type: nil)
|
|
24
|
+
raise ArgumentError, "passing both id and name is not allowed" if id && (name || type)
|
|
25
|
+
raise ArgumentError, "missing keyword: one of id, name, type is required" if !id && !(name || type)
|
|
26
|
+
|
|
27
|
+
if id
|
|
28
|
+
respnose = http_get "records/#{id}"
|
|
29
|
+
RecordSet.new(respnose, editable: @editable, version: self, client: @client, base_path: resource_path)
|
|
30
|
+
else
|
|
31
|
+
record_sets(name: name, type: type).first
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def create(name:, base: nil)
|
|
36
|
+
response = http_post '../', { name: name, base: base }.compact
|
|
37
|
+
Version.new(response, client: @client, base_path: resource_path + '../../')
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def clone(name:)
|
|
41
|
+
create(name: name, base: @id)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def delete
|
|
45
|
+
http_delete '.'
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def activate!
|
|
49
|
+
migrate(name: nil, applied_at: nil)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def <<(record_set)
|
|
53
|
+
raise ArgumentError unless record_set.is_a? RecordSet
|
|
54
|
+
record_set.version = self
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
alias migrate! activate!
|
|
58
|
+
|
|
59
|
+
# if prev version is nil, the latest version will set
|
|
60
|
+
def migrate(name:, applied_at:)
|
|
61
|
+
payload = {
|
|
62
|
+
applied_at: applied_at ? applied_at.getutc.strftime('%FT%TZ') : nil,
|
|
63
|
+
name: name,
|
|
64
|
+
next_version_id: @id
|
|
65
|
+
}.compact
|
|
66
|
+
|
|
67
|
+
response = http_post '../../presets', payload
|
|
68
|
+
Preset.new(response, client: @client, base_path: resource_path + '../../')
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
alias create_migration migrate
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GehirnDns
|
|
4
|
+
class Client
|
|
5
|
+
def zones
|
|
6
|
+
response = get 'zones'
|
|
7
|
+
response.map { |zone| Zone.new(zone, client: self, base_path: '') }
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def zone(id: nil, name: nil)
|
|
11
|
+
raise ArgumentError, "passing both id and name is not allowed" if id && name
|
|
12
|
+
raise ArgumentError, "missing keyword: id or name" if !id && !name
|
|
13
|
+
|
|
14
|
+
if id
|
|
15
|
+
response = get "zones/#{id}"
|
|
16
|
+
zone = Zone.new(response, client: self, base_path: '')
|
|
17
|
+
else
|
|
18
|
+
zone = zones.find { |z| z.name == name }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
raise NotFoundError if zone.nil?
|
|
22
|
+
|
|
23
|
+
zone
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
class Zone < Resource
|
|
28
|
+
attr_reader :id, :name, :editable, :created_at, :current_version_id, :last_modified_at
|
|
29
|
+
|
|
30
|
+
def current_version
|
|
31
|
+
response = http_get "versions/#{current_version_id}"
|
|
32
|
+
Version.new(response, zone: self, client: @client, base_path: resource_path)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def current_record_sets(**args)
|
|
36
|
+
current_version.record_sets(**args)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def current_record_set(**args)
|
|
40
|
+
current_version.record_set(**args)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def versions
|
|
44
|
+
response = http_get 'versions'
|
|
45
|
+
response.map { |version| Version.new(version, zone: self, client: @client, base_path: resource_path) }.sort_by!(&:last_modified_at)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def version(id: nil, name: nil)
|
|
49
|
+
raise ArgumentError, "passing both id and name is not allowed" if id && name
|
|
50
|
+
raise ArgumentError, "missing keyword: id or name" if !id && !name
|
|
51
|
+
|
|
52
|
+
if id
|
|
53
|
+
response = http_get "versions/#{id}"
|
|
54
|
+
version = Version.new(response, zone: self, client: @client, base_path: resource_path)
|
|
55
|
+
else
|
|
56
|
+
version = versions.find { |v| v.name == name }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
raise NotFoundError if version.nil?
|
|
60
|
+
|
|
61
|
+
version
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def presets
|
|
65
|
+
response = http_get 'presets'
|
|
66
|
+
response.map { |preset| Preset.new(preset, client: @client, base_path: resource_path) }.sort_by!(&:applied_at)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
alias migrations presets
|
|
70
|
+
end
|
|
71
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: gehirn_dns
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0.pre
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- kyontan
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2017-11-03 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.14'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '1.14'
|
|
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.0'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '10.0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rspec
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '3.0'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '3.0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: webmock
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '0'
|
|
69
|
+
description: The Gehirn DNS API client for Ruby
|
|
70
|
+
email:
|
|
71
|
+
- kyontan@monora.me
|
|
72
|
+
executables: []
|
|
73
|
+
extensions: []
|
|
74
|
+
extra_rdoc_files: []
|
|
75
|
+
files:
|
|
76
|
+
- ".gitignore"
|
|
77
|
+
- ".rspec"
|
|
78
|
+
- ".rubocop.yml"
|
|
79
|
+
- ".travis.yml"
|
|
80
|
+
- Gemfile
|
|
81
|
+
- LICENSE.md
|
|
82
|
+
- README.md
|
|
83
|
+
- Rakefile
|
|
84
|
+
- bin/setup
|
|
85
|
+
- gehirn_dns.gemspec
|
|
86
|
+
- lib/gehirn_dns.rb
|
|
87
|
+
- lib/gehirn_dns/client.rb
|
|
88
|
+
- lib/gehirn_dns/error.rb
|
|
89
|
+
- lib/gehirn_dns/resource.rb
|
|
90
|
+
- lib/gehirn_dns/resource/preset.rb
|
|
91
|
+
- lib/gehirn_dns/resource/record.rb
|
|
92
|
+
- lib/gehirn_dns/resource/record_set.rb
|
|
93
|
+
- lib/gehirn_dns/resource/version.rb
|
|
94
|
+
- lib/gehirn_dns/resource/zone.rb
|
|
95
|
+
- lib/gehirn_dns/version.rb
|
|
96
|
+
homepage: https://github.com/kyontan/gehirn_dns
|
|
97
|
+
licenses: []
|
|
98
|
+
metadata: {}
|
|
99
|
+
post_install_message:
|
|
100
|
+
rdoc_options: []
|
|
101
|
+
require_paths:
|
|
102
|
+
- lib
|
|
103
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
104
|
+
requirements:
|
|
105
|
+
- - ">="
|
|
106
|
+
- !ruby/object:Gem::Version
|
|
107
|
+
version: '0'
|
|
108
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
109
|
+
requirements:
|
|
110
|
+
- - ">"
|
|
111
|
+
- !ruby/object:Gem::Version
|
|
112
|
+
version: 1.3.1
|
|
113
|
+
requirements: []
|
|
114
|
+
rubyforge_project:
|
|
115
|
+
rubygems_version: 2.6.13
|
|
116
|
+
signing_key:
|
|
117
|
+
specification_version: 4
|
|
118
|
+
summary: The Gehirn DNS API client for Ruby
|
|
119
|
+
test_files: []
|