provider-dsl 1.0.2 → 1.0.3

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: 3d6d3a2df6de7252734eb5eab9cf357e048cb436
4
- data.tar.gz: 74a064161f77165989cd33a498ff0f5881c2f012
3
+ metadata.gz: 1ee0ecca0a46ac35eb09fb34ebe4c2d68358122e
4
+ data.tar.gz: 196c04c12434340acecfd98b424fa286954a346e
5
5
  SHA512:
6
- metadata.gz: 9f3f4977f9bab464fb01966961fc0119bf5a826cb837a9e29d8313e09b657c965bd90343299bd3a689fd89c2866e2be04067ee37ece5885431450ab93672e24d
7
- data.tar.gz: 7b3daa40d77fc8ce17be86421e8f2a851f1fee1d7fbe20e7e9797bd7f068a047721e455bcae350559a2ac5b4d8ede59b64c7850cf8800c4fb744a2fed06bb4a5
6
+ metadata.gz: 4078c18ce5bf90cf359dbba37dc44873c6557cb5211d76856b82c332f356e9b5f3670b767352a7f28c7d732e60c898bb8c8481661fa213a8191000626ab90059
7
+ data.tar.gz: adcd112cf12eaae3f38692c5ec8739b8424d66a5e65afee60d87770d25ddf9b73ae39a3abb88fdfb65f9618890aef00e4333e30c56ca6f39cb69ae7dece8d7c3
@@ -1,25 +1,25 @@
1
- require 'provider_dsl/gandi'
1
+ require 'provider_dsl/gandi_provider'
2
+ require 'provider_dsl/google_cloud_provider'
2
3
  require 'provider_dsl/log'
3
4
 
4
5
  module ProviderDSL
5
6
  # The DSL processor
6
7
  class DSL
7
- def initialize
8
- @logger = Log.instance
9
- end
10
-
11
8
  def execute(glob = nil, &block)
12
9
  Dir[glob].each do |filename|
13
- @logger.log("DSL processing #{filename}")
10
+ Log.instance.log("DSL processing #{filename}")
14
11
  instance_eval(File.read(filename))
15
- @logger.log("DSL completed processing #{filename}")
12
+ Log.instance.log("DSL completed processing #{filename}")
16
13
  end if glob.is_a?(String)
17
14
  instance_eval(&block) if block_given?
18
15
  end
19
16
 
20
- def gandi(parameters, &block)
21
- parameters[:session_factory] = GandiSessionFactory.new unless parameters.key?(:session_factory)
22
- Gandi.new(parameters, &block)
17
+ def gandi(api_key, parameters = {}, &block)
18
+ GandiProvider.new(api_key, parameters, &block)
19
+ end
20
+
21
+ def gcloud(project_name, key_filename, parameters = {}, &block)
22
+ GoogleCloudProvider.new(project_name, key_filename, parameters, &block)
23
23
  end
24
24
  end
25
25
  end
@@ -1,24 +1,23 @@
1
1
  require 'gandi'
2
2
  require 'provider_dsl/gandi_proxy'
3
3
  require 'provider_dsl/zone'
4
+ require 'provider_dsl/record'
4
5
  require 'provider_dsl/log'
5
6
 
6
7
  module ProviderDSL
7
8
  # Gandi session factory
8
9
  class GandiSessionFactory
9
10
  def instance(api_key, environment)
10
- ::Gandi::Session.new(api_key, env: environment)
11
+ Gandi::Session.new(api_key, env: environment)
11
12
  end
12
13
  end
13
14
 
14
15
  # Manage a domain on Gandi
15
- class Gandi
16
+ class GandiProvider
16
17
  attr_reader :name_servers, :zone_name
17
18
 
18
- def initialize(parameters = {}, &block)
19
- session_factory = parameters[:session_factory]
20
- api_key = parameters[:api_key]
21
- raise 'Gandi API key is not a valid string' unless api_key.is_a?(String)
19
+ def initialize(api_key, parameters = {}, &block)
20
+ session_factory = parameters[:session_factory] || GandiSessionFactory.new
22
21
  environment = parameters[:environment] || :production
