collins-cli 0.1.1 → 0.2.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 +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
|
-
|