cl-magic 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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