firespring_dev_commands 2.5.0.pre.alpha.2 → 3.0.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/lib/firespring_dev_commands/audit/report.rb +9 -2
- data/lib/firespring_dev_commands/aws/account.rb +3 -7
- data/lib/firespring_dev_commands/aws/login.rb +0 -20
- data/lib/firespring_dev_commands/bloom_growth/rock.rb +34 -0
- data/lib/firespring_dev_commands/bloom_growth/seat.rb +16 -0
- data/lib/firespring_dev_commands/bloom_growth/user.rb +43 -0
- data/lib/firespring_dev_commands/bloom_growth.rb +132 -0
- data/lib/firespring_dev_commands/common.rb +11 -22
- data/lib/firespring_dev_commands/coverage/base.rb +21 -0
- data/lib/firespring_dev_commands/coverage/cobertura.rb +86 -0
- data/lib/firespring_dev_commands/coverage/none.rb +25 -0
- data/lib/firespring_dev_commands/docker.rb +23 -25
- data/lib/firespring_dev_commands/node.rb +13 -12
- data/lib/firespring_dev_commands/php.rb +14 -10
- data/lib/firespring_dev_commands/platform.rb +1 -1
- data/lib/firespring_dev_commands/ruby.rb +19 -7
- data/lib/firespring_dev_commands/target_process/query.rb +30 -4
- data/lib/firespring_dev_commands/target_process.rb +3 -1
- data/lib/firespring_dev_commands/templates/aws.rb +0 -2
- data/lib/firespring_dev_commands/templates/base_interface.rb +2 -2
- data/lib/firespring_dev_commands/templates/docker/application.rb +2 -2
- data/lib/firespring_dev_commands/templates/docker/node/application.rb +47 -10
- data/lib/firespring_dev_commands/templates/docker/php/application.rb +45 -19
- data/lib/firespring_dev_commands/templates/docker/ruby/application.rb +44 -8
- data/lib/firespring_dev_commands/version.rb +1 -1
- data/lib/firespring_dev_commands.rb +1 -1
- metadata +42 -35
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 72bba6fb9e4d227bad3c551b1ac78eb42819178141353aefbf9a6186bd986175
|
4
|
+
data.tar.gz: d7f127460c9daedbe0bfe7e5902cc40196cd8e383a29c971ae0241ae76978f28
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 10690a7a9e253d00f7ca60d36358893248cd111a07b28918564b1dd79e2f19dcf77ea5d980c905005d05c1f2ed8aba395c595c3af3426c76c431062405f3eb86
|
7
|
+
data.tar.gz: 369af054ddaaeaae52e347ec02a49b26edff5f2b9c1b88c39c4b7061c68ec64ad091d052b193828034bd2f3d6165550fc920f752524ccbe70c8dc828d71beec1
|
@@ -3,11 +3,12 @@ module Dev
|
|
3
3
|
class Audit
|
4
4
|
# The class containing standardized information about an audit report
|
5
5
|
class Report
|
6
|
-
attr_accessor :items, :min_severity, :ignorelist, :filtered_items
|
6
|
+
attr_accessor :items, :min_severity, :error_on_unknown, :ignorelist, :filtered_items
|
7
7
|
|
8
8
|
def initialize(
|
9
9
|
items,
|
10
10
|
min_severity: ENV.fetch('MIN_SEVERITY', nil),
|
11
|
+
error_on_unknown: ENV.fetch('ERROR_ON_UNKNOWN', nil),
|
11
12
|
ignorelist: ENV['IGNORELIST'].to_s.split(/\s*,\s*/)
|
12
13
|
)
|
13
14
|
# Items should be an array of Item objects
|
@@ -15,12 +16,18 @@ module Dev
|
|
15
16
|
raise 'items must all be report items' unless @items.all?(Dev::Audit::Report::Item)
|
16
17
|
|
17
18
|
@min_severity = min_severity || Level::HIGH
|
19
|
+
@error_on_unknown = error_on_unknown
|
18
20
|
@ignorelist = Array(ignorelist).compact
|
19
21
|
end
|
20
22
|
|
21
23
|
# Get all severities greater than or equal to the minimum severity
|
22
24
|
def desired_severities
|
23
|
-
|
25
|
+
max_severity = if error_on_unknown.to_s.strip == 'true'
|
26
|
+
-1
|
27
|
+
else
|
28
|
+
-2
|
29
|
+
end
|
30
|
+
LEVELS.slice(LEVELS.find_index(min_severity)..max_severity)
|
24
31
|
end
|
25
32
|
|
26
33
|
# Run the filters against the report items and filter out any which should be excluded
|
@@ -3,8 +3,7 @@ module Dev
|
|
3
3
|
# Class containing useful methods for interacting with the Aws account
|
4
4
|
class Account
|
5
5
|
# Config object for setting top level Aws account config options
|
6
|
-
|
7
|
-
Config = Struct.new(:root, :children, :default, :registry, :ecr_registry_ids, :login_to_account_ecr_registry, :default_login_role_name)
|
6
|
+
Config = Struct.new(:root, :children, :default, :ecr_registry_ids, :login_to_account_ecr_registry, :default_login_role_name)
|
8
7
|
|
9
8
|
# Instantiates a new top level config object if one hasn't already been created
|
10
9
|
# Yields that config object to any given block
|
@@ -28,8 +27,7 @@ module Dev
|
|
28
27
|
IniFile.new(filename: CONFIG_FILE, default: 'default')
|
29
28
|
end
|
30
29
|
|
31
|
-
|
32
|
-
attr_accessor :root, :children, :default, :registry, :ecr_registry_ids
|
30
|
+
attr_accessor :root, :children, :default, :ecr_registry_ids
|
33
31
|
|
34
32
|
# Instantiate an account object
|
35
33
|
# Requires that root account and at least one child account have been configured
|
@@ -44,11 +42,9 @@ module Dev
|
|
44
42
|
@default = self.class.config.default
|
45
43
|
|
46
44
|
# Create the ecr registry list based off several possible configuration values
|
47
|
-
@ecr_registry_ids =
|
45
|
+
@ecr_registry_ids = Array(self.class.config.ecr_registry_ids)
|
48
46
|
@ecr_registry_ids << Dev::Aws::Profile.new.current if self.class.config.login_to_account_ecr_registry
|
49
|
-
@ecr_registry_ids.concat(Array(self.class.config.ecr_registry_ids))
|
50
47
|
@ecr_registry_ids = @ecr_registry_ids.flatten.compact.reject(&:empty?).uniq
|
51
|
-
@registry = @ecr_registry_ids.first
|
52
48
|
end
|
53
49
|
|
54
50
|
# Returns all configured account information objects
|
@@ -116,17 +116,6 @@ module Dev
|
|
116
116
|
ENV['ECR_REGISTRY'] ||= registry
|
117
117
|
end
|
118
118
|
|
119
|
-
# Authroizes the docker cli to pull/push images from the Aws container registry
|
120
|
-
# (e.g. if docker compose needs to pull an image)
|
121
|
-
# @deprecated Please use {Dev::Aws::Login#registry_login!} instead
|
122
|
-
def docker_login!(registry_id: nil, region: nil)
|
123
|
-
registry_id ||= Dev::Aws::Account.new.ecr_registry_ids.first
|
124
|
-
region ||= Dev::Aws::Credentials.new.logged_in_region || Dev::Aws::DEFAULT_REGION
|
125
|
-
warn '[DEPRECATION] `Dev::Aws::Login#docker_login!` is deprecated. Please use `Dev::Aws::Login#registry_login!` instead.'
|
126
|
-
docker_cli_login!(registry: "#{registry_id}.dkr.ecr.#{region}.amazonaws.com", region:)
|
127
|
-
puts
|
128
|
-
end
|
129
|
-
|
130
119
|
# Authroizes the docker cli to pull/push images from the Aws container registry
|
131
120
|
# (e.g. if docker compose needs to pull an image)
|
132
121
|
private def docker_cli_login!(registry:, region:)
|
@@ -137,15 +126,6 @@ module Dev
|
|
137
126
|
Dev::Common.new.run_command([login_cmd])
|
138
127
|
end
|
139
128
|
|
140
|
-
# Authroizes the docker ruby library to pull/push images from the Aws container registry
|
141
|
-
# @deprecated Please use {Dev::Aws::Login#registry_login!} instead
|
142
|
-
def ecr_login!(registry_id: nil, region: nil)
|
143
|
-
registry_id ||= Dev::Aws::Account.new.ecr_registry_ids.first
|
144
|
-
region ||= Dev::Aws::Credentials.new.logged_in_region || Dev::Aws::DEFAULT_REGION
|
145
|
-
warn '[DEPRECATION] `Dev::Aws::Login#ecr_login!` is deprecated. Please use `Dev::Aws::Login#registry_login!` instead.'
|
146
|
-
docker_lib_login!(registry_id:, region:)
|
147
|
-
end
|
148
|
-
|
149
129
|
# Authroizes the docker ruby library to pull/push images from the Aws container registry
|
150
130
|
private def docker_lib_login!(registry_id:, region:)
|
151
131
|
# Grab your authentication token from AWS ECR
|
@@ -0,0 +1,34 @@
|
|
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
|
@@ -0,0 +1,16 @@
|
|
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
|
@@ -0,0 +1,43 @@
|
|
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
|
@@ -0,0 +1,132 @@
|
|
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
|
@@ -37,28 +37,6 @@ module Dev
|
|
37
37
|
output
|
38
38
|
end
|
39
39
|
|
40
|
-
# Wraps a block of code in a y/n question.
|
41
|
-
# If the user answers 'y' then the block is executed.
|
42
|
-
# If the user answers 'n' then the block is skipped.
|
43
|
-
# @deprecated Please use {Common#when_confirmed} instead
|
44
|
-
def with_confirmation(message, default = 'y', color_message: true)
|
45
|
-
message = "\n #{message}" << '? '.light_green
|
46
|
-
message = message.light_green if color_message
|
47
|
-
print message
|
48
|
-
print '('.light_green << 'y'.light_yellow << '/'.light_green << 'n'.light_yellow << ') '.light_green
|
49
|
-
|
50
|
-
answer = default
|
51
|
-
answer = $stdin.gets unless ENV['NON_INTERACTIVE'] == 'true'
|
52
|
-
|
53
|
-
unless answer.strip.casecmp('y').zero?
|
54
|
-
puts "\n Cancelled.\n".light_yellow
|
55
|
-
exit 1
|
56
|
-
end
|
57
|
-
puts
|
58
|
-
|
59
|
-
yield
|
60
|
-
end
|
61
|
-
|
62
40
|
# Exits unless the user confirms they want to continue
|
63
41
|
# If the user answers 'y' then the code will continue
|
64
42
|
# All other inputs cause the code to exit
|
@@ -187,5 +165,16 @@ module Dev
|
|
187
165
|
return false
|
188
166
|
end
|
189
167
|
end
|
168
|
+
|
169
|
+
# Print the given filesize using the most appropriate units
|
170
|
+
def filesize(size)
|
171
|
+
return '0.0 B' if size.to_i.zero?
|
172
|
+
|
173
|
+
units = %w(B KB MB GB TB Pb EB)
|
174
|
+
exp = (Math.log(size) / Math.log(1024)).to_i
|
175
|
+
exp = 6 if exp > 6
|
176
|
+
|
177
|
+
format('%.1f %s', size.to_f / (1024**exp), units[exp])
|
178
|
+
end
|
190
179
|
end
|
191
180
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Dev
|
2
|
+
module Coverage
|
3
|
+
class Base
|
4
|
+
def php_options
|
5
|
+
raise 'not implemented'
|
6
|
+
end
|
7
|
+
|
8
|
+
def node_options
|
9
|
+
raise 'not implemented'
|
10
|
+
end
|
11
|
+
|
12
|
+
def ruby_options
|
13
|
+
raise 'not implemented'
|
14
|
+
end
|
15
|
+
|
16
|
+
def check(*)
|
17
|
+
raise 'not implemented'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,86 @@
|
|
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
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Dev
|
2
|
+
module Coverage
|
3
|
+
class None < Base
|
4
|
+
def initialize(*)
|
5
|
+
super()
|
6
|
+
end
|
7
|
+
|
8
|
+
def php_options
|
9
|
+
[]
|
10
|
+
end
|
11
|
+
|
12
|
+
def node_options
|
13
|
+
[]
|
14
|
+
end
|
15
|
+
|
16
|
+
def ruby_options
|
17
|
+
[]
|
18
|
+
end
|
19
|
+
|
20
|
+
def check(*)
|
21
|
+
# Nothing to do here
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -77,6 +77,22 @@ module Dev
|
|
77
77
|
_prune('volumes', opts:)
|
78
78
|
end
|
79
79
|
|
80
|
+
def prune_project_volumes(project_name:)
|
81
|
+
project_name = project_name.to_s.strip
|
82
|
+
raise 'No project name defined' if project_name.empty?
|
83
|
+
|
84
|
+
::Docker::Volume.all.each do |volume|
|
85
|
+
next unless volume.info['Name'].start_with?(project_name)
|
86
|
+
|
87
|
+
begin
|
88
|
+
volume.remove
|
89
|
+
LOG.info "Removed volume #{volume.id[0, 12]}"
|
90
|
+
rescue => e
|
91
|
+
LOG.error "Error removing volume #{volume.id[0, 12]}: #{e}"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
80
96
|
# Prunes/removes all unused images
|
81
97
|
def prune_images
|
82
98
|
_prune('images')
|
@@ -103,18 +119,7 @@ module Dev
|
|
103
119
|
LOG.info "\nDeleted #{type.capitalize}"
|
104
120
|
deleted_items = info["#{type}Deleted"] || []
|
105
121
|
deleted_items.each { |it| LOG.info " #{it}" }
|
106
|
-
LOG.info "Total reclaimed space: #{filesize(info['SpaceReclaimed'])}"
|
107
|
-
end
|
108
|
-
|
109
|
-
# Print the given filesize using the most appropriate units
|
110
|
-
private def filesize(size)
|
111
|
-
return '0.0 B' if size.to_i.zero?
|
112
|
-
|
113
|
-
units = %w(B KB MB GB TB Pb EB)
|
114
|
-
exp = (Math.log(size) / Math.log(1024)).to_i
|
115
|
-
exp = 6 if exp > 6
|
116
|
-
|
117
|
-
format('%.1f %s', size.to_f / (1024**exp), units[exp])
|
122
|
+
LOG.info "Total reclaimed space: #{Dev::Common.new.filesize(info['SpaceReclaimed'])}"
|
118
123
|
end
|
119
124
|
|
120
125
|
# Remove docker images with the "force" option set to true
|
@@ -124,22 +129,14 @@ module Dev
|
|
124
129
|
::Docker::Image.remove(images[0].id, force: true) unless images.empty?
|
125
130
|
end
|
126
131
|
|
127
|
-
#
|
128
|
-
|
129
|
-
|
130
|
-
warn '[DEPRECATION] `Docker#container_by_name` is deprecated. Please use `Docker::Compose#container_by_name` instead.'
|
131
|
-
Docker::Compose.new.container_by_name(service_name, prefix, status)
|
132
|
-
end
|
133
|
-
|
134
|
-
# Calls the docker compose method with the given inputs
|
135
|
-
# @deprecated Please use {Docker::Compose#mapped_public_port} instead
|
136
|
-
def mapped_public_port(name, private_port)
|
137
|
-
warn '[DEPRECATION] `Docker#mapped_public_port` is deprecated. Please use `Docker::Compose#mapped_public_port` instead.'
|
138
|
-
Docker::Compose.new.mapped_public_port(name, private_port)
|
132
|
+
# Gets the default working dir of the container
|
133
|
+
def working_dir(container)
|
134
|
+
container.json['Config']['WorkingDir']
|
139
135
|
end
|
140
136
|
|
141
137
|
# Copies the source path on your local machine to the destination path on the container
|
142
138
|
def copy_to_container(container, source_path, dest_path)
|
139
|
+
dest_path = File.join(working_dir(container), dest_path) unless dest_path.start_with?(File::SEPARATOR)
|
143
140
|
LOG.info "Copying #{source_path} to #{dest_path}... "
|
144
141
|
|
145
142
|
container.archive_in(source_path, dest_path, overwrite: true)
|
@@ -154,6 +151,7 @@ module Dev
|
|
154
151
|
# Copies the source path on the container to the destination path on your local machine
|
155
152
|
# If required is set to true, the command will fail if the source path does not exist on the container
|
156
153
|
def copy_from_container(container, source_path, dest_path, required: true)
|
154
|
+
source_path = File.join(working_dir(container), source_path) unless source_path.start_with?(File::SEPARATOR)
|
157
155
|
LOG.info "Copying #{source_path} to #{dest_path}... "
|
158
156
|
|
159
157
|
tar = StringIO.new
|
@@ -205,7 +203,7 @@ module Dev
|
|
205
203
|
arch = "#{arch}/#{variant}" if variant
|
206
204
|
id = image.info&.dig('id')&.split(':')&.last&.slice(0..11)
|
207
205
|
created = timesince(Time.at(image.info&.dig('Created')))
|
208
|
-
size = filesize(image.info&.dig('Size'))
|
206
|
+
size = Dev::Common.new.filesize(image.info&.dig('Size'))
|
209
207
|
|
210
208
|
repo_urls = image.info&.dig('RepoTags')
|
211
209
|
repo_urls ||= ["#{image.info&.dig('RepoDigests')&.first&.split(':')&.first&.split('@')&.first}:<none>"]
|