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.
@@ -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
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+
11
+ # rspec failure tracking
12
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
@@ -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
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.4.1
5
+ before_install: gem install bundler -v 1.14.6
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in gehirn_dns.gemspec
6
+ gemspec
7
+
8
+ gem 'pry', require: false
9
+ gem 'rubocop', require: false
@@ -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.
@@ -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 [![SUSHI-WARE LICENSE](https://img.shields.io/badge/license-SUSHI--WARE%F0%9F%8D%A3-blue.svg)](https://github.com/MakeNowJust/sushi-ware)
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -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
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'gehirn_dns/client'
4
+ require 'gehirn_dns/error'
5
+ require 'gehirn_dns/version'
6
+
7
+ module GehirnDns
8
+ DEFAULT_BASE_URL = 'https://api.gis.gehirn.jp/dns/v1/'
9
+ end
@@ -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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GehirnDns
4
+ VERSION = '1.0.0.pre'
5
+ 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: []