firespring_dev_commands 2.3.4 → 2.5.0.pre.alpha.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/README.md +1 -1
- data/lib/firespring_dev_commands/audit/report.rb +2 -9
- data/lib/firespring_dev_commands/aws/account.rb +1 -1
- data/lib/firespring_dev_commands/aws/cloudformation.rb +3 -10
- data/lib/firespring_dev_commands/aws/login.rb +11 -37
- data/lib/firespring_dev_commands/common.rb +2 -22
- data/lib/firespring_dev_commands/docker/compose.rb +1 -2
- data/lib/firespring_dev_commands/docker/status.rb +0 -20
- data/lib/firespring_dev_commands/docker.rb +16 -86
- data/lib/firespring_dev_commands/eol/aws.rb +2 -10
- data/lib/firespring_dev_commands/eol.rb +2 -22
- data/lib/firespring_dev_commands/git.rb +29 -44
- data/lib/firespring_dev_commands/jira/issue.rb +1 -3
- data/lib/firespring_dev_commands/node.rb +1 -1
- data/lib/firespring_dev_commands/php/audit.rb +0 -4
- data/lib/firespring_dev_commands/php.rb +12 -28
- data/lib/firespring_dev_commands/platform.rb +31 -38
- data/lib/firespring_dev_commands/ruby.rb +3 -6
- data/lib/firespring_dev_commands/target_process/query.rb +4 -30
- data/lib/firespring_dev_commands/target_process/release.rb +1 -1
- data/lib/firespring_dev_commands/target_process/team_assignment.rb +1 -1
- data/lib/firespring_dev_commands/target_process/user.rb +1 -13
- data/lib/firespring_dev_commands/target_process/user_story.rb +1 -1
- data/lib/firespring_dev_commands/target_process/user_story_history.rb +1 -1
- data/lib/firespring_dev_commands/target_process.rb +7 -24
- data/lib/firespring_dev_commands/templates/aws.rb +6 -33
- data/lib/firespring_dev_commands/templates/base_interface.rb +2 -2
- data/lib/firespring_dev_commands/templates/ci.rb +11 -16
- data/lib/firespring_dev_commands/templates/docker/application.rb +2 -2
- data/lib/firespring_dev_commands/templates/docker/node/application.rb +5 -55
- data/lib/firespring_dev_commands/templates/docker/php/application.rb +16 -58
- data/lib/firespring_dev_commands/templates/docker/ruby/application.rb +5 -54
- data/lib/firespring_dev_commands/templates/eol.rb +2 -9
- data/lib/firespring_dev_commands/templates/git.rb +4 -171
- data/lib/firespring_dev_commands/version.rb +1 -1
- data/lib/firespring_dev_commands.rb +1 -1
- metadata +37 -113
- data/lib/firespring_dev_commands/aws/route53.rb +0 -139
- data/lib/firespring_dev_commands/bloom_growth/rock.rb +0 -34
- data/lib/firespring_dev_commands/bloom_growth/seat.rb +0 -16
- data/lib/firespring_dev_commands/bloom_growth/user.rb +0 -43
- data/lib/firespring_dev_commands/bloom_growth.rb +0 -132
- data/lib/firespring_dev_commands/certificate.rb +0 -59
- data/lib/firespring_dev_commands/coverage/base.rb +0 -16
- data/lib/firespring_dev_commands/coverage/cobertura.rb +0 -86
- data/lib/firespring_dev_commands/coverage/none.rb +0 -21
- data/lib/firespring_dev_commands/dns/resource.rb +0 -83
- data/lib/firespring_dev_commands/docker/desktop.rb +0 -61
- data/lib/firespring_dev_commands/eol/node.rb +0 -42
- data/lib/firespring_dev_commands/eol/php.rb +0 -50
- data/lib/firespring_dev_commands/eol/ruby.rb +0 -42
- data/lib/firespring_dev_commands/jira/parent.rb +0 -19
- data/lib/firespring_dev_commands/os.rb +0 -35
- data/lib/firespring_dev_commands/port.rb +0 -24
- data/lib/firespring_dev_commands/target_process/time.rb +0 -32
- data/lib/firespring_dev_commands/templates/aws/services/route53.rb +0 -111
- data/lib/firespring_dev_commands/templates/certificate.rb +0 -41
@@ -1,139 +0,0 @@
|
|
1
|
-
require 'aws-sdk-route53'
|
2
|
-
|
3
|
-
module Dev
|
4
|
-
class Aws
|
5
|
-
# Class for performing Route53 functions
|
6
|
-
class Route53
|
7
|
-
attr_reader :client
|
8
|
-
|
9
|
-
def initialize(domains = nil)
|
10
|
-
@client = ::Aws::Route53::Client.new
|
11
|
-
@domains = Array(domains || [])
|
12
|
-
end
|
13
|
-
|
14
|
-
def zones(&)
|
15
|
-
if @domains.empty?
|
16
|
-
each_zone(&)
|
17
|
-
else
|
18
|
-
each_zone_by_domains(&)
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
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
|
28
|
-
end
|
29
|
-
rescue ::Aws::Route53::Errors::Throttling
|
30
|
-
sleep(1)
|
31
|
-
retry
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
private def each_zone_by_domains(&)
|
36
|
-
@domains.each do |domain|
|
37
|
-
response = client.list_hosted_zones_by_name({dns_name: domain})
|
38
|
-
|
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
|
48
|
-
end
|
49
|
-
end
|
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
|
-
|
57
|
-
private def target_config_id(zone_id)
|
58
|
-
client.list_query_logging_configs(
|
59
|
-
hosted_zone_id: zone_id,
|
60
|
-
max_results: '1'
|
61
|
-
).query_logging_configs&.first&.id
|
62
|
-
end
|
63
|
-
|
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)
|
75
|
-
|
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
|
85
|
-
end
|
86
|
-
puts
|
87
|
-
end
|
88
|
-
|
89
|
-
def list_query_configs
|
90
|
-
zones do |zone|
|
91
|
-
target_config_id = target_config_id(zone.id)
|
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
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
def activate_query_logging(log_group)
|
105
|
-
zones do |zone|
|
106
|
-
response = client.create_query_logging_config(
|
107
|
-
hosted_zone_id: zone.id,
|
108
|
-
cloud_watch_logs_log_group_arn: log_group
|
109
|
-
)
|
110
|
-
puts format('%-50s => %s', zone.id, response.location)
|
111
|
-
rescue ::Aws::Route53::Errors::Throttling
|
112
|
-
sleep(1)
|
113
|
-
retry
|
114
|
-
rescue ::Aws::Route53::Errors::ServiceError => e
|
115
|
-
raise "Error: #{e.message}" unless e.instance_of?(::Aws::Route53::Errors::QueryLoggingConfigAlreadyExists)
|
116
|
-
|
117
|
-
puts format('%-50s => %s', zone.id, e.message)
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
|
-
def deactivate_query_logging
|
122
|
-
zones do |zone|
|
123
|
-
target_config_id = target_config_id(zone.id)
|
124
|
-
if target_config_id
|
125
|
-
client.delete_query_logging_config(
|
126
|
-
id: target_config_id
|
127
|
-
)
|
128
|
-
puts format('%-50s => %s', zone.id, 'Query logging config removed.'.colorize(:green))
|
129
|
-
else
|
130
|
-
puts format('%-50s => %s', zone.id, 'No query logging config assigned.'.colorize(:red))
|
131
|
-
end
|
132
|
-
rescue ::Aws::Route53::Errors::Throttling
|
133
|
-
sleep(1)
|
134
|
-
retry
|
135
|
-
end
|
136
|
-
end
|
137
|
-
end
|
138
|
-
end
|
139
|
-
end
|
@@ -1,34 +0,0 @@
|
|
1
|
-
module Dev
|
2
|
-
class BloomGrowth
|
3
|
-
# Class containing rock information
|
4
|
-
class Rock
|
5
|
-
attr_accessor :data, :id, :type, :name, :owner, :complete, :completion_id, :created, :due
|
6
|
-
attr_reader :state
|
7
|
-
|
8
|
-
def initialize(data)
|
9
|
-
@data = data
|
10
|
-
@id = data['Id']
|
11
|
-
@type = data['Type']
|
12
|
-
@name = data['Name'].to_s.strip
|
13
|
-
@owner = User.new(data['Owner']) if data['Owner']
|
14
|
-
@complete = data['Complete']
|
15
|
-
@completion_id = data['Completion']
|
16
|
-
@created = Time.parse(data['CreateTime']) if data['CreateTime']
|
17
|
-
@due = Time.parse(data['DueDate']) if data['DueDate']
|
18
|
-
@archived = data['Archived']
|
19
|
-
end
|
20
|
-
|
21
|
-
# Convert the completion_id bloom growth gives us into a text version
|
22
|
-
def state
|
23
|
-
case completion_id
|
24
|
-
when 0
|
25
|
-
'Off Track'
|
26
|
-
when 1
|
27
|
-
'On Track'
|
28
|
-
when 2
|
29
|
-
'Complete'
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
@@ -1,16 +0,0 @@
|
|
1
|
-
module Dev
|
2
|
-
class BloomGrowth
|
3
|
-
# Class containing seat information
|
4
|
-
class Seat
|
5
|
-
attr_accessor :data, :id, :type, :name
|
6
|
-
|
7
|
-
def initialize(data)
|
8
|
-
@data = data
|
9
|
-
position = data.dig('Group', 'Position')
|
10
|
-
@id = position&.fetch('Id')
|
11
|
-
@type = position&.fetch('Type')
|
12
|
-
@name = position&.fetch('Name').to_s.strip
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
@@ -1,43 +0,0 @@
|
|
1
|
-
module Dev
|
2
|
-
class BloomGrowth
|
3
|
-
# Class containing user information
|
4
|
-
class User
|
5
|
-
attr_accessor :data, :id, :type, :name, :rocks, :direct_reports, :seats
|
6
|
-
|
7
|
-
def initialize(data)
|
8
|
-
@data = data
|
9
|
-
@id = data['Id']
|
10
|
-
@type = data['Type']
|
11
|
-
@name = data['Name'].to_s.strip
|
12
|
-
@rocks = nil
|
13
|
-
@direct_reports = nil
|
14
|
-
@seats = nil
|
15
|
-
end
|
16
|
-
|
17
|
-
def rocks
|
18
|
-
@rocks ||= [].tap do |ary|
|
19
|
-
Dev::BloomGrowth.new.get("/api/v1/rocks/user/#{id}") do |data|
|
20
|
-
ary << Rock.new(data)
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
def direct_reports
|
26
|
-
@direct_reports ||= [].tap do |ary|
|
27
|
-
Dev::BloomGrowth.new.get("/api/v1/users/#{id}/directreports") do |data|
|
28
|
-
ary << User.new(data)
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
def seats
|
34
|
-
@seats ||= [].tap do |ary|
|
35
|
-
Dev::BloomGrowth.new.get("/api/v1/users/#{id}/seats") do |data|
|
36
|
-
ary << Seat.new(data)
|
37
|
-
puts ary.last.inspect
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
@@ -1,132 +0,0 @@
|
|
1
|
-
require 'net/http'
|
2
|
-
|
3
|
-
module Dev
|
4
|
-
# Class for interacting with the Bloom Growth api
|
5
|
-
class BloomGrowth
|
6
|
-
# The config file to try to load credentials from
|
7
|
-
CONFIG_FILE = "#{Dir.home}/.env.bloom".freeze
|
8
|
-
|
9
|
-
# The text of the username variable key
|
10
|
-
BLOOM_USERNAME = 'BLOOM_USERNAME'.freeze
|
11
|
-
|
12
|
-
# The text of the password variable key
|
13
|
-
BLOOM_PASSWORD = 'BLOOM_PASSWORD'.freeze
|
14
|
-
|
15
|
-
# The text of the token variable key
|
16
|
-
BLOOM_TOKEN = 'BLOOM_TOKEN'.freeze
|
17
|
-
|
18
|
-
# The text of the url variable key
|
19
|
-
BLOOM_URL = 'BLOOM_URL'.freeze
|
20
|
-
|
21
|
-
# Config object for setting top level bloom growth config options
|
22
|
-
Config = Struct.new(:username, :password, :url, :http_debug) do
|
23
|
-
def initialize
|
24
|
-
Dotenv.load(CONFIG_FILE) if File.exist?(CONFIG_FILE)
|
25
|
-
|
26
|
-
self.username = ENV.fetch(BLOOM_USERNAME, nil)
|
27
|
-
self.password = ENV.fetch(BLOOM_PASSWORD, nil)
|
28
|
-
self.url = ENV.fetch(BLOOM_URL, 'https://app.bloomgrowth.com')
|
29
|
-
self.http_debug = false
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
class << self
|
34
|
-
# Instantiates a new top level config object if one hasn't already been created
|
35
|
-
# Yields that config object to any given block
|
36
|
-
# Returns the resulting config object
|
37
|
-
def config
|
38
|
-
@config ||= Config.new
|
39
|
-
yield(@config) if block_given?
|
40
|
-
@config
|
41
|
-
end
|
42
|
-
|
43
|
-
# Alias the config method to configure for a slightly clearer access syntax
|
44
|
-
alias_method :configure, :config
|
45
|
-
end
|
46
|
-
|
47
|
-
attr_accessor :username, :password, :url, :token, :client, :default_headers
|
48
|
-
|
49
|
-
# Initialize a new target process client using the given inputs
|
50
|
-
def initialize(username: self.class.config.username, password: self.class.config.password, url: self.class.config.url)
|
51
|
-
raise 'username is required' if username.to_s.strip.empty?
|
52
|
-
raise 'password is required' if password.to_s.strip.empty?
|
53
|
-
raise 'url is required' if url.to_s.strip.empty?
|
54
|
-
|
55
|
-
@username = username
|
56
|
-
@password = password
|
57
|
-
@url = url
|
58
|
-
uri = URI.parse(@url)
|
59
|
-
@client = Net::HTTP.new(uri.host, uri.port)
|
60
|
-
@client.use_ssl = true
|
61
|
-
@client.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
62
|
-
@client.set_debug_output(LOG) if self.class.config.http_debug
|
63
|
-
@default_headers = {
|
64
|
-
'authorization' => "Bearer #{token}",
|
65
|
-
'content-type' => 'application/json',
|
66
|
-
'accept' => 'application/json'
|
67
|
-
}
|
68
|
-
end
|
69
|
-
|
70
|
-
# Method for getting a bearer token for the bloom growth api. There are a couple of possible logic paths
|
71
|
-
# - If a token has already been defined, use it
|
72
|
-
# - If a token is found in the ENV, use it
|
73
|
-
# - Otherwise, use the username and passowrd that has been configured to request a new token from bloom
|
74
|
-
def token
|
75
|
-
@token ||= ENV.fetch(BLOOM_TOKEN, nil)
|
76
|
-
|
77
|
-
unless @token
|
78
|
-
response = post(
|
79
|
-
'/Token',
|
80
|
-
{
|
81
|
-
grant_type: 'password',
|
82
|
-
userName: username,
|
83
|
-
password:
|
84
|
-
},
|
85
|
-
headers: {
|
86
|
-
'content-type' => 'application/json',
|
87
|
-
'accept' => 'application/json'
|
88
|
-
}
|
89
|
-
)
|
90
|
-
# TODO: Should we look at https://github.com/DannyBen/lightly for caching the token?
|
91
|
-
@token = ENV[BLOOM_TOKEN] = response['access_token']
|
92
|
-
LOG.info("Retrieved BloomGrowth token. Expires on #{Time.now + response['expires_in']}")
|
93
|
-
end
|
94
|
-
|
95
|
-
@token
|
96
|
-
end
|
97
|
-
|
98
|
-
# Return all user objects visible to the logged in user
|
99
|
-
def visible_users(&)
|
100
|
-
[].tap do |ary|
|
101
|
-
get('/api/v1/users/mineviewable') do |user_data|
|
102
|
-
ary << User.new(user_data)
|
103
|
-
end
|
104
|
-
ary.each(&)
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
# Perform a get request to the given path using the given query
|
109
|
-
# Call the given block (if present) with each piece of data
|
110
|
-
# Return all pieces of data
|
111
|
-
def get(path, query_string: nil, headers: default_headers, &)
|
112
|
-
url = path
|
113
|
-
url << "?#{URI.encode_www_form(query_string)}" unless query_string.to_s.strip.empty?
|
114
|
-
|
115
|
-
response = client.request_get(url, headers)
|
116
|
-
raise "Error querying #{url} [#{query_string}]: #{response.inspect}" unless response.response.is_a?(Net::HTTPSuccess)
|
117
|
-
|
118
|
-
JSON.parse(response.body).each(&)
|
119
|
-
nil
|
120
|
-
end
|
121
|
-
|
122
|
-
# Perform a post request to the given path using the gien data
|
123
|
-
# Return the parsed json body
|
124
|
-
def post(path, data, headers: default_headers)
|
125
|
-
data = data.to_json unless data.is_a?(String)
|
126
|
-
response = client.request_post(path, data, headers)
|
127
|
-
raise "Error querying #{url}/#{path}: #{response.inspect}" unless response.response.is_a?(Net::HTTPSuccess)
|
128
|
-
|
129
|
-
JSON.parse(response.body)
|
130
|
-
end
|
131
|
-
end
|
132
|
-
end
|
@@ -1,59 +0,0 @@
|
|
1
|
-
module Dev
|
2
|
-
# Class contains methods for requesting a certificate from route53.
|
3
|
-
# You must have a hosted zone defined for the desired domain
|
4
|
-
class Certificate
|
5
|
-
attr_accessor :domains, :email
|
6
|
-
|
7
|
-
def initialize(domains, email)
|
8
|
-
@domains = Array(domains)
|
9
|
-
@email = email
|
10
|
-
raise 'No certificate domains specified' if domains.empty?
|
11
|
-
end
|
12
|
-
|
13
|
-
# Request the certificate using the route53 docker image
|
14
|
-
# Certificate is stored in /etc/letsencrypt
|
15
|
-
def request
|
16
|
-
puts
|
17
|
-
puts 'Getting SSL Certs For:'
|
18
|
-
puts domains.join("\n")
|
19
|
-
puts
|
20
|
-
puts 'This process can take up to 10 minutes'
|
21
|
-
puts
|
22
|
-
puts Time.now
|
23
|
-
|
24
|
-
# TODO: Really should use the docker api for this
|
25
|
-
cmd = %w(docker run -it --rm --name certbot)
|
26
|
-
cmd << '-e' << 'AWS_ACCESS_KEY_ID'
|
27
|
-
cmd << '-e' << 'AWS_SECRET_ACCESS_KEY'
|
28
|
-
cmd << '-e' << 'AWS_SESSION_TOKEN'
|
29
|
-
cmd << '-v' << '/etc/letsencrypt:/etc/letsencrypt'
|
30
|
-
cmd << 'certbot/dns-route53:latest'
|
31
|
-
cmd << 'certonly'
|
32
|
-
cmd << '-n'
|
33
|
-
cmd << '--agree-tos'
|
34
|
-
cmd << '--dns-route53'
|
35
|
-
cmd << '-d' << domains.join(',')
|
36
|
-
cmd << '--email' << email
|
37
|
-
cmd << '--server' << 'https://acme-v02.api.letsencrypt.org/directory'
|
38
|
-
puts cmd.join(' ')
|
39
|
-
Dev::Common.new.run_command(cmd)
|
40
|
-
end
|
41
|
-
|
42
|
-
# Saves the latest version of the certificate into the given dest_dir
|
43
|
-
def save(dest_dir)
|
44
|
-
raise "directory #{dest_dir} must be an existing directory" unless File.directory?(dest_dir)
|
45
|
-
|
46
|
-
domain = domains.first.sub(/^\*\./, '') # Need to strip off the '*.' if this is a wildcard cert
|
47
|
-
directories = Dir.glob("/etc/letsencrypt/live/#{domain}*/")
|
48
|
-
no_suffix = directories.delete("/etc/letsencrypt/live/#{domain}/")
|
49
|
-
biggest_suffix = directories.max
|
50
|
-
source_dir = biggest_suffix || no_suffix
|
51
|
-
raise "unable to determine certificate directory for #{domain}" unless source_dir
|
52
|
-
|
53
|
-
FileUtils.cp("#{source_dir}privkey.pem", dest_dir, verbose: true)
|
54
|
-
FileUtils.cp("#{source_dir}cert.pem", dest_dir, verbose: true)
|
55
|
-
FileUtils.cp("#{source_dir}chain.pem", dest_dir, verbose: true)
|
56
|
-
FileUtils.cp("#{source_dir}fullchain.pem", dest_dir, verbose: true)
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
@@ -1,16 +0,0 @@
|
|
1
|
-
module Dev
|
2
|
-
module Coverage
|
3
|
-
# Class which defines the methods which must be implemented to function as a coverage class
|
4
|
-
class Base
|
5
|
-
# Raises not implemented
|
6
|
-
def php_options
|
7
|
-
raise 'not implemented'
|
8
|
-
end
|
9
|
-
|
10
|
-
# Raises not implemented
|
11
|
-
def check(*)
|
12
|
-
raise 'not implemented'
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
@@ -1,86 +0,0 @@
|
|
1
|
-
module Dev
|
2
|
-
# Module containing different classes for interfacing with coverage files
|
3
|
-
module Coverage
|
4
|
-
# Class for checking code coverage using cobertura
|
5
|
-
class Cobertura < Base
|
6
|
-
attr_reader :local_filename, :container_filename, :filename, :threshold, :exclude
|
7
|
-
|
8
|
-
def initialize(filename: File.join('coverage', 'cobertura.xml'), threshold: nil, container_path: nil, local_path: nil, exclude: nil)
|
9
|
-
super()
|
10
|
-
|
11
|
-
@filename = filename
|
12
|
-
@local_filename = File.join(local_path || '.', @filename)
|
13
|
-
@container_filename = File.join(container_path || '.', @filename)
|
14
|
-
@threshold = threshold
|
15
|
-
@exclude = (exclude || []).map do |it|
|
16
|
-
next it if it.is_a?(Regex)
|
17
|
-
|
18
|
-
Regex.new(it)
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
# Remove any previous versions of the local file that will be output
|
23
|
-
# return the phpunit options needed to regenerate the cobertura xml file
|
24
|
-
def php_options
|
25
|
-
# Remove any previous coverage info
|
26
|
-
FileUtils.rm_f(local_filename, verbose: true)
|
27
|
-
|
28
|
-
# Return the needed php commands to generate the cobertura report
|
29
|
-
%W(--coverage-cobertura #{container_filename})
|
30
|
-
end
|
31
|
-
|
32
|
-
# Parse the cobertura file and check the lines missed against the desired threshold
|
33
|
-
def check(application: nil)
|
34
|
-
# If an application has been specified and the file does not exist locally, attempt to copy it back from the docker container
|
35
|
-
if application && !File.exist?(local_filename)
|
36
|
-
container = Dev::Docker::Compose.new.container_by_name(application)
|
37
|
-
Dev::Docker.new.copy_from_container(container, container_filename, local_filename, required: true)
|
38
|
-
end
|
39
|
-
|
40
|
-
report = Ox.load(File.read(local_filename))
|
41
|
-
total_missed = report.coverage.locate('packages/package').sum { |package| parse_package_missed(package) }
|
42
|
-
puts "Lines missing coverage was #{total_missed}"
|
43
|
-
puts "Configured threshold was #{threshold}" if threshold
|
44
|
-
raise 'Code coverage not met' if threshold && total_missed > threshold
|
45
|
-
end
|
46
|
-
|
47
|
-
# Go through the package and add up all of the lines that were missed
|
48
|
-
# Ignore if the file was in the exlude list
|
49
|
-
private def parse_package_missed(package)
|
50
|
-
filename = package.attributes[:name]
|
51
|
-
return if exclude.any? { |it| it.match(filename) }
|
52
|
-
|
53
|
-
missed = 0
|
54
|
-
lines_processed = Set.new
|
55
|
-
package.locate('classes/class/lines/line').each do |line|
|
56
|
-
# Don't count lines multiple times
|
57
|
-
line_number = line.attributes[:number]
|
58
|
-
next if lines_processed.include?(line_number)
|
59
|
-
|
60
|
-
lines_processed << line_number
|
61
|
-
missed += 1 unless line.attributes[:hits].to_i.positive?
|
62
|
-
end
|
63
|
-
total = lines_processed.length
|
64
|
-
|
65
|
-
sanity_check_coverage_against_cobertura_values(package, missed, total)
|
66
|
-
missed
|
67
|
-
end
|
68
|
-
|
69
|
-
# Calculate the coverage percent based off the numbers we got and compare to the
|
70
|
-
# value cobertura reported. This is meant as a sanity check that we are reading the data correctly
|
71
|
-
# TODO: This should be removed after the above logic has been vetted
|
72
|
-
private def sanity_check_coverage_against_cobertura_values(package, missed, total)
|
73
|
-
line_rate = package.attributes[:'line-rate']
|
74
|
-
cobertura_reported_coverage = line_rate.to_f
|
75
|
-
cobertura_reported_precision = line_rate.split('.').last.length
|
76
|
-
|
77
|
-
file_coverage = 0.0
|
78
|
-
file_coverage = ((total - missed).to_f / total).round(cobertura_reported_precision) if total.positive?
|
79
|
-
return if file_coverage == cobertura_reported_coverage
|
80
|
-
|
81
|
-
filename = package.attributes[:name]
|
82
|
-
puts "WARNINNG: #{filename} coverage (#{file_coverage}) differed from what cobertura reported (#{cobertura_reported_coverage})"
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
86
|
-
end
|
@@ -1,21 +0,0 @@
|
|
1
|
-
module Dev
|
2
|
-
# Module with a variety of coverage methods for different languages
|
3
|
-
module Coverage
|
4
|
-
# Class which provides methods to effectvely skip coverage
|
5
|
-
class None < Base
|
6
|
-
def initialize(*)
|
7
|
-
super()
|
8
|
-
end
|
9
|
-
|
10
|
-
# Returns the php options for generating code coverage file
|
11
|
-
def php_options
|
12
|
-
[]
|
13
|
-
end
|
14
|
-
|
15
|
-
# Checks the code coverage against the defined threshold
|
16
|
-
def check(*)
|
17
|
-
puts 'Coverage not configured'
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
@@ -1,83 +0,0 @@
|
|
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
|