cl-magic 0.4.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/cl/magic/cl-dk CHANGED
@@ -10,101 +10,18 @@ require 'cl/magic/common/common_options.rb'
10
10
  require 'cl/magic/common/logging.rb'
11
11
  require 'cl/magic/common/parse_and_pick.rb'
12
12
 
13
- @logger = get_logger()
14
-
15
- #
16
- # Util Methods
17
- #
18
-
19
- def get_base_compose_hash()
20
- cmd = "cd #{@working_dir} && docker compose config 2> /dev/null"
21
- return YAML.load(`#{cmd}`)
22
- end
23
-
24
- def get_save_parts_filepath()
25
- return File.join(@working_dir, ".cl-dk-parts.yml")
26
- end
13
+ require_relative 'dk/help_printer'
14
+ require_relative 'dk/yaml_arg_munger'
15
+ require_relative 'dk/world_settings'
16
+ require_relative 'dk/parts_merger'
27
17
 
28
- def get_base_compose_parts_and_make_hashes()
29
- compose_hash = get_base_compose_hash()
30
- dk_parts_hash = {}
31
- dk_make_hash = {}
32
- if compose_hash
33
- compose_hash = merge_world_files(compose_hash, show_help=ARGV.include?("--help"))
34
- dk_parts_hash = compose_hash['x-dk-parts'] ? compose_hash.delete('x-dk-parts') : {}
35
- dk_make_hash = compose_hash['x-dk-make'] ? compose_hash.delete('x-dk-make') : {}
36
- end
37
- return compose_hash, dk_parts_hash, dk_make_hash
38
- end
39
-
40
- # world
41
-
42
- def get_repo_basename()
43
- command = "cd #{@working_dir} && basename $(git remote get-url origin 2> /dev/null) .git"
44
- repo_basename = TTY::Command.new(:printer => :null).run(command).out.gsub('.git', '').strip.chomp
45
- if repo_basename==".git" or repo_basename==""
46
- return File.basename(@working_dir)
47
- end
48
- return repo_basename
49
- end
50
-
51
- def get_world_settings_filepath()
52
- return File.join(".cl-dk-world.yml")
53
- end
54
-
55
- def get_world_settings_hash()
56
- filepath = get_world_settings_filepath()
57
- return File.exist?(filepath) ? YAML.load_file(filepath) : {}
58
- end
59
-
60
- def get_world_path_from_settings()
61
- world_settings = get_world_settings_hash()
62
- if world_settings.key?(:world_path) and world_settings.key?(:context)
63
- return File.join(world_settings[:world_path], world_settings[:context])
64
- end
65
- return ""
66
- end
67
-
68
- def save_world_settings(world_settings_hash)
69
- filepath = get_world_settings_filepath()
70
- tempfile = File.new(filepath, 'w')
71
- tempfile.write(world_settings_hash.to_yaml)
72
- tempfile.close
73
- end
74
-
75
- def get_world_project_path()
76
- repo_basename = get_repo_basename()
77
- world_path = get_world_path_from_settings()
78
- return File.join(world_path, repo_basename) if world_path and repo_basename
79
- return nil
80
- end
18
+ @logger = get_logger()
81
19
 
82
20
  #
83
21
  # Help Methods
84
22
  #
85
23
 
86
- def print_dk_help_line(key, help)
87
- if $stdout.isatty
88
- if help.nil?
89
- @logger.puts("#{key.ljust(15, ' ')} ???no help???")
90
- else
91
- key = key.ljust(15, ' ')
92
- help_parts = help.split(";")
93
-
94
- # first line
95
- @logger.puts(key, help_parts.shift)
96
-
97
- # following lines
98
- padding = "".ljust(15, ' ')
99
- help_parts.each do |p|
100
- @logger.puts(padding, p)
101
- end
102
- @logger.puts("") if help.end_with?(";")
103
- end
104
- end
105
- end
106
-
107
- def print_dk_help(dk_parts_hash, dk_make_hash, args)
24
+ def try_print_dk_help(dk_parts_hash, dk_make_hash, args)
108
25
  no_args = args.empty?()
109
26
  asked_for_help = args.include?('--help')
110
27
  has_dk_commands = dk_parts_hash.keys.any?