23
22
  @domain_name = parameters[:domain_name]
24
23
  @logger = Log.instance
@@ -36,34 +35,38 @@ module ProviderDSL
36
35
  instance_eval(&block) if block_given?
37
36
  end
38
37
 
39
- def execute(&block)
40
- instance_eval(&block)
41
- end
42
-
43
38
  def zone(zone_name, parameters = {}, &block)
44
39
  @logger.log("Zone: #{zone_name}")
45
40
  original_zone = @session.domain.zone.list.select { |data| data['name'] == zone_name }
46
41
  if original_zone.count.zero?
47
42
  zone_id = nil
48
- original_zone = nil
43
+ original_zone = []
49
44
  else
50
45
  zone_id = original_zone.first['id']
51
- original_zone = @session.domain.zone.record.list(zone_id, 0).map { |record| Hash[record] }
46
+ original_zone = @session.domain.zone.record.list(zone_id, 0).map do |record|
47
+ Record.new(record['name'], record['type'], record['value'], record['ttl'])
48
+ end
52
49
  end
53
50
  if block_given?
54
- zone = Zone.new(original_zone.nil? ? [] : original_zone, parameters)
51
+ zone = Zone.new(original_zone, parameters)
55
52
  zone.create(&block)
56
- zone_id = original_zone.nil? ? @session.domain.zone.create(name: zone_name).id : zone_id
57
- if original_zone.nil? || zone.changed?
53
+ zone_id = original_zone.empty? ? @session.domain.zone.create(name: zone_name).id : zone_id
54
+ if original_zone.empty? || zone.changed?
58
55
  @logger.log("Zone records:\n#{zone.to_s(' ')}")
56
+ gandi_zone = []
57
+ zone.records.each do |record|
58
+ record.values.each do |value|
59
+ gandi_zone << { name: record.name, type: record.type, value: value, ttl: record.ttl }
60
+ end
61
+ end
59
62
  version = @session.domain.zone.version.new(zone_id)
60
- @session.domain.zone.record.set(zone_id, version, zone.hash)
63
+ @session.domain.zone.record.set(zone_id, version, gandi_zone)
61
64
  @session.domain.zone.version.set(zone_id, version)
62
65
  @logger.log("Created version #{version} of zone #{zone_name}")
63
66
  else
64
67
  @logger.log("Zone #{zone_name} is unchanged")
65
68
  end
66
- elsif original_zone.nil?
69
+ elsif original_zone.empty?
67
70
  raise "Zone #{zone_name} is undefined"
68
71
  end
69
72
  return unless @domain_name
@@ -1,7 +1,7 @@
1
1
  module ProviderDSL
2
2
  module GemDescription
3
3
  NAME = 'provider-dsl'.freeze
4
- VERSION = '1.0.2'.freeze
4
+ VERSION = '1.0.3'.freeze
5
5
  SUMMARY = 'A DSL for interacting with various service provider APIs'.freeze
6
6
  PAGE = 'https://github.com/sappho/gem-provider_dsl'.freeze
7
7
  AUTHORS = ['Andrew Heald'].freeze
