record_store 1.0.0 → 2.0.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 09372c0d086fbae7cc74ca55aee2a955e1a2f761
4
- data.tar.gz: 5b7bfedc45cb04ac8170286a608df7c83adcc71f
3
+ metadata.gz: e96a230fae8c5a5a73e2c51a2097c5b14094f82d
4
+ data.tar.gz: 55c913c54e64b78454624b0844ebf1e3ca74fa36
5
5
  SHA512:
6
- metadata.gz: 89ee26625e1b23999f383762e9ca785f52b9f89572f0b45b72b68e522139dea82dd52cf01f90a7bc62178aeda67d1e0538b07140f43ed139c13226284eead2f0
7
- data.tar.gz: f3f01c6a5b40cb4714bc0f147070c6d2590731c688a60fe7ae1ca83366fdaedc205ed7f24f2025a9f26239dbafb4328a3923db021101c1ad17348e4ffc5693c5
6
+ metadata.gz: 512431b9f45815ee10a9b8b10228660b944b862a99be5e1ecb42705cab33fd069f8787971881e0a905722ddf20b127c121c091da61af93b93d1ee568b48f66f9
7
+ data.tar.gz: d0f22e77f0a914a0c3ee11cef077cc8bd1e31eb4ce04acc8e351e01c43cff7218831e9621350ab631bcd9e62f9c943e2b690c1f275242be05479567ca36c5b75
data/.gitignore CHANGED
@@ -1,3 +1,4 @@
1
1
  ._*
2
2
  test/vcr_debug.log
3
3
  dev/
4
+ pkg/
data/README.md CHANGED
@@ -80,10 +80,11 @@ When running `bin/record-store apply`, a `Changeset` is generated by comparing t
80
80
  # Development
81
81
 
82
82
  To get started developing on Record Store, run `bin/setup`. This will create a development directory, `dev/`, that mimics what a production directory managing DNS records using Record Store would look like. Use it as a sandbox when developing Record Store.
83
+ You can use `bin/console` to get play with the dev data, or you can `cd` into `dev/` and use `bin/record-store` to test out the CLI.
83
84
 
84
85
  ### Adding new Providers
85
86
 
86
- To add a new Provider, create a class inherriting `Provider` in [`lib/record_store/provider/`](lib/record_store/provider/). The [DynECT provider](lib/record_store/provider/dnsimple.rb) is good to use as a reference implementation.
87
+ To add a new Provider, create a class inheriting `Provider` in [`lib/record_store/provider/`](lib/record_store/provider/). The [DynECT provider](lib/record_store/provider/dynect.rb) is good to use as a reference implementation.
87
88
 
88
89
  **Note**: _there's no need to wrap `Provider#apply_changeset` unless it's necessary to do something before/after making changes to a zone._
89
90
 
data/bin/console ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift File.expand_path('../../lib', __FILE__)
4
+ require 'bundler/setup'
5
+ require 'record_store'
6
+
7
+ RecordStore.zones_path = File.expand_path('../../dev/zones', __FILE__)
8
+
9
+ require 'irb'
10
+ IRB.start
data/dev.yml ADDED
@@ -0,0 +1,10 @@
1
+ # This file is for shopify internal development, feel free to ignore.
2
+ name: recordstore
3
+
4
+ up:
5
+ - ruby: 2.3.1
6
+ - bundler
7
+
8
+ packages:
9
+ - git@github.com:Shopify/dev-shopify.git
10
+
@@ -1,6 +1,7 @@
1
1
  module RecordStore
2
2
  class CLI < Thor
3
3
  class_option :config, desc: 'Path to config.yml', aliases: '-c'
4
+ FORMATS = %w[file directory]
4
5
 
5
6
  def initialize(*args)
6
7
  super
@@ -92,6 +93,7 @@ module RecordStore
92
93
 
93
94
  option :name, desc: 'Zone to download', aliases: '-n', type: :string, required: true
94
95
  option :provider, desc: 'Provider in which this zone exists', aliases: '-p', type: :string
96
+ option :format, desc: 'Format', aliases: '-f', type: :string, default: 'file', enum: FORMATS
95
97
  desc 'download', 'Downloads all records from zone and creates YAML zone definition in zones/ e.g. record-store download --name=shopify.io'
