noms-client 1.9.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.
data/bin/noms ADDED
@@ -0,0 +1,774 @@
1
+ #!/usr/bin/env ruby
2
+ # /* Copyright 2014 Evernote Corporation. All rights reserved.
3
+ # Copyright 2013 Proofpoint, Inc. All rights reserved.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ # */
17
+
18
+ # = NAME
19
+ #
20
+ # noms - CLI for New Operations Management Stack
21
+ #
22
+ # = SYNOPSIS
23
+ #
24
+ # noms command [options] [arguments]
25
+ # cmdb subcommand
26
+ # query [condition [...]] - Query for systems
27
+ # set fqdn field=value [field=value [...]] -
28
+ # Update system attributes
29
+ # show fqdn - Show fields for one system
30
+ # waitfor expr cmdb query - Waitfor CMDB query to have
31
+ # specified number of results
32
+ # instance subcommand - Operate on NOMS instances
33
+ # add [cloud] [attribute=value [...]] -
34
+ # Add a new NOMS instance
35
+ # list [cloud] - List NOMS instances (in cloud)
36
+ # show [cloud/]instanceid
37
+ # show [cloud/]name - Show details of cloud instance
38
+ # (can include --fields console)
39
+ # remove [cloud/]instanceid
40
+ # remove [cloud/]name - Terminate a cloud instance
41
+ # environment subcommand - Operate on NOMS environment
42
+ # add name [environment_name=value] [note=value] -
43
+ # Add a new environment
44
+ # list - List environments
45
+ # tree - Show environments in a tree
46
+ # show name - Show details on <environment>
47
+ # remove name - Remove environment
48
+ # svcinst subcommand - Operate on NOMS service instances
49
+ # add [environment/]service - Add service instance
50
+ # set [environment/]service property=value -
51
+ # Set property on service instance
52
+ # show [environment/]service- Show details on service instance
53
+ # list [environment] - List service instances for environment
54
+ # remove [environment/]service -
55
+ # Remove a service instance from an
56
+ # environment
57
+ # describe item - Describe meta-data for NOMS
58
+ # clouds - List clouds
59
+ # cloud name - Describe named cloud
60
+ # ncc - Describe NCC API endpoint
61
+ # cmdb - Describe system fields
62
+ # Options:
63
+ # --names - Just print entity names, equivalent to
64
+ # --fields name --noheader --nofeedback --nolabel
65
+ # --fields field[,field[,...]]
66
+ # --format {text | json | csv} - Print output in specified format
67
+ # --timeout - Timeout for network operations (especially
68
+ # relevant for waitfor)
69
+ # --noheader
70
+ # --nofeedback
71
+ # --nolabel
72
+ # options as described in Optconfig
73
+ #
74
+ #
75
+ # = DESCRIPTION
76
+ #
77
+ # The +noms+ command is used to set up and manage virtual and physical environments.
78
+ #
79
+ # == Commands
80
+ #
81
+ # * describe
82
+ #
83
+ # The +describe+ command (+desc+ is a synonym) is used to describe different
84
+ # things about the NOMS environment.
85
+ #
86
+ # ** commands
87
+ #
88
+ # List the different commands available
89
+ #
90
+ # * instance
91
+ #
92
+ # The +instance+ command is used for operating on individual instances in the
93
+ # NOMS environment. Use it by invoking subcommands to add, remove and list
94
+ # instances.
95
+ #
96
+ # ** list
97
+ #
98
+ # List instances. Use the *--verbose* option to list more fields
99
+ #
100
+ # Example: +noms instance list us-east-1+
101
+ #
102
+ # ** show
103
+ #
104
+ # Show information about the named instance.
105
+ #
106
+ # Example: +noms instance show us-east-1/webserver1.ops-lab.net+
107
+ #
108
+ # * cmdb
109
+ #
110
+ # Perform operations on the CMDB.
111
+ #
112
+ # ** show
113
+ #
114
+ # Show field values for the specified system (identified by fqdn).
115
+ #
116
+ # Example: +noms cmdb show webserver1.ops-lab.net+
117
+ #
118
+ # ** query
119
+ #
120
+ # Perform a query and display tabular results (see the *--fields*
121
+ # and *--format* option).
122
+ #
123
+ # Example: +noms cmdb query status=allocated data_center_code=XDC1 'tags!~backup'+
124
+ #
125
+ # ** set
126
+ #
127
+ # Set field values for the system in the CMDB.
128
+ #
129
+ # Example: +noms cmdb set webserver1.ops-lab.net status=decommissioned+
130
+ #
131
+ # * waitfor
132
+ #
133
+ # Wait for a CMDB query to have a specified number of result rows. Possible value of
134
+ # the result row count expression is a number signifying a number of rows to exactly match,
135
+ # or '>n' indicating that more than _n_ rows must match. The following syntax is 'cmdb query
136
+ # field-expression', just as with the 'cmdb query' command.
137
+ #
138
+ # Example: +noms waitfor >1 cmdb query status=idle roles=xenserver+
139
+ #
140
+ # == Options
141
+ #
142
+ # * --names
143
+ #
144
+ # Just print the names of the entries. (Synonymous with --noheader --nofeedback --fields=fqdn
145
+ # or --fields=name)
146
+ #
147
+ # Example: +noms cmdb query status=idle --names+
148
+ #
149
+ # Example: +noms instance list --names+
150
+ #
151
+ # * --fields
152
+ #
153
+ # Use the specified comma-separated list of fields for printing results.
154
+ #
155
+ # Example: +noms cmdb query 'status!=decommissioned' --fields=fqdn,operatingsystem,status+
156
+ #
157
+ # * --format
158
+ #
159
+ # Specify the output format. One of: text, csv or json (default is 'text').
160
+ #
161
+ # Example: +noms cmdb show webserver1.ops-lab.net --format=json+
162
+ #
163
+ # * --noheader
164
+ #
165
+ # Don't print column headings (in text or CSV output).
166
+ #
167
+ # * --nofeedback
168
+ #
169
+ # Don't print object counts.
170
+ #
171
+ # = AUTHOR
172
+ #
173
+ # Jeremy Brinkley, <jbrinkley@evernote.com>
174
+ #
175
+
176
+ require 'rubygems'
177
+ require 'optconfig'
178
+ require 'etc'
179
+ require 'csv'
180
+ require 'noms/client/version'
181
+ require 'ncc/client'
182
+ require 'noms/cmdb'
183
+
184
+ $VERSION = NOMS::Client::VERSION
185
+ $me = 'noms'
186
+
187
+ $opt = Optconfig.new('noms', {
188
+ 'ncc=s%' => {
189
+ 'url' => 'http://noms/ncc_api/v2' },
190
+ 'cmdb=s%' => {
191
+ 'url' => 'http://cmdb/cmdb_api/v1' },
192
+ 'fields=s' => '',
193
+ 'mock=s' => nil,
194
+ 'format=s' => 'text',
195
+ 'names!' => false,
196
+ 'label!' => true,
197
+ 'header!' => true,
198
+ 'feedback!' => true,
199
+ 'waitfor-interval=i' => 5,
200
+ 'timeout=i' => 600,
201
+ 'default-environment' => 'production',
202
+ 'default-cloud' => 'ec2',
203
+ 'format-field=s%' => {
204
+ 'svcinst' => {
205
+ 'fields' => [
206
+ 'name', 'type', 'note'
207
+ ],
208
+ 'length' => {
209
+ 'name' => 20,
210
+ 'type' => 20,
211
+ 'note' => 30
212
+ }
213
+ },
214
+ 'environment' => {
215
+ 'fields' => [
216
+ 'name', 'environment_name', 'note',
217
+ ],
218
+ 'length' => {
219
+ 'name' => 20,
220
+ 'environment_name' => 20,
221
+ 'note' => 30
222
+ }
223
+ },
224
+ 'instance' => {
225
+ 'fields' => [
226
+ 'name', 'status', 'size', 'image',
227
+ 'id'],
228
+ 'length' => {
229
+ 'name' => 36,
230
+ 'status' => 10,
231
+ 'size' => 10,
232
+ 'id' => 36,
233
+ 'image' => 15
234
+ }
235
+ },
236
+ 'system' => {
237
+ 'fields' => [
238
+ 'fqdn', 'environment_name', 'status', 'roles', 'ipaddress',
239
+ 'data_center_code'],
240
+ 'length' => {
241
+ 'fqdn' => 36,
242
+ 'environment_name' => 16,
243
+ 'status' => 15,
244
+ 'ipaddress' => 15,
245
+ 'data_center_code' => 11,
246
+ 'svc_id' => 10,
247
+ 'cloud' => 10,
248
+ 'serial_number' => 36,
249
+ 'tags' => 20
250
+ }
251
+ }
252
+ }
253
+ })
254
+
255
+ if $opt['mock']
256
+ NCC::Client.mock! $opt['mock']
257
+ NOMS::CMDB.mock! $opt['mock']
258
+ end
259
+
260
+ case $opt['format']
261
+ when 'csv'
262
+ $opt['feedback'] = false
263
+ when 'json'
264
+ $opt['feedback'] = false
265
+ $opt['header'] = false
266
+ when 'text'
267
+ else
268
+ fatal "Don't know how to produce #{$opt['format']} output"
269
+ end
270
+
271
+ if $opt['names']
272
+ $opt['header'] = false
273
+ $opt['feedback'] = false
274
+ end
275
+
276
+ $opt['cmdb']['timeout'] ||= $opt['timeout']
277
+ $opt['ncc']['timeout'] ||= $opt['timeout']
278
+
279
+ $cmdb = NOMS::CMDB.new($opt)
280
+ $ncc = NCC::Client.new($opt)
281
+
282
+ def parse_format(fields, type)
283
+ dbg "Specified fields: #{fields.inspect}"
284
+ if fields.nil? or fields.empty?
285
+ dbg " no specification"
286
+ elsif fields.respond_to? :to_ary
287
+ dbg " fields are already array"
288
+ else
289
+ dbg " splitting on ','"
290
+ fields = fields.split(',')
291
+ end
292
+ if ! fields.empty?
293
+ fields.map do |fieldname|
294
+ if m = /([^=]+)=(\d+)/.match(fieldname)
295
+ fieldname = m[1]
296
+ $opt['format-field'][type]['length'][fieldname] = m[2].to_i
297
+ end
298
+ fieldname
299
+ end
300
+ else
301
+ nil
302
+ end
303
+ end
304
+
305
+ def fmt_string(type='instance', fields=nil)
306
+ fields = fmt_fields(type, fields)
307
+ fields.map do |field|
308
+ len = $opt['format-field'][type]['length'][field]
309
+ len = field.length unless (len and len > 0)
310
+ "%-#{len}s"
311
+ end.join(" ")
312
+ end
313
+
314
+ def fmt_fields(type='instance', fields=nil)
315
+ if fields.nil?
316
+ fields = parse_format($opt['fields'], type) || $opt['format-field'][type]['fields']
317
+ end
318
+ fields
319
+ end
320
+
321
+ def fmt_line(obj, type='instance', fields=nil)
322
+ fields = fmt_fields(type, fields)
323
+ case $opt['format']
324
+ when 'csv'
325
+ CSV.generate_line(fields.map { |f| obj[f] }).chomp
326
+ else
327
+ fmt_string(type, fields) % fields.map { |f| obj[f] }
328
+ end
329
+ end
330
+
331
+ def fmt_header(type='instance', fields=nil)
332
+ fields = fmt_fields(type, fields)
333
+ case $opt['format']
334
+ when 'csv'
335
+ CSV.generate_line(fields).chomp
336
+ else
337
+ fmt_string(type, fields) % fields
338
+ end
339
+ end
340
+
341
+ def formatted_output(objects, type='instance', fields=nil)
342
+ puts fmt_header(type, fields) if $opt['header']
343
+ results =
344
+ case $opt['format']
345
+ when 'json'
346
+ fields = fmt_fields(type, fields)
347
+ fieldlist = parse_format($opt['fields'], type) || fields
348
+ JSON.pretty_generate(objects.map do |o|
349
+ Hash[fieldlist.map { |f| [f, o[f]] }]
350
+ end)
351
+ else
352
+ objects.map do |obj|
353
+ fmt_line(obj, type, fields)
354
+ end.join("\n")
355
+ end
356
+ results += "\n#{objects.length} objects" if $opt['feedback']
357
+ results
358
+ end
359
+
360
+ def record_formatted_output(object, type='instance', fields=nil)
361
+ fields = fmt_fields(type, fields)
362
+ all_fields = object.keys
363
+ fields = fields & all_fields
364
+ fieldlist = parse_format($opt['fields'], type) || fields + (all_fields - fields)
365
+ case $opt['format']
366
+ when 'json'
367
+ JSON.pretty_generate(Hash[fieldlist.map { |f| [f, object[f]] }])
368
+ when 'csv'
369
+ ($opt['label'] ? CSV.generate_line(fieldlist) : '') +
370
+ CSV.generate_line(fieldlist.map { |f| object[f] })
371
+ else
372
+ fieldlist.map { |f|
373
+ $opt['label'] ? "#{f}: #{object[f]}" : object[f]
374
+ }.join("\n")
375
+ end
376
+ end
377
+
378
+ def extractkvs(args)
379
+ args.inject([{}, []]) do |a, arg|
380
+ if /^(\w+)=/.match arg
381
+ key, value = arg.split('=')
382
+ [a[0].merge({ key.to_sym => value }), a[1]]
383
+ else
384
+ [a[0], a[1] << arg]
385
+ end
386
+ end
387
+ end
388
+
389
+ def parsekvs(args)
390
+ args.inject({}) do |h, arg|
391
+ key, value = arg.split('=')
392
+ h.merge({ key.to_sym => value })
393
+ end
394
+ end
395
+
396
+ def get_username
397
+ Etc.getpwuid(Process.uid).name
398
+ end
399
+
400
+ def fatal(msg)
401
+ $stderr.puts "noms error: #{msg}"
402
+ Process.exit(1)
403
+ end
404
+
405
+ def dbg(msg)
406
+ if $opt.has_key? 'debug' and $opt['debug'] > 0
407
+ puts "DBG(noms): #{msg}"
408
+ end
409
+ end
410
+
411
+ def hash_from_array(a, field='id')
412
+ Hash[a.map { |e| [e[field], e] }]
413
+ end
414
+
415
+ def generic_describe(h)
416
+ h.map do |k, v|
417
+ "#{k}: #{v}"
418
+ end.join("\n")
419
+ end
420
+
421
+ def desc(args)
422
+ what = args.shift
423
+ case what
424
+ when 'commands', nil
425
+ generic_describe({
426
+ 'describe' => 'describe things',
427
+ 'commands' => 'list commands',
428
+ 'cmdb' => 'interact with NOMS CMDB (Inventory)',
429
+ 'instance' => 'interact with clouds (NCC-API)',
430
+ 'waitfor' => 'wait for CMDB query to be satisfied',
431
+ 'environment' => 'interact with NOMS CMDB environments',
432
+ 'svcinst' => 'manage CMDB service instances'
433
+ })
434
+ when 'clouds'
435
+ generic_describe Hash[$ncc.clouds.map { |e| [e['name'],
436
+ e['provider']] }]
437
+ when 'cloud'
438
+ generic_describe $ncc.clouds args.first
439
+ when 'ncc'
440
+ generic_describe $ncc.info
441
+ when 'systems', 'cmdb'
442
+ help = $cmdb.help 'system'
443
+ puts help.inspect
444
+ generic_describe(help.select { |f|
445
+ f.respond_to? :has_key? and f.has_key? 'label' }.map { |f| f['label'] })
446
+ when 'describe', 'desc'
447
+ generic_describe ({
448
+ 'describe' => 'describe resources',
449
+ 'clouds' => 'describe available clouds',
450
+ 'cloud' => 'describe cloud',
451
+ 'ncc' => 'describe NCC API',
452
+ 'systems|cmdb' => 'describe CMDB entries (system)'
453
+ })
454
+ else
455
+ $stderr.puts "Can't describe '#{what}'"
456
+ end
457
+ end
458
+
459
+ def cmdb_query(args)
460
+ $cmdb.query('system', args)
461
+ end
462
+
463
+ def cmdb_show(args)
464
+ fqdn = args.shift
465
+ system = $cmdb.query('system', 'fqdn=' + fqdn).first
466
+ record = if ! args.empty?
467
+ system.keys.inject({}) do |h, k|
468
+ if args.include? k
469
+ h.merge({k => system[k]})
470
+ else
471
+ h
472
+ end
473
+ end
474
+ else
475
+ system
476
+ end
477
+ record
478
+ end
479
+
480
+ def cmdb(args)
481
+ $opt['fields'] = ['fqdn'] if $opt['names']
482
+ cmd = args.shift
483
+ opt, argv = extractkvs args
484
+ case cmd
485
+ when 'query'
486
+ # Note, args, not argv
487
+ results = cmdb_query args
488
+ formatted_output(results, 'system')
489
+ when 'show','info'
490
+ record = cmdb_show args
491
+ record_formatted_output(record, 'system')
492
+ when 'set'
493
+ fqdn = args.shift
494
+ updated = $cmdb.update('system', opt, fqdn)
495
+ ''
496
+ else
497
+ $stderr.puts "Unknown cmdb command '#{cmd}'"
498
+ Process.exit(2)
499
+ end
500
+ end
501
+
502
+ def eval_conditions(count, results)
503
+ expr =
504
+ case count
505
+ when "0", 0
506
+ lambda { |r| r.nil? or r.length == 0 }
507
+ when /^>/
508
+ m = /^>(\d+)/.match count
509
+ ct = m[1].to_i
510
+ lambda { |r| !r.nil? and r.length > ct }
511
+ else
512
+ ct = count.to_i
513
+ lambda { |r| !r.nil? and r.length == ct }
514
+ end
515
+
516
+ expr.call results
517
+ end
518
+
519
+ # TODO: Needs to be factored into general waiting lib
520
+ def waitfor(args)
521
+ count = args.shift
522
+ commands = ['cmdb']
523
+ conditions = []
524
+
525
+ command = args.shift
526
+ subcommand = args.shift
527
+ continuing = true
528
+
529
+ startts = Time.now
530
+ while continuing
531
+ results = case command
532
+ when 'cmdb'
533
+ case subcommand
534
+ when 'query'
535
+ cmdb_query args
536
+ else
537
+ fatal "Can only wait for cmdb query or show"
538
+ end
539
+ else
540
+ fatal "Can only wait for cmdb"
541
+ end
542
+ if eval_conditions(count, results)
543
+ dbg "Conditions satisfied (#{count} records)"
544
+ Process.exit(0)
545
+ continuing = false
546
+ else
547
+ dbg "Conditions not satisfied"
548
+ if (Time.now - startts) > $opt['timeout']
549
+ $stderr.puts "Timed out waiting for #{count} records of " +
550
+ "#{command} #{subcommand} #{args}"
551
+ Process.exit(4)
552
+ end
553
+ dbg "Waiting #{$opt['waitfor-interval']}"
554
+ sleep $opt['waitfor-interval']
555
+ end
556
+ end
557
+ end
558
+
559
+ def find_key(h, k, p=nil)
560
+ puts " - looking for #{k} at path #{p.inspect}"
561
+ if h.has_key? k
562
+ return (p || []) + [k]
563
+ else
564
+ h.keys.each do |nk|
565
+ find_key(h[nk], k, (p || []) + [nk])
566
+ end
567
+ end
568
+ return nil
569
+ end
570
+
571
+ def make_tree(envs, tree={})
572
+ puts "make_tree(#{envs.inspect}, #{tree.inspect})"
573
+ return tree if envs.length == 0
574
+ # Kind of ugly using select for side effects
575
+ pruned_envs = envs.reject do |env|
576
+ puts " checking #{env['name']} < #{env['environment_name']}"
577
+ if env['environment_name'].nil? or env['name'] == env['environment_name']
578
+ puts " root environment, setting"
579
+ tree[env['name']] ||= { }
580
+ true
581
+ elsif path = find_key(tree, env['environment_name'])
582
+ puts " found parent #{env['environment_name']} at #{path.inspect}"
583
+ path_set(tree, path, { env['name'] => { } })
584
+ true
585
+ else
586
+ puts " couldn't find #{env['environment_name']}, save for later"
587
+ false
588
+ end
589
+ end
590
+ make_tree(pruned_envs, tree)
591
+ end
592
+
593
+ def path_set(hash, keypath, value)
594
+ if keypath.length == 1
595
+ hash[keypath[0]] = value
596
+ else
597
+ next_key = keypath.shift
598
+ path_set(hash[next_key], keypath, value)
599
+ end
600
+ end
601
+
602
+ def env(args)
603
+ $opt['fields'] = ['name'] if $opt['names']
604
+ command = args.shift
605
+ case command
606
+ when 'list'
607
+ formatted_output $cmdb.environments, 'environment'
608
+ when 'show', 'info'
609
+ env_name = args.shift
610
+ record_formatted_output($cmdb.environment(env_name), 'environment')
611
+ when 'tree'
612
+ make_tree($cmdb.environments).to_yaml
613
+ when 'add'
614
+ name = args.shift
615
+ attrs = { :environment_name => 'production' }
616
+ attrs.update(args.empty? ? { } : parsekvs(args))
617
+ record_formatted_output($cmdb.create_environment(name, attrs))
618
+ when 'remove'
619
+ name = args.shift
620
+ result = $cmdb.delete_environment name
621
+ # cmdb-api returns '1'. ugh.
622
+ nil unless result != 1
623
+ else
624
+ $stderr.puts "Unknown env command '#{command}'"
625
+ Process.exit(2)
626
+ end
627
+ end
628
+
629
+ def parse_qualified_name(s, default_qualifier)
630
+ qualifier, name = s.split('/', 2)
631
+ if name.nil?
632
+ name = qualifier
633
+ qualifier = default_qualifier
634
+ end
635
+ return qualifier, name
636
+ end
637
+
638
+ def parse_svcinst(s)
639
+ parse_qualified_name(s, $opt['default-environment'])
640
+ end
641
+
642
+ def svcinst(args)
643
+ $opt['fields'] = ['name'] if $opt['names']
644
+ command = args.shift
645
+ case command
646
+ when 'list'
647
+ formatted_output($cmdb.services(args[0] || $opt['default-environment']), 'svcinst')
648
+ when 'show', 'info'
649
+ env, svc = parse_svcinst(args[0])
650
+ record_formatted_output($cmdb.service(env, svc), 'svcinst')
651
+ when 'add'
652
+ env, svc = parse_svcinst(args.shift)
653
+ attrs = parsekvs(args)
654
+ $cmdb.create_service(env, svc, attrs)
655
+ nil
656
+ when 'remove'
657
+ env, svc = parse_svcinst(args.shift)
658
+ $cmdb.delete_service(env, svc)
659
+ when 'set'
660
+ env, svc = parse_svcinst(args.shift)
661
+ attrs = parsekvs(args)
662
+ $cmdb.update_service(env, svc, attrs)
663
+ nil
664
+ else
665
+ $stderr.puts "Unknown svcinst '#{command}'"
666
+ end
667
+ end
668
+
669
+ def outs(output)
670
+ unless [nil, true, false].include? output
671
+ puts output.to_s if (output.to_s and output.to_s.length != 0)
672
+ end
673
+ end
674
+
675
+ def dispatch_command(argv)
676
+ command = argv.shift
677
+ case command
678
+ when 'help'
679
+ $stderr.puts "Use noms --help"
680
+ Process.exit(1)
681
+ when 'describe', 'desc'
682
+ outs desc argv
683
+ when 'cmdb', 'inv'
684
+ outs cmdb argv
685
+ when 'svc', 'svcinst'
686
+ outs svcinst argv
687
+ when 'environment', 'env'
688
+ outs env argv
689
+ when 'instance'
690
+ outs instance argv
691
+ when 'waitfor'
692
+ waitfor argv
693
+ when nil
694
+ $stderr.puts "Usage:\n noms --help\n noms {desc|cmdb|instance|waitfor|...} [arg]"
695
+ Process.exit(1)
696
+ else
697
+ $stderr.puts "Unknown command '#{command}'"
698
+ Process.exit(1)
699
+ end
700
+ end
701
+
702
+ def parse_instance(s)
703
+ parse_qualified_name(s, $opt['default-cloud'])
704
+ end
705
+
706
+ def is_name(n)
707
+ true if /\./.match n
708
+ end
709
+
710
+ def instance(args)
711
+ $opt['fields'] = ['name'] if $opt['names']
712
+ cmd = args.shift
713
+ case cmd
714
+ when 'list', 'ls'
715
+ if args.length < 1
716
+ args.unshift $opt['default-cloud']
717
+ end
718
+ results = $ncc.list *args
719
+ formatted_output(results, 'instance')
720
+ when 'info', 'show'
721
+ if args.length == 2
722
+ cloud, id = *args
723
+ elsif args.length == 1
724
+ cloud, id = parse_instance(args.first)
725
+ else
726
+ fatal "Usage: noms instance show [cloud/]instance"
727
+ end
728
+ if is_name(id)
729
+ instance = $ncc.find_by_name cloud, id
730
+ else
731
+ instance = $ncc.instance cloud, id
732
+ end
733
+ if $opt['fields'].include? 'console'
734
+ console = $ncc.console cloud, instance['id']
735
+ instance['console'] = console['url'] if (console and console.has_key? 'url')
736
+ end
737
+ record_formatted_output instance
738
+ when 'add'
739
+ instanceopt, argv = extractkvs args
740
+ if argv.length == 0
741
+ cloud = $opt['default-cloud']
742
+ elsif argv.length == 1
743
+ cloud = argv.first
744
+ else
745
+ fatal "Usage: noms instance add [cloud] [param=value [...]]"
746
+ end
747
+ attrs = { :username => get_username, :size => 'm1.small' }
748
+ attrs.update(instanceopt.empty? ? { } : instanceopt)
749
+ dbg attrs.inspect
750
+ inst = $ncc.create(cloud, attrs)
751
+ record_formatted_output(inst) unless inst.nil?
752
+ when 'remove', 'rm'
753
+ if args.length == 2
754
+ cloud, id = *args
755
+ elsif args.length == 1
756
+ cloud, id = parse_instance(args.first)
757
+ else
758
+ fatal "Usage: noms instance remove [cloud.]instance"
759
+ end
760
+ fatal("No id to remove") if id.nil?
761
+ if is_name(id)
762
+ attrs = { :name => id }
763
+ else
764
+ attrs = { :id => id }
765
+ end
766
+ $ncc.delete(cloud, attrs)
767
+ else
768
+ $stderr.puts "Unknown instance command #{cmd}"
769
+ Process.exit(3)
770
+ end
771
+ end
772
+
773
+ dispatch_command ARGV
774
+