firespring_dev_commands 2.2.2 → 2.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b60fb9f23da32fe3006bd63627788753dc822b14d16782e253ecdfe3e516ae12
4
- data.tar.gz: 94e3b4fc79749292e56e9db5de9f170def61c64e154daa11ef66e8bdbc4049cf
3
+ metadata.gz: 599f3f484514462356186c8d52adfac32e8aa925f7aeaa488bb5fa109db678f3
4
+ data.tar.gz: 3dffab80edf84b75d073adfc21c1a2c3e0d9dcc666a526f2bda2d5471ff24d6e
5
5
  SHA512:
6
- metadata.gz: 5a53e1b2ffd61b54eabf8ad81559054dec6fd40f9023ebf22ec27aea56cc9c3cd884cc329d0055b133da54792e77bce23ea44119fceb67e87493e8e80d5e25af
7
- data.tar.gz: 10237fa32a8c41d9a9b9c7196d7ded272d5ec4c36ad2cb2d789956acb33ed66a70388d4368b4ac2ed2fc5024aca91381d682cb1ef9f73b226ee5a1c88a44e0bd
6
+ metadata.gz: 0ea2c7d6d09e1e5ad6a85ee3d7d3e90826018bee1afc7c1ce1a206ca8514b658c8ef29534dc5b4082bf185d1540a9292218eb3836f6c3405b44d3ff10d98b484
7
+ data.tar.gz: c06fa865de8acedba8ec7b2934094f7a03fb46e2e1d5a116fc2b12fa4febcf6874940771a843bb022f5f571d5b77e9d4f00d5b3f4930f4cd2f16aa44293a66dd
@@ -1,44 +1,59 @@
1
+ require 'aws-sdk-route53'
2
+
1
3
  module Dev
2
4
  class Aws
3
5
  # Class for performing Route53 functions
4
6
  class Route53
5
7
  attr_reader :client
6
8
 
7
- def initialize(domains)
9
+ def initialize(domains = nil)
8
10
  @client = ::Aws::Route53::Client.new
9
- @domains = domains
11
+ @domains = Array(domains || [])
10
12
  end
11
13
 
12
- private def zones
14
+ def zones(&)
13
15
  if @domains.empty?
14
- all_zones
16
+ each_zone(&)
15
17
  else
16
- zones_by_domain_names(@domains)
18
+ each_zone_by_domains(&)
17
19
  end
18
20
  end
19
21
 
20
- private def all_zones
21
- [].tap do |ary|
22
- Dev::Aws.each_page(client, :list_hosted_zones) do |response|
23
- response.hosted_zones&.each do |hosted_zone|
24
- ary << hosted_zone unless hosted_zone.config.private_zone
25
- end
22
+ private def each_zone
23
+ Dev::Aws.each_page(client, :list_hosted_zones) do |response|
24
+ response.hosted_zones&.each do |hosted_zone|
25
+ next if hosted_zone.config.private_zone
26
+
27
+ yield hosted_zone
26
28
  end
29
+ rescue ::Aws::Route53::Errors::Throttling
30
+ sleep(1)
31
+ retry
27
32
  end
28
33
  end
29
34
 
30
- private def zones_by_domain_names(domains)
31
- [].tap do |ary|
32
- domains.each do |domain_name|
33
- response = client.list_hosted_zones_by_name({dns_name: domain_name})
34
- target = response.hosted_zones.find { |it| it.name.chomp('.') == domain_name }
35
- raise "The #{domain_name} hosted zone not found." unless target
35
+ private def each_zone_by_domains(&)
36
+ @domains.each do |domain|
37
+ response = client.list_hosted_zones_by_name({dns_name: domain})
36
38
 
37
- ary << target
38
- end
39
+ # The 'list_hosted_zones_by_name' returns fuzzy matches (so "foo.com" would return both "bar.foo.com" and "foo.com"
40
+ # So we are only selecting domains that match exactly since that's what we really want here
41
+ targets = response.hosted_zones.select { |it| it.name.chomp('.') == domain }
42
+ raise "The #{domain} hosted zone not found." if targets.empty?
43
+
44
+ targets.each(&)
45
+ rescue ::Aws::Route53::Errors::Throttling
46
+ sleep(1)
47
+ retry
39
48
  end