96
98
  def download
97
99
  name = options.fetch('name')
@@ -107,10 +109,26 @@ module RecordStore
107
109
  end
108
110
 
109
111
  puts "Downloading records for #{name}"
110
- Zone.download(name, provider)
112
+ Zone.download(name, provider, format: options.fetch('format').to_sym)
111
113
  puts "Records have been downloaded & can be found in zones/#{name}.yml"
112
114
  end
113
115
 
116
+ option :name, desc: 'Zone to reformat', aliases: '-n', type: :string, required: false
117
+ option :format, desc: 'Format', aliases: '-f', type: :string, default: 'file', enum: FORMATS
118
+ desc 'reformat', 'Sorts and re-outputs the zone (or all zones) as specified format (file)'
119
+ def reformat
120
+ name = options['name']
121
+ zones = if name
122
+ [Zone.find(name)]
123
+ else
124
+ Zone.all
125
+ end
126
+ zones.each do |zone|
127
+ puts "Writing #{zone.name}"
128
+ zone.write(format: options.fetch('format').to_sym)
129
+ end
130
+ end
131
+
114
132
  option :name, desc: 'Zone to sort', aliases: '-n', type: :string, required: true
115
133
  desc 'sort', 'Sorts the zonefile alphabetically e.g. record-store sort --name=shopify.io'
116
134
  def sort
@@ -1,3 +1,3 @@
1
1
  module RecordStore
2
- VERSION = '1.0.0'
2
+ VERSION = '2.0.0'
3
3
  end
@@ -12,6 +12,13 @@ module RecordStore
12
12
  @provider = provider
13
13
  end
14
14
 
15
+ def to_hash
16
+ {
17
+ provider: provider,
18
+ ignore_patterns: ignore_patterns,
19
+ }
20
+ end
21
+
15
22
  private
16
23
 
17
24
  def validate_zone_config
@@ -8,7 +8,7 @@ module RecordStore
8
8
  end
9
9
 
10
10
  def defined
11
- @defined ||= yaml_files.inject({}) { |zones, file| zones.merge(load_yaml_file(file)) }
11
+ @defined ||= yaml_files.inject({}) { |zones, file| zones.merge(load_yml_zone_definition(file)) }
12
12
  end
13
13
 
14
14
  def [](name)
@@ -21,19 +21,77 @@ module RecordStore
21
21
 
22
22
  def find(name)
23
23
  return unless File.exists?(zone_path = "#{RecordStore.zones_path}/#{name}.yml")
24
- Zone.from_yaml_definition(*YAML.load_file(zone_path).first)
24
+ load_yml_zone_definition(zone_path).first.last
25
+ end
26
+
27
+ def write(name, config:, records:, format: :file)
28
+ raise ArgumentError, "format must be :directory or :file" unless %i(file directory).include?(format)
29
+ name = name.chomp('.')
30
+ zone_file = "#{RecordStore.zones_path}/#{name}.yml"
31
+ zone = { name => { config: config.to_hash } }
32
+ records = records.map(&:to_hash).sort_by! {|r| [r.fetch(:fqdn), r.fetch(:type), r[:nsdname] || r[:address]]}
33
+
34
+ if format == :file
35
+ zone[name][:records] = records
36
+ write_yml_file(zone_file, zone.deep_stringify_keys)
37
+ remove_record_files(name)
38
+ else
39
+ write_yml_file(zone_file, zone.deep_stringify_keys)
40
+ remove_record_files(name)
41
+ write_record_files(name, records)
42
+ end
25
43
  end
26
44
 
27
45
  private
28
46
 
29
- def load_yaml_file(filename)
47
+ def write_yml_file(filename, data)
48
+ lines = data.to_yaml.lines
49
+ lines.shift if lines.first == "---\n"
50
+ File.write(filename, lines.join)
51
+ end
52
+
53
+ def load_yml_zone_definition(filename)
30
54
  result = {}
55
+ dir = File.dirname(filename)
31
56
  YAML.load_file(filename).each do |name, definition|
