provider-dsl 1.0.0
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 +7 -0
- data/lib/provider_dsl/dsl.rb +25 -0
- data/lib/provider_dsl/gandi.rb +98 -0
- data/lib/provider_dsl/gandi_proxy.rb +27 -0
- data/lib/provider_dsl/gem_description.rb +10 -0
- data/lib/provider_dsl/log.rb +36 -0
- data/lib/provider_dsl/rate_limiter.rb +26 -0
- data/lib/provider_dsl/record.rb +26 -0
- data/lib/provider_dsl/zone.rb +127 -0
- metadata +155 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 1e03114f692a9bb504c5c542872a125844180cfb
|
4
|
+
data.tar.gz: 6d5d68f7aa7cc8d21dce62ac696e8abf84259a7e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b055051b2bcb8e59bd06c7088dd05ef42def0657069182cc051fe0b791a6c5148c857ee05bd6d2710cd0cf4866a3fa34c74fea6eb6f9a035d16c24657fdf2924
|
7
|
+
data.tar.gz: 3e0db13f2286a8091d44ceb4ce9c1f72819ee31fe620e1e5d7f2be449cd089141a0ba3c83ee46f3d2a709459bf962d62a7fe43b4f5a4b6e0f68bec57470810f9
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'provider_dsl/gandi'
|
2
|
+
require 'provider_dsl/log'
|
3
|
+
|
4
|
+
module ProviderDSL
|
5
|
+
# The DSL processor
|
6
|
+
class DSL
|
7
|
+
def initialize
|
8
|
+
@logger = Log.instance
|
9
|
+
end
|
10
|
+
|
11
|
+
def execute(glob = nil, &block)
|
12
|
+
Dir[glob].each do |filename|
|
13
|
+
@logger.log("DSL processing #{filename}")
|
14
|
+
instance_eval(File.read(filename))
|
15
|
+
@logger.log("DSL completed processing #{filename}")
|
16
|
+
end if glob.is_a?(String)
|
17
|
+
instance_eval(&block) if block_given?
|
18
|
+
end
|
19
|
+
|
20
|
+
def gandi(parameters, &block)
|
21
|
+
parameters[:session_factory] = GandiSessionFactory.new unless parameters.key?(:session_factory)
|
22
|
+
Gandi.new(parameters, &block)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'gandi'
|
2
|
+
require 'provider_dsl/gandi_proxy'
|
3
|
+
require 'provider_dsl/zone'
|
4
|
+
require 'provider_dsl/log'
|
5
|
+
|
6
|
+
module ProviderDSL
|
7
|
+
#
|
8
|
+
class GandiSessionFactory
|
9
|
+
def instance(api_key, environment)
|
10
|
+
::Gandi::Session.new(api_key, env: environment)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# Manage a domain on Gandi
|
15
|
+
class Gandi
|
16
|
+
attr_reader :name_servers, :zone_name
|
17
|
+
|
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)
|
22
|
+
environment = parameters[:environment] || :production
|
23
|
+
@domain_name = parameters[:domain_name]
|
24
|
+
@logger = Log.instance
|
25
|
+
@logger.confidential(/#{Regexp.quote(api_key)}/)
|
26
|
+
@logger.log("Processing Gandi account in #{environment}")
|
27
|
+
@session = session_factory.instance(api_key, environment)
|
28
|
+
if @domain_name
|
29
|
+
@logger.log("Domain name: #{@domain_name}")
|
30
|
+
@name_servers = @session.domain.info(@domain_name).nameservers.uniq.sort
|
31
|
+
@name_server_addresses =
|
32
|
+
Hash[@session.domain.host.list(@domain_name).map { |data| [data['name'], data['ips'].uniq.sort] }]
|
33
|
+
end
|
34
|
+
@zone_name = nil
|
35
|
+
@required_zone = []
|
36
|
+
instance_eval(&block) if block_given?
|
37
|
+
end
|
38
|
+
|
39
|
+
def execute(&block)
|
40
|
+
instance_eval(&block)
|
41
|
+
end
|
42
|
+
|
43
|
+
def zone(zone_name, parameters = {}, &block)
|
44
|
+
@logger.log("Zone: #{zone_name}")
|
45
|
+
original_zone = @session.domain.zone.list.select { |data| data['name'] == zone_name }
|
46
|
+
if original_zone.count.zero?
|
47
|
+
zone_id = nil
|
48
|
+
original_zone = nil
|
49
|
+
else
|
50
|
+
zone_id = original_zone.first['id']
|
51
|
+
original_zone = @session.domain.zone.record.list(zone_id, 0).map { |record| Hash[record] }
|
52
|
+
end
|
53
|
+
if block_given?
|
54
|
+
zone = Zone.new(original_zone.nil? ? [] : original_zone, parameters)
|
55
|
+
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?
|
58
|
+
@logger.log("Zone records:\n#{zone.to_s(' ')}")
|
59
|
+
version = @session.domain.zone.version.new(zone_id)
|
60
|
+
@session.domain.zone.record.set(zone_id, version, zone.hash)
|
61
|
+
@session.domain.zone.version.set(zone_id, version)
|
62
|
+
@logger.log("Created version #{version} of zone #{zone_name}")
|
63
|
+
else
|
64
|
+
@logger.log("Zone #{zone_name} is unchanged")
|
65
|
+
end
|
66
|
+
elsif original_zone.nil?
|
67
|
+
raise "Zone #{zone_name} is undefined"
|
68
|
+
end
|
69
|
+
return unless @domain_name
|
70
|
+
@logger.log("Attaching Gandi zone #{zone_name} to domain #{@domain_name}")
|
71
|
+
@session.domain.zone.set(@domain_name, zone_id)
|
72
|
+
name_servers!
|
73
|
+
end
|
74
|
+
|
75
|
+
def name_server_addresses(name_server)
|
76
|
+
name_server = "#{name_server}.#{@domain_name}"
|
77
|
+
@name_server_addresses.key?(name_server) ? @name_server_addresses[name_server] : []
|
78
|
+
end
|
79
|
+
|
80
|
+
def name_server_addresses!(name_server, required_name_server_addresses)
|
81
|
+
current_name_server_addresses = name_server_addresses(name_server)
|
82
|
+
required_name_server_addresses = Array(required_name_server_addresses).uniq.sort
|
83
|
+
name_server = "#{name_server}.#{@domain_name}"
|
84
|
+
return if current_name_server_addresses == required_name_server_addresses
|
85
|
+
if @name_server_addresses.key?(name_server)
|
86
|
+
@session.domain.host.update(name_server, required_name_server_addresses)
|
87
|
+
else
|
88
|
+
@session.domain.host.create(name_server, required_name_server_addresses)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def name_servers!(required_name_servers = %w(a.dns.gandi.net b.dns.gandi.net c.dns.gandi.net))
|
93
|
+
@name_servers = Array(required_name_servers).uniq.sort
|
94
|
+
@logger.log((["Setting name servers for Gandi domain #{@domain_name} to:"] + @name_servers).join("\n "))
|
95
|
+
@session.domain.nameservers.set(@domain_name, @name_servers)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'gandi'
|
2
|
+
require 'provider_dsl/rate_limiter'
|
3
|
+
require 'provider_dsl/log'
|
4
|
+
|
5
|
+
module ProviderDSL
|
6
|
+
module GandiProxy
|
7
|
+
# Gandi allow 30 calls to their API every 2 seconds - http://doc.rpc.gandi.net/overview.html#rate-limit
|
8
|
+
# To be safe we'll limit calls to no more than 20 every 2 seconds
|
9
|
+
LIMITER = RateLimiter.new(20, 2)
|
10
|
+
|
11
|
+
def method_missing(method, *arguments)
|
12
|
+
result = super(method, *arguments)
|
13
|
+
return result unless ::Gandi::VALID_METHODS.include?(chained.join('.'))
|
14
|
+
Log.instance.debug((["-> Gandi #{method}"] + [*arguments]).join("\n "))
|
15
|
+
Log.instance.debug("Result:\n#{result.to_yaml}")
|
16
|
+
LIMITER.wait
|
17
|
+
result
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
module Gandi
|
23
|
+
# Mix in this custom call handler to the standard Gandi client
|
24
|
+
class ProxyCall
|
25
|
+
prepend ProviderDSL::GandiProxy
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module ProviderDSL
|
2
|
+
module GemDescription
|
3
|
+
NAME = 'provider-dsl'.freeze
|
4
|
+
VERSION = '1.0.0'.freeze
|
5
|
+
SUMMARY = 'A DSL for interacting with various service provider APIs'.freeze
|
6
|
+
PAGE = 'https://github.com/sappho/gem-provider_dsl'.freeze
|
7
|
+
AUTHORS = ['Andrew Heald'].freeze
|
8
|
+
EMAIL = ['andrew@heald.uk'].freeze
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
|
3
|
+
module ProviderDSL
|
4
|
+
# A simple logger
|
5
|
+
class Log
|
6
|
+
include Singleton
|
7
|
+
|
8
|
+
attr_accessor :callback, :debug
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@log = []
|
12
|
+
@confidential = []
|
13
|
+
@callback = nil
|
14
|
+
@debug = ENV['DSL_DEBUG'] == 'true'
|
15
|
+
end
|
16
|
+
|
17
|
+
def log(message)
|
18
|
+
@confidential.each { |regex| message.gsub!(regex, '******') }
|
19
|
+
@log << message
|
20
|
+
@callback.call(message) unless @callback.nil?
|
21
|
+
end
|
22
|
+
|
23
|
+
def debug(message)
|
24
|
+
log(message) if @debug
|
25
|
+
end
|
26
|
+
|
27
|
+
def confidential(regex)
|
28
|
+
@confidential << regex
|
29
|
+
@confidential.uniq!
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_s
|
33
|
+
@log.join("\n")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module ProviderDSL
|
2
|
+
# Simple rate limiter
|
3
|
+
class RateLimiter
|
4
|
+
def initialize(maximum_calls, time_period)
|
5
|
+
@maximum_calls = maximum_calls
|
6
|
+
@time_period = time_period
|
7
|
+
reset
|
8
|
+
end
|
9
|
+
|
10
|
+
def wait
|
11
|
+
if @call_countdown.zero?
|
12
|
+
delta = Time.now - @timestamp
|
13
|
+
sleep(@time_period - delta) if delta < @time_period
|
14
|
+
reset
|
15
|
+
end
|
16
|
+
@call_countdown -= 1
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def reset
|
22
|
+
@call_countdown = @maximum_calls
|
23
|
+
@timestamp = Time.now
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module ProviderDSL
|
2
|
+
# Manage a DNS record
|
3
|
+
class Record
|
4
|
+
attr_reader :name, :type, :value, :ttl, :hash
|
5
|
+
|
6
|
+
def initialize(name, type, value, ttl)
|
7
|
+
@name = name
|
8
|
+
@type = type
|
9
|
+
@value = value
|
10
|
+
@ttl = ttl
|
11
|
+
@hash = { name: name, type: type, value: value, ttl: ttl }
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_s
|
15
|
+
"#{ttl} #{name} #{type} #{value}"
|
16
|
+
end
|
17
|
+
|
18
|
+
def ==(other)
|
19
|
+
self === other && ttl == other.ttl
|
20
|
+
end
|
21
|
+
|
22
|
+
def ===(other)
|
23
|
+
name == other.name && type == other.type && value == other.value
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require 'ipaddress'
|
2
|
+
require 'provider_dsl/record'
|
3
|
+
require 'provider_dsl/log'
|
4
|
+
|
5
|
+
module ProviderDSL
|
6
|
+
# Manage a DNS zone
|
7
|
+
class Zone
|
8
|
+
attr_reader :records
|
9
|
+
|
10
|
+
def initialize(original_records, parameters = {})
|
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
|
18
|
+
end
|
19
|
+
@records = parameters[:inherit_records] ? @original_records.clone : []
|
20
|
+
@names = []
|
21
|
+
name
|
22
|
+
ttl
|
23
|
+
end
|
24
|
+
|
25
|
+
def create(&block)
|
26
|
+
instance_eval(&block)
|
27
|
+
end
|
28
|
+
|
29
|
+
def name(name = nil, &block)
|
30
|
+
new_names = name.nil? ? @names : [name] + @names
|
31
|
+
effective_name(new_names)
|
32
|
+
if block_given?
|
33
|
+
saved_names = @names
|
34
|
+
@names = new_names
|
35
|
+
instance_eval(&block)
|
36
|
+
@names = saved_names
|
37
|
+
effective_name(@names)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def ttl(ttl = 3600)
|
42
|
+
@ttl = ttl
|
43
|
+
end
|
44
|
+
|
45
|
+
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
|
50
|
+
end
|
51
|
+
|
52
|
+
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
|
57
|
+
end
|
58
|
+
|
59
|
+
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
|
66
|
+
end
|
67
|
+
|
68
|
+
def mx(values)
|
69
|
+
record('MX', values)
|
70
|
+
end
|
71
|
+
|
72
|
+
def txt(values)
|
73
|
+
record('TXT', values)
|
74
|
+
end
|
75
|
+
|
76
|
+
def new_records
|
77
|
+
records.select { |record| @original_records.select { |original| original == record }.count.zero? }
|
78
|
+
end
|
79
|
+
|
80
|
+
def removed_records
|
81
|
+
@original_records.select { |original| records.select { |record| original == record }.count.zero? }
|
82
|
+
end
|
83
|
+
|
84
|
+
def changed?
|
85
|
+
!(new_records + removed_records).count.zero?
|
86
|
+
end
|
87
|
+
|
88
|
+
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)
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
def effective_name(names)
|
99
|
+
@name = names.count.zero? ? '@' : names.join('.')
|
100
|
+
end
|
101
|
+
|
102
|
+
def record(type, values)
|
103
|
+
values = Array(values)
|
104
|
+
keepers = values.select { |value| value == '?' }.count
|
105
|
+
if keepers > 0
|
106
|
+
raise "Keeper ? flag is inconsistently used for #{@name} #{type} #{values}" if keepers != 1 || values.count != 1
|
107
|
+
@original_records.select { |original| original.name == @name && original.type == type }.each do |original|
|
108
|
+
add(original)
|
109
|
+
end
|
110
|
+
else
|
111
|
+
values.each do |value|
|
112
|
+
value = yield(value) if block_given?
|
113
|
+
add(Record.new(@name, type, value, @ttl))
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def add(record)
|
119
|
+
@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] }
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
metadata
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: provider-dsl
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Andrew Heald
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-10-29 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '11.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '11.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rubocop
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.44.1
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.44.1
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.5'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.5'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec-mocks
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.5'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.5'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: ipaddress
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 0.8.3
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.8.3
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: gandi
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '3.3'
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: 3.3.27
|
93
|
+
type: :runtime
|
94
|
+
prerelease: false
|
95
|
+
version_requirements: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - "~>"
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '3.3'
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: 3.3.27
|
103
|
+
- !ruby/object:Gem::Dependency
|
104
|
+
name: map
|
105
|
+
requirement: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - "~>"
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '6.6'
|
110
|
+
type: :runtime
|
111
|
+
prerelease: false
|
112
|
+
version_requirements: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - "~>"
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '6.6'
|
117
|
+
description: See the project home page for more information
|
118
|
+
email:
|
119
|
+
- andrew@heald.uk
|
120
|
+
executables: []
|
121
|
+
extensions: []
|
122
|
+
extra_rdoc_files: []
|
123
|
+
files:
|
124
|
+
- lib/provider_dsl/dsl.rb
|
125
|
+
- lib/provider_dsl/gandi.rb
|
126
|
+
- lib/provider_dsl/gandi_proxy.rb
|
127
|
+
- lib/provider_dsl/gem_description.rb
|
128
|
+
- lib/provider_dsl/log.rb
|
129
|
+
- lib/provider_dsl/rate_limiter.rb
|
130
|
+
- lib/provider_dsl/record.rb
|
131
|
+
- lib/provider_dsl/zone.rb
|
132
|
+
homepage: https://github.com/sappho/gem-provider_dsl
|
133
|
+
licenses: []
|
134
|
+
metadata: {}
|
135
|
+
post_install_message:
|
136
|
+
rdoc_options: []
|
137
|
+
require_paths:
|
138
|
+
- lib
|
139
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
140
|
+
requirements:
|
141
|
+
- - ">="
|
142
|
+
- !ruby/object:Gem::Version
|
143
|
+
version: '0'
|
144
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
145
|
+
requirements:
|
146
|
+
- - ">="
|
147
|
+
- !ruby/object:Gem::Version
|
148
|
+
version: '0'
|
149
|
+
requirements: []
|
150
|
+
rubyforge_project:
|
151
|
+
rubygems_version: 2.5.1
|
152
|
+
signing_key:
|
153
|
+
specification_version: 4
|
154
|
+
summary: A DSL for interacting with various service provider APIs
|
155
|
+
test_files: []
|