knife-tidy 1.2.0 → 2.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fe2a778de3020252562b8788aca9ad4c2a84c50621b1659c867b7b5b5db89d5e
4
- data.tar.gz: b305e31422e5cf415021bed9a0e3251550d6962b5ed0ba75a98c213741e2f077
3
+ metadata.gz: b559f18636e7cc22f399f44e5f3efd3e3c43d84c1fc5c34a1291ab6dcf8e1a6c
4
+ data.tar.gz: 067e18dcec7889510d0b95b443fa54d3f2e38577bdcffe71a01e1199ed956c03
5
5
  SHA512:
6
- metadata.gz: e0edc9e58ccd32ba959e0cdc1f1200b937459f0daad9547f302f4878da2d889a626b0dcedd8b90e8515e0c425f298ad3daa2a0169ba62530865dd998a85a69a0
7
- data.tar.gz: 980af3d17bb27eb7cf060e9e56c0f8419a9e3f557b78702ed3ef05106cf2dbf4323f025bbdf3cc76ae24bc50d71e98c51c45b9b99892ffd9c7adc7117ced2884
6
+ metadata.gz: d08512efe742bfe15cb2f52ed36e9f68d0e6aa26726cedd5649fb46917538e80bf8a9b1b264bb22d7e5c5706d2d7094c0d6ea320cbe39689605e30baa184be2e
7
+ data.tar.gz: 62b1d8f9465d6abe63e33996894f64f7db08eacf0cdff7801abb5751c0fcef3950458ed7ebbbc5f369b03e7e9855176a488ae737f7d4b50252011fe6525dfba6
@@ -1,35 +1,35 @@
1
- require 'chef/knife/tidy_base'
1
+ require "chef/knife/tidy_base"
2
2
 
3
3
  class Chef
4
4
  class Knife
5
5
  class TidyBackupClean < Knife
6
6
  deps do
7
- require 'chef/cookbook_loader'
8
- require 'chef/cookbook/metadata'
9
- require 'chef/role'
10
- require 'chef/run_list'
11
- require 'chef/tidy_substitutions'
12
- require 'chef/tidy_acls'
13
- require 'ffi_yajl'
14
- require 'fileutils'
15
- require 'securerandom'
7
+ require "chef/cookbook_loader"
8
+ require "chef/cookbook/metadata"
9
+ require "chef/role"
10
+ require "chef/run_list"
11
+ require "chef/tidy_substitutions"
12
+ require "chef/tidy_acls"
13
+ require "ffi_yajl"
14
+ require "fileutils"
15
+ require "securerandom"
16
16
  end
17
17
 
18
- banner 'knife tidy backup clean (options)'
18
+ banner "knife tidy backup clean (options)"
19
19
 
20
20
  include Knife::TidyBase
21
21
 
22
22
  option :backup_path,
23
- long: '--backup-path path/to/backup',
24
- description: 'The path to the knife-ec-backup backup directory'
23
+ long: "--backup-path path/to/backup",
24
+ description: "The path to the knife-ec-backup backup directory"
25
25
 
26
26
  option :gsub_file,
27
- long: '--gsub-file path/to/gsub/file',
28
- description: 'The path to the file used for substitutions. If non-existant, a boiler plate one will be created.'
27
+ long: "--gsub-file path/to/gsub/file",
28
+ description: "The path to the file used for substitutions. If non-existant, a boiler plate one will be created."
29
29
 
30
30
  option :gen_gsub,
31
- long: '--gen-gsub',
32
- description: 'Generate a new boiler plate global substitutions file: \'substitutions.json\'.'
31
+ long: "--gen-gsub",
32
+ description: "Generate a new boiler plate global substitutions file: 'substitutions.json'."
33
33
 
34
34
  def run
35
35
  FileUtils.rm_f(action_needed_file_path)
@@ -40,7 +40,7 @@ class Chef
40
40
  end
41
41
 
42
42
  unless config[:backup_path] && ::File.directory?(config[:backup_path])
43
- ui.error 'Must specify valid --backup-path'
43
+ ui.error "Must specify valid --backup-path"
44
44
  exit 1
45
45
  end
46
46
 
@@ -73,25 +73,25 @@ class Chef
73
73
  def validate_user_emails
74
74
  emails_seen = []