@@ -115,15 +32,9 @@ def print_dk_help(dk_parts_hash, dk_make_hash, args)
115
32
  puts ""
116
33
  puts "Run docker compose while munging yamls in sophisticated ways."
117
34
  puts ""
118
- if get_repo_basename
119
- puts "PROJ INFO"
120
- puts " - Repo basename: #{get_repo_basename}"
121
- puts " - World filepath: #{get_world_project_path()}"
122
- puts ""
123
- end
124
35
  puts "PROJ PARTS"
125
36
  dk_parts_hash.keys.each do |key|
126
- print_dk_help_line(key, dk_parts_hash[key].fetch('help'))
37
+ @help_printer.print_dk_help_line(key, dk_parts_hash[key].fetch('help'))
127
38
  end
128
39
  puts ""
129
40
  puts "YML TOKENS"
@@ -133,208 +44,10 @@ def print_dk_help(dk_parts_hash, dk_make_hash, args)
133
44
  puts " - <dk-project-path> absolute filepath to world/project directory"
134
45
  puts " - <dk-working-path> absolute filepath to location dk command was run from"
135
46
  puts ""
136
- puts "ADDITIONAL TURNKEY COMMANDS"
137
- puts " - dk set-world sets the location of the world directory"
138
- puts " - dk make turnkey commands for a project"
139
- puts " - dk set-parts save parts so they are automatically applied to commands"
140
- puts ""
141
47
  puts "-------------------------"
142
48
  end
143
49
  end
144
50
 
