mux_tf 0.12.0 → 0.14.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: 924f8b4cc0e90b4c636e3ffcc806c0e48036380ac81ee83b1dc21a437950e76e
4
- data.tar.gz: 3bea784dc0db5091efa2dcba48c653b812d618b88c8dc04b997b59cc8e810e0c
3
+ metadata.gz: f9a03af7a131fd34e58d5fb27d3c578154f49246634a684980e6ebc16aa28d08
4
+ data.tar.gz: d093d1604dabbeb1aed8ef5b1ebc3e33a0b5e3575602f7b3802796b2b51f7a7a
5
5
  SHA512:
6
- metadata.gz: 3dbd88ebf68ae0bb27116ce5b553a04668a746cfdd89ed1e646fc6f75961cf8c61d66f2a4ba46d0baaa17c6fdaa3b99704485632a5787a492037e75707e06b1a
7
- data.tar.gz: bd9bf482078f080477f1871b02c256ad06b7bcb4e323adb8ee35fe056b9f8fd34bec4993c6b1611fac161b09b80db11c3d36395fdb42bef0d174e3ec666f8745
6
+ metadata.gz: 7809b83e63a8aa17671ef6fbaac79a09cd7aa8216fdd1ccd390f2e32ee0b4ec0a85e4f6653d4c4bbbafcb99d47076413a9135f970f4d4c0f7f8608b2e8858f5f
7
+ data.tar.gz: c9dd73ed72236b568c7bbdc34ede9701e1d1db94403ccd2e84ee611cd58500e73810f50c9c0bdd6817c2ef7f8bd5d504fd945d86d340db95d9246e797376efb4
data/exe/tf_current CHANGED
@@ -11,6 +11,8 @@ begin
11
11
  rescue Interrupt
12
12
  warn "\nInterrupted"
13
13
  exit 1
14
+ rescue SystemExit => e
15
+ exit e.status
14
16
  rescue Exception => e # rubocop:disable Lint/RescueException
15
17
  warn e.full_message
16
18
  warn "<press enter>"
data/exe/tf_mux CHANGED
@@ -11,6 +11,8 @@ begin
11
11
  rescue Interrupt
12
12
  warn "\nInterrupted"
13
13
  exit 1
14
+ rescue SystemExit => e
15
+ exit e.status
14
16
  rescue Exception => e # rubocop:disable Lint/RescueException
15
17
  warn e.full_message
16
18
  warn "<press enter>"
data/exe/tf_plan_summary CHANGED
@@ -11,6 +11,8 @@ begin
11
11
  rescue Interrupt
12
12
  warn "\nInterrupted"
13
13
  exit 1
14
+ rescue SystemExit => e
15
+ exit e.status
14
16
  rescue Exception => e # rubocop:disable Lint/RescueException
15
17
  warn e.full_message
16
18
  warn "<press enter>"
data/lib/deps.rb CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  require "bundler/inline"
4
4
 
5
- gemfile do
5
+ dep_def = proc do
6
6
  gemspec(path: File.join(__dir__, ".."))
7
7
  end
8
+
9
+ begin
10
+ gemfile(&dep_def)
11
+ rescue Bundler::GemNotFound
12
+ gemfile(true) do
13
+ source "https://rubygems.org"
14
+ instance_exec(&dep_def)
15
+ end
16
+ end
@@ -4,13 +4,14 @@ require "bundler"
4
4
 
5
5
  module MuxTf
6
6
  module Cli
7
- module Current
7
+ module Current # rubocop:disable Metrics/ModuleLength
8
8
  extend TerraformHelpers
9
9
  extend PiotrbCliUtils::Util
10
10
  extend PiotrbCliUtils::CriCommandSupport
11
11
  extend PiotrbCliUtils::CmdLoop
12
+ include Coloring
12
13
 
13
- class << self
14
+ class << self # rubocop:disable Metrics/ClassLength
14
15
  def run(args)
15
16
  version_check
16
17
 
@@ -19,8 +20,21 @@ module MuxTf
19
20
  return
20
21
  end
21
22
 