@@ -0,0 +1,100 @@
1
+ require 'google/cloud'
2
+ require 'provider_dsl/zone'
3
+ require 'provider_dsl/record'
4
+ require 'provider_dsl/log'
5
+
6
+ module ProviderDSL
7
+ # Google Cloud session factory
8
+ class GCloudSessionFactory
9
+ def instance(project_name, key_filename, parameters)
10
+ Google::Cloud.new(
11
+ project_name, key_filename,
12
+ timeout: parameters[:timeout] || 0, retries: parameters[:retries] || 0
13
+ )
14
+ end
15
+ end
16
+
17
+ # Manage a domain on Google Cloud
18
+ class GoogleCloudProvider
19
+ EXCLUDE = %w(SOA NS).freeze
20
+
21
+ def initialize(project_name, key_filename, parameters = {}, &block)
22
+ session_factory = parameters[:session_factory] || GCloudSessionFactory.new
23
+ @logger = Log.instance
24
+ @logger.log("Processing Google Cloud project #{project_name}")
25
+ @dns = session_factory.instance(project_name, key_filename, parameters).dns
26
+ instance_eval(&block) if block_given?
27
+ end
28
+
29
+ def zone(domain_name, description, parameters = {}, &block)
30
+ zone_name = domain_name.tr('.', '-')
31
+ zone_style_domain_name = "#{domain_name}."
32
+ domain_name_regex = Regexp.quote(zone_style_domain_name)
33
+ @logger.log("Zone: #{zone_name} for domain #{domain_name}")
34
+ if @dns.zones.select { |zone| zone.name == zone_name }.count.zero?
35
+ gcloud_zone = nil
36
+ zone = Zone.new([], parameters)
37
+ else
38
+ gcloud_zone = @dns.zone(zone_name)
39
+ records = gcloud_zone.records
40
+ records = records.map do |record|
41
+ unless record.name =~ /^(|([a-zA-Z0-9\-\.]+)\.)#{domain_name_regex}$/
42
+ raise "Google Cloud returned invalid record name #{record.name}"
43
+ end
44
+ name = Regexp.last_match(1).empty? ? '@' : Regexp.last_match(2)
45
+ data = record.data.map do |value|
46
+ if %w(CNAME MX).include?(record.type)
47
+ if value =~ /^((|[0-9]+ +)[a-zA-Z0-9\-\.]+)\.#{domain_name_regex}$/
48
+ value = Regexp.last_match(1)
49
+ end
50
+ end
51
+ value
52
+ end
53
+ EXCLUDE.include?(record.type) ? nil : Record.new(name, record.type, data, record.ttl)
54
+ end
55
+ zone = Zone.new(records.select { |record| record }, parameters)
56
+ end
57
+ zone.create(&block)
58
+ if gcloud_zone.nil? || zone.changed?
59
+ @logger.log("Zone records:\n#{zone.to_s(' ')}")
60
+ unless gcloud_zone
61
+ gcloud_zone = @dns.create_zone(zone_name, zone_style_domain_name, description: description)
62
+ @logger.log("Created zone #{zone_name}")
63
+ end
64
+ deletions = []
65
+ additions = []
66
+ zone.removed_records.each do |record|
67
+ # gcloud_zone.remove(record.name, record.type)
68
+ deletions << dns_record(record, zone_style_domain_name)
69
+ @logger.log("Removing #{record}")
70
+ end
71
+ zone.new_or_changed_records.each do |record|
72
+ # gcloud_zone.replace(record.name, record.type, record.ttl, record.values)
73
+ additions << dns_record(record, zone_style_domain_name)
74
+ @logger.log("Creating or replacing #{record}")
75
+ end
76
+ gcloud_zone.update(additions, deletions)
77
+ else
78
+ @logger.log("Zone #{zone_name} is unchanged")
79
+ end
80
+ end
81
+
82
+ private
83
+
84
+ def dns_record(record, zone_style_domain_name)
85
+ name = record.name != '@' ? "#{record.name}.#{zone_style_domain_name}" : zone_style_domain_name
86
+ if %w(CNAME MX).include?(record.type)
87
+ data = record.values.map do |value|
88
+ if value.end_with?('.')
89
+ value
90
+ else
91
+ "#{value == '@' ? '' : "#{name}."}#{zone_style_domain_name}"
92
+ end
93
+ end
94
+ else
95
+ data = record.values
96
+ end
97
+ Google::Cloud::Dns::Record.new(name, record.type, record.ttl, data)
98
+ end
99
+ end
100
+ end
@@ -1,18 +1,34 @@
1
1
  module ProviderDSL
2
2
  # Manage a DNS record
3
3
  class Record
4
- attr_reader :name, :type, :value, :ttl, :hash
4
+ attr_reader :name, :type, :values, :ttl
5
5
 
