collins-cli 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +95 -36
- data/bin/collins +34 -15
- data/lib/collins/cli/find.rb +161 -0
- data/lib/collins/cli/formatter.rb +74 -0
- data/lib/collins/cli/ipam.rb +99 -0
- data/lib/collins/cli/log.rb +173 -0
- data/lib/collins/cli/mixins.rb +73 -0
- data/lib/collins/cli/modify.rb +144 -0
- data/lib/collins/cli/power.rb +77 -0
- data/lib/collins/cli/provision.rb +80 -0
- data/lib/collins-cli.rb +13 -0
- data/spec/collins__cli__find_spec.rb +48 -0
- data/spec/collins__cli__log_spec.rb +4 -0
- data/spec/collins__cli__modify_spec.rb +4 -0
- data/spec/collins__cli__power_spec.rb +4 -0
- data/spec/collins__cli__provision_spec.rb +4 -0
- data/spec/spec_helper.rb +2 -0
- metadata +52 -11
- data/bin/collins-action +0 -130
- data/bin/collins-find +0 -225
- data/bin/collins-log +0 -143
- data/bin/collins-modify +0 -149
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: collins-cli
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gabe Conradi
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-11-
|
11
|
+
date: 2014-11-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: colorize
|
@@ -52,25 +52,60 @@ dependencies:
|
|
52
52
|
- - ~>
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: 0.2.11
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 10.4.0
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 10.4.0
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ~>
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 3.1.0
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ~>
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 3.1.0
|
55
83
|
description: CLI utilities to interact with the Collins API
|
56
84
|
email:
|
57
85
|
- gabe@tumblr.com
|
58
86
|
- gummybearx@gmail.com
|
59
87
|
executables:
|
60
88
|
- collins
|
61
|
-
- collins-action
|
62
|
-
- collins-find
|
63
|
-
- collins-log
|
64
|
-
- collins-modify
|
65
89
|
extensions: []
|
66
90
|
extra_rdoc_files: []
|
67
91
|
files:
|
92
|
+
- lib/collins/cli/find.rb
|
93
|
+
- lib/collins/cli/formatter.rb
|
94
|
+
- lib/collins/cli/ipam.rb
|
95
|
+
- lib/collins/cli/log.rb
|
96
|
+
- lib/collins/cli/mixins.rb
|
97
|
+
- lib/collins/cli/modify.rb
|
98
|
+
- lib/collins/cli/power.rb
|
99
|
+
- lib/collins/cli/provision.rb
|
100
|
+
- lib/collins-cli.rb
|
68
101
|
- bin/collins
|
69
|
-
- bin/collins-action
|
70
|
-
- bin/collins-find
|
71
|
-
- bin/collins-log
|
72
|
-
- bin/collins-modify
|
73
102
|
- README.md
|
103
|
+
- spec/collins__cli__find_spec.rb
|
104
|
+
- spec/collins__cli__log_spec.rb
|
105
|
+
- spec/collins__cli__modify_spec.rb
|
106
|
+
- spec/collins__cli__power_spec.rb
|
107
|
+
- spec/collins__cli__provision_spec.rb
|
108
|
+
- spec/spec_helper.rb
|
74
109
|
homepage: http://github.com/byxorna/collins-cli
|
75
110
|
licenses:
|
76
111
|
- Apache License 2.0
|
@@ -95,4 +130,10 @@ rubygems_version: 2.0.14
|
|
95
130
|
signing_key:
|
96
131
|
specification_version: 4
|
97
132
|
summary: CLI utilities to interact with the Collins API
|
98
|
-
test_files:
|
133
|
+
test_files:
|
134
|
+
- spec/collins__cli__find_spec.rb
|
135
|
+
- spec/collins__cli__log_spec.rb
|
136
|
+
- spec/collins__cli__modify_spec.rb
|
137
|
+
- spec/collins__cli__power_spec.rb
|
138
|
+
- spec/collins__cli__provision_spec.rb
|
139
|
+
- spec/spec_helper.rb
|
data/bin/collins-action
DELETED
@@ -1,130 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
# collins-action
|
3
|
-
# provision and manage assets in collins easily from the CLI
|
4
|
-
|
5
|
-
require 'collins_auth'
|
6
|
-
require 'yaml'
|
7
|
-
require 'optparse'
|
8
|
-
require 'etc'
|
9
|
-
require 'colorize'
|
10
|
-
|
11
|
-
SUCCESS = "SUCCESS".colorize(:color => :green)
|
12
|
-
ERROR = "ERROR".colorize(:color => :red)
|
13
|
-
ALLOWABLE_POWER_ACTIONS = ['reboot','rebootsoft','reboothard','on','off','poweron','poweroff','identify']
|
14
|
-
options = {
|
15
|
-
:timeout => 120,
|
16
|
-
:build_contact => Etc.getlogin,
|
17
|
-
:provision => { }
|
18
|
-
}
|
19
|
-
|
20
|
-
basename = File.basename(File.realpath($0))
|
21
|
-
parser = OptionParser.new do |opts|
|
22
|
-
opts.banner = "Usage: #{basename} [options]"
|
23
|
-
#TODO -s to show provisoining_profiles
|
24
|
-
#TODO update IPMI stuff with ipmi_update
|
25
|
-
#TODO create IPMI with ipmi_create
|
26
|
-
#TODO implement IP allocation
|
27
|
-
opts.separator "Actions:"
|
28
|
-
opts.on('-P','--provision',"Provision assets (see Provisioning flags).") {|v| options[:mode] = :provision }
|
29
|
-
opts.on('-S','--power-status',"Show IPMI power status.") {|v| options[:mode] = :power_status }
|
30
|
-
opts.on('-A','--power-action ACTION',String,"Perform IPMI power ACTION on assets"){|v| options[:mode] = :power ; options[:power_action] = v}
|
31
|
-
|
32
|
-
opts.separator ""
|
33
|
-
opts.separator "Provisioning Flags:"
|
34
|
-
opts.on('-n','--nodeclass NODECLASS',String,"Nodeclass to provision as. (Required)") {|v| options[:provision][:nodeclass] = v }
|
35
|
-
opts.on('-p','--pool POOL',String,"Provision with pool POOL.") {|v| options[:provision][:pool] = v }
|
36
|
-
opts.on('-r','--role ROLE',String,"Provision with primary role ROLE.") {|v| options[:provision][:primary_role] = v }
|
37
|
-
opts.on('-R','--secondary-role ROLE',String,"Provision with secondary role ROLE.") {|v| options[:provision][:secondary_role] = v }
|
38
|
-
opts.on('-s','--suffix SUFFIX',String,"Provision with suffix SUFFIX.") {|v| options[:provision][:suffix] = v }
|
39
|
-
opts.on('-a','--activate',"Activate server on provision (useful with SL plugin) (Default: ignored)") {|v| options[:provision][:activate] = true }
|
40
|
-
opts.on('-b','--build-contact USER',String,"Build contact. (Default: #{options[:build_contact]})") {|v| options[:build_contact] = v }
|
41
|
-
|
42
|
-
opts.separator ""
|
43
|
-
opts.separator "General:"
|
44
|
-
opts.on('-t','--tags TAG[,...]',Array,"Tags to work on, comma separated") {|v| options[:tags] = v.map(&:to_sym)}
|
45
|
-
opts.on('-C','--config CONFIG',String,'Use specific Collins config yaml for Collins::Client') {|v| options[:config] = v}
|
46
|
-
opts.on('-h','--help',"Help") {puts opts ; exit 0}
|
47
|
-
|
48
|
-
opts.separator ""
|
49
|
-
opts.separator "Examples:"
|
50
|
-
opts.separator <<_EXAMPLES_
|
51
|
-
Provision some machines:
|
52
|
-
cf -Sunallocated -arack_position:716|#{basename} -P -napiwebnode6 -RALL
|
53
|
-
Show power status:
|
54
|
-
cf ^dev6-gabe|#{basename} -S
|
55
|
-
Power cycle a bunch of machines:
|
56
|
-
#{basename} -t 001234,004567,007890 -A reboot
|
57
|
-
_EXAMPLES_
|
58
|
-
end.parse!
|
59
|
-
|
60
|
-
if ARGV.size > 0
|
61
|
-
# anything else left in ARGV is garbage
|
62
|
-
puts "Not sure what I am supposed to do with these arguments: #{ARGV.join(' ')}"
|
63
|
-
puts parser
|
64
|
-
exit 1
|
65
|
-
end
|
66
|
-
|
67
|
-
|
68
|
-
abort "See --help for #{basename} usage" unless [:provision, :power_status, :power].include? options[:mode]
|
69
|
-
abort "You need to specify at least a nodeclass when provisioning" if options[:mode] == :provision && options[:provision][:nodeclass].nil?
|
70
|
-
if options[:mode] == :power
|
71
|
-
# convert what we allow to be specified to what collins::power allows
|
72
|
-
options[:power_action] = 'rebootsoft' if options[:power_action] == 'reboot'
|
73
|
-
abort "Unknown power action #{options[:power_action]}, expecting one of #{ALLOWABLE_POWER_ACTIONS.join(',')}" unless ALLOWABLE_POWER_ACTIONS.include? options[:power_action]
|
74
|
-
begin
|
75
|
-
options[:power_action] = Collins::Power.normalize_action options[:power_action]
|
76
|
-
rescue => e
|
77
|
-
abort "Unknown power action #{options[:power_action]}! #{e.message}"
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
if options[:tags].nil? or options[:tags].empty?
|
82
|
-
# read tags from stdin. first field on the line is the tag
|
83
|
-
input = ARGF.readlines
|
84
|
-
options[:tags] = input.map{|l| l.split(/\s+/)[0] rescue nil}.compact.uniq
|
85
|
-
end
|
86
|
-
|
87
|
-
begin
|
88
|
-
@collins = Collins::Authenticator.setup_client timeout: options[:timeout], config_file: options[:config], prompt: true
|
89
|
-
rescue => e
|
90
|
-
abort "Unable to set up Collins client! #{e.message}"
|
91
|
-
end
|
92
|
-
|
93
|
-
def api_call desc, method, *varargs
|
94
|
-
success,message = begin
|
95
|
-
[@collins.send(method,*varargs),nil]
|
96
|
-
rescue => e
|
97
|
-
[false,e.message]
|
98
|
-
end
|
99
|
-
puts "#{success ? SUCCESS : ERROR}: #{desc}#{message.nil? ? nil : " (%s)" % e.message}"
|
100
|
-
success
|
101
|
-
end
|
102
|
-
|
103
|
-
action_successes = []
|
104
|
-
options[:tags].each do |t|
|
105
|
-
case options[:mode]
|
106
|
-
when :provision
|
107
|
-
action_string = "#{t} provisioning with #{options[:provision].map{|k,v| "#{k}:#{v}"}.join(" ")} by #{options[:build_contact]}... "
|
108
|
-
printf action_string
|
109
|
-
begin
|
110
|
-
res = @collins.provision(t, options[:provision][:nodeclass], options[:build_contact], options[:provision])
|
111
|
-
puts (res ? SUCCESS : ERROR )
|
112
|
-
action_successes << res
|
113
|
-
rescue => e
|
114
|
-
puts "#{ERROR} (#{e.message})"
|
115
|
-
action_successes << false
|
116
|
-
end
|
117
|
-
when :power_status
|
118
|
-
begin
|
119
|
-
s = @collins.power_status(t)
|
120
|
-
puts "#{SUCCESS}: #{t} power status is #{s}"
|
121
|
-
rescue => e
|
122
|
-
puts "#{ERROR}: Unable to query power status for #{t}#{e.message.nil? ? nil : " (%s)" % e.message}"
|
123
|
-
end
|
124
|
-
when :power
|
125
|
-
action_successes << api_call("#{t} performing #{options[:power_action]}", :power!, t, options[:power_action])
|
126
|
-
end
|
127
|
-
end
|
128
|
-
|
129
|
-
exit action_successes.all? ? 0 : 1
|
130
|
-
|
data/bin/collins-find
DELETED
@@ -1,225 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
# stands for collins-find
|
3
|
-
# look up hosts quickly from collins from the CLI
|
4
|
-
|
5
|
-
require 'collins_auth'
|
6
|
-
require 'yaml'
|
7
|
-
require 'json'
|
8
|
-
require 'optparse'
|
9
|
-
|
10
|
-
#TODO: querying for :status or :state with -a status:maintenance doesnt play nice with -Smaintenance,allocated
|
11
|
-
#TODO: we construct the query for states and statuses only from the parameters to --status (ignoring any -a attributes)
|
12
|
-
#TODO: add a formatting option to display the assets in a different way (not a table)
|
13
|
-
|
14
|
-
# this gets stuffed into the collins find call
|
15
|
-
query_opts = {
|
16
|
-
:operation => 'AND',
|
17
|
-
:size => 9999,
|
18
|
-
}
|
19
|
-
# these are all the attributes we search for
|
20
|
-
# may be converted into a CQL query
|
21
|
-
search_attrs = { }
|
22
|
-
options = {
|
23
|
-
:display => :table, # how to display the results
|
24
|
-
:separator => "\t",
|
25
|
-
:attributes => {}, # additional attributes to query for
|
26
|
-
:columns => [:tag, :hostname, :nodeclass, :status, :pool, :primary_role, :secondary_role],
|
27
|
-
:column_override => [], # if set, these are the columns to display
|
28
|
-
:timeout => 120,
|
29
|
-
:show_header => false, # if the header for columns should be displayed
|
30
|
-
:config => nil # collins config to give to setup_client
|
31
|
-
}
|
32
|
-
|
33
|
-
|
34
|
-
basename = File.basename(File.realpath($0))
|
35
|
-
abort "See --help for #{basename} usage" if ARGV.empty?
|
36
|
-
OptionParser.new do |opts|
|
37
|
-
opts.banner = "Usage: #{basename} [options] [hostnamepattern]"
|
38
|
-
opts.separator "Query options:"
|
39
|
-
opts.on('-t','--tag TAG[,...]',Array, "Assets with tag[s] TAG") {|v| search_attrs[:tag] = v}
|
40
|
-
opts.on('-T','--type TYPE',String, "Only show assets with type TYPE") {|v| search_attrs[:type] = v}
|
41
|
-
opts.on('-n','--nodeclass NODECLASS[,...]',Array, "Assets in nodeclass NODECLASS") {|v| search_attrs[:nodeclass] = v}
|
42
|
-
opts.on('-p','--pool POOL[,...]',Array, "Assets in pool POOL") {|v| search_attrs[:pool] = v}
|
43
|
-
opts.on('-s','--size SIZE',Integer, "Number of assets to return (Default: #{query_opts[:size]})") {|v| query_opts[:size] = v}
|
44
|
-
opts.on('-r','--role ROLE[,...]',Array,"Assets in primary role ROLE") {|v| search_attrs[:primary_role] = v}
|
45
|
-
opts.on('-R','--secondary-role ROLE[,...]',Array,"Assets in secondary role ROLE") {|v| search_attrs[:secondary_role] = v}
|
46
|
-
opts.on('-i','--ip-address IP[,...]',Array,"Assets with IP address[es]") {|v| search_attrs[:ip_address] = v}
|
47
|
-
opts.on('-S','--status STATUS[:STATE][,...]',Array,"Asset status (and optional state after :)") do |v|
|
48
|
-
# in order to know what state was paired with what status, lets store the original params
|
49
|
-
# so the query constructor can create the correct CQL query
|
50
|
-
options[:status_state] = v
|
51
|
-
search_attrs[:status], search_attrs[:state] = v.inject([[],[]]) do |memo,s|
|
52
|
-
status,state = s.split(':')
|
53
|
-
memo[0] << status.upcase if not status.nil? and not status.empty?
|
54
|
-
memo[1] << state.upcase if not state.nil? and not state.empty?
|
55
|
-
memo
|
56
|
-
end
|
57
|
-
end
|
58
|
-
opts.on('-a','--attribute attribute[:value[,...]]',String,"Arbitrary attributes and values to match in query. : between key and value") do |x|
|
59
|
-
x.split(',').each do |p|
|
60
|
-
a,v = p.split(':')
|
61
|
-
a = a.to_sym
|
62
|
-
if not search_attrs[a].nil? and not search_attrs[a].is_a? Array
|
63
|
-
# its a single value, turn it into an array
|
64
|
-
search_attrs[a] = [search_attrs[a]]
|
65
|
-
end
|
66
|
-
if search_attrs[a].is_a? Array
|
67
|
-
# already multivalue, append
|
68
|
-
search_attrs[a] << v
|
69
|
-
else
|
70
|
-
search_attrs[a] = v
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
opts.separator ""
|
76
|
-
opts.separator "Table formatting:"
|
77
|
-
opts.on('-H','--show-header',"Show header fields in output") {options[:show_header] = true}
|
78
|
-
opts.on('-c','--columns ATTRIBUTES',Array,"Attributes to output as columns, comma separated (Default: #{options[:columns].map(&:to_s).join(',')})") {|v| options[:column_override] = v.map(&:to_sym)}
|
79
|
-
opts.on('-x','--extra-columns ATTRIBUTES',Array,"Show these columns in addition to the default columns, comma separated") {|v| options[:columns].push(v.map(&:to_sym)).flatten! }
|
80
|
-
opts.on('-f','--field-separator SEPARATOR',String,"Separator between columns in output (Default: #{options[:separator]})") {|v| options[:separator] = v}
|
81
|
-
|
82
|
-
opts.separator ""
|
83
|
-
opts.separator "Robot formatting:"
|
84
|
-
opts.on('-l','--link',"Output link to assets found in web UI") {options[:display] = :link}
|
85
|
-
opts.on('-j','--json',"Output results in JSON (NOTE: This probably wont be what you expected)") {options[:display] = :json}
|
86
|
-
opts.on('-y','--yaml',"Output results in YAML") {options[:display] = :yaml}
|
87
|
-
|
88
|
-
opts.separator ""
|
89
|
-
opts.separator "Extra options:"
|
90
|
-
opts.on('--expire SECONDS',Integer,"Timeout in seconds (0 == forever)") {|v| options[:timeout] = v}
|
91
|
-
opts.on('-C','--config CONFIG',String,'Use specific Collins config yaml for Collins::Client') {|v| options[:config] = v}
|
92
|
-
opts.on('-h','--help',"Help") {puts opts ; exit 0}
|
93
|
-
|
94
|
-
opts.separator ""
|
95
|
-
opts.separator <<_EXAMPLES_
|
96
|
-
Examples:
|
97
|
-
Query for devnodes in DEVEL pool that are VMs
|
98
|
-
cf -n develnode -p DEVEL
|
99
|
-
Query for asset 001234, and show its system_password
|
100
|
-
cf -t 001234 -x system_password
|
101
|
-
Query for all decommissioned VM assets
|
102
|
-
cf -a is_vm:true -S decommissioned
|
103
|
-
Query for hosts matching hostname '^web6-'
|
104
|
-
cf ^web6-
|
105
|
-
Query for all develnode6 nodes with a value for PUPPET_SERVER
|
106
|
-
cf -n develnode6 -a puppet_server -H
|
107
|
-
_EXAMPLES_
|
108
|
-
end.parse!
|
109
|
-
|
110
|
-
# hostname is the final option, no flags
|
111
|
-
search_attrs[:hostname] = ARGV.shift
|
112
|
-
# fix bug where assets wont get found if they dont have that meta attribute
|
113
|
-
search_attrs.delete(:hostname) if search_attrs[:hostname].nil?
|
114
|
-
|
115
|
-
# if nothing passed to us, lets not search for EVERYTHING
|
116
|
-
#abort "You need to query for _something_, see --help" if
|
117
|
-
# selector_keys.all? {|key| options[key].nil?} and options[:attributes].empty?
|
118
|
-
|
119
|
-
# for any search attributes, lets not pass arrays of 1 element
|
120
|
-
# as that will confuse as_query?
|
121
|
-
search_attrs.each do |k,v|
|
122
|
-
if v.is_a? Array
|
123
|
-
search_attrs[k] = v.first if v.length == 1
|
124
|
-
search_attrs[k] = nil if v.empty?
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
def as_query?(attrs)
|
129
|
-
attrs.any?{|k,v| v.is_a? Array}
|
130
|
-
end
|
131
|
-
|
132
|
-
def convert_to_query(op, attrs, options)
|
133
|
-
# we want to support being able to query -Smaintenance:noop,:running,:provisioning_problem
|
134
|
-
# and not have the states ored together. Handle status/state pairs separately
|
135
|
-
basic_query = attrs.reject {|k,v| [:status,:state].include?(k)}.map do |k,v|
|
136
|
-
next if v.nil?
|
137
|
-
if v.is_a? Array
|
138
|
-
"(" + v.map{|x| "#{k} = #{x}"}.join(' OR ') + ")"
|
139
|
-
else
|
140
|
-
"#{k} = #{v}"
|
141
|
-
end
|
142
|
-
end.compact.join(" #{op} ")
|
143
|
-
# because they are provided in pairs, lets handle them together
|
144
|
-
# create the (( STATUS = maintenance AND STATE = noop) OR (STATE = provisioning_problem)) query
|
145
|
-
if options[:status_state]
|
146
|
-
status_query = options[:status_state].flat_map do |ss|
|
147
|
-
h = {}
|
148
|
-
h[:status], h[:state] = ss.split(':')
|
149
|
-
h[:status] = nil if h[:status].nil? or h[:status].empty?
|
150
|
-
h[:state] = nil if h[:state].nil? or h[:state].empty?
|
151
|
-
"( " + h.map {|k,v| v.nil? ? nil : "#{k.to_s.upcase} = #{v}"}.compact.join(" AND ") + " )"
|
152
|
-
end.compact.join(' OR ')
|
153
|
-
status_query = "( #{status_query} )"
|
154
|
-
end
|
155
|
-
[basic_query,status_query].reject {|q| q.nil? or q.empty?}.join(" #{op} ")
|
156
|
-
end
|
157
|
-
|
158
|
-
def display_as_robot_talk(assets, format = :json)
|
159
|
-
puts assets.send("to_#{format}".to_sym)
|
160
|
-
end
|
161
|
-
def display_as_table(assets, columns, separator, show_header = false)
|
162
|
-
# lets figure out how wide each column is, including header
|
163
|
-
column_width_pairs = columns.map do |column|
|
164
|
-
# grab all attributes == column and figure out max width
|
165
|
-
width = assets.map{|a| (column == :state) ? a.send(column).label.to_s.length : a.send(column).to_s.length}.max
|
166
|
-
width = [width, column.to_s.length].max if show_header
|
167
|
-
[column,width]
|
168
|
-
end
|
169
|
-
column_width_map = Hash[column_width_pairs]
|
170
|
-
|
171
|
-
if show_header
|
172
|
-
$stderr.puts column_width_map.map{|c,w| "%-#{w}s" % c}.join(separator)
|
173
|
-
end
|
174
|
-
assets.each do |a|
|
175
|
-
puts column_width_map.map {|c,w| v = (c == :state) ? a.send(c).label : a.send(c) ; "%-#{w}s" % v }.join(separator)
|
176
|
-
end
|
177
|
-
end
|
178
|
-
def display_as_link assets, client
|
179
|
-
assets.each do |a|
|
180
|
-
puts "#{client.host}/asset/#{a.tag}"
|
181
|
-
end
|
182
|
-
end
|
183
|
-
|
184
|
-
# merge search_attrs into query
|
185
|
-
if as_query?(search_attrs)
|
186
|
-
query_opts[:query] = convert_to_query(query_opts[:operation], search_attrs, options)
|
187
|
-
#puts "Query: #{query_opts[:query]}"
|
188
|
-
else
|
189
|
-
query_opts.merge!(search_attrs)
|
190
|
-
end
|
191
|
-
|
192
|
-
begin
|
193
|
-
collins = Collins::Authenticator.setup_client timeout: options[:timeout], config_file: options[:config], prompt: true
|
194
|
-
rescue => e
|
195
|
-
abort "Unable to set up Collins client! #{e.message}"
|
196
|
-
end
|
197
|
-
|
198
|
-
begin
|
199
|
-
assets = collins.find(query_opts)
|
200
|
-
if assets.length > 0
|
201
|
-
case options[:display]
|
202
|
-
when :table
|
203
|
-
# if the user passed :column_override, respect that absolutely. otherwise, the columns to display
|
204
|
-
# should be options[:columns] + any extra attributes queried for. this way ```cf -c hostname -a is_vm:true```
|
205
|
-
# wont return 2 columns; only the one you asked for
|
206
|
-
columns = if options[:column_override].empty?
|
207
|
-
options[:columns].concat(search_attrs.keys).compact.uniq
|
208
|
-
else
|
209
|
-
options[:column_override]
|
210
|
-
end
|
211
|
-
display_as_table(assets,columns,options[:separator],options[:show_header])
|
212
|
-
when :link
|
213
|
-
display_as_link assets, collins
|
214
|
-
when :json,:yaml
|
215
|
-
display_as_robot_talk(assets,options[:display])
|
216
|
-
else
|
217
|
-
abort "I don't know how to display assets in #{options[:display]} format!"
|
218
|
-
end
|
219
|
-
else
|
220
|
-
abort "No assets found"
|
221
|
-
end
|
222
|
-
rescue => e
|
223
|
-
abort "Error querying collins: #{e.message}"
|
224
|
-
end
|
225
|
-
|
data/bin/collins-log
DELETED
@@ -1,143 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
# stands for collins-log
|
3
|
-
# looks at logs on assets
|
4
|
-
|
5
|
-
# TODO: make poll_wait tunable
|
6
|
-
# TODO: add options to sort ascending or descending on date
|
7
|
-
# TODO: implement searching logs (is this really useful?)
|
8
|
-
# TODO: add duplicate line detection and compression (...)
|
9
|
-
|
10
|
-
require 'collins_auth'
|
11
|
-
require 'yaml'
|
12
|
-
require 'optparse'
|
13
|
-
require 'colorize'
|
14
|
-
require 'set'
|
15
|
-
|
16
|
-
log_levels = Collins::Api::Logging::Severity.constants.map(&:to_s)
|
17
|
-
|
18
|
-
@options = {
|
19
|
-
:tags => [],
|
20
|
-
:show_all => false,
|
21
|
-
:poll_wait => 2,
|
22
|
-
:follow => false,
|
23
|
-
:severities => [],
|
24
|
-
:timeout => 20,
|
25
|
-
:sev_colors => {
|
26
|
-
'EMERGENCY' => {:color => :red, :background => :light_blue},
|
27
|
-
'ALERT' => {:color => :red},
|
28
|
-
'CRITICAL' => {:color => :black, :background => :red},
|
29
|
-
'ERROR' => {:color => :red},
|
30
|
-
'WARNING' => {:color => :yellow},
|
31
|
-
'NOTICE' => {},
|
32
|
-
'INFORMATIONAL' => {:color => :green},
|
33
|
-
'DEBUG' => {:color => :blue},
|
34
|
-
'NOTE' => {:color => :light_cyan},
|
35
|
-
},
|
36
|
-
:config => nil
|
37
|
-
}
|
38
|
-
@search_opts = {
|
39
|
-
:size => 20,
|
40
|
-
:filter => nil,
|
41
|
-
}
|
42
|
-
|
43
|
-
basename = File.basename(File.realpath($0))
|
44
|
-
abort "See --help for #{basename} usage" if ARGV.empty?
|
45
|
-
OptionParser.new do |opts|
|
46
|
-
opts.banner = "Usage: #{basename} [options]"
|
47
|
-
opts.on('-a','--all',"Show logs from ALL assets") {|v| @options[:show_all] = true}
|
48
|
-
opts.on('-n','--number LINES',Integer,"Show the last LINES log entries. (Default: #{@search_opts[:size]})") {|v| @search_opts[:size] = v}
|
49
|
-
opts.on('-t','--tags TAGS',Array,"Tags to work on, comma separated") {|v| @options[:tags] = v}
|
50
|
-
opts.on('-f','--follow',"Poll for logs every #{@options[:poll_wait]} seconds") {|v| @options[:follow] = true}
|
51
|
-
opts.on('-s','--severity SEVERITY[,...]',Array,"Log severities to return (Defaults to all). Use !SEVERITY to exclude one.") {|v| @options[:severities] = v.map(&:upcase) }
|
52
|
-
#opts.on('-i','--interleave',"Interleave all log entries (Default: groups by asset)") {|v| options[:interleave] = true}
|
53
|
-
opts.on('-C','--config CONFIG',String,'Use specific Collins config yaml for Collins::Client') {|v| @options[:config] = v}
|
54
|
-
opts.on('-h','--help',"Help") {puts opts ; exit 0}
|
55
|
-
opts.separator ""
|
56
|
-
opts.separator <<_EOE_
|
57
|
-
Severities:
|
58
|
-
#{Collins::Api::Logging::Severity.to_a.map{|s| s.colorize(@options[:sev_colors][s])}.join(", ")}
|
59
|
-
|
60
|
-
Examples:
|
61
|
-
Show last 20 logs for an asset
|
62
|
-
#{basename} -t 001234
|
63
|
-
Show last 100 logs for an asset
|
64
|
-
#{basename} -t 001234 -n100
|
65
|
-
Show last 10 logs for 2 assets that are ERROR severity
|
66
|
-
#{basename} -t 001234,001235 -n10 -sERROR
|
67
|
-
Show last 10 logs all assets that are not note or informational severity
|
68
|
-
#{basename} -a -n10 -s'!informational,!note'
|
69
|
-
Show last 10 logs for all web nodes that are provisioned having verification in the message
|
70
|
-
cf -S provisioned -n webnode\$ | #{basename} -n10 -s debug | grep -i verification
|
71
|
-
_EOE_
|
72
|
-
end.parse!
|
73
|
-
|
74
|
-
|
75
|
-
abort "Log severities #{@options[:severities].join(',')} are invalid! Use one of #{log_levels.join(', ')}" unless @options[:severities].all? {|l| Collins::Api::Logging::Severity.valid?(l.tr('!','')) }
|
76
|
-
@search_opts[:filter] = @options[:severities].join(';')
|
77
|
-
|
78
|
-
if @options[:tags].empty? and not @options[:show_all]
|
79
|
-
# read tags from stdin. first field on the line is the tag
|
80
|
-
input = ARGF.readlines
|
81
|
-
@options[:tags] = input.map{|l| l.split(/\s+/)[0] rescue nil}.compact.uniq
|
82
|
-
end
|
83
|
-
|
84
|
-
abort "You need to give me some assets to display logs; see --help" if @options[:tags].empty? and not @options[:show_all]
|
85
|
-
|
86
|
-
begin
|
87
|
-
@collins = Collins::Authenticator.setup_client timeout: @options[:timeout], config_file: @options[:config], prompt: true
|
88
|
-
rescue => e
|
89
|
-
abort "Unable to set up Collins client! #{e.message}"
|
90
|
-
end
|
91
|
-
|
92
|
-
def output_logs(logs)
|
93
|
-
# colorize output before computing width of fields
|
94
|
-
logs.map! do |l|
|
95
|
-
l.TYPE = @options[:sev_colors].has_key?(l.TYPE) ? l.TYPE.colorize(@options[:sev_colors][l.TYPE]) : l.TYPE
|
96
|
-
l
|
97
|
-
end
|
98
|
-
# show newest last
|
99
|
-
sorted_logs = logs.sort_by {|l| l.CREATED }
|
100
|
-
tag_width = sorted_logs.map{|l| l.ASSET_TAG.length}.max
|
101
|
-
sev_width = sorted_logs.map{|l| l.TYPE.length}.max
|
102
|
-
time_width = sorted_logs.map{|l| l.CREATED.length}.max
|
103
|
-
sorted_logs.each do |l|
|
104
|
-
puts "%-#{time_width}s: %-#{sev_width}s %-#{tag_width}s %s" % [l.CREATED, l.TYPE, l.ASSET_TAG, l.MESSAGE]
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
def grab_logs
|
109
|
-
if @options[:tags].empty?
|
110
|
-
begin
|
111
|
-
@collins.all_logs(@search_opts)
|
112
|
-
rescue => e
|
113
|
-
$stderr.puts "Unable to fetch logs:".colorize(@options[:sev_colors]['WARNING']) + " #{e.message}"
|
114
|
-
[]
|
115
|
-
end
|
116
|
-
else
|
117
|
-
@options[:tags].flat_map do |t|
|
118
|
-
begin
|
119
|
-
@collins.logs(t, @search_opts)
|
120
|
-
rescue => e
|
121
|
-
$stderr.puts "Unable to fetch logs for #{t}:".colorize(@options[:sev_colors]['WARNING']) + " #{e.message}"
|
122
|
-
[]
|
123
|
-
end
|
124
|
-
end
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
begin
|
129
|
-
all_logs = grab_logs
|
130
|
-
logs_seen = all_logs.map(&:ID).to_set
|
131
|
-
output_logs(all_logs)
|
132
|
-
while @options[:follow]
|
133
|
-
sleep @options[:poll_wait]
|
134
|
-
logs = grab_logs
|
135
|
-
new_logs = logs.reject {|l| logs_seen.include?(l.ID)}
|
136
|
-
output_logs(new_logs)
|
137
|
-
logs_seen = logs_seen | new_logs.map(&:ID)
|
138
|
-
end
|
139
|
-
rescue Interrupt => e
|
140
|
-
exit 0
|
141
|
-
end
|
142
|
-
|
143
|
-
|