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 +4 -4
- data/lib/provider_dsl/dsl.rb +10 -10
- data/lib/provider_dsl/{gandi.rb → gandi_provider.rb} +20 -17
- data/lib/provider_dsl/gem_description.rb +1 -1
- data/lib/provider_dsl/google_cloud_provider.rb +100 -0
- data/lib/provider_dsl/record.rb +26 -6
- data/lib/provider_dsl/zone.rb +27 -40
- metadata +32 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1ee0ecca0a46ac35eb09fb34ebe4c2d68358122e
|
4
|
+
data.tar.gz: 196c04c12434340acecfd98b424fa286954a346e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4078c18ce5bf90cf359dbba37dc44873c6557cb5211d76856b82c332f356e9b5f3670b767352a7f28c7d732e60c898bb8c8481661fa213a8191000626ab90059
|
7
|
+
data.tar.gz: adcd112cf12eaae3f38692c5ec8739b8424d66a5e65afee60d87770d25ddf9b73ae39a3abb88fdfb65f9618890aef00e4333e30c56ca6f39cb69ae7dece8d7c3
|
data/lib/provider_dsl/dsl.rb
CHANGED
@@ -1,25 +1,25 @@
|
|
1
|
-
require 'provider_dsl/
|
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
|
-
|
10
|
+
Log.instance.log("DSL processing #{filename}")
|
14
11
|
instance_eval(File.read(filename))
|
15
|
-
|
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
|
-
|
22
|
-
|
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
|
-
|
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
|
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 =
|
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
|
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
|
51
|
+
zone = Zone.new(original_zone, parameters)
|
55
52
|
zone.create(&block)
|
56
|
-
zone_id = original_zone.
|
57
|
-
if original_zone.
|
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,
|
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.
|
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.
|
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
|
data/lib/provider_dsl/record.rb
CHANGED
@@ -1,18 +1,34 @@
|
|
1
1
|
module ProviderDSL
|
2
2
|
# Manage a DNS record
|
3
3
|
class Record
|
4
|
-
attr_reader :name, :type, :
|
4
|
+
attr_reader :name, :type, :values, :ttl
|
5
5
|
|
6
|
-
def initialize(name, type,
|
6
|
+
def initialize(name, type, values, ttl)
|
7
7
|
@name = name
|
8
8
|
@type = type
|
9
|
-
@
|
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
|
-
|
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} #{
|
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
|
-
|
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
|
data/lib/provider_dsl/zone.rb
CHANGED
@@ -9,14 +9,12 @@ module ProviderDSL
|
|
9
9
|
|
10
10
|
def initialize(original_records, parameters = {})
|
11
11
|
@logger = Log.instance
|
12
|
-
@original_records =
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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)
|
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)
|
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
|
-
|
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
|
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
|
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
|
-
!(
|
74
|
+
!(new_or_changed_records + removed_records).count.zero?
|
86
75
|
end
|
87
76
|
|
88
77
|
def to_s(prefix = '', suffix = '')
|
89
|
-
"#{prefix}#{
|
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
|
-
|
112
|
-
value
|
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
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
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.
|
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-
|
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/
|
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
|