40
49
  end
41
50
 
51
+ private def ip_address(domain)
52
+ Addrinfo.ip(domain.to_s.strip)&.ip_address
53
+ rescue SocketError
54
+ "Unable to resolve domain: #{domain}"
55
+ end
56
+
42
57
  private def target_config_id(zone_id)
43
58
  client.list_query_logging_configs(
44
59
  hosted_zone_id: zone_id,
@@ -46,61 +61,78 @@ module Dev
46
61
  ).query_logging_configs&.first&.id
47
62
  end
48
63
 
49
- private def pretty_puts(output)
50
- # Find the maximum length of the keys
51
- max_key_length = output.keys.map(&:to_s).max_by(&:length).length
64
+ # Get the hosted zone details for the zone id
65
+ private def details(zone_id)
66
+ response = client.get_hosted_zone(id: zone_id)
67
+ [response.hosted_zone, response.delegation_set]
68
+ end
69
+
70
+ def list_zone_details
71
+ zones do |zone|
72
+ puts
73
+ zone_details, delegation_set = details(zone.id)
74
+ dns_resource = Dev::Dns::Resource.new(zone_details.name)
52
75
 
53
- output.each do |key, value|
54
- puts "#{key.to_s.ljust(max_key_length)}\t=>\t#{value}"
76
+ puts "#{zone_details.name.chomp('.').light_white} (#{zone_details.id}):"
77
+ puts format(' %-50s %s', 'Delegation Set:', delegation_set.id)
78
+ puts format(' %-50s %s', 'Delegation Defined Nameservers:', delegation_set.name_servers.sort.join(', '))
79
+ puts format(' %-50s %s', 'DNS Reported Nameservers:', dns_resource.recursive_nameserver_lookup.sort.join(', '))
80
+ puts format(' %-50s %s', 'DNS Reported Nameserver IPs:', dns_resource.recursive_nameserver_lookup.sort.map { |it| dns_resource.recursive_a_lookup(it) }.join(', '))
81
+ puts format(' %-50s %s', 'Domain Apex IP Resolution:', dns_resource.recursive_a_lookup.sort.join(', '))
82
+ rescue ::Aws::Route53::Errors::Throttling
83
+ sleep(1)
84
+ retry
55
85
  end
86
+ puts
56
87
  end
57
88
 
58
89
  def list_query_configs
59
- output = {}
60
- zones.each do |zone|
90
+ zones do |zone|
61
91
  target_config_id = target_config_id(zone.id)
62
-
63
- output[zone.name] = if target_config_id
64
- "Config\t=>\t#{target_config_id}".colorize(:green)
65
- else
66
- 'No query logging config assigned.'.colorize(:red)
67
- end
92
+ message = if target_config_id
93
+ "Config\t=>\t#{target_config_id}".colorize(:green)
94
+ else
95
+ 'No query logging config assigned.'.colorize(:red)
96
+ end
97
+ puts format('%-50s => %s', zone.name, message)
98
+ rescue ::Aws::Route53::Errors::Throttling
99
+ sleep(1)
100
+ retry
68
101
  end
69
-
70
- pretty_puts(output)
71
102
  end
72
103
 
73
104
  def activate_query_logging(log_group)
74
- output = {}
75
-
76
- zones.each do |zone|
105
+ zones do |zone|
77
106
  response = client.create_query_logging_config(
78
107
  hosted_zone_id: zone.id,
79
108
  cloud_watch_logs_log_group_arn: log_group
80
109
  )
81
- output[zone.id] = response.location
110
+ puts format('%-50s => %s', zone.id, response.location)
111
+ rescue ::Aws::Route53::Errors::Throttling
112
+ sleep(1)
113
+ retry
82
114
  rescue ::Aws::Route53::Errors::ServiceError => e
83
115
  raise "Error: #{e.message}" unless e.instance_of?(::Aws::Route53::Errors::QueryLoggingConfigAlreadyExists)
84
116
 