57
+ definition['records'] ||= []
58
+ Dir["#{dir}/#{name}/*__*.yml"].each do |record_file|
59
+ definition['records'] += load_yml_record_definitions(name, record_file)
60
+ end
32
61
  result[name] = Zone.from_yaml_definition(name, definition)
33
62
  end
34
63
  result
35
64
  end
36
65
 
66
+ def load_yml_record_definitions(name, record_file)
67
+ type, domain = File.basename(record_file, '.yml').split('__')
68
+ Array.wrap(YAML.load_file(record_file)).map do |record_definition|
69
+ record_definition.merge(fqdn: "#{domain}.#{name}", type: type)
70
+ end
71
+ end
72
+
73
+ def remove_record_files(name)
74
+ dir = "#{RecordStore.zones_path}/#{name}"
75
+ File.unlink(*Dir["#{dir}/*"])
76
+ Dir.unlink(dir)
77
+ rescue Errno::ENOENT
78
+ end
79
+
80
+ def write_record_files(name, records)
81
+ dir = "#{RecordStore.zones_path}/#{name}"
82
+ Dir.mkdir(dir)
83
+ records.group_by { |record| [record.fetch(:fqdn), record.fetch(:type)] }.each do |(fqdn, type), grouped_records|
84
+ grouped_records.each do |record|
85
+ record.delete(:fqdn)
86
+ record.delete(:type)
87
+ record.deep_stringify_keys!
88
+ end
89
+ grouped_records = grouped_records.first if grouped_records.size == 1
90
+ domain = fqdn.chomp('.').chomp(name).chomp('.')
91
+ write_yml_file("#{dir}/#{type}__#{domain}.yml", grouped_records)
92
+ end
93
+ end
94
+
37
95
  def yaml_files
38
96
  Dir["#{RecordStore.zones_path}/*.yml"]
39
97
  end
@@ -59,19 +117,13 @@ module RecordStore
59
117
  new(name, definition.deep_symbolize_keys)
60
118
  end
61
119
 
62
- def self.download(name, provider_name)
120
+ def self.download(name, provider_name, **write_options)
63
121
  dns = new(name, config: {provider: provider_name}).provider
64
122
  current_records = dns.retrieve_current_records
65
-
66
- File.write("#{RecordStore.zones_path}/#{name}.yml", {
67
- name => {
68
- config: {
69
- provider: provider_name,
70
- ignore_patterns: [{type: "NS", fqdn: "#{name}."}],
71
- },
72
- records: current_records.map(&:to_hash).sort_by! {|r| [r.fetch(:fqdn), r.fetch(:type), r[:nsdname] || r[:address]]}
73
- }
74
- }.deep_stringify_keys.to_yaml.gsub("---\n", ''))
123
+ write(name, records: current_records, config: {
124
+ provider: provider_name,
125
+ ignore_patterns: [{type: "NS", fqdn: "#{name}."}],
126
+ }, **write_options)
75
127
  end
76
128
 
77
129
  def initialize(name, records: [], config: {})
@@ -106,6 +158,10 @@ module RecordStore
106
158
  Provider.const_get(config.provider).new(zone: name.gsub(/\.\z/, ''))
107
159
  end
108
160
 
161
+ def write(**write_options)
162
+ self.class.write(name, config: config, records: records, **write_options)
163
+ end
164
+
109
165
  private
110
166
 
111
167
  def build_records(records)
data/lib/record_store.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require 'json'
2
2
  require 'yaml'
3
3
  require 'active_support'
4
+ require 'active_support/core_ext/array/wrap'
4
5
  require 'active_support/core_ext/hash/keys'
5
6
  require 'active_support/core_ext/string'
6
7
  require 'active_model'
@@ -0,0 +1,2 @@
1
+ ttl: 86400
2
+ address: 10.10.10.42
@@ -0,0 +1,2 @@
1
+ ttl: 300
2
+ txtdata: polo
@@ -0,0 +1,2 @@
1
+ ttl: 300
2
+ txtdata: pong
@@ -2,36 +2,5 @@ dnsimple.example.com:
2
2
  config:
3
3
  provider: DNSimple
4
4
  ignore_patterns:
5
- - type: NS
6
- fqdn: dnsimple.example.com.
7
-
8
- records:
9
5
  - type: NS
