dnsmadeeasy 0.3.5 → 1.0.1
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/.github/workflows/ruby.yml +31 -0
- data/.gitignore +2 -0
- data/.rubocop.yml +6 -0
- data/.rubocop_todo.yml +272 -107
- data/.travis.yml +2 -1
- data/CLAUDE.md +67 -0
- data/Gemfile +9 -1
- data/README.md +506 -310
- data/Rakefile +4 -4
- data/dnsmadeeasy.gemspec +44 -27
- data/docs/badges/coverage_badge.svg +21 -0
- data/docs/dry-cli-based-tools.webloc +8 -0
- data/docs/plan-zone-management.md +756 -0
- data/docs/plans/01-plan-ruby4-baseline.md +38 -0
- data/docs/plans/02-plan-dry-cli-launcher.md +44 -0
- data/docs/plans/03-plan-account-command.md +43 -0
- data/docs/plans/04-plan-zone-record-model.md +48 -0
- data/docs/plans/05-plan-zone-parser.md +41 -0
- data/docs/plans/06-plan-zone-formatter.md +43 -0
- data/docs/plans/07-plan-zone-export.md +58 -0
- data/docs/plans/08-plan-zone-diff.md +42 -0
- data/docs/plans/09-plan-zone-apply.md +69 -0
- data/docs/plans/10-plan-docs-cleanup.md +55 -0
- data/docs/spec-zone-management.md +242 -0
- data/exe/dme +13 -2
- data/exe/dmez +6 -0
- data/lib/dme.rb +6 -6
- data/lib/dnsmadeeasy/api/client.rb +31 -32
- data/lib/dnsmadeeasy/cli/box_output.rb +9 -0
- data/lib/dnsmadeeasy/cli/commands/account.rb +222 -0
- data/lib/dnsmadeeasy/cli/commands/base.rb +119 -0
- data/lib/dnsmadeeasy/cli/commands/legacy_operation.rb +30 -0
- data/lib/dnsmadeeasy/cli/commands/version.rb +22 -0
- data/lib/dnsmadeeasy/cli/commands/zone.rb +326 -0
- data/lib/dnsmadeeasy/cli/commands.rb +19 -0
- data/lib/dnsmadeeasy/cli/input.rb +14 -0
- data/lib/dnsmadeeasy/cli/launcher.rb +53 -0
- data/lib/dnsmadeeasy/cli/message_helpers.rb +81 -0
- data/lib/dnsmadeeasy/cli/reported_error.rb +10 -0
- data/lib/dnsmadeeasy/credentials/api_keys.rb +11 -9
- data/lib/dnsmadeeasy/credentials/yaml_file.rb +28 -27
- data/lib/dnsmadeeasy/credentials.rb +3 -4
- data/lib/dnsmadeeasy/runner.rb +102 -98
- data/lib/dnsmadeeasy/types.rb +19 -0
- data/lib/dnsmadeeasy/version.rb +2 -1
- data/lib/dnsmadeeasy/zone/aname_flattener.rb +63 -0
- data/lib/dnsmadeeasy/zone/apply_executor.rb +189 -0
- data/lib/dnsmadeeasy/zone/apply_result.rb +22 -0
- data/lib/dnsmadeeasy/zone/diff.rb +152 -0
- data/lib/dnsmadeeasy/zone/file.rb +26 -0
- data/lib/dnsmadeeasy/zone/parser.rb +172 -0
- data/lib/dnsmadeeasy/zone/plan.rb +28 -0
- data/lib/dnsmadeeasy/zone/plan_action.rb +29 -0
- data/lib/dnsmadeeasy/zone/plan_renderer.rb +91 -0
- data/lib/dnsmadeeasy/zone/provider_record.rb +18 -0
- data/lib/dnsmadeeasy/zone/record.rb +44 -0
- data/lib/dnsmadeeasy/zone/record_set.rb +23 -0
- data/lib/dnsmadeeasy/zone/remote_adapter.rb +94 -0
- data/lib/dnsmadeeasy/zone/remote_records.rb +26 -0
- data/lib/dnsmadeeasy/zone/serializer.rb +115 -0
- data/lib/dnsmadeeasy.rb +61 -31
- metadata +184 -23
|
@@ -13,7 +13,7 @@ module DnsMadeEasy
|
|
|
13
13
|
# Immutable instance with key and secret.
|
|
14
14
|
#
|
|
15
15
|
class ApiKeys
|
|
16
|
-
API_KEY_REGEX = /^([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})
|
|
16
|
+
API_KEY_REGEX = /^([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})$/
|
|
17
17
|
|
|
18
18
|
attr_reader :api_key,
|
|
19
19
|
:api_secret,
|
|
@@ -24,7 +24,7 @@ module DnsMadeEasy
|
|
|
24
24
|
include Sym
|
|
25
25
|
|
|
26
26
|
def initialize(key, secret, encryption_key = nil, default: false, account: nil)
|
|
27
|
-
raise InvalidCredentialKeys,
|
|
27
|
+
raise InvalidCredentialKeys, 'Key and Secret can not be nil' if key.nil? || secret.nil?
|
|
28
28
|
|
|
29
29
|
@default = default
|
|
30
30
|
@account = account
|
|
@@ -38,17 +38,19 @@ module DnsMadeEasy
|
|
|
38
38
|
@api_secret = secret
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
return if valid?
|
|
42
|
+
|
|
43
|
+
raise InvalidCredentialKeys,
|
|
44
|
+
"Key [#{api_key}] or Secret [#{api_secret}] has failed validation for its format"
|
|
42
45
|
end
|
|
43
46
|
|
|
44
47
|
def sym_resolve(encryption_key)
|
|
45
48
|
null_output = ::File.open('/dev/null', 'w')
|
|
46
|
-
result = Sym::Application.new({ cache_passwords: true, key: encryption_key }, $stdin, null_output,
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
end
|
|
49
|
+
result = Sym::Application.new({ cache_passwords: true, key: encryption_key }, $stdin, null_output,
|
|
50
|
+
null_output).execute
|
|
51
|
+
raise InvalidCredentialKeys, "Unable to decrypt the data, error is: #{result[:exception]}" if result.is_a?(Hash)
|
|
52
|
+
|
|
53
|
+
result
|
|
52
54
|
end
|
|
53
55
|
|
|
54
56
|
def to_s
|
|
@@ -21,28 +21,7 @@ module DnsMadeEasy
|
|
|
21
21
|
return nil if mash.nil?
|
|
22
22
|
|
|
23
23
|
creds = if mash.accounts.is_a?(Array)
|
|
24
|
-
|
|
25
|
-
mash.accounts.find { |a| a.name == account.to_s }
|
|
26
|
-
elsif
|
|
27
|
-
# rubocop:todo Layout/ConditionPosition
|
|
28
|
-
mash.accounts.size == 1
|
|
29
|
-
# rubocop:enable Layout/ConditionPosition
|
|
30
|
-
mash.accounts.first
|
|
31
|
-
else
|
|
32
|
-
mash.accounts.find(&:default_account)
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
unless account
|
|
36
|
-
raise DnsMadeEasy::APIKeyAndSecretMissingError,
|
|
37
|
-
(account ? "account #{account} was not found" : 'default account does not exist')
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
unless account.credentials
|
|
41
|
-
raise DnsMadeEasy::InvalidCredentialsFormatError,
|
|
42
|
-
'Expected account entry to have "credentials" key'
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
account.credentials
|
|
24
|
+
credentials_from_array(mash.accounts, account&.to_s)
|
|
46
25
|
|
|
47
26
|
elsif mash.credentials
|
|
48
27
|
mash.credentials
|
|
@@ -52,11 +31,11 @@ module DnsMadeEasy
|
|
|
52
31
|
'expected either "accounts" or "credentials" as the top-level key'
|
|
53
32
|
end
|
|
54
33
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
34
|
+
return nil unless creds
|
|
35
|
+
|
|
36
|
+
ApiKeys.new creds.api_key,
|
|
37
|
+
creds.api_secret,
|
|
38
|
+
encryption_key || creds.encryption_key
|
|
60
39
|
end
|
|
61
40
|
|
|
62
41
|
def to_s
|
|
@@ -65,6 +44,28 @@ module DnsMadeEasy
|
|
|
65
44
|
|
|
66
45
|
private
|
|
67
46
|
|
|
47
|
+
def credentials_from_array(accounts, account_name = nil)
|
|
48
|
+
account = if account_name
|
|
49
|
+
accounts.find { |a| a.name == account_name }
|
|
50
|
+
elsif accounts.size == 1
|
|
51
|
+
accounts.first
|
|
52
|
+
else
|
|
53
|
+
accounts.find(&:default_account)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
unless account
|
|
57
|
+
raise DnsMadeEasy::APIKeyAndSecretMissingError,
|
|
58
|
+
(account ? "account #{account} was not found" : 'Default account does not exist')
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
unless account.credentials
|
|
62
|
+
raise DnsMadeEasy::InvalidCredentialsFormatError,
|
|
63
|
+
'Expected an account entry to have the "credentials" key'
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
account.credentials
|
|
67
|
+
end
|
|
68
|
+
|
|
68
69
|
def parse!
|
|
69
70
|
self.mash = Hashie::Extensions::SymbolizeKeys.symbolize_keys(load_hash)
|
|
70
71
|
end
|
|
@@ -82,16 +82,15 @@ module DnsMadeEasy
|
|
|
82
82
|
def keys_from_file(file: default_credentials_path,
|
|
83
83
|
account: nil,
|
|
84
84
|
encryption_key: nil)
|
|
85
|
-
|
|
86
85
|
YamlFile.new(file: file).keys(account: account,
|
|
87
86
|
encryption_key: encryption_key)
|
|
88
87
|
end
|
|
89
88
|
|
|
90
89
|
# @return String path to the default credentials file.
|
|
91
90
|
def default_credentials_path(user: nil)
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
91
|
+
File.expand_path(
|
|
92
|
+
user ? Dir.home(user) + '/.dnsmadeeasy/credentials.yml' : '~/.dnsmadeeasy/credentials.yml'
|
|
93
|
+
)
|
|
95
94
|
end
|
|
96
95
|
end
|
|
97
96
|
end
|
data/lib/dnsmadeeasy/runner.rb
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
+
# vim: ft=ruby
|
|
5
|
+
|
|
4
6
|
require 'colored2'
|
|
5
7
|
require 'awesome_print'
|
|
6
8
|
require 'dnsmadeeasy'
|
|
@@ -9,12 +11,13 @@ require 'etc'
|
|
|
9
11
|
|
|
10
12
|
module DnsMadeEasy
|
|
11
13
|
class Runner
|
|
12
|
-
SUPPORTED_FORMATS = %w
|
|
14
|
+
SUPPORTED_FORMATS = %w[json json_pretty yaml pp].freeze
|
|
15
|
+
SUPPORTED_FORMAT_FLAGS = SUPPORTED_FORMATS.map { |f| "--#{f}" }.freeze
|
|
13
16
|
|
|
14
17
|
attr_accessor :format, :argv, :operation
|
|
15
18
|
|
|
16
19
|
def initialize(argv = nil)
|
|
17
|
-
self.argv
|
|
20
|
+
self.argv = argv || ARGV.dup
|
|
18
21
|
self.format = process_flags_format
|
|
19
22
|
end
|
|
20
23
|
|
|
@@ -44,76 +47,73 @@ module DnsMadeEasy
|
|
|
44
47
|
end
|
|
45
48
|
0
|
|
46
49
|
rescue ArgumentError => e
|
|
50
|
+
warn(e.backtrace.join("\n").yellow)
|
|
47
51
|
sig = method_signature(e, method)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
print_signature(method, sig) :
|
|
51
|
-
print_error('Action', method.to_s.bold.yellow.to_s, 'has generated an error'.red, exception: e)
|
|
52
|
-
# rubocop:enable Style/MultilineTernaryOperator
|
|
52
|
+
print_signature(method, sig) if sig.shift == method.to_s
|
|
53
|
+
print_error('Action', method.to_s.bold.yellow.to_s, 'has generated an error'.red, exception: e)
|
|
53
54
|
1
|
|
54
|
-
rescue NoMethodError
|
|
55
|
+
rescue NoMethodError
|
|
55
56
|
print_error('Action', method.to_s.bold.yellow.to_s, 'is not valid.'.red)
|
|
56
57
|
puts 'HINT: try running ' + 'dme operations'.bold.green + ' to see the list of valid operations.'
|
|
57
58
|
2
|
|
58
|
-
rescue Net::
|
|
59
|
+
rescue Net::HTTPClientException => e
|
|
59
60
|
print_error(exception: e)
|
|
60
61
|
3
|
|
61
62
|
end
|
|
62
63
|
|
|
63
64
|
def print_error(*args, exception: nil)
|
|
64
65
|
unless args.empty?
|
|
65
|
-
# rubocop:todo Naming/HeredocDelimiterNaming
|
|
66
66
|
puts <<~EOF
|
|
67
|
-
#
|
|
68
|
-
#{'Error:'.bold.red} #{args.join(' ').red}
|
|
67
|
+
#{'Error:'.bold.red} #{args.join(' ').red}
|
|
69
68
|
EOF
|
|
70
69
|
end
|
|
71
70
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
EOF
|
|
78
|
-
end
|
|
71
|
+
return unless exception
|
|
72
|
+
|
|
73
|
+
puts <<~EOF
|
|
74
|
+
#{'Exception: '.bold.red}#{exception.inspect.red}
|
|
75
|
+
EOF
|
|
79
76
|
end
|
|
80
77
|
|
|
81
78
|
def print_signature(method, sig)
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
#
|
|
85
|
-
#{'Error: '.bold.yellow}
|
|
86
|
-
#{'You are missing some arguments for this operation:'.red}
|
|
79
|
+
puts <<~EOF
|
|
80
|
+
#{'Error: '.bold.yellow}
|
|
81
|
+
#{'You are missing some arguments for this operation:'.red}
|
|
87
82
|
|
|
88
|
-
|
|
89
|
-
|
|
83
|
+
#{'Correct Usage: '.bold.yellow}
|
|
84
|
+
#{method.to_s.bold.green} #{sig.join(' ').blue}
|
|
90
85
|
|
|
91
86
|
EOF
|
|
92
87
|
end
|
|
93
88
|
|
|
94
89
|
def process_flags_format
|
|
95
|
-
if argv.first
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
90
|
+
if argv.first =~ /^op(erations)?$/i
|
|
91
|
+
print_supported_operations
|
|
92
|
+
|
|
93
|
+
elsif argv.include?('--help') || argv.include?('-h')
|
|
94
|
+
print_help_message
|
|
95
|
+
|
|
96
|
+
elsif !(argv & SUPPORTED_FORMAT_FLAGS).empty?
|
|
97
|
+
format_flag = (argv & SUPPORTED_FORMAT_FLAGS).first
|
|
98
|
+
format = format_flag.delete '--'
|
|
99
|
+
argv.delete(format_flag)
|
|
100
100
|
unless SUPPORTED_FORMATS.include?(format)
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
warn "Error: format #{format.bold.red} is not supported."
|
|
102
|
+
warn "Supported values are: #{SUPPORTED_FORMATS.join(', ')}"
|
|
103
103
|
exit 1
|
|
104
104
|
end
|
|
105
|
-
|
|
106
|
-
print_supported_operations
|
|
107
|
-
exit 0
|
|
105
|
+
format
|
|
108
106
|
end
|
|
109
|
-
format
|
|
110
107
|
end
|
|
111
108
|
|
|
112
109
|
def configure_authentication
|
|
113
|
-
credentials_file = ENV['DNSMADEEASY_CREDENTIALS_FILE'] ||
|
|
110
|
+
credentials_file = ENV['DNSMADEEASY_CREDENTIALS_FILE'] ||
|
|
111
|
+
DnsMadeEasy::Credentials.default_credentials_path(user: Etc.getlogin)
|
|
112
|
+
|
|
114
113
|
if ENV['DNSMADEEASY_API_KEY'] && ENV['DNSMADEEASY_API_SECRET']
|
|
115
114
|
DnsMadeEasy.api_key = ENV['DNSMADEEASY_API_KEY']
|
|
116
115
|
DnsMadeEasy.api_secret = ENV['DNSMADEEASY_API_SECRET']
|
|
116
|
+
|
|
117
117
|
elsif credentials_file && ::File.exist?(credentials_file)
|
|
118
118
|
keys = DnsMadeEasy::Credentials.keys_from_file(file: credentials_file)
|
|
119
119
|
if keys
|
|
@@ -122,64 +122,73 @@ module DnsMadeEasy
|
|
|
122
122
|
end
|
|
123
123
|
end
|
|
124
124
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
125
|
+
return unless DnsMadeEasy.api_key.nil?
|
|
126
|
+
|
|
127
|
+
print_error('API Key/Secret was not detected or read from file')
|
|
128
|
+
puts('You can also set two environment variables: ')
|
|
129
|
+
puts(' • DNSMADEEASY_API_KEY and ')
|
|
130
|
+
puts(' • DNSMADEEASY_API_SECRET')
|
|
131
|
+
exit 123
|
|
131
132
|
end
|
|
132
133
|
|
|
133
134
|
def print_usage_message
|
|
134
|
-
# rubocop:todo Naming/HeredocDelimiterNaming
|
|
135
135
|
puts <<~EOF
|
|
136
|
-
#
|
|
137
|
-
#{'
|
|
138
|
-
|
|
139
|
-
#{"dme [ #{SUPPORTED_FORMATS.map { |f| "--#{f}" }.join(' | ')} ] operation [ arg1 arg2 ... ] ".bold.green}
|
|
140
|
-
|
|
141
|
-
#{'# Print suported operations:'.dark}
|
|
142
|
-
#{'dme op[erations]'.bold.green}
|
|
136
|
+
#{'Usage:'.bold.yellow}
|
|
137
|
+
#{'# Execute an API call:'.dark}
|
|
138
|
+
#{"dme [ #{SUPPORTED_FORMATS.map { |f| "--#{f}" }.join(' | ')} ] operation [ arg1 arg2 ... ] ".bold.green}
|
|
143
139
|
|
|
140
|
+
#{'# Print suported operations:'.dark}
|
|
141
|
+
#{'dme op[erations]'.bold.green}
|
|
144
142
|
EOF
|
|
145
143
|
end
|
|
146
144
|
|
|
147
145
|
def print_help_message
|
|
148
146
|
print_usage_message
|
|
149
|
-
|
|
150
|
-
# rubocop:todo Naming/HeredocDelimiterNaming
|
|
151
147
|
puts <<~EOF
|
|
152
|
-
# rubocop:enable Naming/HeredocDelimiterNaming
|
|
153
|
-
#{header 'Credentials'}
|
|
154
|
-
Store your credentials in a YAML file
|
|
155
|
-
#{DnsMadeEasy::Credentials.default_credentials_path(user: ENV['USER'])} as follows:
|
|
156
148
|
|
|
149
|
+
#{header 'Credentials'}
|
|
150
|
+
Store your credentials in a YAML file #{DnsMadeEasy::Credentials.default_credentials_path(user: ENV.fetch('USER', nil)).blue}
|
|
151
|
+
as follows:
|
|
152
|
+
|
|
153
|
+
#{'# ~/.dnsmadeeasy/credentials.yml'.bold.black}
|
|
157
154
|
#{'credentials:
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
#{'accounts:
|
|
164
|
-
- name: production
|
|
165
|
-
credentials:
|
|
166
|
-
api_key: XXXX
|
|
167
|
-
api_secret: YYYY
|
|
168
|
-
encryption_key: my_key
|
|
169
|
-
- name: development
|
|
170
|
-
default_account: true
|
|
171
|
-
credentials:
|
|
172
|
-
api_key: ZZZZ
|
|
173
|
-
api_secret: WWWW'.bold.magenta}
|
|
174
|
-
|
|
175
|
-
#{header 'Examples:'}
|
|
176
|
-
#{'dme domain moo.com
|
|
177
|
-
dme --json domain moo.com
|
|
178
|
-
dme find_all moo.com A www
|
|
179
|
-
dme find_first moo.com CNAME vpn-west
|
|
180
|
-
dme --yaml find_first moo.com CNAME vpn-west'.green}
|
|
155
|
+
api_key: XXXX
|
|
156
|
+
api_secret: YYYY'.cyan}
|
|
157
|
+
|
|
158
|
+
Or, you can use a multi-account version, but in that case it helps if you
|
|
159
|
+
specify which of the accounts is the default:
|
|
181
160
|
|
|
182
161
|
EOF
|
|
162
|
+
|
|
163
|
+
puts <<-YAML
|
|
164
|
+
#{'# ~/.dnsmadeeasy/credentials.yml'.bold.black}
|
|
165
|
+
#{'accounts:
|
|
166
|
+
- name: production
|
|
167
|
+
credentials:
|
|
168
|
+
api_key: XXXX
|
|
169
|
+
api_secret: YYYY
|
|
170
|
+
encryption_key: my_key
|
|
171
|
+
- name: development
|
|
172
|
+
default_account: true
|
|
173
|
+
credentials:
|
|
174
|
+
api_key: ZZZZ
|
|
175
|
+
api_secret: WWWW'.cyan}
|
|
176
|
+
YAML
|
|
177
|
+
|
|
178
|
+
puts header 'Examples:'
|
|
179
|
+
|
|
180
|
+
puts '
|
|
181
|
+
$ dme domain moo.com
|
|
182
|
+
$ dme domain moo.com --yaml > moo.yml
|
|
183
|
+
|
|
184
|
+
$ dme all moo.com
|
|
185
|
+
$ dme find_all moo.com A www
|
|
186
|
+
$ dme find_first moo.com CNAME vpn-west
|
|
187
|
+
|
|
188
|
+
$ dme update_record moo.com www A 11.3.43.56
|
|
189
|
+
$ dme create_record moo.com ftp CNAME www.moo.com.
|
|
190
|
+
|
|
191
|
+
$ dme --yaml find_first moo.com CNAME vpn-west'.green
|
|
183
192
|
exit 1
|
|
184
193
|
end
|
|
185
194
|
|
|
@@ -188,17 +197,14 @@ module DnsMadeEasy
|
|
|
188
197
|
end
|
|
189
198
|
|
|
190
199
|
def print_supported_operations
|
|
191
|
-
# rubocop:todo Naming/HeredocDelimiterNaming
|
|
192
200
|
puts <<~EOF
|
|
193
|
-
#
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
#{'http://www.rubydoc.info/gems/dnsmadeeasy/DnsMadeEasy/Api/Client'.blue.bold.underlined}
|
|
198
|
-
|
|
199
|
-
#{header 'Valid Operations Are:'}
|
|
200
|
-
#{DnsMadeEasy::Api::Client.public_operations.join("\n ").green.bold}
|
|
201
|
+
#{header 'Actions:'}
|
|
202
|
+
Checkout the README and RubyDoc for the arguments to each operation,
|
|
203
|
+
which is basically a method on a DnsMadeEasy::Api::Client instance.
|
|
204
|
+
#{'http://www.rubydoc.info/gems/dnsmadeeasy/DnsMadeEasy/Api/Client'.blue.bold.underlined}
|
|
201
205
|
|
|
206
|
+
#{header 'Valid Operations Are:'}
|
|
207
|
+
#{DnsMadeEasy::Api::Client.public_operations.join("\n ").green.bold}
|
|
202
208
|
EOF
|
|
203
209
|
end
|
|
204
210
|
|
|
@@ -210,7 +216,7 @@ module DnsMadeEasy
|
|
|
210
216
|
require 'pp'
|
|
211
217
|
pp result
|
|
212
218
|
else
|
|
213
|
-
m = "to_#{format}"
|
|
219
|
+
m = :"to_#{format}"
|
|
214
220
|
puts result.send(m) if result.respond_to?(m)
|
|
215
221
|
end
|
|
216
222
|
else
|
|
@@ -220,26 +226,24 @@ module DnsMadeEasy
|
|
|
220
226
|
|
|
221
227
|
# e.backtrack.first looks like this:
|
|
222
228
|
# ..../dnsmadeeasy/lib/dnsmadeeasy/api/client.rb:143:in `create_a_record'
|
|
223
|
-
# rubocop:todo Naming/MethodParameterName
|
|
224
229
|
def method_signature(e, method)
|
|
225
230
|
file, line, call_method = e.backtrace.first.split(':')
|
|
226
|
-
call_method
|
|
231
|
+
call_method = call_method.gsub('\'', '').split('`').last
|
|
227
232
|
if call_method && call_method.to_sym == method.to_sym
|
|
228
233
|
source_line = File.open(file).to_a[line.to_i - 1].chomp!
|
|
229
234
|
if source_line =~ /def #{method}/
|
|
230
|
-
signature = source_line.strip.gsub(
|
|
235
|
+
signature = source_line.strip.gsub(',', '').split(/[ ()]/)
|
|
231
236
|
signature.shift # remove def
|
|
232
|
-
return signature.reject { |a| a =~ /^([={}
|
|
237
|
+
return signature.reject { |a| a =~ /^([={})(])*$/ }
|
|
233
238
|
end
|
|
234
239
|
end
|
|
235
240
|
[]
|
|
236
241
|
rescue StandardError
|
|
237
242
|
[]
|
|
238
243
|
end
|
|
239
|
-
# rubocop:enable Naming/MethodParameterName
|
|
240
244
|
|
|
241
|
-
def puts(*
|
|
242
|
-
super
|
|
245
|
+
def puts(*)
|
|
246
|
+
super
|
|
243
247
|
end
|
|
244
248
|
end
|
|
245
249
|
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'dry/types'
|
|
4
|
+
|
|
5
|
+
module DnsMadeEasy
|
|
6
|
+
# Shared dry-rb types used by value objects.
|
|
7
|
+
module Types
|
|
8
|
+
include Dry.Types()
|
|
9
|
+
|
|
10
|
+
DNS_RECORD_TYPES = %w[A AAAA CNAME ANAME MX NS PTR SPF SRV TXT].freeze
|
|
11
|
+
|
|
12
|
+
StrictString = Strict::String
|
|
13
|
+
NonEmptyString = StrictString.constrained(min_size: 1)
|
|
14
|
+
RecordType = StrictString.enum(*DNS_RECORD_TYPES)
|
|
15
|
+
Ttl = Coercible::Integer.constrained(gteq: 0)
|
|
16
|
+
OptionalInteger = Coercible::Integer.optional
|
|
17
|
+
OptionalString = StrictString.optional
|
|
18
|
+
end
|
|
19
|
+
end
|
data/lib/dnsmadeeasy/version.rb
CHANGED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'dry/monads'
|
|
4
|
+
require 'resolv'
|
|
5
|
+
require 'dnsmadeeasy/zone/record'
|
|
6
|
+
|
|
7
|
+
module DnsMadeEasy
|
|
8
|
+
module Zone
|
|
9
|
+
# Flattens ANAME records into resolved A records for RFC-portable
|
|
10
|
+
# exports. Resolution is a point-in-time snapshot, so every conversion
|
|
11
|
+
# is reported as a notice.
|
|
12
|
+
class AnameFlattener
|
|
13
|
+
include Dry::Monads[:result]
|
|
14
|
+
|
|
15
|
+
def initialize(records, resolver: nil)
|
|
16
|
+
@records = records
|
|
17
|
+
@resolver = resolver || default_resolver
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def call
|
|
21
|
+
flattened = []
|
|
22
|
+
notices = []
|
|
23
|
+
|
|
24
|
+
records.each do |record|
|
|
25
|
+
if record.type == 'ANAME'
|
|
26
|
+
addresses = resolve(record)
|
|
27
|
+
return Failure(["Unable to resolve ANAME target #{record.value} for --strict-rfc export"]) if addresses.empty?
|
|
28
|
+
|
|
29
|
+
addresses.each { |address| flattened << record.new(type: 'A', value: address) }
|
|
30
|
+
notices << flatten_notice(record, addresses)
|
|
31
|
+
else
|
|
32
|
+
flattened << record
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
Success([flattened, notices])
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
attr_reader :records,
|
|
42
|
+
:resolver
|
|
43
|
+
|
|
44
|
+
def resolve(record)
|
|
45
|
+
resolver.call(record.value)
|
|
46
|
+
rescue Resolv::ResolvError
|
|
47
|
+
[]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def flatten_notice(record, addresses)
|
|
51
|
+
"Flattened ANAME #{record.owner} -> #{record.value} into A #{addresses.join(', ')} (point-in-time snapshot)"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def default_resolver
|
|
55
|
+
lambda do |target|
|
|
56
|
+
Resolv::DNS.open do |dns|
|
|
57
|
+
dns.getresources(target, Resolv::DNS::Resource::IN::A).map { |resource| resource.address.to_s }
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|