85
- output[zone.id] = e.message
117
+ puts format('%-50s => %s', zone.id, e.message)
86
118
  end
87
- pretty_puts(output)
88
119
  end
89
120
 
90
121
  def deactivate_query_logging
91
- output = {}
92
- zones.each do |zone|
122
+ zones do |zone|
93
123
  target_config_id = target_config_id(zone.id)
94
124
  if target_config_id
95
125
  client.delete_query_logging_config(
96
126
  id: target_config_id
97
127
  )
98
- output[zone.id] = 'Query logging config removed.'.colorize(:green)
128
+ puts format('%-50s => %s', zone.id, 'Query logging config removed.'.colorize(:green))
99
129
  else
100
- output[zone.id] = 'No query logging config assigned.'.colorize(:red)
130
+ puts format('%-50s => %s', zone.id, 'No query logging config assigned.'.colorize(:red))
101
131
  end
132
+ rescue ::Aws::Route53::Errors::Throttling
133
+ sleep(1)
134
+ retry
102
135
  end
103
- pretty_puts(output)
104
136
  end
105
137
  end
106
138
  end
@@ -0,0 +1,83 @@
1
+ module Dev
2
+ class Dns
3
+ class Resource
4
+ attr_reader :domain
5
+
6
+ def initialize(domain)
7
+ @domain = domain
8
+ end
9
+
10
+ # Returns whether or not the given value is a valid IPv4 or IPv6 address
11
+ def self.ip?(value)
12
+ ipv4?(value) || ipv6?(value)
13
+ end
14
+
15
+ # Returns whether or not the given value is a valid IPv4 address
16
+ def self.ipv4?(value)
17
+ value.match?(Resolv::IPv4::Regex)
18
+ end
19
+
20
+ # Returns whether or not the given value is a valid IPv6 address
21
+ def self.ipv6?(value)
22
+ value.match?(Resolv::IPv6::Regex)
23
+ end
24
+
25
+ # Recursively determine the correct nameservers for the given domain.
26
+ # If nameservers are not found, strip subdomains off until we've reached the TLD
27
+ def recursive_nameserver_lookup(name = domain)
28
+ records = lookup(name, type: Resolv::DNS::Resource::IN::NS)
29
+
30
+ # Strip the subdomain and try again if we didn't find any nameservers (this can happen with wildcards)
31
+ return recursive_nameserver_lookup(name.split('.', 2).last) if records.empty?
32
+
33
+ # Look up the IPs for the nameservers
34
+ records
35
+ end
36
+
37
+ # Recursively attempt to find an A record for the given domain.
38
+ # If one isn't found, also check for CNAMEs continually until we have either found an IP or run out of things to check
39
+ def recursive_a_lookup(name = domain)
40
+ # Try looking up an A record first. If we find one, we are done.
41
+ records = lookup(name, type: Resolv::DNS::Resource::IN::A)
42
+ return records unless records.empty?
43
+
44
+ # Try looking up a CNAME record
45
+ records = lookup(name, type: Resolv::DNS::Resource::IN::CNAME)
46
+
47
+ # If we didn't find an A record _or_ a CNAME, just return empty
48
+ return records if records.empty?
49
+
50
+ # If we found more than one CNAME that is a DNS error
51
+ raise "Found more than one CNAME entry for #{name}. This is not allowed by DNS" if records.length > 1
52
+
53
+ recursive_a_lookup(records.first)
54
+ end
55
+
56
+ # Lookup the given name using the record type provided.
57
+ def lookup(name = domain, type: Resolv::DNS::Resource::IN::A)
58
+ # Validate the type
59
+ raise 'lookup type must be a Resolv::DNS::Resource' unless type.ancestors.include?(Resolv::DNS::Resource)
60
+
61
+ # If we were given a tld, return empty
62
+ return [] unless name.include?('.')
63
+
64
+ # Look up NS records for the given host
65
+ records = Resolv::DNS.new.getresources(name, type)
66
+
67
+ # Return the record names
68
+ records.map do |record|
69
+ if record.respond_to?(:address)
70
+ record.address.to_s
71
+ elsif record.respond_to?(:name)
72
+ record.name.to_s
73
+ else
74
+ ''
75
+ end
76
+ end
77
+ rescue
78
+ sleep(1)
79
+ retry
80
+ end
81
+ end
82
+ end
83
+ end
@@ -6,16 +6,21 @@ module Dev
6
6
  module Services