6
- def initialize(name, type, value, ttl)
6
+ def initialize(name, type, values, ttl)
7
7
  @name = name
8
8
  @type = type
9
- @value = value
9
+ @values = Array(values).map do |value|
10
+ case type
11
+ when 'AAAA'
12
+ raise "#{value} is not a valid IPv6 address" unless IPAddress.valid_ipv6?(value)
13
+ IPAddress(value).compressed
14
+ when 'A'
15
+ raise "#{value} is not a valid IPv4 address" unless IPAddress.valid_ipv4?(value)
16
+ IPAddress(value).octets.join('.')
17
+ when 'CNAME', 'MX', 'TXT'
18
+ value
19
+ else
20
+ raise "Record #{name} #{type} has unhandled type"
21
+ end
22
+ end.uniq.sort
10
23
  @ttl = ttl
11
- @hash = { name: name, type: type, value: value, ttl: ttl }
24
+ raise "No values for record #{self}" if @values.empty?
25
+ return unless type == 'CNAME'
26
+ raise "Record #{self} must have only one value" if @values.count != 1
27
+ raise "Record #{self} is invalid on the naked domain" if name == '@'
12
28
  end
13
29
 
14
30
  def to_s
15
- "#{ttl} #{name} #{type} #{value}"
31
+ "#{ttl} #{name} #{type} #{values}"
16
32
  end
17
33
 
18
34
  def ==(other)
@@ -20,7 +36,11 @@ module ProviderDSL
20
36
  end
21
37
 
22
38
  def ===(other)
23
- name == other.name && type == other.type && value == other.value
39
+ same_name_and_type(other) && values == other.values
40
+ end
41
+
42
+ def same_name_and_type(other)
43
+ name == other.name && type == other.type
24
44
  end
25
45
  end
26
46
  end
@@ -9,14 +9,12 @@ module ProviderDSL
9
9
 
10
10
  def initialize(original_records, parameters = {})
11
11
  @logger = Log.instance
12
- @original_records = original_records.map do |record|
13
- if record.is_a?(Hash)
14
- Record.new(record['name'], record['type'], record['value'], record['ttl'])
15
- else
16
- record
17
- end
12
+ @original_records = []
13
+ @records = []
14
+ original_records.each do |record|
15
+ @original_records = add(@original_records, record)
16
+ @records = add(@records, record) if parameters[:inherit_records]
18
17
  end
19
- @records = parameters[:inherit_records] ? @original_records.clone : []
20
18
  @names = []
21
19
  name
22
20
  ttl
@@ -43,26 +41,15 @@ module ProviderDSL
43
41
  end
44
42
 
45
43
  def aaaa(ip_addresses)
46
- record('AAAA', ip_addresses) do |ip_address|
47
- raise "#{ip_address} is not a valid IPv6 address" unless IPAddress.valid_ipv6?(ip_address)
48
- IPAddress(ip_address).compressed
49
- end
44
+ record('AAAA', ip_addresses)
50
45
  end
51
46
 
52
47
  def a(ip_addresses)
53
- record('A', ip_addresses) do |ip_address|
54
- raise "#{ip_address} is not a valid IPv4 address" unless IPAddress.valid_ipv4?(ip_address)
55
- IPAddress(ip_address).octets.join('.')
56
- end
48
+ record('A', ip_addresses)
57
49
  end
58
50
 
59
51
  def cname(value)
60
- value = String(value)
61
- raise "CNAME #{value} cannot be defined for a naked domain" if @name == '@'
62
- record('CNAME', value) do
63
- @records = records.select { |other| !(other.type == 'CNAME' && other.name == @name) }
64
- value
65
- end
52
+ record('CNAME', value)
66
53
  end
67
54
 
68
55
  def mx(values)
@@ -73,24 +60,22 @@ module ProviderDSL
73
60
  record('TXT', values)
74
61
  end
75
62
 
76
- def new_records
63
+ def new_or_changed_records
77
64
  records.select { |record| @original_records.select { |original| original == record }.count.zero? }
