firespring_dev_commands 1.3.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/LICENSE +21 -0
- data/README.md +83 -0
- data/lib/firespring_dev_commands/audit/report/item.rb +33 -0
- data/lib/firespring_dev_commands/audit/report/levels.rb +36 -0
- data/lib/firespring_dev_commands/audit/report.rb +49 -0
- data/lib/firespring_dev_commands/aws/account/info.rb +15 -0
- data/lib/firespring_dev_commands/aws/account.rb +164 -0
- data/lib/firespring_dev_commands/aws/cloudformation/parameters.rb +26 -0
- data/lib/firespring_dev_commands/aws/cloudformation.rb +188 -0
- data/lib/firespring_dev_commands/aws/codepipeline.rb +96 -0
- data/lib/firespring_dev_commands/aws/credentials.rb +136 -0
- data/lib/firespring_dev_commands/aws/login.rb +131 -0
- data/lib/firespring_dev_commands/aws/parameter.rb +32 -0
- data/lib/firespring_dev_commands/aws/profile.rb +55 -0
- data/lib/firespring_dev_commands/aws/s3.rb +42 -0
- data/lib/firespring_dev_commands/aws.rb +10 -0
- data/lib/firespring_dev_commands/boolean.rb +7 -0
- data/lib/firespring_dev_commands/common.rb +112 -0
- data/lib/firespring_dev_commands/daterange.rb +171 -0
- data/lib/firespring_dev_commands/docker/compose.rb +271 -0
- data/lib/firespring_dev_commands/docker/status.rb +38 -0
- data/lib/firespring_dev_commands/docker.rb +276 -0
- data/lib/firespring_dev_commands/dotenv.rb +6 -0
- data/lib/firespring_dev_commands/env.rb +38 -0
- data/lib/firespring_dev_commands/eol/product_version.rb +86 -0
- data/lib/firespring_dev_commands/eol.rb +58 -0
- data/lib/firespring_dev_commands/git/info.rb +13 -0
- data/lib/firespring_dev_commands/git.rb +420 -0
- data/lib/firespring_dev_commands/jira/issue.rb +33 -0
- data/lib/firespring_dev_commands/jira/project.rb +13 -0
- data/lib/firespring_dev_commands/jira/user/type.rb +20 -0
- data/lib/firespring_dev_commands/jira/user.rb +31 -0
- data/lib/firespring_dev_commands/jira.rb +78 -0
- data/lib/firespring_dev_commands/logger.rb +8 -0
- data/lib/firespring_dev_commands/node/audit.rb +39 -0
- data/lib/firespring_dev_commands/node.rb +107 -0
- data/lib/firespring_dev_commands/php/audit.rb +71 -0
- data/lib/firespring_dev_commands/php.rb +109 -0
- data/lib/firespring_dev_commands/rake.rb +24 -0
- data/lib/firespring_dev_commands/ruby/audit.rb +30 -0
- data/lib/firespring_dev_commands/ruby.rb +113 -0
- data/lib/firespring_dev_commands/second.rb +22 -0
- data/lib/firespring_dev_commands/tar/pax_header.rb +49 -0
- data/lib/firespring_dev_commands/tar/type_flag.rb +49 -0
- data/lib/firespring_dev_commands/tar.rb +149 -0
- data/lib/firespring_dev_commands/templates/aws.rb +84 -0
- data/lib/firespring_dev_commands/templates/base_interface.rb +54 -0
- data/lib/firespring_dev_commands/templates/ci.rb +138 -0
- data/lib/firespring_dev_commands/templates/docker/application.rb +177 -0
- data/lib/firespring_dev_commands/templates/docker/default.rb +200 -0
- data/lib/firespring_dev_commands/templates/docker/node/application.rb +145 -0
- data/lib/firespring_dev_commands/templates/docker/php/application.rb +190 -0
- data/lib/firespring_dev_commands/templates/docker/ruby/application.rb +146 -0
- data/lib/firespring_dev_commands/templates/eol.rb +23 -0
- data/lib/firespring_dev_commands/templates/git.rb +147 -0
- data/lib/firespring_dev_commands/version.rb +11 -0
- data/lib/firespring_dev_commands.rb +21 -0
- metadata +436 -0
@@ -0,0 +1,276 @@
|
|
1
|
+
require 'docker'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
# TODO: Make these configurable?
|
5
|
+
# Set docker write timeout to 1 hour
|
6
|
+
Excon.defaults[:write_timeout] = 3600
|
7
|
+
|
8
|
+
# Set docker read timeout to 1 hour
|
9
|
+
Excon.defaults[:read_timeout] = 3600
|
10
|
+
|
11
|
+
module Dev
|
12
|
+
# Class contains many useful methods for interfacing with the docker api
|
13
|
+
class Docker
|
14
|
+
# Config object for setting top level docker config options
|
15
|
+
Config = Struct.new(:min_version, :max_version) do
|
16
|
+
def initialize
|
17
|
+
self.min_version = nil
|
18
|
+
self.max_version = nil
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class << self
|
23
|
+
# Instantiates a new top level config object if one hasn't already been created
|
24
|
+
# Yields that config object to any given block
|
25
|
+
# Returns the resulting config object
|
26
|
+
def config
|
27
|
+
@config ||= Config.new
|
28
|
+
yield(@config) if block_given?
|
29
|
+
@config
|
30
|
+
end
|
31
|
+
|
32
|
+
# Alias the config method to configure for a slightly clearer access syntax
|
33
|
+
alias_method :configure, :config
|
34
|
+
|
35
|
+
# Returns the version of the docker engine running on the system
|
36
|
+
def version
|
37
|
+
@version ||= JSON.parse(::Docker.connection.get('/version'))['Version']
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def initialize
|
42
|
+
check_version
|
43
|
+
end
|
44
|
+
|
45
|
+
# Checks the min and max version against the current docker version if they have been configured
|
46
|
+
def check_version
|
47
|
+
min_version = self.class.config.min_version
|
48
|
+
raise "requires docker version >= #{min_version} (found #{self.class.version})" if min_version && !Dev::Common.new.version_greater_than(min_version, self.class.version)
|
49
|
+
|
50
|
+
max_version = self.class.config.max_version
|
51
|
+
raise "requires docker version < #{max_version} (found #{self.class.version})" if max_version && Dev::Common.new.version_greater_than(max_version, self.class.version)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Prunes/removes all unused containers, networks, volumes, and images
|
55
|
+
def prune
|
56
|
+
prune_containers
|
57
|
+
prune_networks
|
58
|
+
prune_volumes
|
59
|
+
prune_images
|
60
|
+
end
|
61
|
+
|
62
|
+
# Prunes/removes all unused containers
|
63
|
+
def prune_containers
|
64
|
+
_prune('containers')
|
65
|
+
end
|
66
|
+
|
67
|
+
# Prunes/removes all unused networks
|
68
|
+
def prune_networks
|
69
|
+
_prune('networks')
|
70
|
+
end
|
71
|
+
|
72
|
+
# Prunes/removes all unused volumes
|
73
|
+
def prune_volumes
|
74
|
+
_prune('volumes')
|
75
|
+
end
|
76
|
+
|
77
|
+
# Prunes/removes all unused images
|
78
|
+
def prune_images
|
79
|
+
_prune('images')
|
80
|
+
end
|
81
|
+
|
82
|
+
# Private method which actually calls the prune endpoint on the docker api connection
|
83
|
+
private def _prune(type)
|
84
|
+
response = ::Docker.connection.post("/#{type.downcase}/prune", {})
|
85
|
+
format_prune(type, response)
|
86
|
+
rescue ::Docker::Error::ServerError => e
|
87
|
+
# Specifically check for 'prune already running' error and retry if found
|
88
|
+
if /already running/.match?(e.to_s)
|
89
|
+
sleep 2
|
90
|
+
retry
|
91
|
+
end
|
92
|
+
raise
|
93
|
+
end
|
94
|
+
|
95
|
+
# Callback method which formats the output from the prune method to give details on what was cleaned and how much space was reclaimed
|
96
|
+
private def format_prune(type, response)
|
97
|
+
info = JSON.parse(response)
|
98
|
+
type = type.capitalize
|
99
|
+
|
100
|
+
LOG.info "\nDeleted #{type.capitalize}"
|
101
|
+
deleted_items = info["#{type}Deleted"] || []
|
102
|
+
deleted_items.each { |it| LOG.info " #{it}" }
|
103
|
+
LOG.info "Total reclaimed space: #{filesize(info['SpaceReclaimed'])}"
|
104
|
+
end
|
105
|
+
|
106
|
+
# Print the given filesize using the most appropriate units
|
107
|
+
private def filesize(size)
|
108
|
+
return '0.0 B' if size.to_i.zero?
|
109
|
+
|
110
|
+
units = %w(B KB MB GB TB Pb EB)
|
111
|
+
exp = (Math.log(size) / Math.log(1024)).to_i
|
112
|
+
exp = 6 if exp > 6
|
113
|
+
|
114
|
+
format('%.1f %s', size.to_f / (1024**exp), units[exp])
|
115
|
+
end
|
116
|
+
|
117
|
+
# Remove docker images with the "force" option set to true
|
118
|
+
# This will remove the images even if they are currently in use and cause unintended side effects.
|
119
|
+
def force_remove_images(name_and_tag)
|
120
|
+
images = ::Docker::Image.all(filter: name_and_tag)
|
121
|
+
::Docker::Image.remove(images[0].id, force: true) unless images.empty?
|
122
|
+
end
|
123
|
+
|
124
|
+
# Calls the docker compose method with the given inputs
|
125
|
+
# @deprecated Please use {Docker::Compose#container_by_name} instead
|
126
|
+
def container_by_name(service_name, prefix = nil, status: [Docker::Status::RUNNING])
|
127
|
+
warn '[DEPRECATION] `Docker#container_by_name` is deprecated. Please use `Docker::Compose#container_by_name` instead.'
|
128
|
+
Docker::Compose.new.container_by_name(service_name, prefix, status)
|
129
|
+
end
|
130
|
+
|
131
|
+
# Calls the docker compose method with the given inputs
|
132
|
+
# @deprecated Please use {Docker::Compose#mapped_public_port} instead
|
133
|
+
def mapped_public_port(name, private_port)
|
134
|
+
warn '[DEPRECATION] `Docker#mapped_public_port` is deprecated. Please use `Docker::Compose#mapped_public_port` instead.'
|
135
|
+
Docker::Compose.new.mapped_public_port(name, private_port)
|
136
|
+
end
|
137
|
+
|
138
|
+
# Copies the source path on your local machine to the destination path on the container
|
139
|
+
def copy_to_container(container, source_path, dest_path)
|
140
|
+
LOG.info "Copying #{source_path} to #{dest_path}... "
|
141
|
+
|
142
|
+
container.archive_in(source_path, dest_path, overwrite: true)
|
143
|
+
return unless File.directory?(source_path)
|
144
|
+
|
145
|
+
dest_file = File.basename(source_path)
|
146
|
+
# TODO: Can we find a better solution for this? Seems pretty brittle
|
147
|
+
retcode = container.exec(['bash', '-c', "cd #{dest_path}; tar -xf #{dest_file}; rm -f #{dest_file}"])[-1]
|
148
|
+
raise 'Unable to unpack on container' unless retcode.zero?
|
149
|
+
end
|
150
|
+
|
151
|
+
# Copies the source path on the container to the destination path on your local machine
|
152
|
+
# If required is set to true, the command will fail if the source path does not exist on the container
|
153
|
+
def copy_from_container(container, source_path, dest_path, required: true)
|
154
|
+
LOG.info "Copying #{source_path} to #{dest_path}... "
|
155
|
+
|
156
|
+
tar = StringIO.new
|
157
|
+
begin
|
158
|
+
container.archive_out(source_path) do |chunk|
|
159
|
+
tar.write(chunk)
|
160
|
+
end
|
161
|
+
rescue => e
|
162
|
+
raise e if required
|
163
|
+
|
164
|
+
puts 'Not Found'
|
165
|
+
end
|
166
|
+
|
167
|
+
Dev::Tar.new(tar).unpack(source_path, dest_path)
|
168
|
+
end
|
169
|
+
|
170
|
+
# Display a nicely formatted table of images and their associated information
|
171
|
+
def print_images
|
172
|
+
reposize = 70
|
173
|
+
tagsize = 79
|
174
|
+
imagesize = 15
|
175
|
+
createsize = 20
|
176
|
+
sizesize = 10
|
177
|
+
total = reposize + tagsize + imagesize + createsize + sizesize
|
178
|
+
|
179
|
+
# If there is additional width available, add it to the repo and tag columns
|
180
|
+
additional = [((Rake.application.terminal_width - total) / 2).floor, 0].max
|
181
|
+
reposize += additional
|
182
|
+
tagsize += additional
|
183
|
+
|
184
|
+
format = "%-#{reposize}s%-#{tagsize}s%-#{imagesize}s%-#{createsize}s%s"
|
185
|
+
puts format(format, :REPOSITORY, :TAG, :'IMAGE ID', :CREATED, :SIZE)
|
186
|
+
::Docker::Image.all.each do |image|
|
187
|
+
image_info(image).each do |repo, tag, id, created, size|
|
188
|
+
puts format(format, repo, tag, id, created, size)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
194
|
+
# Take the given image and grab all of the parts of it which will be used to display the image info
|
195
|
+
private def image_info(image)
|
196
|
+
[].tap do |ary|
|
197
|
+
id = image.info&.dig('id')&.split(':')&.last&.slice(0..11)
|
198
|
+
created = timesince(Time.at(image.info&.dig('Created')))
|
199
|
+
size = filesize(image.info&.dig('Size'))
|
200
|
+
|
201
|
+
repo_urls = image.info&.dig('RepoTags')
|
202
|
+
repo_urls ||= ["#{image.info&.dig('RepoDigests')&.first&.split(':')&.first&.split('@')&.first}:<none>"]
|
203
|
+
repo_urls.each do |repo_url|
|
204
|
+
repo, tag = repo_url.split(':')
|
205
|
+
tag ||= '<none>'
|
206
|
+
ary << [repo, tag, id, created, size]
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
211
|
+
|
212
|
+
# Display a nicely formatted table of containers and their associated information
|
213
|
+
def print_containers
|
214
|
+
idsize = 15
|
215
|
+
imagesize = 25
|
216
|
+
commandsize = 25
|
217
|
+
createsize = 17
|
218
|
+
statussize = 16
|
219
|
+
portsize = 25
|
220
|
+
namesize = 10
|
221
|
+
total = idsize + imagesize + commandsize + createsize + statussize + portsize + namesize
|
222
|
+
|
223
|
+
# If there is additional width available, add it to the repo and tag columns
|
224
|
+
additional = [((Rake.application.terminal_width - total) / 2).floor, 0].max
|
225
|
+
imagesize += additional
|
226
|
+
portsize += additional
|
227
|
+
|
228
|
+
format = "%-#{idsize}s%-#{imagesize}s%-#{commandsize}s%-#{createsize}s%-#{statussize}s%-#{portsize}s%s"
|
229
|
+
puts format(format, :'CONTAINER ID', :IMAGE, :COMMAND, :CREATED, :STATUS, :PORTS, :NAMES)
|
230
|
+
::Docker::Container.all.each do |container|
|
231
|
+
id, image, command, created, status, ports, names = container_info(container)
|
232
|
+
puts format(format, id, image, command, created, status, ports, names)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
237
|
+
# Take the given container and grab all of the parts of it which will be used to display the container info
|
238
|
+
private def container_info(container)
|
239
|
+
id = container.id&.slice(0..11)
|
240
|
+
image = container.info&.dig('Image')
|
241
|
+
|
242
|
+
command = container.info&.dig('Command')&.truncate(20)
|
243
|
+
created = timesince(Time.at(container.info&.dig('Created')))
|
244
|
+
status = container.info&.dig('Status')
|
245
|
+
|
246
|
+
ports = container.info&.dig('Ports')&.map do |port_info|
|
247
|
+
ip = port_info['IP']
|
248
|
+
private_port = port_info['PrivatePort']
|
249
|
+
public_port = port_info['PublicPort']
|
250
|
+
type = port_info['Type']
|
251
|
+
next "#{private_port}/#{type}" unless ip && public_port
|
252
|
+
|
253
|
+
"#{ip}:#{public_port}->#{private_port}/#{type}"
|
254
|
+
end&.join(', ')
|
255
|
+
names = container.info&.dig('Names')&.map { |name| name.split('/').last }&.join(', ')
|
256
|
+
|
257
|
+
[id, image, command, created, status, ports, names]
|
258
|
+
end
|
259
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
260
|
+
|
261
|
+
# Print the time since the given time in the most appropriate unit
|
262
|
+
private def timesince(time)
|
263
|
+
return '' unless time && time.is_a?(Time)
|
264
|
+
|
265
|
+
time_since = (Time.now - time).to_i
|
266
|
+
return "#{time_since} seconds ago" if time_since <= Dev::Second::PER_MINUTE
|
267
|
+
return "#{(time_since / Dev::Second::PER_MINUTE).ceil} minutes ago" if time_since <= Dev::Second::PER_HOUR
|
268
|
+
return "#{(time_since / Dev::Second::PER_HOUR).ceil} hours ago" if time_since <= Dev::Second::PER_DAY
|
269
|
+
return "#{(time_since / Dev::Second::PER_DAY).ceil} days ago" if time_since <= Dev::Second::PER_WEEK
|
270
|
+
return "#{(time_since / Dev::Second::PER_WEEK).ceil} weeks ago" if time_since <= Dev::Second::PER_MONTH
|
271
|
+
return "#{(time_since / Dev::Second::PER_MONTH).ceil} months ago" if time_since <= Dev::Second::PER_YEAR
|
272
|
+
|
273
|
+
"#{(time_since / Dev::Second::PER_YEAR).ceil} years ago"
|
274
|
+
end
|
275
|
+
end
|
276
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Dev
|
2
|
+
# Class instance represents a '.env' file and includes methods to read/write the given filename
|
3
|
+
class Env
|
4
|
+
require 'pathname'
|
5
|
+
|
6
|
+
attr_accessor :envfile, :data
|
7
|
+
|
8
|
+
def initialize(filename = '.env')
|
9
|
+
@envfile = Pathname.new(filename)
|
10
|
+
@data = {}
|
11
|
+
return unless File.exist?(@envfile)
|
12
|
+
|
13
|
+
File.readlines(@envfile).each do |line|
|
14
|
+
key, value = line.split('=')
|
15
|
+
@data[key.to_s.strip.to_sym] = value.to_s.strip
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Get the value of the key from the loaded env data
|
20
|
+
def get(key)
|
21
|
+
data[key.to_s.strip.to_sym]
|
22
|
+
end
|
23
|
+
|
24
|
+
# Set the value of the key in the loaded env data
|
25
|
+
def set(key, value)
|
26
|
+
data[key.to_s.strip.to_sym] = value.to_s.strip
|
27
|
+
end
|
28
|
+
|
29
|
+
# Write the current env data back to the original file
|
30
|
+
def write
|
31
|
+
File.open(envfile, 'w') do |file|
|
32
|
+
data.each do |key, value|
|
33
|
+
file.write("#{key}=#{value}\n")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
module Dev
|
4
|
+
class EndOfLife
|
5
|
+
# Class which tracks a specific product and provides methods for determining the end of life date
|
6
|
+
class ProductVersion
|
7
|
+
attr_accessor :name, :cycle, :eol, :description
|
8
|
+
|
9
|
+
def initialize(name, cycle, description = nil)
|
10
|
+
@name = name
|
11
|
+
@cycle = cycle
|
12
|
+
@eol_date = nil
|
13
|
+
@eol = nil
|
14
|
+
@description = description
|
15
|
+
end
|
16
|
+
|
17
|
+
# Print the status information for the product with additional coloring to show eol status
|
18
|
+
def print_status
|
19
|
+
puts to_s_colorize
|
20
|
+
end
|
21
|
+
|
22
|
+
# Returns whether this product version is currently EOL
|
23
|
+
def eol
|
24
|
+
populate_eol_info unless @eol
|
25
|
+
@eol
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns the date at which this product is EOL
|
29
|
+
def eol_date
|
30
|
+
populate_eol_info unless @eol_date
|
31
|
+
@eol_date
|
32
|
+
end
|
33
|
+
|
34
|
+
# Populates the eol and eol_date values
|
35
|
+
# If eol is a boolean then the eol_date will be set to nil
|
36
|
+
def populate_eol_info
|
37
|
+
detail = product_detail(name, cycle)
|
38
|
+
eol = detail['eol']
|
39
|
+
if eol.boolean?
|
40
|
+
@eol = eol
|
41
|
+
else
|
42
|
+
@eol_date = Date.parse(eol)
|
43
|
+
@eol = @eol_date < Date.today
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Returns the product details for the product and cycle based off the api response and any manually configured dates
|
48
|
+
def product_detail(product, cycle)
|
49
|
+
detail = {}
|
50
|
+
|
51
|
+
uri = URI.parse("#{END_OF_LIFE_API_URL}/#{product}/#{cycle}.json")
|
52
|
+
response = Net::HTTP.get_response(uri)
|
53
|
+
detail = JSON.parse(response.body) if response.is_a?(Net::HTTPSuccess)
|
54
|
+
|
55
|
+
# If EOL info is a boolean or missing from the current details, overwrite with the manual date (if present)
|
56
|
+
manual_date = Dev::EndOfLife.config.manual_dates["#{product}_#{cycle.tr('.', '_')}".to_sym]
|
57
|
+
detail['eol'] = manual_date if manual_date && (detail['eol'].boolean? || detail['eol'].nil?)
|
58
|
+
|
59
|
+
raise "unable to query eol detail for #{name}, #{cycle}" if detail.empty?
|
60
|
+
|
61
|
+
detail
|
62
|
+
end
|
63
|
+
|
64
|
+
# Returns a string representation of the product and its eol status
|
65
|
+
def to_s
|
66
|
+
message = " #{name} (#{cycle}) is EOL on #{eol_date || 'n/a'}"
|
67
|
+
message << " (#{(eol_date - Date.today).to_i} days)" if eol_date
|
68
|
+
format '%-60s %s', message, description
|
69
|
+
end
|
70
|
+
|
71
|
+
# Returns the string representation of the product with additional coloring
|
72
|
+
def to_s_colorize
|
73
|
+
return to_s.light_red if eol
|
74
|
+
|
75
|
+
if eol_date
|
76
|
+
return to_s.light_green if eol_date > (Date.today + 240)
|
77
|
+
return to_s.light_yellow if eol_date > (Date.today + 60)
|
78
|
+
|
79
|
+
return to_s.light_magenta
|
80
|
+
end
|
81
|
+
|
82
|
+
to_s.light_white
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Dev
|
2
|
+
# Class that contains methods for checking product versions of all tracked projects
|
3
|
+
class EndOfLife
|
4
|
+
# The URL of the end of life project api
|
5
|
+
END_OF_LIFE_API_URL = 'https://endoflife.date/api'.freeze
|
6
|
+
|
7
|
+
# Config object for setting top level git config options
|
8
|
+
Config = Struct.new(:product_versions, :manual_dates) do
|
9
|
+
def initialize
|
10
|
+
self.product_versions = []
|
11
|
+
self.manual_dates = {}
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class << self
|
16
|
+
# Instantiates a new top level config object if one hasn't already been created
|
17
|
+
# Yields that config object to any given block
|
18
|
+
# Returns the resulting config object
|
19
|
+
def config
|
20
|
+
@config ||= Config.new
|
21
|
+
yield(@config) if block_given?
|
22
|
+
@config
|
23
|
+
end
|
24
|
+
|
25
|
+
# Alias the config method to configure for a slightly clearer access syntax
|
26
|
+
alias_method :configure, :config
|
27
|
+
end
|
28
|
+
|
29
|
+
attr_accessor :url, :products, :product_versions
|
30
|
+
|
31
|
+
def initialize(product_versions: self.class.config.product_versions)
|
32
|
+
@product_versions = Array(product_versions)
|
33
|
+
raise 'product version must be of type Dev::EndOfLife::ProductVersions' unless @product_versions.all?(Dev::EndOfLife::ProductVersion)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Returns all products supported by the EOL api
|
37
|
+
def products
|
38
|
+
unless @products
|
39
|
+
uri = URI.parse("#{END_OF_LIFE_API_URL}/all.json")
|
40
|
+
response = Net::HTTP.get_response(uri)
|
41
|
+
raise 'unable to query products' unless response.is_a?(Net::HTTPSuccess)
|
42
|
+
|
43
|
+
@products = JSON.parse(response.body)
|
44
|
+
end
|
45
|
+
|
46
|
+
@products
|
47
|
+
end
|
48
|
+
|
49
|
+
# Prints all of the product version statuses
|
50
|
+
# Raises an error if any products are EOL
|
51
|
+
def check
|
52
|
+
puts
|
53
|
+
product_versions.sort_by(&:name).each(&:print_status)
|
54
|
+
puts
|
55
|
+
raise 'found EOL versions' if product_versions.any?(&:eol)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|