record_store 1.0.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
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