23
+ unless args.empty?
24
+ root_cmd = build_root_cmd
25
+ valid_commands = root_cmd.subcommands.map(&:name)
26
+
27
+ if args[0] && valid_commands.include?(args[0])
28
+ stop_reason = catch(:stop) {
29
+ root_cmd.run(args, {}, hard_exit: true)
30
+ }
31
+ log pastel.red("Stopped: #{stop_reason}") if stop_reason
32
+ return
33
+ end
34
+ end
35
+
22
36
  folder_name = File.basename(Dir.getwd)
23
- log "Processing #{Paint[folder_name, :cyan]} ..."
37
+ log "Processing #{pastel.cyan(folder_name)} ..."
24
38
 
25
39
  ENV["TF_IN_AUTOMATION"] = "1"
26
40
  ENV["TF_INPUT"] = "0"
@@ -32,19 +46,16 @@ module MuxTf
32
46
  return launch_cmd_loop(:error) unless upgrade_status == :ok
33
47
  end
34
48
 
35
- plan_status, @plan_meta = create_plan(plan_filename)
49
+ plan_status = run_plan
36
50
 
37
51
  case plan_status
38
52
  when :ok
39
- log "no changes, exiting", depth: 1
53
+ log "exiting", depth: 1
40
54
  when :error
41
- log "something went wrong", depth: 1
42
55
  launch_cmd_loop(plan_status)
43
- when :changes
44
- log "Printing Plan Summary ...", depth: 1
45
- pretty_plan_summary(plan_filename)
56
+ when :changes # rubocop:disable Lint/DuplicateBranch
46
57
  launch_cmd_loop(plan_status)
47
- when :unknown
58
+ when :unknown # rubocop:disable Lint/DuplicateBranch
48
59
  launch_cmd_loop(plan_status)
49
60
  end
50
61
  end
@@ -58,38 +69,105 @@ module MuxTf
58
69
  def version_check
59
70
  return unless VersionCheck.has_updates?
60
71
 
61
- log Paint["=" * 80, :yellow]
62
- log "New version of #{Paint['mux_tf', :cyan]} is available!"
63
- log "You are currently on version: #{Paint[VersionCheck.current_gem_version, :yellow]}"
64
- log "Latest version found is: #{Paint[VersionCheck.latest_gem_version, :green]}"
65
- log "Run `#{Paint['gem install mux_tf', :green]}` to update!"
66
- log Paint["=" * 80, :yellow]
72
+ log pastel.yellow("=" * 80)
73
+ log "New version of #{pastel.cyan('mux_tf')} is available!"
74
+ log "You are currently on version: #{pastel.yellow(VersionCheck.current_gem_version)}"
75
+ log "Latest version found is: #{pastel.green(VersionCheck.latest_gem_version)}"
76
+ log "Run `#{pastel.green('gem install mux_tf')}` to update!"
77
+ log pastel.yellow("=" * 80)
67
78
  end
68
79
 
69
- def run_validate
70
- remedies = PlanFormatter.process_validation(validate)
71
- process_remedies(remedies)
80
+ # block is expected to return a touple, the first element is a list of remedies
81
+ # the rest are any additional results
82
+ def remedy_retry_helper(from:, level: 1, attempt: 0, &block)
83
+ catch(:abort) do
84
+ until attempt > 1
85
+ attempt += 1
86
+ remedies, *results = block.call
87
+ return results if remedies.empty?
88
+
89
+ remedy_status, _remedy_results = process_remedies(remedies, from: from, level: level)
90
+ return unless remedy_status
91
+ end
92
+ log "!! giving up because attempt: #{attempt}"
93
+ end
72
94
  end
73
95
 
74
- def process_remedies(remedies)
96
+ # returns boolean true if succeeded
97
+ def run_validate(level: 1)
98
+ remedy_retry_helper(from: :validate, level: level) do
99
+ validation_info = validate
100
+ PlanFormatter.print_validation_errors(validation_info)
101
+ remedies = PlanFormatter.process_validation(validation_info)
102
+ [remedies, validation_info]
103
+ end
104
+ end
105
+
106
+ def process_remedies(remedies, from: nil, level: 1, retry_count: 0) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
107
+ remedies = remedies.dup
108
+ remedy = nil
109
+ wrap_log = lambda do |msg, color: nil|
110
+ [
111
+ from ? pastel.cyan("#{from} -> ") : nil,
112
+ pastel.cyan(remedy ? "[remedy: #{remedy}]" : "[process remedies]"),
113
+ " ",
114
+ color ? pastel.decorate(msg, color) : msg,
115
+ " ",
116
+ level > 1 ? pastel.cyan("[lv #{level}]") : nil,
117
+ retry_count.positive? ? pastel.cyan("[try #{retry_count}]") : nil
118
+ ].compact.join
119
+ end
120
+ results = {}
121
+ if retry_count > 5
122
+ log wrap_log["giving up because retry_count: #{retry_count}", color: :yellow], depth: 1
123
+ log wrap_log["unprocessed remedies: #{remedies.to_a}", color: :red], depth: 1
124
+ return [false, results]
125
+ end
75
126
  if remedies.delete? :init
