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