145
- def print_make_help(dk_parts_hash, dk_make_hash)
146
- if $stdout.isatty
147
- puts ""
148
- puts "Usage: dk [DK_PARTS] make COMMAND"
149
- puts ""
150
- puts "Make commands designed to make your developer experience more turnkey"
151
- puts ""
152
- puts "Parts:"
153
- dk_parts_hash.keys.each do |key|
154
- print_dk_help_line(key, dk_parts_hash[key].fetch('help'))
155
- end
156
- puts ""
157
- puts "Commands:"
158
- dk_make_hash.keys.each do |key|
159
- print_dk_help_line(key, dk_make_hash[key].fetch('help'))
160
- end
161
- puts ""
162
- else
163
- dk_make_hash.keys.each do |key|
164
- puts key
165
- end
166
- end
167
- end
168
-
169
- def print_set_world_help()
170
- puts ""
171
- puts "Usage: dk set-world PATH"
172
- puts ""
173
- puts "Set the folder that contains dk-world configuration"
174
- puts ""
175
- end
176
-
177
- def print_set_parts_help()
178
- compose_hash, dk_parts_hash, dk_make_hash = get_base_compose_parts_and_make_hashes()
179
-
180
- # print help
181
- puts ""
182
- puts "Usage: dk [DK_PARTS] set-parts"
183
- puts ""
184
- puts "Set parts for a project and apply to every 'dk' command"
185
- puts ""
186
- if dk_parts_hash.keys.any?
187
- puts "Parts:"
188
- dk_parts_hash.keys.each do |key|
189
- print_dk_help_line(key, dk_parts_hash[key].fetch('help'))
190
- end
191
- puts ""
192
- end
193
- end
194
-
195
- #
196
- # Yaml Merging
197
- #
198
-
199
- class ::Hash
200
- def dk_merge(second)
201
- merger = proc { |_, v1, v2|
202
- if Hash === v1 && Hash === v2
203
- v1.merge(v2, &merger)
204
- else
205
- if Array === v1 && Array === v2
206
- if v2.first=="<dk-replace>"
207
- v2[1..-1] # everything but the first item
208
- else
209
- v1 | v2 # union arrays
210
- end
211
- else
212
- if [:undefined, nil, :nil].include?(v2)
213
- v1
214
- else
215
- v2
216
- end
217
- end
218
- end
219
- }
220
- merge(second.to_h, &merger)
221
- end
222
- def dk_reject!(&blk)
223
- self.each do |k, v|
224
- v.dk_reject!(&blk) if v.is_a?(Hash)
225
- self.delete(k) if blk.call(k, v)
226
- end
227
- end
228
- end
229
-
230
- def dk_merge_and_remove(compose_hash, yml_hash)
231
- # remove help & merge
232
- clean_yml = yml_hash.clone
233
- clean_yml.delete('help')
234
- return compose_hash.dk_merge(clean_yml).dk_reject! { |k, v| v=='<dk-remove>' }
235
- end
236
-
237
- def merge_world_files(compose_hash, show_help=false)
238
- dk_proj_path = get_world_project_path()
239
- if dk_proj_path
240
- print_dk_help_line("dk-project-path", "#{dk_proj_path}") if show_help and $stdout.isatty
241
-
242
- Dir.glob("#{dk_proj_path}/*.yml").sort.each do |filepath|
243
- print_dk_help_line("dk-world", "#{filepath}") if show_help and $stdout.isatty
244
-
245
- # read file and replace
246
- contents = File.read(filepath)
247
- contents.gsub!('<dk-world-path>', get_world_path_from_settings())
248
- contents.gsub!('<dk-project-path>', dk_proj_path)
249
- contents.gsub!('<dk-working-path>', @working_dir)
250
-
251
- # yml merge
252
- yml_hash = YAML.load(contents)
253
- compose_hash = dk_merge_and_remove(compose_hash, yml_hash)
254
- end
255
- end
256
- return compose_hash
257
- end
258
-
259
- def get_saved_parts(dk_parts_hash)
260
- saved_part_keys = []
261
- filepath = get_save_parts_filepath()
262
-
263
- # merge
264
- if File.exist?(filepath)
265
- saved_parts = YAML.load_file(filepath)
266
- saved_parts.each do |potential_part_key|
267
- dk_part = dk_parts_hash.fetch(potential_part_key, nil) # yml detail
268
- if dk_part
269
- saved_part_keys << potential_part_key
270
- else
271
- @logger.error "invalid saved part: #{potential_part_key}"
272
- exit 1
273
- end
274
- end
275
- end
276
- return saved_part_keys
277
- end
278
-
279
- def merge_parts_save_and_prep_args(compose_hash, dk_parts_hash, dk_make_hash, saved_part_keys)
280
- tempfile = File.new(File.join(@working_dir, ".cl-dk.yml"), 'w')
281
- args = ARGV.clone
282
-
283
- # dk help
284
- print_dk_help(dk_parts_hash, dk_make_hash, args)
285
-
286
- def print_and_merge_part(part_key, dk_part, compose_hash)
287
-
288
- # print
289
- if $stdout.isatty
290
- help_str = dk_part.fetch('help')
291
- print_dk_help_line("#{part_key}", "#{help_str ? '- ' + help_str : ''}") if dk_part.keys.any?
292
- end
293
- # merge
294
- return dk_merge_and_remove(compose_hash, dk_part)
295
- end
296
-
297
- # saved parts
298
- saved_part_keys.each do |potential_part_key|
299
- dk_part = dk_parts_hash.fetch(potential_part_key, nil) # yml detail
300
- compose_hash = print_and_merge_part(potential_part_key, dk_part, compose_hash) if dk_part
301
- end
302
-
303
- # arg parts
304
- selected_part_keys = []
305
- while true
306
- potential_part_key = args.first
307
- dk_part = dk_parts_hash.fetch(potential_part_key, nil) # yml detail
308
- if dk_part
309
-
310
- # unless already applied
311
- unless saved_part_keys.include?(potential_part_key)
312
- compose_hash = print_and_merge_part(potential_part_key, dk_part, compose_hash)
313
- end
314
- selected_part_keys << potential_part_key
315
- args.shift # remove part arg
316
- else
317
- break
318
- end
319
- end
320
- @logger.puts "" if $stdout.isatty # tailing line break
321
- tempfile.write(compose_hash.to_yaml) # write it to the tempfile
322
-
323
- # clone args
324
- args = args.clone
325
-
326
- # remove existing '-f' flag, if needed
327
- file_flag_index = args.index('-f')
328
- if file_flag_index==0
329
- args.delete_at(file_flag_index)
330
- args.delete_at(file_flag_index)
331
- end
332
- args.unshift('-f', tempfile.path) # add new '-f' flag
333
-
334
- tempfile.close
335
- return args, selected_part_keys
336
- end
337
-
338
51
  #
339
52
  # Run: DK
340
53
  #
@@ -345,147 +58,29 @@ def run_dk(compose_args)
345
58
  end
346
59
 
347
60
  #