76
- log "Running terraform init ...", depth: 2
77
- remedies = PlanFormatter.init_status_to_remedies(*PlanFormatter.run_tf_init)
78
- if process_remedies(remedies)
79
- remedies = PlanFormatter.process_validation(validate)
80
- return false unless process_remedies(remedies)
81
- end
127
+ remedy = :init
128
+ log wrap_log["Running terraform init ..."], depth: 2
129
+ exit_code, meta = PlanFormatter.run_tf_init
130
+ print_errors_and_warnings(meta)
131
+ remedies = PlanFormatter.init_status_to_remedies(exit_code, meta)
132
+ status, r_results = process_remedies(remedies, from: from, level: level + 1)
133
+ results.merge!(r_results)
134
+ return [true, r_results] if status
135
+ end
136
+ if remedies.delete?(:plan)
137
+ remedy = :plan
138
+ log wrap_log["Running terraform plan ..."], depth: 2
139
+ plan_status = run_plan(retry_count: retry_count)
140
+ results[:plan_status] = plan_status
141
+ return [false, results] unless [:ok, :changes].include?(plan_status)
82
142
  end
83
143
  if remedies.delete? :reconfigure
84
- log "Running terraform init ...", depth: 2
85
- remedies = PlanFormatter.init_status_to_remedies(*PlanFormatter.run_tf_init(reconfigure: true))
86
- return false unless process_remedies(remedies)
144
+ remedy = :reconfigure
145
+ log wrap_log["Running terraform init ..."], depth: 2
146
+ result = remedy_retry_helper(from: :reconfigure, level: level + 1, attempt: retry_count) {
147
+ exit_code, meta = PlanFormatter.run_tf_init(reconfigure: true)
148
+ print_errors_and_warnings(meta)
149
+ remedies = PlanFormatter.init_status_to_remedies(exit_code, meta)
150
+ [remedies, exit_code, meta]
151
+ }
152
+ unless result
153
+ log wrap_log["Failed", color: :red], depth: 2
154
+ return [false, result]
155
+ end
156
+ end
157
+ if remedies.delete? :user_error
158
+ remedy = :user_error
159
+ log wrap_log["user error encountered!", color: :red]
160
+ log wrap_log["-" * 40, color: :red]
161
+ log wrap_log["!! User Error, Please fix the issue and try again", color: :red]
162
+ log wrap_log["-" * 40, color: :red]
163
+ return [false, results]
87
164
  end
88
165
  unless remedies.empty?
89
- log "unprocessed remedies: #{remedies.to_a}", depth: 1
90
- return false
166
+ remedy = nil
167
+ log wrap_log["Unprocessed remedies: #{remedies.to_a}", color: :red], depth: 1 if level == 1
168
+ return [false, results]
91
169
  end
92
- true
170
+ [true, results]
93
171
  end
94
172
 
95
173
  def validate
@@ -108,7 +186,7 @@ module MuxTf
108
186
  when 2
109
187
  [:changes, meta]
110
188
  else
111
- log Paint["terraform plan exited with an unknown exit code: #{exit_code}", :yellow]
189
+ log pastel.yellow("terraform plan exited with an unknown exit code: #{exit_code}")
112
190
  [:unknown, meta]
113
191
  end
114
192
  end
@@ -118,9 +196,9 @@ module MuxTf
118
196
 
119
197
  case status
120
198
  when :error, :unknown
121
- log Paint["Dropping to command line so you can fix the issue!", :red]
199
+ log pastel.red("Dropping to command line so you can fix the issue!")
122
200
  when :changes
123
- log Paint["Dropping to command line so you can review the changes.", :yellow]
201
+ log pastel.yellow("Dropping to command line so you can review the changes.")
124
202
  end
