provider-dsl 1.0.2 → 1.0.3

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: 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