78
65
  end
79
66
 
80
67
  def removed_records
81
- @original_records.select { |original| records.select { |record| original == record }.count.zero? }
68
+ @original_records.select do |original|
69
+ records.select { |record| original == record }.count.zero?
70
+ end
82
71
  end
83
72
 
84
73
  def changed?
85
- !(new_records + removed_records).count.zero?
74
+ !(new_or_changed_records + removed_records).count.zero?
86
75
  end
87
76
 
88
77
  def to_s(prefix = '', suffix = '')
89
- "#{prefix}#{sorted_records.join("#{suffix}\n#{prefix}")}#{suffix}"
90
- end
91
-
92
- def hash
93
- sorted_records.map(&:hash)
78
+ "#{prefix}#{records.join("#{suffix}\n#{prefix}")}#{suffix}"
94
79
  end
95
80
 
96
81
  private
@@ -105,23 +90,25 @@ module ProviderDSL
105
90
  if keepers > 0
106
91
  raise "Keeper ? flag is inconsistently used for #{@name} #{type} #{values}" if keepers != 1 || values.count != 1
107
92
  @original_records.select { |original| original.name == @name && original.type == type }.each do |original|
108
- add(original)
93
+ @records = add(@records, original)
109
94
  end
110
95
  else
111
- values.each do |value|
112
- value = yield(value) if block_given?
113
- add(Record.new(@name, type, value, @ttl))
114
- end
96
+ @records =
97
+ add(@records, Record.new(@name, type, values.map { |value| block_given? ? yield(value) : value }, @ttl))
115
98
  end
116
99
  end
117
100
 
118
- def add(record)
101
+ def add(records, record)
119
102
  @logger.log("Adding #{record}")
120
- @records = records.select { |other| !(record === other) } + [record]
121
- end
122
-
123
- def sorted_records
124
- records.sort_by { |record| [record.name, record.type, record.value] }
103
+ values = record.values
104
+ records = records.select do |other|
105
+ match = record.same_name_and_type(other)
106
+ values = other.values + values if match
107
+ !match
108
+ end
109
+ values = values.last if record.type == 'CNAME'
110
+ records << Record.new(record.name, record.type, values, record.ttl)
111
+ records.sort_by { |sort| [sort.name, sort.type] }
125
112
  end
126
113
  end
127
114
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: provider-dsl
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Heald
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-10-29 00:00:00.000000000 Z
11
+ date: 2016-11-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -100,6 +100,34 @@ dependencies:
100
100
  - - ">="
101
101
  - !ruby/object:Gem::Version
102
102
  version: 3.3.27
103
+ - !ruby/object:Gem::Dependency
104
+ name: gcloud
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: 0.21.0
110
+ type: :runtime
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: 0.21.0
117
+ - !ruby/object:Gem::Dependency
118
+ name: google-cloud-error_reporting
119
+ requirement: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - "~>"
122
+ - !ruby/object:Gem::Version
123
+ version: 0.21.0
124
+ type: :runtime
125
+ prerelease: false
126
+ version_requirements: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - "~>"
129
+ - !ruby/object:Gem::Version
130
+ version: 0.21.0
103
131
  - !ruby/object:Gem::Dependency
104
132
  name: map
105
133
  requirement: !ruby/object:Gem::Requirement
@@ -122,9 +150,10 @@ extensions: []
122
150
  extra_rdoc_files: []
123
151
  files:
124
152
  - lib/provider_dsl/dsl.rb
125
- - lib/provider_dsl/gandi.rb
153
+ - lib/provider_dsl/gandi_provider.rb
126
154
  - lib/provider_dsl/gandi_proxy.rb
127
155
  - lib/provider_dsl/gem_description.rb
156
+ - lib/provider_dsl/google_cloud_provider.rb
128
157
  - lib/provider_dsl/log.rb
129
158
  - lib/provider_dsl/rate_limiter.rb
130
159
  - lib/provider_dsl/record.rb