75
75
  tidy.global_user_names.each do |user|
76
- email = ''
76
+ email = ""
77
77
  ui.stdout.puts "INFO: Validating #{user}"
78
78
  the_user = FFI_Yajl::Parser.parse(::File.read(::File.join(tidy.users_path, "#{user}.json")), symbolize_names: false)
79
- if the_user.key?('email') && the_user['email'].match(/\A[^@\s]+@[^@\s]+\z/)
80
- if emails_seen.include?(the_user['email'])
79
+ if the_user.key?("email") && the_user["email"].match(/\A[^@\s]+@[^@\s]+\z/)
80
+ if emails_seen.include?(the_user["email"])
81
81
  ui.stdout.puts "REPAIRING: Already saw #{user}'s email, creating a unique one."
82
82
  email = tidy.unique_email
83
83
  new_user = the_user.dup
84
- new_user['email'] = email
84
+ new_user["email"] = email
85
85
  tidy.save_user(new_user)
86
86
  emails_seen.push(email)
87
87
  else
88
- emails_seen.push(the_user['email'])
88
+ emails_seen.push(the_user["email"])
89
89
  end
90
90
  else
91
91
  ui.stdout.puts "REPAIRING: User #{user} does not have a valid email, creating a unique one."
92
92
  email = tidy.unique_email
93
93
  new_user = the_user.dup
94
- new_user['email'] = email
94
+ new_user["email"] = email
95
95
  tidy.save_user(new_user)
96
96
  emails_seen.push(email)
97
97
  end
@@ -108,9 +108,9 @@ class Chef
108
108
  unless org_object.keys.count == 3 # cheapo, maybe expect the exact names?
109
109
  ui.stdout.puts "REPAIRING: org object for #{org} contains extra/missing fields. Fixing that for you"
110
110
  # quick/dirty attempt at fixing any of the required fields in case they're nil
111
- good_name = org_object['name'] || org
112
- good_full_name = org_object['full_name'] || org
113
- good_guid = org_object['guid'] || SecureRandom.uuid.delete('-')
111
+ good_name = org_object["name"] || org
112
+ good_full_name = org_object["full_name"] || org
113
+ good_guid = org_object["guid"] || SecureRandom.uuid.delete("-")
114
114
  fixed_org_object = { name: good_name, full_name: good_full_name, guid: good_guid }
115
115
 
116
116
  write_org_object(org, fixed_org_object)
@@ -118,14 +118,14 @@ class Chef
118
118
  end
119
119
 
120
120
  def load_org_object(org)
121
- JSON.parse(File.read(File.join(tidy.org_path(org), 'org.json')))
121
+ JSON.parse(File.read(File.join(tidy.org_path(org), "org.json")))
122
122
  rescue Errno::ENOENT, JSON::ParserError
123
123
  ui.stdout.puts "REPAIRING: org object for organization #{org} is missing or corrupt. Generating a new one"
124
- return { name: org, full_name: org, guid: SecureRandom.uuid.delete('-') }
124
+ { name: org, full_name: org, guid: SecureRandom.uuid.delete("-") }
125
125
  end
126
126
 
127
127
  def write_org_object(org, org_object)
128
- File.write(File.join(tidy.org_path(org), 'org.json'), JSON.pretty_generate(org_object))
128
+ File.write(File.join(tidy.org_path(org), "org.json"), JSON.pretty_generate(org_object))
129
129
  end
130
130
 
131
131
  def add_cookbook_name_to_metadata(cookbook_name, rb_path)
@@ -133,13 +133,13 @@ class Chef
133
133
  content = IO.readlines(rb_path)
134
134
  new_content = content.reject { |line| line =~ /^name\s+/ }
135
135
  name_field = "name '#{cookbook_name}'\n"
136
- IO.write rb_path, name_field + new_content.join('')
136
+ IO.write rb_path, name_field + new_content.join("")
137
137
  end
138
138
 
139
139
  def fix_cookbook_names(org)
140
140
  for_each_cookbook_path(org) do |cookbook_path|
141
- rb_path = ::File.join(cookbook_path, 'metadata.rb')
142
- json_path = ::File.join(cookbook_path, 'metadata.json')
141
+ rb_path = ::File.join(cookbook_path, "metadata.rb")
142
+ json_path = ::File.join(cookbook_path, "metadata.json")
143
143
  # next unless ::File.exist?(rb_path)
