dslimple 1.1.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: dce8b719b5649f05c0aeb1797495cbba95c8498c
4
- data.tar.gz: a3109feace113af1c23069889437e116d9684832
2
+ SHA256:
3
+ metadata.gz: c0e4eb296b2f5d52fc5b6e2193573eb53c1799f34e9eb6829e9c0976b20a3384
4
+ data.tar.gz: ff7fbd6fd7854910d2c297a1358a1563dbcef9b43364fc7a3135f3feaad523d5
5
5
  SHA512:
6
- metadata.gz: f2507e4cf8ddba6bc18f92e4e94e189952b7841a8fc3a6a466b38326b8f70f7f407ea0985c31b2459a0c43b32f9450150317ae4dc4ca4171af5d7384d840a133
7
- data.tar.gz: 90a69e82def3664778c36c3617d7700ff8c5dbfd590a2d01d59addbede675bba08f3ced38638c69b6a132b1bb7c55f0344ca2391f6d5ee27c5d542c8a87a3dbd
6
+ metadata.gz: 22bcecdfd5cbc84328903cf2ab8fbac86c188da4977ace84cff5eafac18c76ab2213674c89d68911ceea169277c9fe280cff4ed0d1599b71104391a68360e576
7
+ data.tar.gz: 98035651710a0273eaebb74fdba659226ea94e225a59f11da494f2fc4e2c0646be78d975d37ddc274a703b9548e5a95f9408352114e8f90cf786f5425a44e89c
data/.gitignore CHANGED
@@ -7,5 +7,6 @@
7
7
  /pkg/
8
8
  /spec/reports/
9
9
  /tmp/
10
- /Domainfile
11
- /domainfiles
10
+ /Zonefile
11
+ /zonefiles
12
+ /.envrc
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
data/dslimple.gemspec CHANGED
@@ -21,8 +21,9 @@ Gem::Specification.new do |spec|
21
21
 
22
22
  spec.add_development_dependency 'bundler', '~> 1.11'
23
23
  spec.add_development_dependency 'rake', '~> 10.0'
24
+ spec.add_development_dependency 'rspec', '~> 3.0'
24
25
  spec.add_development_dependency 'rubocop'
25
26
 
26
- spec.add_dependency 'dnsimple', '~> 3.0'
27
+ spec.add_dependency 'dnsimple', '~> 4.0'
27
28
  spec.add_dependency 'thor', '~> 0.19'
28
29
  end
@@ -1,5 +1,5 @@
1
1
  require 'dslimple'
2
- require 'pp'
2
+ require 'dslimple/query_builder'
3
3
 
4
4
  class Dslimple::Applier
5
5
  OPERATION_COLORS = {
@@ -8,11 +8,10 @@ class Dslimple::Applier
8
8
  deletion: :red
9
9
  }.freeze
10
10
 
11
- attr_reader :api_client, :account, :shell, :options
11
+ attr_reader :client, :shell, :options
12
12
 
13
- def initialize(api_client, account, shell, options = {})
14
- @api_client = api_client
15
- @account = account
13
+ def initialize(client, shell, options = {})
14
+ @client = client
16
15
  @shell = shell
17
16
  @options = options
18
17
  end
@@ -22,10 +21,10 @@ class Dslimple::Applier
22
21
 
23
22
  dsl.execute
24
23
 
25
- expected_domains = dsl.transform
26
- expected_domains.select! { |domain| options[:only].include?(domain.name) } if options[:only].any?
24
+ expected_zones = dsl.transform
25
+ expected_zones.select! { |zone| options[:only].include?(zone.name) } unless options[:only].empty?
27
26
 
28
- @buildler = Dslimple::QueryBuilder.new(fetch_domains, expected_domains)
27
+ @buildler = Dslimple::QueryBuilder.new(fetch_zones, expected_zones, options)
29
28
  @buildler.execute
30
29
  queries = @buildler.filtered_queries(options)
31
30
 
@@ -41,11 +40,10 @@ class Dslimple::Applier
41
40
  apply(queries)
42
41
  end
43
42
 
44
- def fetch_domains
45
- domains = api_client.domains.all_domains(account.id).data.map { |domain| Dslimple::Domain.new(domain.name, api_client, account, id: domain.id) }
46
- domains.each(&:fetch_records!)
47
- domains.select! { |domain| options[:only].include?(domain.name) } if options[:only].any?
48
- domains
43
+ def fetch_zones
44
+ zones = client.all_zones(with_records: true)
45
+ zones.select! { |zone| options[:only].include?(zone.name) } if options[:only].any?
46
+ zones
49
47
  end
50
48
 
51
49
  def show_plan(queries)
@@ -59,7 +57,7 @@ class Dslimple::Applier
59
57
  shell.say('Apply', :bold)
60
58
  queries.each do |query|
61
59
  show_query(query)
62
- query.execute(api_client, account)
60
+ query.execute(client, client.account_id)
63
61
  end
64
62
  end
65
63
 
data/lib/dslimple/cli.rb CHANGED
@@ -2,30 +2,32 @@ require 'thor'
2
2
  require 'dnsimple'