125
203
  cmd_loop(status)
126
204
  end
@@ -135,9 +213,9 @@ module MuxTf
135
213
  prompt = "#{folder_name} => "
136
214
  case status
137
215
  when :error, :unknown
138
- prompt = "[#{Paint[status.to_s, :red]}] #{prompt}"
216
+ prompt = "[#{pastel.red(status.to_s)}] #{prompt}"
139
217
  when :changes
140
- prompt = "[#{Paint[status.to_s, :yellow]}] #{prompt}"
218
+ prompt = "[#{pastel.yellow(status.to_s)}] #{prompt}"
141
219
  end
142
220
 
143
221
  run_cmd_loop(prompt) do |cmd|
@@ -157,11 +235,31 @@ module MuxTf
157
235
  root_cmd.add_command(upgrade_cmd)
158
236
  root_cmd.add_command(reconfigure_cmd)
159
237
  root_cmd.add_command(interactive_cmd)
238
+ root_cmd.add_command(plan_details_cmd)
160
239
 
161
240
  root_cmd.add_command(exit_cmd)
241
+ root_cmd.add_command(define_cmd("help", summary: "Show help for commands") { |_opts, _args, cmd| puts cmd.supercommand.help })
162
242
  root_cmd
163
243
  end
164
244
 
245
+ def plan_summary_text
246
+ plan_filename = PlanFilenameGenerator.for_path
247
+ if File.exist?("#{plan_filename}.txt") && File.mtime("#{plan_filename}.txt").to_f >= File.mtime(plan_filename).to_f
248
+ File.read("#{plan_filename}.txt")
249
+ else
250
+ puts "Inspecting Changes ... #{plan_filename}"
251
+ data = PlanUtils.text_version_of_plan_show(plan_filename)
252
+ File.write("#{plan_filename}.txt", data)
253
+ data
254
+ end
255
+ end
256
+
257
+ def plan_details_cmd
258
+ define_cmd("details", summary: "Show Plan Details") do |_opts, _args, _cmd|
259
+ puts plan_summary_text
260
+ end
261
+ end
262
+
165
263
  def plan_cmd
166
264
  define_cmd("plan", summary: "Re-run plan") do |_opts, _args, _cmd|
167
265
  run_validate && run_plan
@@ -182,28 +280,30 @@ module MuxTf
182
280
 
183
281
  def shell_cmd
184
282
  define_cmd("shell", summary: "Open your default terminal in the current folder") do |_opts, _args, _cmd|
185
- log Paint["Launching shell ...", :yellow]
186
- log Paint["When it exits you will be back at this prompt.", :yellow]
283
+ log pastel.yellow("Launching shell ...")
284
+ log pastel.yellow("When it exits you will be back at this prompt.")
187
285
  system ENV.fetch("SHELL")
188
286
  end
189
287
  end
190
288
 
191
289
  def force_unlock_cmd
192
- define_cmd("force-unlock", summary: "Force unlock state after encountering a lock error!") do
290
+ define_cmd("force-unlock", summary: "Force unlock state after encountering a lock error!") do # rubocop:disable Metrics/BlockLength
193
291
  prompt = TTY::Prompt.new(interrupt: :noop)
194
292
 
195
- table = TTY::Table.new(header: %w[Field Value])
196
- table << ["Lock ID", @plan_meta["ID"]]
197
- table << ["Operation", @plan_meta["Operation"]]
198
- table << ["Who", @plan_meta["Who"]]
199
- table << ["Created", @plan_meta["Created"]]
293
+ lock_info = @last_lock_info
294
+
295
+ if lock_info
296
+ table = TTY::Table.new(header: %w[Field Value])
297
+ table << ["Lock ID", lock_info[:lock_id]]
298
+ table << ["Operation", lock_info[:operation]]
299
+ table << ["Who", lock_info[:who]]
300
+ table << ["Created", lock_info[:created]]
200
301
 
201
- puts table.render(:unicode, padding: [0, 1])
302
+ puts table.render(:unicode, padding: [0, 1])
202
303
 
