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.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/Gemfile +4 -0
- data/LICENSE +191 -0
- data/README.rst +16 -0
- data/Rakefile +21 -0
- data/TODO +8 -0
- data/bin/ansible-cmdb +70 -0
- data/bin/noms +774 -0
- data/control.m4 +8 -0
- data/etc/noms.conf +11 -0
- data/lib/ncc/client.rb +108 -0
- data/lib/noms/client/version.rb +7 -0
- data/lib/noms/cmdb.rb +136 -0
- data/lib/noms/errors.rb +11 -0
- data/lib/noms/httpclient.rb +508 -0
- data/lib/noms/nagui.rb +57 -0
- data/lib/spec_helper.rb +28 -0
- data/noms-client.gemspec +26 -0
- data/spec/01nomscmdb_spec.rb +21 -0
- data/spec/02noms.sh +8 -0
- data/spec/03ncc_spec.rb +90 -0
- data/spec/04cmdb-mock_spec.rb +214 -0
- data/spec/05restmock-persist_spec.rb +83 -0
- data/spec/06noms-mock.sh +35 -0
- data/spec/07noms-admin.sh +91 -0
- metadata +148 -0
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
|
+
|