3
3
  require 'json'
4
4
  require 'dslimple'
5
+ require 'dslimple/dsl'
6
+ require 'dslimple/client'
5
7
 
6
8
  class Dslimple::CLI < Thor
7
9
  include Thor::Actions
8
10
 
9
- SANDBOX_API_ENDPOINT = 'https://api.sandbox.dnsimple.com'.freeze
10
- USER_AGENT = "DSLimple: Simple CLI DNSimple client(v#{Dslimple::VERSION})".freeze
11
-
12
- class_option :email, type: :string, aliases: %w(-e), desc: 'Your E-Mail address'
13
- class_option :api_token, type: :string, aliases: %w(-t), desc: 'Your API token'
14
- class_option :domain_token, type: :string, aliases: %w(-dt), desc: 'Your Domain API token'
11
+ class_option :access_token, type: :string, aliases: %w(-a), desc: 'Your API token'
15
12
  class_option :sandbox, type: :boolean, default: ENV['DSLIMPLE_ENV'] == 'test', desc: 'Use sandbox API(at sandbox.dnsimple.com)'
16
13
  class_option :debug, type: :boolean, default: false
17
14
 
18
15
  desc 'export', 'Export domain specifications'
19
16
  method_option :only, type: :array, default: [], aliases: %w(-o), desc: 'Specify domains for export'
20
- method_option :file, type: :string, default: 'Domainfile', aliases: %w(-f), desc: 'Export Domainfile path'
21
- method_option :dir, type: :string, default: './domainfiles', aliases: %w(-d), desc: 'Export directory path for split'
17
+ method_option :file, type: :string, default: 'Zonefile', aliases: %w(-f), desc: 'Export zonefile path(- is stdout)'
18
+ method_option :dir, type: :string, default: './zonefiles', aliases: %w(-d), desc: 'Export directory path for split'
22
19
  method_option :split, type: :boolean, default: false, aliases: %w(-s), desc: 'Export with split by domains'
23
20
  method_option :modeline, type: :boolean, default: false, aliases: %w(-m), desc: 'Export with modeline for Vim'
24
- method_option :soa_and_ns, type: :boolean, default: false, desc: 'Export without SOA and NS records'
21
+ method_option :ignore, type: :array, default: ['system', 'child'], desc: 'Ignore record types'
25
22
  def export
26
- exporter = Dslimple::Exporter.new(api_client, account, options)
23
+ require 'dslimple/exporter'
24
+ fd = options[:file] == '-' ? STDOUT : File.open(options[:file].to_s, 'w')
25
+
26
+ exporter = Dslimple::Exporter.new(client, fd, options)
27
27
 
28
28
  exporter.execute
29
+
30
+ fd.close
29
31
  rescue => e
30
32
  rescue_from(e)
31
33
  end
@@ -33,13 +35,15 @@ class Dslimple::CLI < Thor
33
35
  desc 'apply', 'Apply domain specifications'
34
36
  method_option :only, type: :array, default: [], aliases: %w(-o), desc: 'Specify domains for apply'
35
37
  method_option :dry_run, type: :boolean, default: false, aliases: %w(-d)
36
- method_option :file, type: :string, default: 'Domainfile', aliases: %w(-f), desc: 'Source Domainfile path'
37
- method_option :addition, type: :boolean, default: true, desc: 'Add specified records'
38
- method_option :modification, type: :boolean, default: true, desc: 'Modify specified records'
39
- method_option :deletion, type: :boolean, default: true, desc: 'Delete unspecified records'
38
+ method_option :ignore, type: :array, default: ['system', 'child'], desc: 'Ignore record types'
39
+ method_option :file, type: :string, default: 'Zonefile', aliases: %w(-f), desc: 'Source Zonefile path'
40
+ method_option :addition, type: :boolean, default: true, desc: 'Allow add records'
41
+ method_option :modification, type: :boolean, default: true, desc: 'Allow modify records'
42
+ method_option :deletion, type: :boolean, default: true, desc: 'Allow delete records'
40
43
  method_option :yes, type: :boolean, default: false, aliases: %w(-y), desc: 'Do not confirm on before apply'
41
44
  def apply
42
- applier = Dslimple::Applier.new(api_client, account, self, options)
45
+ require 'dslimple/applier'
46
+ applier = Dslimple::Applier.new(client, self, options)
43
47
 
44
48
  applier.execute
45
49
  rescue => e
@@ -50,17 +54,10 @@ class Dslimple::CLI < Thor
50
54
 
51
55
  private
52
56
 