203
- if @plan_meta && @plan_meta["error"] == "lock"
204
304
  done = catch(:abort) {
205
- if @plan_meta["Operation"] != "OperationTypePlan" && !prompt.yes?(
206
- "Are you sure you want to force unlock a lock for operation: #{@plan_meta['Operation']}",
305
+ if lock_info[:operation] != "OperationTypePlan" && !prompt.yes?(
306
+ "Are you sure you want to force unlock a lock for operation: #{lock_info[:operation]}",
207
307
  default: false
208
308
  )
209
309
  throw :abort
@@ -214,19 +314,19 @@ module MuxTf
214
314
  default: false
215
315
  )
216
316
 
217
- status = tf_force_unlock(id: @plan_meta["ID"])
317
+ status = tf_force_unlock(id: lock_info[:lock_id])
218
318
  if status.success?
219
319
  log "Done!"
220
320
  else
221
- log Paint["Failed with status: #{status}", :red]
321
+ log pastel.red("Failed with status: #{status}")
222
322
  end
223
323
 
224
324
  true
225
325
  }
226
326
 
227
- log Paint["Aborted", :yellow] unless done
327
+ log pastel.yellow("Aborted") unless done
228
328
  else
229
- log Paint["No lock error or no plan ran!", :red]
329
+ log pastel.red("No lock error or no plan ran!")
230
330
  end
231
331
  end
232
332
  end
@@ -243,8 +343,9 @@ module MuxTf
243
343
 
244
344
  def reconfigure_cmd
245
345
  define_cmd("reconfigure", summary: "Reconfigure modules/plguins") do |_opts, _args, _cmd|
246
- status, meta = PlanFormatter.run_tf_init(reconfigure: true)
247
- if status != 0
346
+ exit_code, meta = PlanFormatter.run_tf_init(reconfigure: true)
347
+ print_errors_and_warnings(meta)
348
+ if exit_code != 0
248
349
  log meta.inspect unless meta.empty?
249
350
  log "Reconfigure Failed!"
250
351
  end
@@ -257,7 +358,7 @@ module MuxTf
257
358
  begin
258
359
  abort_message = catch(:abort) { plan.run_interactive }
259
360
  if abort_message
260
- log Paint["Aborted: #{abort_message}", :red]
361
+ log pastel.red("Aborted: #{abort_message}")
261
362
  else
262
363
  run_plan
263
364
  end
@@ -268,8 +369,78 @@ module MuxTf
268
369
  end
269
370
  end
270
371
 
271
- def run_plan(targets: [])
272
- plan_status, @plan_meta = create_plan(plan_filename, targets: targets)
372
+ def print_errors_and_warnings(meta) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize
373
+ message = []
374
+ message << pastel.yellow("#{meta[:warnings].length} Warnings") if meta[:warnings]
375
+ message << pastel.red("#{meta[:errors].length} Errors") if meta[:errors]
376
+ if message.length.positive?
377
+ log ""
378
+ log "Encountered: #{message.join(' and ')}"
379
+ log ""
380
+ end
381
+
382
+ meta[:warnings]&.each do |warning|
383
+ log "-" * 20
384
+ log pastel.yellow("Warning: #{warning[:message]}")
385
+ warning[:body]&.each do |line|
386
+ log pastel.yellow(line), depth: 1
387
+ end
388
+ log ""
389
+ end
390
+
391
+ meta[:errors]&.each do |error|
392
+ log "-" * 20
393
+ log pastel.red("Error: #{error[:message]}")
394
+ error[:body]&.each do |line|
395
+ log pastel.red(line), depth: 1
396
+ end
397
+ log ""
398
+ end
399
+
400
+ return unless message.length.positive?
401
+
402
+ log ""
403
+ end
404
+
405
+ def detect_remedies_from_plan(meta)
406
+ remedies = Set.new
407
+ meta[:errors]&.each do |error|
408
+ remedies << :plan if error[:message].include?("timeout while waiting for plugin to start")
409
+ end
410
+ remedies << :unlock if lock_error?(meta)
411
+ remedies
412
+ end
413
+
414
+ def lock_error?(meta)
415
+ meta && meta["error"] == "lock"
416
+ end
417
+
418
+ def extract_lock_info(meta)
419
+ {
420
+ lock_id: meta["ID"],
421
+ operation: meta["Operation"],
422
+ who: meta["Who"],
423
+ created: meta["Created"]
424
+ }
425
+ end
426
+
427
+ def run_plan(targets: [], level: 1, retry_count: 0)
428
+ plan_status, = remedy_retry_helper(from: :plan, level: level, attempt: retry_count) {
429
+ @last_lock_info = nil
430
+
431
+ plan_status, meta = create_plan(plan_filename, targets: targets)
432
+
433
+ print_errors_and_warnings(meta)
434
+
435
+ remedies = detect_remedies_from_plan(meta)
436
+
437
+ if remedies.include?(:unlock)
438
+ @last_lock_info = extract_lock_info(meta)
439
+ throw :abort, [plan_status, meta]
440
+ end
441
+
442
+ [remedies, plan_status, meta]
443
+ }
273
444
 