10
6
  fqdn: dnsimple.example.com.
11
- ttl: 3600
12
- nsdname: ns1.dnsimple.com.
13
- - type: NS
14
- fqdn: dnsimple.example.com.
15
- ttl: 3600
16
- nsdname: ns2.dnsimple.com.
17
- - type: NS
18
- fqdn: dnsimple.example.com.
19
- ttl: 3600
20
- nsdname: ns3.dnsimple.com.
21
- - type: NS
22
- fqdn: dnsimple.example.com.
23
- ttl: 3600
24
- nsdname: ns4.dnsimple.com.
25
-
26
- - type: A
27
- fqdn: a-record.dnsimple.example.com.
28
- address: 10.10.10.42
29
- ttl: 86400
30
- - type: TXT
31
- fqdn: ping.dnsimple.example.com.
32
- txtdata: pong
33
- ttl: 300
34
- - type: TXT
35
- fqdn: marco.dnsimple.example.com.
36
- txtdata: polo
37
- ttl: 300
@@ -2,36 +2,18 @@ dynect.example.com:
2
2
  config:
3
3
  provider: DynECT
4
4
  ignore_patterns:
5
- - type: NS
6
- fqdn: dynect.example.com.
7
-
8
- records:
9
- - type: NS
10
- fqdn: dynect.example.com.
11
- ttl: 86400
12
- nsdname: ns1.p19.dynect.net.
13
- - type: NS
14
- fqdn: dynect.example.com.
15
- ttl: 86400
16
- nsdname: ns2.p19.dynect.net.
17
- - type: NS
18
- fqdn: dynect.example.com.
19
- ttl: 86400
20
- nsdname: ns3.p19.dynect.net.
21
5
  - type: NS
22
6
  fqdn: dynect.example.com.
23
- ttl: 86400
24
- nsdname: ns4.p19.dynect.net.
25
-
26
- - type: A
27
- fqdn: a-record.dynect.example.com.
28
- address: 10.10.10.42
29
- ttl: 86400
30
- - type: TXT
31
- fqdn: ping.dynect.example.com.
32
- txtdata: pong
33
- ttl: 300
34
- - type: TXT
35
- fqdn: marco.dynect.example.com.
36
- txtdata: polo
37
- ttl: 300
7
+ records:
8
+ - type: A
9
+ fqdn: a-record.dynect.example.com.
10
+ ttl: 86400
11
+ address: 10.10.10.42
12
+ - type: TXT
13
+ fqdn: marco.dynect.example.com.
14
+ ttl: 300
15
+ txtdata: polo
16
+ - type: TXT
17
+ fqdn: ping.dynect.example.com.
18
+ ttl: 300
19
+ txtdata: pong
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: record_store
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Willem van Bergen
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2016-07-12 00:00:00.000000000 Z
12
+ date: 2016-08-18 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: thor
@@ -209,10 +209,12 @@ files:
209
209
  - LICENSE
210
210
  - README.md
211
211
  - Rakefile
212
+ - bin/console
212
213
  - bin/record-store
213
214
  - bin/setup
214
215
  - bin/test
215
216
  - circle.yml
217
+ - dev.yml
216
218
  - lib/record_store.rb
217
219
  - lib/record_store/changeset.rb
218
220
  - lib/record_store/cli.rb
@@ -240,6 +242,9 @@ files:
240
242
  - template/config.yml
241
243
  - template/secrets.json
242
244
  - template/zones/dnsimple.example.com.yml
245
+ - template/zones/dnsimple.example.com/A__a-record.yml
246
+ - template/zones/dnsimple.example.com/TXT__marco.yml
247
+ - template/zones/dnsimple.example.com/TXT__ping.yml
243
248
  - template/zones/dynect.example.com.yml
244
249
  homepage: https://github.com/Shopify/record_store
245
250
  licenses:
@@ -261,7 +266,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
261
266
  version: '0'
262
267
  requirements: []
263
268
  rubyforge_project:
264
- rubygems_version: 2.2.5
269
+ rubygems_version: 2.5.1
265
270
  signing_key:
266
271
  specification_version: 4
267
272
  summary: Manage DNS using git