144
144
  cookbook_name = tidy.cookbook_name_from_path(cookbook_path)
145
145
  if ::File.exist?(rb_path)
@@ -148,10 +148,10 @@ class Chef
148
148
  else
149
149
  if ::File.exist?(json_path)
150
150
  metadata = FFI_Yajl::Parser.parse(::File.read(json_path), symbolize_names: false)
151
- if metadata['name'] != cookbook_name
152
- metadata['name'] = cookbook_name
151
+ if metadata["name"] != cookbook_name
152
+ metadata["name"] = cookbook_name
153
153
  ui.stdout.puts "REPAIRING: Correcting `name` in #{json_path}`"
154
- ::File.open(json_path, 'w') do |f|
154
+ ::File.open(json_path, "w") do |f|
155
155
  f.write(Chef::JSONCompat.to_json_pretty(metadata))
156
156
  end
157
157
  end
@@ -176,7 +176,7 @@ class Chef
176
176
  end
177
177
 
178
178
  def broken_cookooks_add(org, cookbook)
179
- broken_path = ::File.join(tidy.org_path(org), 'cookbooks.broken')
179
+ broken_path = ::File.join(tidy.org_path(org), "cookbooks.broken")
180
180
  FileUtils.mkdir(broken_path) unless ::File.directory?(broken_path)
181
181
  Dir[::File.join(tidy.cookbooks_path(org), "#{cookbook}*")].each do |cb|
182
182
  FileUtils.mv(cb, broken_path, verbose: true, force: true)
@@ -191,17 +191,17 @@ class Chef
191
191
  end
192
192
 
193
193
  def fix_chef_sugar_metadata
194
- Dir[::File.join(tidy.backup_path, 'organizations/*/cookbooks/chef-sugar*/metadata.rb')].each do |file|
195
- ui.stdout.puts 'INFO: Searching for known chef-sugar problems when uploading.'
194
+ Dir[::File.join(tidy.backup_path, "organizations/*/cookbooks/chef-sugar*/metadata.rb")].each do |file|
195
+ ui.stdout.puts "INFO: Searching for known chef-sugar problems when uploading."
196
196
  s = Chef::TidySubstitutions.new(nil, tidy)
197
197
  version = s.cookbook_version_from_path(file)
198
198
  patterns = [
199
199
  {
200
- search: '^require .*/lib/chef/sugar/version',
200
+ search: "^require .*/lib/chef/sugar/version",
201
201
  replace: "# require File.expand_path('../lib/chef/sugar/version', *__FILE__)",
202
202
  },
203
203
  {
204
- search: '^version *Chef::Sugar::VERSION',
204
+ search: "^version *Chef::Sugar::VERSION",
205
205
  replace: "version '#{version}'",
206
206
  },
207
207
  ]
@@ -214,46 +214,46 @@ class Chef
214
214
  def fix_self_dependencies(org)
215
215
  for_each_cookbook_path(org) do |cookbook_path|
216
216
  name = tidy.cookbook_name_from_path(cookbook_path)
217
- md_path = ::File.join(cookbook_path, 'metadata.rb')
217
+ md_path = ::File.join(cookbook_path, "metadata.rb")
218
218
  unless ::File.exist?(md_path)
219
219
  ui.stdout.puts "INFO: No metadata.rb in #{cookbook_path} - skipping"
220
220
  next
221
221
  end
222
222
  Chef::TidySubstitutions.new(nil, tidy).sub_in_file(
223
- ::File.join(cookbook_path, 'metadata.rb'),
223
+ ::File.join(cookbook_path, "metadata.rb"),
224
224
  Regexp.new("^depends +['\"]#{name}['\"]"),
225
225
  "# depends '#{name}' # knife-tidy was here")
226
226
  end
227
227
  end
228
228
 
229
229
  def fix_metadata_fields(cookbook_path)
230
- json_path = ::File.join(cookbook_path, 'metadata.json')
230
+ json_path = ::File.join(cookbook_path, "metadata.json")
231
231
  metadata = FFI_Yajl::Parser.parse(::File.read(json_path), symbolize_names: false)
232
232
  md = metadata.dup
233
233
  metadata.each_pair do |key, value|