53
- def account
54
- @account ||= api_client.identity.whoami.data[:account]
55
- end
56
-
57
- def api_client
58
- @api_client ||= Dnsimple::Client.new(
59
- username: options[:email] || ENV['DSLIMPLE_EMAIL'],
57
+ def client
58
+ @aclient ||= Dslimple::Client.new(
60
59
  access_token: options[:api_token] || ENV['DSLIMPLE_API_TOKEN'] || ENV['DSLIMPLE_ACCESS_TOKEN'],
61
- domain_api_token: options[:domain_token] || ENV['DSLIMPLE_DOMAIN_TOKEN'],
62
- base_url: options[:sandbox] ? SANDBOX_API_ENDPOINT : nil,
63
- user_agent: USER_AGENT
60
+ sandbox: options[:sandbox]
64
61
  )
65
62
  end
66
63
 
@@ -0,0 +1,67 @@
1
+ require 'dnsimple'
2
+ require 'dslimple/zone'
3
+ require 'dslimple/record'
4
+
5
+ class Dslimple::Client < Dnsimple::Client
6
+ SANDBOX_URL = 'https://api.sandbox.dnsimple.com'
7
+ USER_AGENT = "dslimple v#{Dslimple::VERSION}"
8
+
9
+ def initialize(sandbox: false, **options)
10
+ options[:base_url] = SANDBOX_URL if sandbox
11
+ options[:user_agent] = USER_AGENT
12
+
13
+ options[:access_token] ||= ENV['DNSIMPLE_ACCESS_TOKEN']
14
+
15
+ super(options)
16
+ end
17
+
18
+ def account_id
19
+ return @account_id if instance_variable_defined?(:@account_id)
20
+
21
+ whoami = identity.whoami.data
22
+ @account_id = whoami.user&.id
23
+ @account_id = whoami.account.id if whoami.account
24
+
25
+ @account_id
26
+ end
27
+
28
+ def all_zones(with_records: false)
29
+ response = zones.list_zones(account_id)
30
+
31
+ zones = []
32
+ while response
33
+ zones = zones + response.data.map do |domain|
34
+ zone = Dslimple::Zone.new(domain.name)
35
+ zone.records = all_records(zone) if with_records
36
+ zone
37
+ end
38
+
39
+ response = if response.page < response.total_pages
40
+ zones.list_zones(account_id, domain, page: response.page + 1)
41
+ else
42
+ nil
43
+ end
44
+ end
45
+ zones
46
+ end
47
+
48
+ def all_records(zone)
49
+ response = zones.list_records(account_id, zone.name)
50
+
51
+ records = []
52
+ while response
53
+ records = records + response.data.map do |record|
54
+ record = Dslimple::Record.new(record)
55
+ record.zone = zone.name
56
+ record
57
+ end
58
+
59
+ response = if response.page < response.total_pages
60
+ zones.list_records(account_id, domain, page: response.page + 1)
61
+ else
62
+ nil
63
+ end
64
+ end
65
+ records
66
+ end
67
+ end
@@ -1,25 +1,39 @@
1
1
  require 'dslimple/dsl'
2
2
 
3
3
  class Dslimple::DSL::Record
4
- attr_reader :name, :content, :options
4
+ ATTRIBUTES = %i[zone name type content ttl priority regions]
5
+ attr_accessor *ATTRIBUTES
5
6
 
6
- def initialize(name, options = {}, &block)
7
- @name = name
8
- @options = options
7
+ def initialize(options = {}, &block)
8
+ options.each_pair do |key, val|
9
+ break unless respond_to?("#{key}=")
10
+
11
+ send("#{key}=", val)
12
+ end
9
13
 
10
14
  returned_content = instance_eval(&block)
11
15
  @content ||= returned_content
12
16
  end
13
17
 
14
- def priority(n)
15
- @options[:priority] = n.to_s.to_i
18
+ def [](key)
19
+ respond_to?(key) ? send(key) : nil
20
+ end
21
+
22
+ ATTRIBUTES.each do |attr|
23
+ define_method(attr) do |v = nil|
24
+ v ? instance_variable_set("@#{attr}", v) : instance_variable_get("@#{attr}")
25
+ end
26
+
27
+ define_method("#{attr}=") do |v|
28
+ instance_variable_set("@#{attr}", v)
29
+ end
16
30
  end
17
31
 
18
- def ttl(n)
19
- @options[:ttl] = n.to_s.to_i
32
+ def region(v = nil)
33
+ self.regions = [v].flatten
20
34
  end
21
35
 
22
- def content(c = nil)
23
- c ? @content = c : @content
36
+ def region=(v)
37
+ self.regions = [v].flatten
24
38
  end
25
39
  end
@@ -1,6 +1,7 @@
1
- require 'dslimple/dsl'
1
+ require 'dslimple/dsl/record'
2
+ require 'dslimple/record'
2
3
 
3
- class Dslimple::DSL::Domain
4
+ class Dslimple::DSL::Zone
4
5
  attr_reader :name, :records
5
6
 
6
7
  def initialize(name, &block)
@@ -15,11 +16,12 @@ class Dslimple::DSL::Domain
15
16
  options = options.merge(name)
16
17
  name = ''
17
18
  end
19
+ options = options.merge(zone: @name, name: name)
18
20
 
19
- @records << Dslimple::DSL::Record.new(name, options, &block)
21
+ @records << Dslimple::DSL::Record.new(options, &block)
20
22
  end
21
23
 
22
- Dslimple::Record::RECORD_TYPES.each do |type|
24
+ Dslimple::Record::RECORD_TYPES.map(&:downcase).each do |type|
23
25
  class_eval(<<-EOC)
24
26
  def #{type}_record(name = {}, options = {}, &block)
25
27
  record(name, options.merge(type: :#{type}), &block)
data/lib/dslimple/dsl.rb CHANGED
@@ -5,7 +5,7 @@ class Dslimple::DSL
5
5
  def initialize(file, context = {})
6
6
  @file = Pathname.new(file)
7
7
  @dir = @file.dirname
8
- @domains = []
8
+ @zones = []
9
9
  @files = []
10
10
 
11
11
  @context = context
@@ -20,6 +20,8 @@ class Dslimple::DSL
20
20
  evaluate(@dir.join(path))
21
21
  elsif @dir.join("#{path}.rb").exist?
22
22
  evaluate(@dir.join("#{path}.rb"))
23
+ elsif @dir.join("#{path}.zone").exist?
24
+ evaluate(@dir.join("#{path}.zone"))
23
25
  else
24
26
  Kernel.require(path)
25
27
  end
@@ -34,15 +36,15 @@ class Dslimple::DSL
34
36
  raise Dslimple::DSL::Error, "#{e.class}: #{e.message}", cleanup_backtrace(e.backtrace)
35
37
  end
36
38
 
37
- def domain(name, &block)
38
- @domains << Dslimple::DSL::Domain.new(name, &block)
39
+ def zone(name, &block)
40
+ @zones << Dslimple::DSL::Zone.new(name, &block)
39
41
  end
40
42
 
41
43
  def transform
42
- @domains.map do |domain|
43
- Dslimple::Domain.new(domain.name, nil, nil).tap do |model|
44
- model.records = domain.records.map do |record|
45
- Dslimple::Record.new(model, record.options[:type], record.name, record.content, record.options)
44
+ @zones.map do |zone|
45
+ Dslimple::Zone.new(zone.name).tap do |model|
46
+ model.records = zone.records.map do |record|
47
+ Dslimple::Record.new(record)
46
48
  end
47
49
  end
48
50
  end
@@ -60,6 +62,6 @@ class Dslimple::DSL
60
62
  end
61
63
  end
62
64
 
63
- require 'dslimple/dsl/domain'
65
+ require 'dslimple/dsl/zone'
64
66
  require 'dslimple/dsl/record'
65
67
  require 'dslimple/dsl/error'
@@ -2,55 +2,55 @@ require 'pathname'
2
2
  require 'dslimple'
3
3
 
4
4
  class Dslimple::Exporter
5
- attr_reader :api_client, :account, :options, :domains
5
+ attr_reader :client, :file, :options, :zones, :onlies
6
6
 
7
- def initialize(api_client, account, options)
8
- @api_client = api_client
9
- @account = account
7
+ def initialize(client, file, options)
8
+ @client = client
9
+ @file = file
10
10
  @options = options
11
- @domains = []
11
+ @onlies = [options[:only]].flatten.map(&:to_s).reject(&:empty?)
12
12
  end
13
13
 
14
14
  def execute
15
- @domains = fetch_domains
15
+ @zones = client.all_zones(with_records: true)
16
16
 
17
17
  if options[:split] && options[:dir]
18
- split_export(options[:dir], options[:file])
18
+ split_export(file, options[:dir])
19
19
  else
20
- export(options[:file], domains)
20
+ export(file, clean_zones(zones))
21
21
  end
22
22
  end
23
23
 
24
- def fetch_domains
25
- domains = api_client.domains.all_domains(account.id).data.map { |domain| Dslimple::Domain.new(domain.name, api_client, account) }
26
- domains.each(&:fetch_records!)
27
- domains.select! { |domain| options[:only].include?(domain.name) } unless options[:only].empty?
28
- domains
24
+ def export(fd, export_zones)
25
+ write_modeline(fd)
26
+ fd.puts export_zones.map { |zone| zone.to_dsl(options) }.join("\n")
29
27
  end
30
28
 
31
- def export(file, export_domains)
32
- File.open(file.to_s, 'w') do |fd|
33
- write_modeline(fd)
34
- fd.puts export_domains.map { |domain| domain.to_dsl(options) }.join("\n")
35
- end
36
- end
37
-
38
- def split_export(dir, file)
29
+ def split_export(fd, dir)
39
30
  dir = Pathname.new(dir)
40
- file = Pathname.new(file)
41
31
  Dir.mkdir(dir.to_s) unless dir.directory?
42
32
 
43
- File.open(file.to_s, 'w') do |fd|
44
- write_modeline(fd)
45
- domains.each do |domain|
46
- domainfile = dir.join(domain.name)
47
- export(domainfile, [domain])
48
- fd.puts "require '#{domainfile.relative_path_from(file.dirname)}'"
33
+ write_modeline(file)
34
+ clean_zones(zones).each do |zone|
35
+ zonefile = dir.join("#{zone.name}.zone")
36
+ File.open(zonefile, 'w') do |fd|
37
+ export(fd, [zone])
49
38
  end
39
+ fd.puts "require '#{zonefile.relative_path_from(file.dirname)}'"
50
40
  end
51
41
  end
52
42
 
53
43
  def write_modeline(fd)
54
44
  fd << "# -*- mode: ruby -*-\n# vi: set ft=ruby :\n\n" if options[:modeline]
55
45
  end
46
+
47
+ protected
48
+
49
+ def clean_zones(zones)
50
+ return zones if onlies.empty?
51
+
52
+ zones.select do |zone|
53
+ onlies.include?(zone.name)
54
+ end
55
+ end
56
56
  end
@@ -1,12 +1,12 @@
1
1
  require 'dslimple'
2
2
 
3
3
  class Dslimple::Query
4
- attr_reader :operation, :target, :domain, :params
4
+ attr_reader :operation, :target, :zone, :params
5
5
 
6
- def initialize(operation, target, domain, params = {})
6
+ def initialize(operation, target, zone, params = {})
7
7
  @operation = operation
8
8
  @target = target
9
- @domain = domain
9
+ @zone = zone
10
10
  @params = params
11
11
  end
12
12
 
@@ -19,17 +19,17 @@ class Dslimple::Query
19
19
  end
20
20
 
21
21
  def to_s
22
- if target == :domain
23
- domain.name.to_s
22
+ if target == :zone
23
+ zone.to_s
24
24
  else
25
- %(#{params[:record_type].to_s.rjust(5)} #{params[:name].to_s.rjust(10)}.#{domain.name} (#{record_options.join(', ')}) "#{params[:content]}")
25
+ %(#{params[:type].to_s.rjust(5)} #{params[:name].to_s.rjust(10)}.#{zone.to_s} (#{record_options.join(', ')}) "#{params[:content]}")
26
26
  end
27
27
  end
28
28
 
29
29
  def record_options
30
30
  options = []
31
31
  params.each_pair do |k, v|
32
- options << "#{k}: #{v}" if %i(ttl prio).include?(k) && v
32
+ options << "#{k}: #{v}" if %i(ttl priority regions).include?(k) && v
33
33
  end
34
34
  options
35
35
  end
@@ -38,23 +38,25 @@ class Dslimple::Query
38
38
  __send__("execute_#{target}", api_client, account)
39
39
  end
40
40
 
41
- def execute_domain(api_client, account)
41
+ def execute_zone(api_client, account_id)
42
42
  case operation
43
43
  when :addition
44
- api_client.registrar.register_domain(account.id, domain.name, registrant_id: account.id, auto_renew: true)
44
+ # TODO: Support registration
45
+ # api_client.registrar.register_zone(account_id, zone, registrant_id: account.id, auto_renew: true)
45
46
  when :deletion
46
- api_client.domains.delete_domain(account.id, domain.name)
47
+ # TODO: Support deletion
48
+ # api_client.zones.delete_zone(account_id, zone)
47
49
  end
48
50
  end
49
51
 
50
- def execute_record(api_client, account)
52
+ def execute_record(api_client, account_id)
51
53
  case operation
52
54
  when :addition
53
- api_client.zones.create_record(account.id, domain.name, params)
55
+ api_client.zones.create_record(account_id, zone, params)
54
56
  when :modification
55
- api_client.zones.update_record(account.id, domain.name, params[:id], params)
57
+ api_client.zones.update_record(account_id, zone, params[:id], params)
56
58
  when :deletion
57
- api_client.zones.delete_record(account.id, domain.name, params[:id])
59
+ api_client.zones.delete_record(account_id, zone, params[:id])
58
60
  end
59
61
  end
60
62
  end
@@ -1,41 +1,43 @@
1
1
  require 'dslimple'
2
+ require 'dslimple/query'
2
3
 
3
4
  class Dslimple::QueryBuilder
4
- attr_reader :queries
5
- attr_reader :expected_domains, :current_domains
5
+ attr_reader :queries, :options
6
+ attr_reader :expected_zones, :current_zones
6
7
  attr_reader :append_records, :change_records, :delete_records
7
8
 
8
- def initialize(current_domains, expected_domains)
9
- @current_domains = Hash[*current_domains.map { |domain| [domain.name, domain] }.flatten]
10
- @expected_domains = Hash[*expected_domains.map { |domain| [domain.name, domain] }.flatten]
9
+ def initialize(current_zones, expected_zones, options = {})
10
+ @current_zones = Hash[*current_zones.map { |zone| [zone.name, zone] }.flatten]
11
+ @expected_zones = Hash[*expected_zones.map { |zone| [zone.name, zone] }.flatten]
12
+ @options = options
11
13
  end
12
14
 
13
- def append_domains
14
- @append_domains ||= expected_domains.values.reject { |domain| current_domains.key?(domain.name) }
15
+ def append_zones
16
+ @append_zones ||= expected_zones.values.reject { |zone| current_zones.key?(zone.name) }
15
17
  end
16
18
 
17
- def delete_domains
18
- @delete_domains ||= current_domains.values.reject { |domain| expected_domains.key?(domain.name) }
19
+ def delete_zones
20
+ @delete_zones ||= current_zones.values.reject { |zone| expected_zones.key?(zone.name) }
19
21
  end
20
22
 
21
23
  def execute
22
- @append_records = append_domains.map(&:records_without_soa_ns).flatten
24
+ @append_records = append_zones.map { |z| z.clean_records(options[:ignore]) }.flatten
23
25
  @change_records = []
24
- @delete_records = delete_domains.map(&:records_without_soa_ns).flatten
26
+ @delete_records = delete_zones.map { |z| z.clean_records(options[:ignore]) }.flatten
25
27
 
26
- expected_domains.each_pair do |name, domain|
27
- execute_records(name, domain)
28
+ expected_zones.each_pair do |name, zone|
29
+ execute_records(name, zone)
28
30
  end
29
31
 
30
32
  build_queries
31
33
  end
32
34
 
33
- def execute_records(domain_name, domain)
34
- current_domain = current_domains[domain_name]
35
- return unless current_domain
35
+ def execute_records(zone_name, zone)
36
+ current_zone = current_zones[zone_name]
37
+ return unless current_zone
36
38
 
37
- current_records = current_domain.records_without_soa_ns.dup
38
- domain.records_without_soa_ns.each do |record|
39
+ current_records = current_zone.clean_records(options[:ignore]).dup
40
+ zone.clean_records(options[:ignore]).each do |record|
39
41
  at = current_records.index { |current| current == record }
40
42
  current_record = at ? current_records.slice!(at) : nil
41
43
  like_record = current_records.find { |current| current === record }
@@ -53,24 +55,24 @@ class Dslimple::QueryBuilder
53
55
  def build_queries
54
56
  @queries = []
55
57
 
56
- append_domains.each do |domain|
57
- @queries << Dslimple::Query.new(:addition, :domain, domain)
58
+ append_zones.each do |zone|
59
+ @queries << Dslimple::Query.new(:addition, :zone, zone)
58
60
  end
59
61
 
60
62
  append_records.each do |record|
61
- @queries << Dslimple::Query.new(:addition, :record, record.domain, record.to_params)
63
+ @queries << Dslimple::Query.new(:addition, :record, record.zone, record.to_params)
62
64
  end
63
65
 
64
66
  change_records.each do |old, new|
65
- @queries << Dslimple::Query.new(:modification, :record, new.domain, new.to_params.merge(id: old.id))
67
+ @queries << Dslimple::Query.new(:modification, :record, new.zone, new.to_params.merge(id: old.id))
66
68
  end
67
69
 
68
70
  delete_records.each do |record|
69
- @queries << Dslimple::Query.new(:deletion, :record, record.domain, record.to_params)
71
+ @queries << Dslimple::Query.new(:deletion, :record, record.zone, record.to_params)
70
72
  end
71
73
 
72
- delete_domains.each do |domain|
73
- @queries << Dslimple::Query.new(:deletion, :domain, domain)
74
+ delete_zones.each do |zone|
75
+ @queries << Dslimple::Query.new(:deletion, :zone, zone)
74
76
  end
75
77
 
76
78
  @queries
@@ -1,114 +1,82 @@
1
1
  require 'dslimple'
2
+ require 'dnsimple'
3
+ require 'dslimple'
2
4
 
3
5
  class Dslimple::Record
4
- RECORD_TYPES = %i(a alias cname mx spf url txt ns srv naptr ptr aaaa sshfp hinfo pool).freeze
5
- ALIAS_PREFIX = /\AALIAS for /
6
- SPF_PREFIX = /\Av=spf1 /
7
-
8
- attr_reader :domain, :type, :name, :id, :ttl, :priority, :content
9
-
10
- def self.cleanup_records(records)
11
- records = records.dup
12
- alias_records = records.select(&:alias?)
13
- spf_records = records.select(&:spf?)
14
- txt_records = records.select { |record| record.like_spf? || record.like_alias? }
15
-
16
- txt_records.each do |record|
17
- reject = record.like_spf? ? spf_records.any? { |r| record.eql_spf?(r) } : alias_records.any? { |r| record.eql_alias?(r) }
18
-
19
- records.delete(record) if reject
20
- end
6
+ RECORD_TYPES = %w[A ALIAS CNAME MX SPF URL TXT NS SRV NAPTR PTR AAAA SSHFP HINFO POOL CAA]
7
+ DEFAULT_REGIONS = ['global']
21
8
 
22
- records
23
- end
24
-
25
- RECORD_TYPES.each do |type|
26
- class_eval(<<-EOC)
27
- def #{type}?
28
- type == :#{type}
29
- end
30
- EOC
31
- end
9
+ attr_accessor :id, :zone, :name, :type, :ttl, :priority, :content, :regions, :system_record
32
10
 
33
- def initialize(domain, type, name, content, options = {})
34
- @domain = domain
35
- @type = type.to_s.downcase.to_sym
36
- @name = name
37
- @content = content
38
- @ttl = options[:ttl]
39
- @priority = options[:priority]
40
- @id = options[:id]
41
- end
42
-
43
- def escaped_name
44
- Dslimple.escape_single_quote(name)
45
- end
11
+ def initialize(attrs = {})
12
+ attrs = normalize_attrs(attrs) if attrs.is_a?(Dnsimple::Struct::ZoneRecord)
46
13
 
47
- def escaped_content
48
- Dslimple.escape_single_quote(content)
14
+ @id = attrs[:id]
15
+ @zone = attrs[:zone_id] || attrs[:zone]
16
+ @name = attrs[:name]
17
+ @type = attrs[:type].to_s.downcase.to_sym
18
+ @ttl = attrs[:ttl]
19
+ @parent_id = attrs[:parent_id]
20
+ @priority = attrs[:priority]
21
+ @content = attrs[:content]
22
+ @regions = attrs[:regions] || DEFAULT_REGIONS
23
+ @system_record = attrs[:system_record]
49
24
  end
50
25
 
51
- def like_spf?
52
- txt? && content.match(SPF_PREFIX)
26
+ def [](key)
27
+ send(key)
53
28
  end
54
29
 
55
- def like_alias?
56
- txt? && content.match(ALIAS_PREFIX)
30
+ def system_record?
31
+ @system_record == true
57
32
  end
58
33
 
59
- def eql_spf?(spf_record)
60
- spf_record.ttl == ttl && spf_record.content == content
61
- end
62
-
63
- def eql_alias?(alias_record)
64
- alias_record.ttl == ttl && content == "ALIAS for #{alias_record.content}"
34
+ def child_record?
35
+ !@parent_id.nil?
65
36
  end
66
37
 
67
38
  def ==(other)
68
- other.is_a?(Dslimple::Record) && other.domain == domain && other.hash == hash
39
+ other.is_a?(self.class) && hash == other.hash
69
40
  end
70
- alias_method :eql, :==
71
41
 
72
42
  def ===(other)
73
- other.is_a?(Dslimple::Record) && other.domain == domain && other.rough_hash == rough_hash
43
+ other.is_a?(self.class) && %i[zone name type].all? { |attr| send(attr).to_s == other.send(attr).to_s }
74
44
  end
75
45
 
76
46
  def hash
77
- "#{type}:#{name}:#{content}:#{ttl}:#{priority}"
78
- end
79
-
80
- def rough_hash
81
- "#{type}:#{name}:#{content}"
47
+ [zone.to_s, name, type, ttl, priority, content, regions.join('|'), !!system_record].hash
82
48
  end
83
49
 
84
- def to_dsl_options
85
- options = []
86
- options << "'#{escaped_name}'" unless escaped_name.empty?
87
- options << "ttl: #{ttl}" if ttl
88
- options << "priority: #{priority}" if priority
89
- options.join(', ')
90
- end
91
-
92
- def to_dsl
93
- <<"EOD"
94
- #{type}_record #{to_dsl_options} do
95
- '#{escaped_content}'
96
- end
97
- EOD
50
+ def to_dsl(options = {})
51
+ [
52
+ " #{type.to_s.downcase}_record(#{name.inspect}) do",
53
+ priority ? " priority #{priority.inspect}" : "",
54
+ ttl ? " ttl #{ttl}" : "",
55
+ regions != DEFAULT_REGIONS ? " regions #{regions.inspect}" : "",
56
+ " content #{content.inspect}",
57
+ " end",
58
+ ].reject(&:empty?).join("\n")
98
59
  end
99
60
 
100
61
  def to_params
101
62
  {
102
- id: id,
103
- type: type.to_s.upcase,
63
+ id: @id,
104
64
  name: name,
65
+ type: type.to_s.upcase,
105
66
  content: content,
106
67
  ttl: ttl,
107
- priority: priority
68
+ priority: priority,
69
+ regions: regions,
108
70
  }
109
71
  end
110
72
 
111
- def inspect
112
- "<Dslimple::Record #{type.to_s.upcase} #{name}: #{content}>"
73
+ private
74
+
75
+ def normalize_attrs(record)
76
+ attrs = {}
77
+ record.instance_variables.each do |var|
78
+ attrs[:"#{var.to_s.sub(/^@/, '')}"] = record.instance_variable_get(var)
79
+ end
80
+ attrs
113
81
  end
114
82
  end
@@ -1,3 +1,3 @@
1
1
  module Dslimple
2
- VERSION = '1.1.0'.freeze
2
+ VERSION = '2.0.0'.freeze
3
3
  end
@@ -0,0 +1,46 @@
1
+ require 'dslimple'
2
+ require 'dslimple/record'
3
+
4
+ class Dslimple::Zone
5
+ attr_reader :name
6
+ attr_accessor :records
7
+
8
+ def initialize(name)
9
+ @name = name
10
+ @records = []
11
+ end
12
+
13
+ def ==(other)
14
+ other.is_a?(self.class) && hash == other.hash && records.hash == other.records.hash
15
+ end
16
+
17
+ def ===(other)
18
+ other.is_a?(self.class) && hash == other.hash
19
+ end
20
+
21
+ def hash
22
+ [name, records.map(&:hash)].hash
23
+ end
24
+
25
+ def to_s
26
+ name
27
+ end
28
+
29
+ def to_dsl(options = {})
30
+ <<DSL
31
+ zone #{name.inspect} do
32
+ #{clean_records(options[:ignore]).map { |record| record.to_dsl(options) }.join("\n\n")}
33
+ end
34
+ DSL
35
+ end
36
+
37
+ def clean_records(ignores)
38
+ ignores = [ignores].flatten.map(&:to_s)
39
+
40
+ records.select do |record|
41
+ next false if ignores.include?('system') && record.system_record?
42
+ next false if ignores.include?('child') && record.child_record?
43
+ true
44
+ end
45
+ end
46
+ end
data/lib/dslimple.rb CHANGED
@@ -1,18 +1 @@
1
1
  require 'dslimple/version'
2
- require 'dslimple/domain'
3
- require 'dslimple/record'
4
- require 'dslimple/dsl'
5
- require 'dslimple/query'
6
- require 'dslimple/query_builder'
7
- require 'dslimple/exporter'
8
- require 'dslimple/applier'
9
-
10
- module Dslimple
11
- ESCAPE_SINGLE_QUOTE_REGEXP = /[^\\]'/
12
-
13
- def self.escape_single_quote(string)
14
- string.gsub(ESCAPE_SINGLE_QUOTE_REGEXP) do |match|
15
- match[0] + "\\'"
16
- end
17
- end
18
- end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dslimple
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sho Kusano
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-05-02 00:00:00.000000000 Z
11
+ date: 2018-03-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
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'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: rubocop
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -58,14 +72,14 @@ dependencies:
58
72
  requirements:
59
73
  - - "~>"
60
74
  - !ruby/object:Gem::Version
61
- version: '3.0'
75
+ version: '4.0'
62
76
  type: :runtime
63
77
  prerelease: false
64
78
  version_requirements: !ruby/object:Gem::Requirement
65
79
  requirements:
66
80
  - - "~>"
67
81
  - !ruby/object:Gem::Version
68
- version: '3.0'
82
+ version: '4.0'
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: thor
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -90,6 +104,7 @@ extensions: []
90
104
  extra_rdoc_files: []
91
105
  files:
92
106
  - ".gitignore"
107
+ - ".rspec"
93
108
  - ".rubocop.yml"
94
109
  - Gemfile
95
110
  - LICENSE.txt
@@ -102,16 +117,17 @@ files:
102
117
  - lib/dslimple.rb
103
118
  - lib/dslimple/applier.rb
104
119
  - lib/dslimple/cli.rb
105
- - lib/dslimple/domain.rb
120
+ - lib/dslimple/client.rb
106
121
  - lib/dslimple/dsl.rb
107
- - lib/dslimple/dsl/domain.rb
108
122
  - lib/dslimple/dsl/error.rb
109
123
  - lib/dslimple/dsl/record.rb
124
+ - lib/dslimple/dsl/zone.rb
110
125
  - lib/dslimple/exporter.rb
111
126
  - lib/dslimple/query.rb
112
127
  - lib/dslimple/query_builder.rb
113
128
  - lib/dslimple/record.rb
114
129
  - lib/dslimple/version.rb
130
+ - lib/dslimple/zone.rb
115
131
  homepage: https://github.com/zeny-io/dslimple
116
132
  licenses:
117
133
  - MIT
@@ -132,7 +148,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
132
148
  version: '0'
133
149
  requirements: []
134
150
  rubyforge_project:
135
- rubygems_version: 2.5.1
151
+ rubygems_version: 2.7.6
136
152
  signing_key:
137
153
  specification_version: 4
138
154
  summary: DSLimple is a tool to manage DNSimple.
@@ -1,49 +0,0 @@
1
- require 'dslimple'
2
-
3
- class Dslimple::Domain
4
- attr_reader :name, :id
5
- attr_accessor :api_client, :account, :records
6
-
7
- def initialize(name, api_client, account, options = {})
8
- @name = name
9
- @id = options[:id]
10
- @api_client = api_client
11
- @account = account
12
- @records = []
13
- end
14
-
15
- def escaped_name
16
- Dslimple.escape_single_quote(name)
17
- end
18
-
19
- def records_without_soa_ns
20
- records.select { |record| record.type != :soa && record.type != :ns }
21
- end
22
-
23
- def fetch_records
24
- api_client.zones.all_records(account.id, name).data.map do |record|
25
- Dslimple::Record.new(self, record.type, record.name, record.content, ttl: record.ttl, priority: record.priority, id: record.id)
26
- end
27
- end
28
-
29
- def fetch_records!
30
- @records = fetch_records
31
- cleanup_records!
32
- end
33
-
34
- def cleanup_records!
35
- @records = Dslimple::Record.cleanup_records(records)
36
- end
37
-
38
- def ==(other)
39
- other.is_a?(Dslimple::Domain) && other.name == name
40
- end
41
- alias_method :eql, :==
42
-
43
- def to_dsl(options = {})
44
- <<"EOD"
45
- domain '#{escaped_name}' do
46
- #{(options[:soa_and_ns] ? records : records_without_soa_ns).map(&:to_dsl).join("\n")}end
47
- EOD
48
- end
49
- end