348
- # Run: SET WORLD
61
+ # Features
349
62
  #
350
63
 
351
- def run_dk_set_world()
352
- world_settings = get_world_settings_hash()
64
+ def do_work()
353
65
 
354
- # world path
355
- world_path = ARGV[1]
356
- if world_path.nil?
357
- print_set_world_help
358
- exit
359
- else
360
- if world_path.start_with?("/") or world_path.start_with?("~")
361
- world_path = File.expand_path(world_path)
362
- else
363
- world_path = File.expand_path(File.join(@working_dir, world_path))
364
- end
365
- end
66
+ # utils
67
+ @help_printer = HelpPrinter.new(@logger)
68
+ @world_settings = WorldSettings.new(@working_dir)
69
+ @yaml_arg_munger = YamlArgMunger.new(@working_dir, @world_settings)
70
+ @parts_merger = PartsMerger.new(@working_dir, @yaml_arg_munger, @help_printer, @logger)
366
71
 
367
- if TTY::Prompt.new.yes?("Set world to: #{world_path}?")
368
- world_settings[:world_path] = world_path
369
- save_world_settings(world_settings)
370
- run_dk_set_context()
72
+ # world files
73
+ compose_hash, dk_parts_hash, dk_make_hash = @yaml_arg_munger.get_base_compose_parts_and_make_hashes()
74
+ if compose_hash
75
+ try_print_dk_help(dk_parts_hash, dk_make_hash, ARGV.clone)
76
+ compose_hash, selected_parts, shifted_args = @parts_merger.merge_parts(compose_hash, dk_parts_hash, ARGV.clone)
77
+ final_args = @yaml_arg_munger.save_yaml_and_adjust_args(compose_hash, shifted_args)
78
+ run_dk(final_args)
371
79
  else
372
- exit
373
- end
374
-
375
- end
376
-
377
- def run_dk_set_context()
378
- world_settings = get_world_settings_hash()
379
- unless world_settings[:world_path] and File.directory?(world_settings[:world_path])
380
- @logger.error "no world path set."
80
+ @logger.error "no docker configuration found"
381
81
  exit 1
382
82
  end
383
83
 
384
- # read folder from world path (aka. context)
385
- all_dir = Dir.chdir(world_settings[:world_path]) do
386
- Dir.glob('*').select { |f| File.directory? f and f != "common" }
387
- end
388
- selected_dir = pick_single_result(all_dir.collect {|f| [f]}, "select context").first
389
- world_settings[:context] = selected_dir
390
- save_world_settings(world_settings)
391
-
392
- @logger.puts "world set!"
393
- exit
394
- end
395
-
396
- #
397
- # Run: SET PARTS
398
- #
399
-
400
- def run_dk_set_parts()
401
- parts = ARGV[1..]
402
- filepath = get_save_parts_filepath()
403
- if parts.any?
404
- tempfile = File.new(filepath, 'w')
405
- tempfile.write(ARGV[1..].to_yaml) # write it to the tempfile
406
- tempfile.close
407
- else
408
- if File.exist?(filepath)
409
- File.delete(filepath)
410
- @logger.info "parts cleared"
411
- else
412
- print_set_parts_help()
413
- end
414
- end
415
- exit
416
- end
417
-
418
- #
419
- # Run: MAKE
420
- #
421
-
422
- def run_dk_make(compose_args, dk_make_hash, dk_parts_hash, selected_part_keys)
423
- # print help?
424
- no_make_command = (compose_args.count == 3)
425
- if no_make_command
426
- print_make_help(dk_parts_hash, dk_make_hash)
427
- else
428
-
429
- #
430
- # supports running multiple commands in a row
431
- # ex. dk make down up
432
- #
433
-
434
- make_commands = compose_args[3..]
435
- make_commands.each_with_index do |key, i|
436
-
437
- if not dk_make_hash.has_key?(key)
438
- @logger.error "#{key} - command not found"
439
- exit 1
440
- else
441
-
442
- all_commands = dk_make_hash[key]["commands"]
443
-
444
- #
445
- # all commands run in a subprocess except the last one, so
446
- # the last command can run in the forground and be
447
- # interactive (ex. dk make down up bash)
448
- #
449
-
450
- # construct last command
451
- last_cmd = if make_commands.length == i+1
452
- all_commands.pop
453
- else
454
- nil
455
- end
456
-
457
- # run background commands
458
- all_commands.each do |c|
459
- cmd = prep_make_command(c, selected_part_keys)
460
- `#{cmd}` # run in sub-process
461
- end
462
-
463
- # run last command in foreground
464
- if last_cmd
465
- cmd = prep_make_command(last_cmd, selected_part_keys)
466
- exec(cmd)
467
- end
468
- end
469
- end
470
-
471
- end
472
- end
473
-
474
- def prep_make_command(c, selected_part_keys)
475
- c = interpolate_parts_into_command(c, selected_part_keys)
476
-
477
- # logging
478
- @logger.puts "" if $stdout.isatty
479
- @logger.wait(c)
480
- cmd = "cd #{@working_dir} && #{c}"
481
- end
482
-
483
- def interpolate_parts_into_command(cmd, selected_part_keys)
484
- if selected_part_keys.any?
485
- parts = selected_part_keys.join(' ')
486
- return cmd.gsub("cl dk", "cl dk #{parts}")
487
- end
488
- return cmd
489
84
  end