234
234
  if value.nil?
235
235
  ui.stdout.puts "REPAIRING: Fixing null value for key #{key} in #{json_path}"
236
- md[key] = 'default value'
236
+ md[key] = "default value"
237
237
  end
238
238
  end
239
- if metadata.key?('platforms')
240
- metadata['platforms'].each_pair do |key, value|
239
+ if metadata.key?("platforms")
240
+ metadata["platforms"].each_pair do |key, value|
241
241
  # platform key cannot contain comma delimited values
242
- md['platforms'].delete(key) if key =~ /,/
242
+ md["platforms"].delete(key) if key =~ /,/
243
243
  if value.is_a?(Array) && value.empty?
244
244
  ui.stdout.puts "REPAIRING: Fixing empty platform key for for key #{key} in #{json_path}"
245
- md['platforms'][key] = '>= 0.0.0'
245
+ md["platforms"][key] = ">= 0.0.0"
246
246
  end
247
247
  end
248
248
  end
249
- ::File.open(json_path, 'w') do |f|
249
+ ::File.open(json_path, "w") do |f|
250
250
  f.write(Chef::JSONCompat.to_json_pretty(md))
251
251
  end
252
252
  end
253
253
 
254
254
  def generate_metadata_from_file(cookbook, path)
255
- md_path = ::File.join(path, 'metadata.rb')
256
- json_path = ::File.join(path, 'metadata.json')
255
+ md_path = ::File.join(path, "metadata.rb")
256
+ json_path = ::File.join(path, "metadata.json")
257
257
  if !::File.exist?(md_path) && !::File.exist?(json_path)
258
258
  create_minimal_metadata(path)
259
259
  end
@@ -265,8 +265,8 @@ class Chef
265
265
  md = Chef::Cookbook::Metadata.new
266
266
  md.name(cookbook)
267
267
  md.from_file(md_path)
268
- json_file = ::File.join(path, 'metadata.json')
269
- ::File.open(json_file, 'w') do |f|
268
+ json_file = ::File.join(path, "metadata.json")
269
+ ::File.open(json_file, "w") do |f|
270
270
  f.write(Chef::JSONCompat.to_json_pretty(md))
271
271
  end
272
272
  rescue Exceptions::ObsoleteDependencySyntax, Exceptions::InvalidVersionConstraint => e
@@ -280,18 +280,18 @@ class Chef
280
280
  def create_minimal_metadata(cookbook_path)
281
281
  name = tidy.cookbook_name_from_path(cookbook_path)
282
282
  components = cookbook_path.split(File::SEPARATOR)
283
- name_version = components[components.index('cookbooks') + 1]
283
+ name_version = components[components.index("cookbooks") + 1]
284
284
  version = name_version.match(/\d+\.\d+\.\d+/).to_s
285
285
  metadata = {}
286
- metadata['name'] = name
287
- metadata['version'] = version
288
- metadata['description'] = 'the description'
289
- metadata['long_description'] = 'the long description'
290
- metadata['maintainer'] = 'the maintainer'
291
- metadata['maintainer_email'] = 'the maintainer email'
292
- rb_file = ::File.join(cookbook_path, 'metadata.rb')
286
+ metadata["name"] = name
287
+ metadata["version"] = version
288
+ metadata["description"] = "the description"
289
+ metadata["long_description"] = "the long description"
290
+ metadata["maintainer"] = "the maintainer"
291
+ metadata["maintainer_email"] = "the maintainer email"
292
+ rb_file = ::File.join(cookbook_path, "metadata.rb")
293
293
  ui.stdout.puts "REPAIRING: no metadata files exist for #{cookbook_path}, creating #{rb_file}"
294
- ::File.open(rb_file, 'w') do |f|
294
+ ::File.open(rb_file, "w") do |f|
295
295
  metadata.each_pair do |key, value|
296
296
  f.write("#{key} '#{value}'\n")
297
297
  end
@@ -306,15 +306,15 @@ class Chef
306
306
 
307
307
  def orgs
308
308
  @orgs ||= if config[:org_list]
309
- config[:org_list].split(',')
309
+ config[:org_list].split(",")
310
310
  else