274
445
  case plan_status
275
446
  when :ok
@@ -277,25 +448,30 @@ module MuxTf
277
448
  when :error
278
449
  log "something went wrong", depth: 1
279
450
  when :changes
280
- log "Printing Plan Summary ...", depth: 1
281
- pretty_plan_summary(plan_filename)
451
+ unless ENV["JSON_PLAN"]
452
+ log "Printing Plan Summary ...", depth: 1
453
+ pretty_plan_summary(plan_filename)
454
+ end
455
+ puts plan_summary_text if ENV["JSON_PLAN"]
282
456
  when :unknown
283
457
  # nothing
284
458
  end
459
+
285
460
  plan_status
286
461
  end
287
462
 
463
+ public :run_plan
464
+
288
465
  def run_upgrade
289
466
  exit_code, meta = PlanFormatter.run_tf_init(upgrade: true)
467
+ print_errors_and_warnings(meta)
290
468
  case exit_code
291
469
  when 0
292
470
  [:ok, meta]
293
471
  when 1
294
472
  [:error, meta]
295
- # when 2
296
- # [:changes, meta]
297
473
  else
298
- log Paint["terraform init upgrade exited with an unknown exit code: #{exit_code}", :yellow]
474
+ log pastel.yellow("terraform init upgrade exited with an unknown exit code: #{exit_code}")
299
475
  [:unknown, meta]
300
476
  end
301
477
  end
@@ -6,6 +6,7 @@ module MuxTf
6
6
  extend PiotrbCliUtils::Util
7
7
  extend PiotrbCliUtils::ShellHelpers
8
8
  extend TerraformHelpers
9
+ import Coloring
9
10
 
10
11
  class << self
11
12
  def run(args)
@@ -33,7 +34,7 @@ module MuxTf
33
34
 
34
35
  if options[:interactive]
35
36
  abort_message = catch(:abort) { plan.run_interactive }
36
- log Paint["Aborted: #{abort_message}", :red] if abort_message
37
+ log pastel.red("Aborted: #{abort_message}") if abort_message
37
38
  else
38
39
  if options[:hierarchy]
39
40
  plan.nested_summary.each do |line|
data/lib/mux_tf/cli.rb CHANGED
@@ -2,16 +2,18 @@
2
2
 
3
3
  module MuxTf
4
4
  module Cli
5
+ extend PiotrbCliUtils::Util
6
+
5
7
  def self.run(mode, args)
6
8
  case mode
7
9
  when :mux
8
- require_relative "./cli/mux"
10
+ require_relative "cli/mux"
9
11
  MuxTf::Cli::Mux.run(args)
10
12
  when :current
11
- require_relative "./cli/current"
13
+ require_relative "cli/current"
12
14
  MuxTf::Cli::Current.run(args)
13
15
  when :plan_summary
14
- require_relative "./cli/plan_summary"
16
+ require_relative "cli/plan_summary"
15
17
  MuxTf::Cli::PlanSummary.run(args)
16
18
  else
17
19
  fail_with "unhandled mode: #{mode.inspect}"
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MuxTf
4
+ module Coloring
5
+ def pastel
6
+ self.class.pastel
7
+ end
8
+
9
+ def self.included(other)
10
+ other.extend(ClassMethods)
11
+ end
12
+
13
+ module ClassMethods
14
+ def pastel
15
+ instance = Pastel.new
16
+ instance.alias_color(:orange, :yellow)
17
+ instance.alias_color(:gray, :bright_black)
18
+ instance.alias_color(:grey, :bright_black)
19
+ instance
20
+ end
21
+ end
22
+ end
23
+ end