cl-magic 0.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.
@@ -0,0 +1,402 @@
1
+ #!/usr/bin/env ruby
2
+ require 'yaml'
3
+ require 'json'
4
+ require 'tty-command'
5
+ require 'tempfile'
6
+
7
+ require 'cl/magic/common/common_options.rb'
8
+ require 'cl/magic/common/logging.rb'
9
+
10
+ @logger = get_logger()
11
+
12
+ #
13
+ # Util Methods
14
+ #
15
+
16
+ def get_base_compose_hash()
17
+ cmd = "cd #{@working_dir} && docker compose config"
18
+ return YAML.load(`#{cmd}`)
19
+ end
20
+
21
+ def get_repo_basename()
22
+ command = "cd #{@working_dir} && basename $(git remote get-url origin 2> /dev/null) .git"
23
+ return TTY::Command.new(:printer => :null).run(command).out.gsub('.git', '').strip.chomp
24
+ end
25
+
26
+ def get_dk_world_project_path()
27
+ repo_basename = get_repo_basename()
28
+ return "dk-world/#{repo_basename}" if repo_basename
29
+ return nil
30
+ end
31
+
32
+ def get_save_parts_filepath()
33
+ return File.join(@working_dir, ".cl-dk-parts.yml")
34
+ end
35
+
36
+ #
37
+ # Print Help Methods
38
+ #
39
+
40
+ def print_dk_help_line(key, help)
41
+ if help.nil?
42
+ @logger.puts("#{key.ljust(15, ' ')} ???no help???")
43
+ else
44
+ key = key.ljust(15, ' ')
45
+ help_parts = help.split(";")
46
+
47
+ # first line
48
+ @logger.puts(key, help_parts.shift)
49
+
50
+ # following lines
51
+ padding = "".ljust(15, ' ')
52
+ help_parts.each do |p|
53
+ @logger.puts(padding, p)
54
+ end
55
+ puts("") if help.end_with?(";")
56
+ end
57
+ end
58
+
59
+ def print_dk_help(dk_parts_hash, dk_make_hash, args)
60
+ no_args = args.empty?()
61
+ asked_for_help = args.include?('--help')
62
+ has_dk_commands = dk_parts_hash.keys.any?
63
+
64
+ if no_args or asked_for_help
65
+ if has_dk_commands
66
+ puts ""
67
+ puts "Usage: dk [DK_PARTS] [COMPOSE_OPTIONS] COMPOSE_COMMAND"
68
+ puts ""
69
+ puts "Run docker compose while munging yamls in sophisticated ways."
70
+ puts ""
71
+ if get_repo_basename
72
+ puts "PROJ INFO:"
73
+ puts " - Repo basename: #{get_repo_basename}"
74
+ puts " - World filepath: #{File.join(@working_dir, get_dk_world_project_path())}"
75
+ puts ""
76
+ end
77
+ puts "PROJ PARTS:"
78
+ dk_parts_hash.keys.each do |key|
79
+ print_dk_help_line(key, dk_parts_hash[key].fetch('help'))
80
+ end
81
+ puts ""
82
+ puts "YML TOKENS"
83
+ puts " - <dk-remove>"
84
+ puts " - <dk-replace>"
85
+ puts " - <dk-project-path>"
86
+ puts " - <dk-world-path>"
87
+ puts " - <dk-working-path>"
88
+ puts ""
89
+ puts "ADDITIONAL TURNKEY COMMANDS:"
90
+ puts " - dk make"
91
+ puts " - dk save-parts"
92
+ puts ""
93
+ puts "-------------------------"
94
+ end
95
+ end
96
+ end
97
+
98
+ def print_make_help(dk_parts_hash, dk_make_hash)
99
+ puts ""
100
+ puts "Usage: dk [DK_PARTS] make COMMAND"
101
+ puts ""
102
+ puts "Make commands designed to make your developer experience more turnkey"
103
+ puts ""
104
+ puts "Parts:"
105
+ dk_parts_hash.keys.each do |key|
106
+ print_dk_help_line(key, dk_parts_hash[key].fetch('help'))
107
+ end
108
+ puts ""
109
+ puts "Commands:"
110
+ dk_make_hash.keys.each do |key|
111
+ print_dk_help_line(key, dk_make_hash[key].fetch('help'))
112
+ end
113
+ puts ""
114
+ end
115
+
116
+ def print_save_parts_help(dk_parts_hash)
117
+ puts ""
118
+ puts "Usage: dk [DK_PARTS] save-parts"
119
+ puts ""
120
+ puts "Save parts in project and apply every time"
121
+ puts ""
122
+ puts "Parts:"
123
+ dk_parts_hash.keys.each do |key|
124
+ print_dk_help_line(key, dk_parts_hash[key].fetch('help'))
125
+ end
126
+ puts ""
127
+ end
128
+
129
+ #
130
+ # Yaml Merging
131
+ #
132
+
133
+ class ::Hash
134
+ def dk_merge(second)
135
+ merger = proc { |_, v1, v2|
136
+ if Hash === v1 && Hash === v2
137
+ v1.merge(v2, &merger)
138
+ else
139
+ if Array === v1 && Array === v2
140
+ if v2.first=="<dk-replace>"
141
+ v2[1..-1] # everything but the first item
142
+ else
143
+ v1 | v2 # union arrays
144
+ end
145
+ else
146
+ if [:undefined, nil, :nil].include?(v2)
147
+ v1
148
+ else
149
+ v2
150
+ end
151
+ end
152
+ end
153
+ }
154
+ merge(second.to_h, &merger)
155
+ end
156
+ def dk_reject!(&blk)
157
+ self.each do |k, v|
158
+ v.dk_reject!(&blk) if v.is_a?(Hash)
159
+ self.delete(k) if blk.call(k, v)
160
+ end
161
+ end
162
+ end
163
+
164
+ def dk_merge_and_remove(compose_hash, yml_hash)
165
+ # remove help & merge
166
+ clean_yml = yml_hash.clone
167
+ clean_yml.delete('help')
168
+ return compose_hash.dk_merge(clean_yml).dk_reject! { |k, v| v=='<dk-remove>' }
169
+ end
170
+
171
+ def merge_world_files(compose_hash, show_help=false)
172
+ dk_proj_path = get_dk_world_project_path()
173
+ if dk_proj_path
174
+ print_dk_help_line("dk-project-path", "#{dk_proj_path}") if show_help
175
+
176
+ Dir.glob("#{dk_proj_path}/*.yml").sort.each do |filepath|
177
+ print_dk_help_line("dk-world", "#{filepath}") if show_help
178
+
179
+ # read file and replace
180
+ contents = File.read(filepath)
181
+ contents.gsub!('<dk-world-path>', File.join(Dir.pwd, "dk-world"))
182
+ contents.gsub!('<dk-project-path>', File.join(Dir.pwd, dk_proj_path))
183
+ contents.gsub!('<dk-working-path>', @working_dir)
184
+
185
+ # yml merge
186
+ yml_hash = YAML.load(contents)
187
+ compose_hash = dk_merge_and_remove(compose_hash, yml_hash)
188
+ end
189
+ end
190
+ return compose_hash
191
+ end
192
+
193
+ def get_saved_parts(dk_parts_hash)
194
+ saved_part_keys = []
195
+ filepath = get_save_parts_filepath()
196
+
197
+ # merge
198
+ if File.exist?(filepath)
199
+ saved_parts = YAML.load_file(filepath)
200
+ saved_parts.each do |potential_part_key|
201
+ dk_part = dk_parts_hash.fetch(potential_part_key, nil) # yml detail
202
+ if dk_part
203
+ saved_part_keys << potential_part_key
204
+ else
205
+ @logger.error "invalid saved part: #{potential_part_key}"
206
+ exit 1
207
+ end
208
+ end
209
+ end
210
+ return saved_part_keys
211
+ end
212
+
213
+ def merge_parts_save_and_prep_args(compose_hash, dk_parts_hash, dk_make_hash, saved_part_keys)
214
+ tempfile = File.new(File.join(@working_dir, ".cl-dk.yml"), 'w')
215
+ args = ARGV.clone
216
+
217
+ # dk help
218
+ print_dk_help(dk_parts_hash, dk_make_hash, args)
219
+
220
+ def print_and_merge_part(part_key, dk_part, compose_hash)
221
+ # print
222
+ help_str = dk_part.fetch('help')
223
+ print_dk_help_line("#{part_key}", "#{help_str ? '- ' + help_str : ''}") if dk_part.keys.any?
224
+
225
+ # merge
226
+ return dk_merge_and_remove(compose_hash, dk_part)
227
+ end
228
+
229
+ # saved parts
230
+ saved_part_keys.each do |potential_part_key|
231
+ dk_part = dk_parts_hash.fetch(potential_part_key, nil) # yml detail
232
+ compose_hash = print_and_merge_part(potential_part_key, dk_part, compose_hash) if dk_part
233
+ end
234
+
235
+ # arg parts
236
+ selected_part_keys = []
237
+ while true
238
+ potential_part_key = args.first
239
+ dk_part = dk_parts_hash.fetch(potential_part_key, nil) # yml detail
240
+ if dk_part
241
+
242
+ # unless already applied
243
+ unless saved_part_keys.include?(potential_part_key)
244
+ compose_hash = print_and_merge_part(potential_part_key, dk_part, compose_hash)
245
+ end
246
+ selected_part_keys << potential_part_key
247
+ args.shift # remove part arg
248
+ else
249
+ break
250
+ end
251
+ end
252
+ puts "" # tailing line break
253
+ tempfile.write(compose_hash.to_yaml) # write it to the tempfile
254
+
255
+ # clone args
256
+ args = args.clone
257
+
258
+ # remove existing '-f' flag, if needed
259
+ file_flag_index = args.index('-f')
260
+ if file_flag_index==0
261
+ args.delete_at(file_flag_index)
262
+ args.delete_at(file_flag_index)
263
+ end
264
+ args.unshift('-f', tempfile.path) # add new '-f' flag
265
+
266
+ tempfile.close
267
+ return args, selected_part_keys
268
+ end
269
+
270
+ #
271
+ # Run: DK
272
+ #
273
+
274
+ def run_dk(compose_args)
275
+ cmd = "cd #{@working_dir} && docker compose #{compose_args.join(' ')}"
276
+ exec(cmd)
277
+ end
278
+
279
+ #
280
+ # Run: SAVE PARTS
281
+ #
282
+
283
+ def run_dk_save_parts(compose_args, dk_parts_hash, selected_part_keys)
284
+ filepath = get_save_parts_filepath()
285
+ if selected_part_keys.any?
286
+ tempfile = File.new(filepath, 'w')
287
+ tempfile.write(selected_part_keys.to_yaml) # write it to the tempfile
288
+ else
289
+ if File.exist?(filepath)
290
+ File.delete(filepath)
291
+ @logger.info "parts cleared"
292
+ else
293
+ print_save_parts_help(dk_parts_hash)
294
+ end
295
+ end
296
+ end
297
+
298
+ #
299
+ # Run: MAKE
300
+ #
301
+
302
+ def run_dk_make(compose_args, dk_make_hash, dk_parts_hash, selected_part_keys)
303
+ # print help?
304
+ no_make_command = (compose_args.count == 3)
305
+ if no_make_command
306
+ print_make_help(dk_parts_hash, dk_make_hash)
307
+ else
308
+
309
+ #
310
+ # supports running multiple commands in a row
311
+ # ex. dk make down up
312
+ #
313
+
314
+ make_commands = compose_args[3..]
315
+ make_commands.each_with_index do |key, i|
316
+
317
+ if not dk_make_hash.has_key?(key)
318
+ puts "#{key} - command not found"
319
+ exit 1
320
+ else
321
+
322
+ all_commands = dk_make_hash[key]["commands"]
323
+
324
+ #
325
+ # all commands run in a subprocess except the last one, so
326
+ # the last command can run in the forground and be
327
+ # interactive (ex. dk make down up bash)
328
+ #
329
+
330
+ # construct last command
331
+ last_cmd = if make_commands.length == i+1
332
+ all_commands.pop
333
+ else
334
+ nil
335
+ end
336
+
337
+ # run background commands
338
+ all_commands.each do |c|
339
+ cmd = prep_make_command(c, selected_part_keys)
340
+ `#{cmd}` # run in sub-process
341
+ end
342
+
343
+ # run last command in foreground
344
+ if last_cmd
345
+ cmd = prep_make_command(last_cmd, selected_part_keys)
346
+ exec(cmd)
347
+ end
348
+ end
349
+ end
350
+
351
+ end
352
+ end
353
+
354
+ def prep_make_command(c, selected_part_keys)
355
+ c = interpolate_parts_into_command(c, selected_part_keys)
356
+
357
+ # logging
358
+ @logger.puts ""
359
+ @logger.wait(c)
360
+ cmd = "cd #{@working_dir} && #{c}"
361
+ end
362
+
363
+ def interpolate_parts_into_command(cmd, selected_part_keys)
364
+ if selected_part_keys.any?
365
+ parts = selected_part_keys.join(' ')
366
+ return cmd.gsub("cl dk", "cl dk #{parts}")
367
+ end
368
+ return cmd
369
+ end
370
+
371
+ #
372
+ # Main
373
+ #
374
+
375
+ @working_dir = ENV['CL_WORKING_DIR'] # passed through cl-magic to here
376
+
377
+ # get compose settings
378
+ compose_hash = get_base_compose_hash()
379
+ if compose_hash
380
+
381
+ # world
382
+ compose_hash = merge_world_files(compose_hash, show_help=ARGV.include?("--help"))
383
+
384
+ # x-dk fragments
385
+ dk_parts_hash = compose_hash['x-dk-parts'] ? compose_hash.delete('x-dk-parts') : {}
386
+ dk_make_hash = compose_hash['x-dk-make'] ? compose_hash.delete('x-dk-make') : {}
387
+
388
+ # parts
389
+ saved_part_keys = get_saved_parts(dk_parts_hash)
390
+ compose_args, selected_part_keys = merge_parts_save_and_prep_args(compose_hash, dk_parts_hash, dk_make_hash, saved_part_keys)
391
+
392
+ # sub-command
393
+ case compose_args[2]
394
+ when "make"
395
+ all_part_keys = selected_part_keys + saved_part_keys
396
+ run_dk_make(compose_args, dk_make_hash, dk_parts_hash, all_part_keys)
397
+ when "save-parts"
398
+ run_dk_save_parts(compose_args, dk_parts_hash, selected_part_keys)
399
+ else
400
+ run_dk(compose_args)
401
+ end
402
+ end
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env ruby
2
+ require 'optparse'
3
+ require 'optparse/subcommand'
4
+ require 'tty-command'
5
+ require 'tty-prompt'
6
+
7
+ require 'cl/magic/common/common_options.rb'
8
+ require 'cl/magic/common/logging.rb'
9
+ require 'cl/magic/common/gcloud.rb'
10
+ require 'cl/magic/common/kubectl.rb'
11
+
12
+ @logger = get_logger()
13
+ @cl_cmd_name = File.basename(__FILE__).split('-').join(' ')
14
+
15
+ #
16
+ # Features
17
+ #
18
+
19
+ def do_work(options)
20
+ envkey_str = `cd #{@working_dir} && envkey-source`
21
+ envkey_array = envkey_str.split(' ')
22
+ result = if options[:filter]
23
+ envkey_array.select {|o|
24
+ o.gsub("'","").start_with? options[:filter]
25
+ }
26
+ else
27
+ envkey_array
28
+ end
29
+ puts result
30
+ end
31
+
32
+ #
33
+ # Options
34
+ #
35
+
36
+ options = {}
37
+ global_banner = <<DOC
38
+
39
+ A sandbox to try things
40
+
41
+ Usage: #{@cl_cmd_name} [options]
42
+
43
+ DOC
44
+
45
+ global = OptionParser.new do |g|
46
+ g.banner = global_banner
47
+ add_help_and_verbose(g)
48
+
49
+ g.on("-f", "--filter string", "filter by string (i.e. starts with)") do |v|
50
+ options[:filter] = v
51
+ end
52
+ end
53
+
54
+ #
55
+ # Run
56
+ #
57
+
58
+ @working_dir = ENV['CL_WORKING_DIR'] # passed through cl-magic to here
59
+ global.parse(ARGV)
60
+
61
+ # display full command
62
+ if options[:filter]
63
+ write_history("""#{@cl_cmd_name} connect \\
64
+ --filter=#{options[:filter]}
65
+ """)
66
+ else
67
+ write_history("""#{@cl_cmd_name}""")
68
+ end
69
+
70
+ do_work(options)
@@ -0,0 +1,135 @@
1
+ #!/usr/bin/env ruby
2
+ require 'optparse'
3
+ require 'optparse/subcommand'
4
+ require 'tty-command'
5
+ require 'tty-prompt'
6
+
7
+ require 'cl/magic/common/common_options.rb'
8
+ require 'cl/magic/common/logging.rb'
9
+ require 'cl/magic/common/gcloud.rb'
10
+
11
+ @logger = get_logger()
12
+ @cl_cmd_name = File.basename(__FILE__).split('-').join(' ')
13
+
14
+ #
15
+ # Features
16
+ #
17
+
18
+ def set_project_instance_and_user(options)
19
+ project = pick_gcloud_project(options[:project])
20
+ instance = pick_single_result(get_sql_instances, 'Pick sql instance', options[:instance])
21
+ user = pick_single_result(get_sql_users(instance), 'Pick sql user', options[:user])
22
+ return project, instance, user
23
+ end
24
+
25
+ def set_password(options)
26
+ project, instance, user = set_project_instance_and_user(options)
27
+
28
+ # display full command
29
+ write_history("""#{@cl_cmd_name} set-password \\
30
+ --project=#{project.first} \\
31
+ --instance=#{instance.first} \\
32
+ --user=#{user.first}
33
+ """)
34
+
35
+ # run
36
+ @logger.puts ""
37
+ cmd = "gcloud sql users set-password #{user.first} --instance=#{instance.first} --prompt-for-password"
38
+ @logger.wait cmd
39
+ exec(cmd)
40
+ end
41
+
42
+ def connect(options)
43
+ project, instance, user = set_project_instance_and_user(options)
44
+ database = pick_single_result(get_sql_databases(instance), "Which database?")
45
+
46
+ # display full command
47
+ write_history("""#{@cl_cmd_name} connect \\
48
+ --project=#{project.first} \\
49
+ --instance=#{instance.first} \\
50
+ --user=#{user.first} \\
51
+ --database=#{database.first}
52
+ """)
53
+
54
+ # run
55
+ @logger.puts ""
56
+ cmd = "gcloud sql connect #{instance.first} --user=#{user.first} --database=#{database.first}"
57
+ @logger.wait cmd
58
+ exec(cmd)
59
+ end
60
+
61
+ #
62
+ # Options
63
+ #
64
+
65
+ options = {}
66
+ global_banner = <<DOC
67
+
68
+ A tool for everything google cloud sql
69
+
70
+ Usage: #{@cl_cmd_name} [options]
71
+
72
+ DOC
73
+
74
+ set_password_banner = <<DOC
75
+
76
+ Set the password for a sql user
77
+
78
+ Usage: #{@cl_cmd_name} set-password [options]
79
+
80
+ DOC
81
+
82
+ connect_banner = <<DOC
83
+
84
+ Connect to a sql database
85
+
86
+ Usage: #{@cl_cmd_name} connect [options]
87
+
88
+ DOC
89
+
90
+ def add_project_instance_and_user_options(s, options)
91
+ s.on("-p", "--project PROJECT", "google cloud project") do |v|
92
+ options[:project] = v
93
+ end
94
+ s.on("-i", "--instance INSTANCE", "google cloud sql instance") do |v|
95
+ options[:instance] = v
96
+ end
97
+ s.on("-u", "--user USER", "google cloud sql user") do |v|
98
+ options[:user] = v
99
+ end
100
+ end
101
+
102
+ global = OptionParser.new do |g|
103
+ g.banner = global_banner
104
+ add_help_and_verbose(g)
105
+
106
+ g.subcommand 'set-password' do |s|
107
+ s.banner = set_password_banner
108
+ options[:action] = :set_password
109
+ add_project_instance_and_user_options(s, options)
110
+ end
111
+
112
+ g.subcommand 'connect' do |s|
113
+ s.banner = connect_banner
114
+ options[:action] = :connect
115
+ add_project_instance_and_user_options(s, options)
116
+ s.on("-d", "--database DATABASE", "google cloud sql database name") do |v|
117
+ options[:user] = v
118
+ end
119
+ end
120
+ end
121
+
122
+ #
123
+ # Run
124
+ #
125
+
126
+ global.parse(ARGV)
127
+
128
+ case options[:action]
129
+ when :set_password
130
+ set_password(options)
131
+ when :connect
132
+ connect(options)
133
+ else
134
+ puts global.parse! %w[--help]
135
+ end