311
- Dir[::File.join(tidy.backup_path, 'organizations', '*')].map { |dir| ::File.basename(dir) }
311
+ Dir[::File.join(tidy.backup_path, "organizations", "*")].map { |dir| ::File.basename(dir) }
312
312
  end
313
313
  end
314
314
 
315
315
  def for_each_cookbook_basename(org)
316
316
  cookbooks_seen = []
317
- Dir[::File.join(tidy.cookbooks_path(org), '**-**')].each do |cookbook|
317
+ Dir[::File.join(tidy.cookbooks_path(org), "**-**")].each do |cookbook|
318
318
  name = tidy.cookbook_name_from_path(cookbook)
319
319
  unless cookbooks_seen.include?(name)
320
320
  cookbooks_seen.push(name)
@@ -324,29 +324,29 @@ class Chef
324
324
  end
325
325
 
326
326
  def for_each_cookbook_path(org)
327
- Dir[::File.join(tidy.cookbooks_path(org), '**')].each do |cookbook|
327
+ Dir[::File.join(tidy.cookbooks_path(org), "**")].each do |cookbook|
328
328
  yield cookbook
329
329
  end
330
330
  end
331
331
 
332
332
  def action_needed_file_path
333
- ::File.expand_path('knife-tidy-actions-needed.txt')
333
+ ::File.expand_path("knife-tidy-actions-needed.txt")
334
334
  end
335
335
 
336
336
  def action_needed(msg)
337
- ::File.open(action_needed_file_path, 'a') do |f|
337
+ ::File.open(action_needed_file_path, "a") do |f|
338
338
  f.write(msg + "\n")
339
339
  end
340
340
  end
341
341
 
342
342
  def write_role(path, role)
343
- ::File.open(path, 'w') do |f|
343
+ ::File.open(path, "w") do |f|
344
344
  f.write(Chef::JSONCompat.to_json_pretty(role))
345
345
  end
346
346
  end
347
347
 
348
348
  def for_each_role(org)
349
- Dir[::File.join(tidy.roles_path(org), '*.json')].each do |role|
349
+ Dir[::File.join(tidy.roles_path(org), "*.json")].each do |role|
350
350
  yield role
351
351
  end
352
352
  end
@@ -355,22 +355,22 @@ class Chef
355
355
  the_role = FFI_Yajl::Parser.parse(::File.read(role_path), symbolize_names: false)
356
356
  new_role = the_role.clone
357
357
  rl = Chef::RunList.new
358
- new_role['run_list'] = []
359
- the_role['run_list'].each do |item|
358
+ new_role["run_list"] = []
359
+ the_role["run_list"].each do |item|
360
360
  begin
361
361
  rl << item
362
- new_role['run_list'].push(item)
362
+ new_role["run_list"].push(item)
363
363
  rescue ArgumentError
364
364
  ui.stdout.puts "REPAIRING: Invalid Recipe Item: #{item} in run_list from #{role_path}"
365
365
  end
366
366
  end
367
- if the_role.key?('env_run_lists')
368
- the_role['env_run_lists'].each_pair do |key, value|
369
- new_role['env_run_lists'][key] = []
367
+ if the_role.key?("env_run_lists")
368
+ the_role["env_run_lists"].each_pair do |key, value|
369
+ new_role["env_run_lists"][key] = []
370
370
  value.each do |item|
371
371
  begin
372
372
  rl << item
373
- new_role['env_run_lists'][key].push(item)
373
+ new_role["env_run_lists"][key].push(item)
374
374
  rescue ArgumentError
375
375
  ui.stdout.puts "REPAIRING: Invalid Recipe Item: #{item} in env_run_lists #{key} from #{role_path}"
376
376
  end
@@ -394,13 +394,13 @@ class Chef
394
394
 
395
395
  def validate_clients_group(org)
396
396
  ui.stdout.puts "INFO: validating all clients for org #{org} exist in clients group"
397
- clients_group_path = ::File.join(tidy.groups_path(org), 'clients.json')
397
+ clients_group_path = ::File.join(tidy.groups_path(org), "clients.json")
398
398
  existing_group_data = FFI_Yajl::Parser.parse(::File.read(clients_group_path), symbolize_names: false)