490
85
 
491
86
  #
@@ -494,32 +89,4 @@ end
494
89
 
495
90
  @working_dir = ENV['CL_WORKING_DIR'] # passed through cl-magic to here
496
91
 
497
- # SET commands first
498
- case ARGV.first
499
- when "set-world"
500
- run_dk_set_world()
501
- when "set-context"
502
- run_dk_set_context()
503
- when "set-parts"
504
- run_dk_set_parts()
505
- else
506
- compose_hash, dk_parts_hash, dk_make_hash = get_base_compose_parts_and_make_hashes()
507
- if compose_hash
508
-
509
- # parts & args
510
- saved_part_keys = get_saved_parts(dk_parts_hash)
511
- compose_args, selected_part_keys = merge_parts_save_and_prep_args(compose_hash, dk_parts_hash, dk_make_hash, saved_part_keys)
512
-
513
- # sub-command
514
- case compose_args[2]
515
- when "make"
516
- all_part_keys = selected_part_keys + saved_part_keys
517
- run_dk_make(compose_args, dk_make_hash, dk_parts_hash, all_part_keys)
518
- else
519
- run_dk(compose_args)
520
- end
521
- else
522
- @logger.error "no docker configuration found"
523
- exit 1
524
- end
525
- end
92
+ do_work()
@@ -0,0 +1,174 @@
1
+ #!/usr/bin/env ruby
2
+ # cli for your compose projects
3
+ require 'optparse'
4
+ require 'optparse/subcommand'
5
+
6
+ require 'cl/magic/common/common_options.rb'
7
+ require 'cl/magic/common/logging.rb'
8
+
9
+ require_relative 'dk/help_printer'
10
+ require_relative 'dk/yaml_arg_munger'
11
+ require_relative 'dk/world_settings'
12
+ require_relative 'dk/parts_merger'
13
+
14
+ @logger = get_logger()
15
+ @cl_cmd_name = File.basename(__FILE__).split('-').join(' ')
16
+
17
+ def print_make_help(dk_parts_hash, dk_make_hash)
18
+ if $stdout.isatty
19
+ puts ""
20
+ puts "Usage: dk make [DK_PARTS] COMMAND"
21
+ puts ""
22
+ puts "Make commands designed to make your developer experience more turnkey"
23
+ puts ""
24
+ puts "Parts:"
25
+ dk_parts_hash.keys.each do |key|
26
+ @help_printer.print_dk_help_line(key, dk_parts_hash[key].fetch('help'))
27
+ end
28
+ puts ""
29
+ puts "Commands:"
30
+ dk_make_hash.keys.each do |key|
31
+ @help_printer.print_dk_help_line(key, dk_make_hash[key].fetch('help'))
32
+ end
33
+ puts ""
34
+ else
35
+ dk_make_hash.keys.each do |key|
36
+ puts key
37
+ end
38
+ end
39
+ end
40
+
41
+ def run_dk_make(final_args, dk_make_hash, dk_parts_hash, selected_parts)
42
+
43
+ # print help?
44
+ no_make_command = (final_args.count == 2)
45
+ if no_make_command
46
+ print_make_help(dk_parts_hash, dk_make_hash)
47
+ else
48
+
49
+ #
50
+ # supports running multiple commands in a row
51
+ # ex. dk make down up
52
+ #
53
+
54
+ make_commands = final_args[2..]
55
+ make_commands.each_with_index do |key, i|
56
+
57
+ if not dk_make_hash.has_key?(key)
58
+ @logger.error "#{key} - command not found"
59
+ exit 1
60
+ else
61
+
62
+ all_commands = dk_make_hash[key]["commands"]
63
+
64
+ #
65
+ # all commands run in a subprocess except the last one, so
66
+ # the last command can run in the forground and be
67
+ # interactive (ex. dk make down up bash)
68
+ #
69
+
70
+ # construct last command
71
+ last_cmd = if make_commands.length == i+1
72
+ all_commands.pop
73
+ else
74
+ nil
75
+ end
76
+
77
+ # run background commands
78
+ all_commands.each do |c|
79
+ cmd = prep_make_command(c, selected_parts)
80
+ `#{cmd}` # run in sub-process
81
+ end
82
+
83
+ # run last command in foreground
84
+ if last_cmd
85
+ cmd = prep_make_command(last_cmd, selected_parts)
86
+ exec(cmd)
87
+ end
88
+ end
89
+ end
90
+
91
+ end
92
+ end
93
+
94
+ def prep_make_command(c, selected_parts)
95
+ # logging
96
+ @logger.puts "" if $stdout.isatty
97
+ @logger.wait(c.gsub("cl dk", "dk"))
98
+
99
+ # run command
100
+ c = interpolate_parts_into_command(c, selected_parts)
101
+ cmd = "cd #{@working_dir} && #{c}"
102
+ end
103
+
104
+ def interpolate_parts_into_command(cmd, selected_parts)
105
+ cmd = "cl #{cmd}" if cmd.start_with?("dk")
106
+
107
+ # subcommands: don't need interpolated parts
108
+ ['parts', 'make', 'make-world', 'world'].each do |subcommand|
109
+ return cmd if cmd.start_with?("cl dk #{subcommand}")
110
+ end
111
+
112
+ if selected_parts.any?
113
+ parts = selected_parts.join(' ')
114
+ if cmd.include? "cl dk make"
115
+ return cmd.gsub("cl dk make", "cl dk make #{parts}")
116
+ else
117
+ return cmd.gsub("cl dk", "cl dk #{parts}")
118
+ end
119
+ end
120
+ return cmd
121
+ end
122
+
123
+ #
124
+ # Features
125
+ #
126
+
127
+ def do_work()
128
+
129
+ # utils
130
+ @help_printer = HelpPrinter.new(@logger)
131
+ @world_settings = WorldSettings.new(@working_dir)
132
+ @yaml_arg_munger = YamlArgMunger.new(@working_dir, @world_settings)
133
+ @parts_merger = PartsMerger.new(@working_dir, @yaml_arg_munger, @help_printer, @logger)
134
+
135
+ # world files
136
+ compose_hash, dk_parts_hash, dk_make_hash = @yaml_arg_munger.get_base_compose_parts_and_make_hashes()
137
+ if compose_hash
138
+ compose_hash, selected_parts, shifted_args = @parts_merger.merge_parts(compose_hash, dk_parts_hash, ARGV.clone)
139
+ final_args = @yaml_arg_munger.save_yaml_and_adjust_args(compose_hash, shifted_args)
140
+ run_dk_make(final_args, dk_make_hash, dk_parts_hash, selected_parts)
141
+ else
142
+ @logger.error "no docker configuration found"
143
+ exit 1
144
+ end
145
+
146
+ end
147
+
148
+ #
149
+ # Options
150
+ #
151
+
152
+ options = {}
153
+ global_banner = <<DOC
154
+
155
+ Cli for your compose projects
156
+
157
+ Usage: #{@cl_cmd_name} [options]
158
+
159
+ DOC
160
+
161
+ global = OptionParser.new do |g|
162
+ g.banner = global_banner
163
+ add_help_and_verbose(g)
164
+ end
165
+
166
+ #
167
+ # Run
168
+ #
169
+
170
+ @working_dir = ENV['CL_WORKING_DIR'] # passed through cl-magic to here
171
+
172
+ global.parse(ARGV)
173
+
174
+ do_work()