7
7
  # Class contains rake templates for managing your AWS settings and logging in
8
8
  class Route53 < Dev::Template::BaseInterface
9
- # Create the rake task which ensures active credentials are present
10
- def create_ensure_credentials_task!
9
+ def create_list_zone_details_task!
11
10
  # Have to set a local variable to be accessible inside of the instance_eval block
12
11
  exclude = @exclude
13
12
 
14
13
  DEV_COMMANDS_TOP_LEVEL.instance_eval do
15
- return if exclude.include?(:ensure_aws_credentials)
14
+ return if exclude.include?(:list_details)
16
15
 
17
- task ensure_aws_credentials: %w(init) do
18
- raise 'AWS Credentials not found / expired' unless Dev::Aws::Credentials.new.active?
16
+ namespace :aws do
17
+ namespace :hosted_zone do
18
+ desc 'print details for all hosted zones'
19
+ task list_details: %w(ensure_aws_credentials) do
20
+ route53 = Dev::Aws::Route53.new(ENV['DOMAINS'].to_s.strip.split(','))
21
+ route53.list_zone_details
22
+ end
23
+ end
19
24
  end
20
25
  end
21
26
  end
@@ -26,9 +31,9 @@ module Dev
26
31
  exclude = @exclude
27
32
 
28
33
  DEV_COMMANDS_TOP_LEVEL.instance_eval do
29
- namespace :aws do
30
- return if exclude.include?(:dns_logging)
34
+ return if exclude.include?(:dns_logging_activate)
31
35
 
36
+ namespace :aws do
32
37
  namespace :hosted_zone do
33
38
  namespace :dns_logging do
34
39
  desc 'Activates query logging for all hosted zones by default.' \
@@ -56,9 +61,9 @@ module Dev
56
61
  exclude = @exclude
57
62
 
58
63
  DEV_COMMANDS_TOP_LEVEL.instance_eval do
59
- namespace :aws do
60
- return if exclude.include?(:dns_logging)
64
+ return if exclude.include?(:dns_logging_deactivate)
61
65
 
66
+ namespace :aws do
62
67
  namespace :hosted_zone do
63
68
  namespace :dns_logging do
64
69
  desc 'Deactivates query logging for all hosted zones by default. ' \
@@ -81,9 +86,9 @@ module Dev
81
86
  exclude = @exclude
82
87
 
83
88
  DEV_COMMANDS_TOP_LEVEL.instance_eval do
84
- namespace :aws do
85
- return if exclude.include?(:dns_logging)
89
+ return if exclude.include?(:dns_logging_config)
86
90
 
91
+ namespace :aws do
87
92
  namespace :hosted_zone do
88
93
  namespace :dns_logging do
89
94
  desc 'Lists the current config for domain(s). ' \
@@ -6,6 +6,6 @@ module Dev
6
6
  # Use 'v.v.v.pre.alpha.v' for pre-release vesions
7
7
  # Use 'v.v.v.beta.v for beta versions
8
8
  # Use semantic versioning for any releases (https://semver.org/)
9
- VERSION = '2.2.2'.freeze
9
+ VERSION = '2.2.3'.freeze
10
10
  end
11
11
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: firespring_dev_commands
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.2
4
+ version: 2.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Firespring
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-06-28 00:00:00.000000000 Z
11
+ date: 2024-07-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -353,6 +353,7 @@ files:
353
353
  - lib/firespring_dev_commands/coverage/cobertura.rb
354
354
  - lib/firespring_dev_commands/coverage/none.rb
355
355
  - lib/firespring_dev_commands/daterange.rb
356
+ - lib/firespring_dev_commands/dns/resource.rb
356
357
  - lib/firespring_dev_commands/docker.rb
357
358
  - lib/firespring_dev_commands/docker/compose.rb
358
359
  - lib/firespring_dev_commands/docker/desktop.rb