399
- existing_group_data['clients'] = [] unless existing_group_data.key?('clients')
400
- if existing_group_data['clients'].length != tidy.client_names(org).length
399
+ existing_group_data["clients"] = [] unless existing_group_data.key?("clients")
400
+ if existing_group_data["clients"].length != tidy.client_names(org).length
401
401
  ui.stdout.puts "REPAIRING: Adding #{(existing_group_data['clients'].length - tidy.client_names(org).length).abs} missing clients into #{org}'s client group file #{clients_group_path}"
402
- existing_group_data['clients'] = (existing_group_data['clients'] + tidy.client_names(org)).uniq
403
- ::File.open(clients_group_path, 'w') do |f|
402
+ existing_group_data["clients"] = (existing_group_data["clients"] + tidy.client_names(org)).uniq
403
+ ::File.open(clients_group_path, "w") do |f|
404
404
  f.write(Chef::JSONCompat.to_json_pretty(existing_group_data))
405
405
  end
406
406
  end
@@ -412,16 +412,16 @@ class Chef
412
412
  invitations = FFI_Yajl::Parser.parse(::File.read(invite_file), symbolize_names: false)
413
413
  invitations_new = []
414
414
  invitations.each do |invite|
415
- if invite['username'].nil?
415
+ if invite["username"].nil?
416
416
  ui.stdout.puts "REPAIRING: Dropping corrupt invitations for #{org} in file #{invite_file}"
417
417
  else
418
418
  invite_hash = {}
419
- invite_hash['id'] = invite['id']
420
- invite_hash['username'] = invite['username']
419
+ invite_hash["id"] = invite["id"]
420
+ invite_hash["username"] = invite["username"]
421
421
  invitations_new.push(invite_hash)
422
422
  end
423
423
  end
424
- ::File.open(invite_file, 'w') do |f|
424
+ ::File.open(invite_file, "w") do |f|
425
425
  f.write(Chef::JSONCompat.to_json_pretty(invitations_new))
426
426
  end
427
427
  end
@@ -15,8 +15,8 @@
15
15
  # limitations under the License.
16
16
  #
17
17
 
18
- require 'chef/knife'
19
- require 'chef/server_api'
18
+ require "chef/knife"
19
+ require "chef/server_api"
20
20
 
21
21
  class Chef
22
22
  class Knife
@@ -24,19 +24,19 @@ class Chef
24
24
  def self.included(includer)
25
25
  includer.class_eval do
26
26
  deps do
27
- require 'chef/tidy_server'
28
- require 'chef/tidy_common'
27
+ require "chef/tidy_server"
28
+ require "chef/tidy_common"
29
29
  end
30
30
 
31
31
  option :org_list,
32
- long: '--orgs ORG1,ORG2',
33
- description: 'Only apply to objects in the named organizations'
32
+ long: "--orgs ORG1,ORG2",
33
+ description: "Only apply to objects in the named organizations"
34
34
  end
35
35
  end
36
36
 
37
37
  def server
38
38
  @server ||= if Chef::Config.chef_server_root.nil?
39
- ui.warn('chef_server_root not found in knife configuration; using chef_server_url')
39
+ ui.warn("chef_server_root not found in knife configuration; using chef_server_url")
40
40
  Chef::TidyServer.from_chef_server_url(Chef::Config.chef_server_url)
41
41
  else
42
42
  Chef::TidyServer.new(Chef::Config.chef_server_root)
@@ -56,19 +56,19 @@ class Chef
56
56
  end
57
57
 
58
58
  def completion_message
59
- ui.stdout.puts ui.color('** Finished **', :magenta).to_s
59
+ ui.stdout.puts ui.color("** Finished **", :magenta).to_s
60
60
  end
61
61
 
62
62
  def action_needed_file_path
63
- ::File.expand_path('knife-tidy-actions-needed.txt')
63
+ ::File.expand_path("knife-tidy-actions-needed.txt")
64
64
  end
65
65
 
66
66
  def server_warnings_file_path
67
- ::File.expand_path('reports/knife-tidy-server-warnings.txt')
67
+ ::File.expand_path("reports/knife-tidy-server-warnings.txt")
68
68
  end
69
69
 
70
70
  def action_needed(msg, file_path = action_needed_file_path)
71
- ::File.open(file_path, 'a') do |f|
71
+ ::File.open(file_path, "a") do |f|
72
72
  f.write(msg + "\n")
73
73
  end
74
74
  end