collins-cli 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +173 -0
- data/bin/collins +21 -0
- data/bin/collins-action +130 -0
- data/bin/collins-find +225 -0
- data/bin/collins-log +143 -0
- data/bin/collins-modify +149 -0
- metadata +98 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 087cbec35df74ca5deb78665d0d40547483968d3
|
4
|
+
data.tar.gz: dcc8cc251bc58b3e6ca6df9881de6eeffef00caf
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5d3b4860eede427118996a3740233e494df10b0bc46c94a2394f01d78fb1d20b3796a528d8353900bb8774f655f7bf9e86d24594c331b7fa56ac22b71471b319
|
7
|
+
data.tar.gz: 19aa32592ffcf6cac34565ae87ca37bcd35c7ecc182138c2464bcceba65898258ac25fb11853f3d29fb627fe16c589fe5824bba081c7e60e6e70ff7857a6a4a8
|
data/README.md
ADDED
@@ -0,0 +1,173 @@
|
|
1
|
+
collins-cli
|
2
|
+
===========
|
3
|
+
|
4
|
+
CLI scripts for interacting with Collins API
|
5
|
+
|
6
|
+
[![Gem Version](https://badge.fury.io/rb/collins-cli.svg)](http://badge.fury.io/rb/collins-cli)
|
7
|
+
|
8
|
+
## Overview
|
9
|
+
|
10
|
+
Main entry point is the ```collins``` binary.
|
11
|
+
|
12
|
+
Usage: collins <command> [options]
|
13
|
+
Available commands:
|
14
|
+
query, find: Search for assets in Collins
|
15
|
+
modify, set: Add and remove attributes, change statuses, and log to assets
|
16
|
+
log: Display log messages on assets
|
17
|
+
provision, action: Provision, control power status, allocate IPs, update IPMI info
|
18
|
+
|
19
|
+
## Searching - collins-find
|
20
|
+
|
21
|
+
Usage: collins-find [options] [hostnamepattern]
|
22
|
+
Query options:
|
23
|
+
-t, --tag TAG[,...] Assets with tag[s] TAG
|
24
|
+
-T, --type TYPE Only show assets with type TYPE
|
25
|
+
-n, --nodeclass NODECLASS[,...] Assets in nodeclass NODECLASS
|
26
|
+
-p, --pool POOL[,...] Assets in pool POOL
|
27
|
+
-s, --size SIZE Number of assets to return (Default: 9999)
|
28
|
+
-r, --role ROLE[,...] Assets in primary role ROLE
|
29
|
+
-R, --secondary-role ROLE[,...] Assets in secondary role ROLE
|
30
|
+
-i, --ip-address IP[,...] Assets with IP address[es]
|
31
|
+
-S STATUS[:STATE][,...], Asset status (and optional state after :)
|
32
|
+
--status
|
33
|
+
-a attribute[:value[,...]], Arbitrary attributes and values to match in query. : between key and value
|
34
|
+
--attribute
|
35
|
+
|
36
|
+
Table formatting:
|
37
|
+
-H, --show-header Show header fields in output
|
38
|
+
-c, --columns ATTRIBUTES Attributes to output as columns, comma separated (Default: tag,hostname,nodeclass,status,pool,primary_role,secondary_role)
|
39
|
+
-x, --extra-columns ATTRIBUTES Show these columns in addition to the default columns, comma separated
|
40
|
+
-f, --field-separator SEPARATOR Separator between columns in output (Default: )
|
41
|
+
|
42
|
+
Robot formatting:
|
43
|
+
-l, --link Output link to assets found in web UI
|
44
|
+
-j, --json Output results in JSON (NOTE: This probably wont be what you expected)
|
45
|
+
-y, --yaml Output results in YAML
|
46
|
+
|
47
|
+
Extra options:
|
48
|
+
--expire SECONDS Timeout in seconds (0 == forever)
|
49
|
+
-C, --config CONFIG Use specific Collins config yaml for Collins::Client
|
50
|
+
-h, --help Help
|
51
|
+
|
52
|
+
Examples:
|
53
|
+
Query for devnodes in DEVEL pool that are VMs
|
54
|
+
cf -n develnode -p DEVEL
|
55
|
+
Query for asset 001234, and show its system_password
|
56
|
+
cf -t 001234 -x system_password
|
57
|
+
Query for all decommissioned VM assets
|
58
|
+
cf -a is_vm:true -S decommissioned
|
59
|
+
Query for hosts matching hostname '^web6-'
|
60
|
+
cf ^web6-
|
61
|
+
Query for all develnode6 nodes with a value for PUPPET_SERVER
|
62
|
+
cf -n develnode6 -a puppet_server -H
|
63
|
+
|
64
|
+
## Logging - collins-log
|
65
|
+
|
66
|
+
Usage: collins-log [options]
|
67
|
+
-a, --all Show logs from ALL assets
|
68
|
+
-n, --number LINES Show the last LINES log entries. (Default: 20)
|
69
|
+
-t, --tags TAGS Tags to work on, comma separated
|
70
|
+
-f, --follow Poll for logs every 2 seconds
|
71
|
+
-s, --severity SEVERITY[,...] Log severities to return (Defaults to all). Use !SEVERITY to exclude one.
|
72
|
+
-C, --config CONFIG Use specific Collins config yaml for Collins::Client
|
73
|
+
-h, --help Help
|
74
|
+
|
75
|
+
Severities:
|
76
|
+
EMERGENCY, ALERT, CRITICAL, ERROR, WARNING, NOTICE, INFORMATIONAL, DEBUG, NOTE
|
77
|
+
|
78
|
+
Examples:
|
79
|
+
Show last 20 logs for an asset
|
80
|
+
collins-log -t 001234
|
81
|
+
Show last 100 logs for an asset
|
82
|
+
collins-log -t 001234 -n100
|
83
|
+
Show last 10 logs for 2 assets that are ERROR severity
|
84
|
+
collins-log -t 001234,001235 -n10 -sERROR
|
85
|
+
Show last 10 logs all assets that are not note or informational severity
|
86
|
+
collins-log -a -n10 -s'!informational,!note'
|
87
|
+
Show last 10 logs for all web nodes that are provisioned having verification in the message
|
88
|
+
cf -S provisioned -n webnode$ | collins-log -n10 -s debug | grep -i verification
|
89
|
+
|
90
|
+
## Modification - collins-modify
|
91
|
+
|
92
|
+
Usage: collins-modify [options]
|
93
|
+
-a attribute:value, Set attribute=value. : between key and value. attribute will be uppercased.
|
94
|
+
--set-attribute
|
95
|
+
-d, --delete-attribute attribute Delete attribute.
|
96
|
+
-S, --set-status status[:state] Set status (and optionally state) to status:state. Requires --reason
|
97
|
+
-r, --reason REASON Reason for changing status/state.
|
98
|
+
-l, --log MESSAGE Create a log entry.
|
99
|
+
-L, --level LEVEL Set log level. Default level is NOTE.
|
100
|
+
-t, --tags TAGS Tags to work on, comma separated
|
101
|
+
-C, --config CONFIG Use specific Collins config yaml for Collins::Client
|
102
|
+
-h, --help Help
|
103
|
+
|
104
|
+
Allowed values (uppercase or lowercase is accepted):
|
105
|
+
Status (-S,--set-status):
|
106
|
+
ALLOCATED, CANCELLED, DECOMMISSIONED, INCOMPLETE, MAINTENANCE, NEW, PROVISIONED, PROVISIONING, UNALLOCATED
|
107
|
+
States (-S,--set-status):
|
108
|
+
ALLOCATED ->
|
109
|
+
CLAIMED, SPARE, RUNNING_UNMONITORED, UNMONITORED
|
110
|
+
MAINTENANCE ->
|
111
|
+
AWAITING_REVIEW, HARDWARE_PROBLEM, HW_TESTING, HARDWARE_UPGRADE, IPMI_PROBLEM, MAINT_NOOP, NETWORK_PROBLEM, RELOCATION, PROVISIONING_PROBLEM
|
112
|
+
ANY ->
|
113
|
+
RUNNING, STARTING, STOPPING, TERMINATED
|
114
|
+
Log levels (-L,--level):
|
115
|
+
EMERGENCY, ALERT, CRITICAL, ERROR, WARNING, NOTICE, INFORMATIONAL, DEBUG, NOTE
|
116
|
+
|
117
|
+
Examples:
|
118
|
+
Set an attribute on some hosts:
|
119
|
+
collins-modify -t 001234,004567 -a my_attribute:true
|
120
|
+
Delete an attribute on some hosts:
|
121
|
+
collins-modify -t 001234,004567 -d my_attribute
|
122
|
+
Delete and add attribute at same time:
|
123
|
+
collins-modify -t 001234,004567 -a new_attr:test -d old_attr
|
124
|
+
Set machine into maintenace noop:
|
125
|
+
collins-modify -t 001234 -S maintenance:maint_noop -r "I do what I want"
|
126
|
+
Set machine back to allocated:
|
127
|
+
collins-modify -t 001234 -S allocated:running -r "Back to allocated"
|
128
|
+
Set machine back to new without setting state:
|
129
|
+
collins-modify -t 001234 -S new -r "Dunno why you would want this"
|
130
|
+
Create a log entry:
|
131
|
+
collins-modify -t 001234 -l'computers are broken and everything is horrible' -Lwarning
|
132
|
+
Read from stdin:
|
133
|
+
cf -n develnode | collins-modify -d my_attribute
|
134
|
+
cf -n develnode -S allocated | collins-modify -a collectd_version:5.2.1-52
|
135
|
+
echo -e "001234\n001235\n001236"| collins-modify -a test_attribute:'hello world'
|
136
|
+
|
137
|
+
## Actions - collins-action
|
138
|
+
|
139
|
+
Usage: collins-action [options]
|
140
|
+
Actions:
|
141
|
+
-P, --provision Provision assets (see Provisioning flags).
|
142
|
+
-S, --power-status Show IPMI power status.
|
143
|
+
-A, --power-action ACTION Perform IPMI power ACTION on assets
|
144
|
+
|
145
|
+
Provisioning Flags:
|
146
|
+
-n, --nodeclass NODECLASS Nodeclass to provision as. (Required)
|
147
|
+
-p, --pool POOL Provision with pool POOL.
|
148
|
+
-r, --role ROLE Provision with primary role ROLE.
|
149
|
+
-R, --secondary-role ROLE Provision with secondary role ROLE.
|
150
|
+
-s, --suffix SUFFIX Provision with suffix SUFFIX.
|
151
|
+
-a, --activate Activate server on provision (useful with SL plugin) (Default: ignored)
|
152
|
+
-b, --build-contact USER Build contact. (Default: gabe)
|
153
|
+
|
154
|
+
General:
|
155
|
+
-t, --tags TAG[,...] Tags to work on, comma separated
|
156
|
+
-C, --config CONFIG Use specific Collins config yaml for Collins::Client
|
157
|
+
-h, --help Help
|
158
|
+
|
159
|
+
Examples:
|
160
|
+
Provision some machines:
|
161
|
+
cf -Sunallocated -arack_position:716|collins-action -P -napiwebnode6 -RALL
|
162
|
+
Show power status:
|
163
|
+
cf ^dev6-gabe|collins-action -S
|
164
|
+
Power cycle a bunch of machines:
|
165
|
+
collins-action -t 001234,004567,007890 -A reboot
|
166
|
+
|
167
|
+
## TODO
|
168
|
+
|
169
|
+
I know the architecture is BRUTAL. This was all organically created; I need to refactor stuff out into libraries to facilitate code sharing between the utilities
|
170
|
+
|
171
|
+
* Implement IP allocation in collins-action
|
172
|
+
* Implement IPMI stuff in collins-action
|
173
|
+
* Share code between binaries
|
data/bin/collins
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
ALLOWED_ACTIONS = {
|
4
|
+
:find => ['query','find'],
|
5
|
+
:modify => ['modify','set'],
|
6
|
+
:log => ['log'],
|
7
|
+
:action => ['provision','action']
|
8
|
+
}
|
9
|
+
action = ARGV.shift
|
10
|
+
target,_ = ALLOWED_ACTIONS.select {|k,v| v.any? {|handle| ! %r|^#{action}|.match(handle).nil? } }.first
|
11
|
+
if action.nil? || target.nil?
|
12
|
+
abort <<_MESSAGE_
|
13
|
+
Usage: #{File.basename(File.realpath($0))} <command> [options]
|
14
|
+
Available commands:
|
15
|
+
query, find: Search for assets in Collins
|
16
|
+
modify, set: Add and remove attributes, change statuses, and log to assets
|
17
|
+
log: Display log messages on assets
|
18
|
+
provision, action: Provision, control power status, allocate IPs, update IPMI info
|
19
|
+
_MESSAGE_
|
20
|
+
end
|
21
|
+
exec File.join(__dir__,"collins-#{target}"), *ARGV
|
data/bin/collins-action
ADDED
@@ -0,0 +1,130 @@
|
|
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
ADDED
@@ -0,0 +1,225 @@
|
|
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
ADDED
@@ -0,0 +1,143 @@
|
|
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
|
+
|
data/bin/collins-modify
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# stands for collins-modify
|
3
|
+
# perform actions on asset attribtues in collins easily
|
4
|
+
|
5
|
+
require 'collins_auth'
|
6
|
+
require 'yaml'
|
7
|
+
require 'optparse'
|
8
|
+
|
9
|
+
SUCCESS = "SUCCESS"
|
10
|
+
ERROR = "ERROR"
|
11
|
+
VALID_STATUSES = ["ALLOCATED","CANCELLED","DECOMMISSIONED","INCOMPLETE","MAINTENANCE","NEW","PROVISIONED","PROVISIONING","UNALLOCATED"]
|
12
|
+
#TODO: this shouldnt be hardcoded. we should pull this from the API instead?
|
13
|
+
# should elegantly support user-defined states without changing this script
|
14
|
+
VALID_STATES = {
|
15
|
+
"ALLOCATED" => ["CLAIMED","SPARE","RUNNING_UNMONITORED","UNMONITORED"],
|
16
|
+
"MAINTENANCE" => ["AWAITING_REVIEW","HARDWARE_PROBLEM","HW_TESTING","HARDWARE_UPGRADE","IPMI_PROBLEM","MAINT_NOOP","NETWORK_PROBLEM","RELOCATION",'PROVISIONING_PROBLEM'],
|
17
|
+
"ANY" => ["RUNNING","STARTING","STOPPING","TERMINATED"],
|
18
|
+
}
|
19
|
+
log_levels = Collins::Api::Logging::Severity.constants.map(&:to_s)
|
20
|
+
|
21
|
+
options = {
|
22
|
+
:query_size => 9999,
|
23
|
+
:attributes => {},
|
24
|
+
:delete_attributes => [],
|
25
|
+
:log_level => 'NOTE',
|
26
|
+
:timeout => 120,
|
27
|
+
:config => nil
|
28
|
+
}
|
29
|
+
basename = File.basename(File.realpath($0))
|
30
|
+
parser = OptionParser.new do |opts|
|
31
|
+
opts.banner = "Usage: #{basename} [options]"
|
32
|
+
opts.on('-a','--set-attribute attribute:value',String,"Set attribute=value. : between key and value. attribute will be uppercased.") do |x|
|
33
|
+
if not x.include? ':'
|
34
|
+
puts '--set-attribute requires attribute:value, missing :value'
|
35
|
+
puts opts.help
|
36
|
+
exit 1
|
37
|
+
end
|
38
|
+
a,v = x.split(':')
|
39
|
+
options[:attributes][a.upcase.to_sym] = v
|
40
|
+
end
|
41
|
+
opts.on('-d','--delete-attribute attribute',String,"Delete attribute.") {|v| options[:delete_attributes] << v.to_sym }
|
42
|
+
opts.on('-S','--set-status status[:state]',String,'Set status (and optionally state) to status:state. Requires --reason') do |v|
|
43
|
+
status,state = v.split(':')
|
44
|
+
options[:status] = status.upcase if not status.nil? and not status.empty?
|
45
|
+
options[:state] = state.upcase if not state.nil? and not state.empty?
|
46
|
+
end
|
47
|
+
opts.on('-r','--reason REASON',String,"Reason for changing status/state.") {|v| options[:reason] = v }
|
48
|
+
opts.on('-l','--log MESSAGE',String,"Create a log entry.") do |v|
|
49
|
+
options[:log_message] = v
|
50
|
+
end
|
51
|
+
opts.on('-L','--level LEVEL',String, log_levels + log_levels.map(&:downcase),"Set log level. Default level is #{options[:log_level]}.") do |v|
|
52
|
+
options[:log_level] = v.upcase
|
53
|
+
end
|
54
|
+
opts.on('-t','--tags TAGS',Array,"Tags to work on, comma separated") {|v| options[:tags] = v.map(&:to_sym)}
|
55
|
+
opts.on('-C','--config CONFIG',String,'Use specific Collins config yaml for Collins::Client') {|v| options[:config] = v}
|
56
|
+
opts.on('-h','--help',"Help") {puts opts ; exit 0}
|
57
|
+
opts.separator ""
|
58
|
+
opts.separator "Allowed values (uppercase or lowercase is accepted):"
|
59
|
+
opts.separator <<_EOF_
|
60
|
+
Status (-S,--set-status):
|
61
|
+
#{VALID_STATUSES.join(', ')}
|
62
|
+
States (-S,--set-status):
|
63
|
+
#{VALID_STATES.keys.map {|k| "#{k} ->\n #{VALID_STATES[k].join(', ')}"}.join "\n "}
|
64
|
+
Log levels (-L,--level):
|
65
|
+
#{log_levels.join(', ')}
|
66
|
+
_EOF_
|
67
|
+
opts.separator ""
|
68
|
+
opts.separator "Examples:"
|
69
|
+
opts.separator <<_EOF_
|
70
|
+
Set an attribute on some hosts:
|
71
|
+
#{basename} -t 001234,004567 -a my_attribute:true
|
72
|
+
Delete an attribute on some hosts:
|
73
|
+
#{basename} -t 001234,004567 -d my_attribute
|
74
|
+
Delete and add attribute at same time:
|
75
|
+
#{basename} -t 001234,004567 -a new_attr:test -d old_attr
|
76
|
+
Set machine into maintenace noop:
|
77
|
+
#{basename} -t 001234 -S maintenance:maint_noop -r "I do what I want"
|
78
|
+
Set machine back to allocated:
|
79
|
+
#{basename} -t 001234 -S allocated:running -r "Back to allocated"
|
80
|
+
Set machine back to new without setting state:
|
81
|
+
#{basename} -t 001234 -S new -r "Dunno why you would want this"
|
82
|
+
Create a log entry:
|
83
|
+
#{basename} -t 001234 -l'computers are broken and everything is horrible' -Lwarning
|
84
|
+
Read from stdin:
|
85
|
+
cf -n develnode | #{basename} -d my_attribute
|
86
|
+
cf -n develnode -S allocated | #{basename} -a collectd_version:5.2.1-52
|
87
|
+
echo -e "001234\\n001235\\n001236"| #{basename} -a test_attribute:'hello world'
|
88
|
+
_EOF_
|
89
|
+
end.parse!
|
90
|
+
|
91
|
+
if ARGV.size > 0
|
92
|
+
# anything else left in ARGV is garbage
|
93
|
+
puts "Not sure what I am supposed to do with these arguments: #{ARGV.join(' ')}"
|
94
|
+
puts parser
|
95
|
+
exit 1
|
96
|
+
end
|
97
|
+
|
98
|
+
abort "See --help for #{basename} usage" if options[:attributes].empty? and options[:delete_attributes].empty? and options[:status].nil? and options[:log_message].nil?
|
99
|
+
abort "You need to provide a --reason when changing asset states!" if not options[:status].nil? and options[:reason].nil?
|
100
|
+
#TODO this is never checked because we are making option parser vet our options for levels. Catch OptionParser::InvalidArgument?
|
101
|
+
abort "Log level #{options[:log_level]} is invalid! Use one of #{log_levels.join(', ')}" unless Collins::Api::Logging::Severity.valid?(options[:log_level])
|
102
|
+
|
103
|
+
# if any statuses or states, validate them against allowed values
|
104
|
+
unless options[:status].nil?
|
105
|
+
abort "Invalid status #{options[:status]} (Should be in #{VALID_STATUSES.join(', ')})" unless VALID_STATUSES.include? options[:status]
|
106
|
+
states_for_status = VALID_STATES["ANY"].concat((VALID_STATES[options[:status]].nil?) ? [] : VALID_STATES[options[:status]])
|
107
|
+
abort "State #{options[:state]} doesn't apply to status #{options[:status]} (Should be one of #{states_for_status.join(', ')})" unless options[:state].nil? or states_for_status.include?(options[:state])
|
108
|
+
end
|
109
|
+
|
110
|
+
if options[:tags].nil? or options[:tags].empty?
|
111
|
+
# read tags from stdin. first field on the line is the tag
|
112
|
+
input = ARGF.readlines
|
113
|
+
options[:tags] = input.map{|l| l.split(/\s+/)[0] rescue nil}.compact.uniq
|
114
|
+
end
|
115
|
+
|
116
|
+
begin
|
117
|
+
@collins = Collins::Authenticator.setup_client timeout: options[:timeout], config_file: options[:config], prompt: true
|
118
|
+
rescue => e
|
119
|
+
abort "Unable to set up Collins client! #{e.message}"
|
120
|
+
end
|
121
|
+
|
122
|
+
def api_call desc, method, *varargs
|
123
|
+
success,message = begin
|
124
|
+
[@collins.send(method,*varargs),nil]
|
125
|
+
rescue => e
|
126
|
+
[false,e.message]
|
127
|
+
end
|
128
|
+
puts "#{success ? SUCCESS : ERROR}: #{desc}#{message.nil? ? nil : " (%s)" % e.message}"
|
129
|
+
success
|
130
|
+
end
|
131
|
+
|
132
|
+
exit_clean = true
|
133
|
+
options[:tags].each do |t|
|
134
|
+
if options[:log_message]
|
135
|
+
exit_clean = api_call("#{t} create #{options[:log_level].downcase} log #{options[:log_message].inspect}", :log!, t, options[:log_message], options[:log_level]) && exit_clean
|
136
|
+
end
|
137
|
+
options[:attributes].each do |k,v|
|
138
|
+
exit_clean = api_call("#{t} set #{k}=#{v}", :set_attribute!, t, k, v) && exit_clean
|
139
|
+
end
|
140
|
+
options[:delete_attributes].each do |k|
|
141
|
+
exit_clean = api_call("#{t} delete #{k}", :delete_attribute!, t, k) && exit_clean
|
142
|
+
end
|
143
|
+
if options[:status]
|
144
|
+
exit_clean = api_call("#{t} set status to #{options[:status]}#{options[:state] ? ":#{options[:state]}" : ''}", :set_status!, t, :status => options[:status], :state => options[:state], :reason => options[:reason]) && exit_clean
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
exit exit_clean ? 0 : 1
|
149
|
+
|
metadata
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: collins-cli
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Gabe Conradi
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-11-26 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: colorize
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.7.3
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.7.3
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: collins_auth
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.1.2
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.1.2
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: collins_client
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.2.11
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.2.11
|
55
|
+
description: CLI utilities to interact with the Collins API
|
56
|
+
email:
|
57
|
+
- gabe@tumblr.com
|
58
|
+
- gummybearx@gmail.com
|
59
|
+
executables:
|
60
|
+
- collins
|
61
|
+
- collins-action
|
62
|
+
- collins-find
|
63
|
+
- collins-log
|
64
|
+
- collins-modify
|
65
|
+
extensions: []
|
66
|
+
extra_rdoc_files: []
|
67
|
+
files:
|
68
|
+
- bin/collins
|
69
|
+
- bin/collins-action
|
70
|
+
- bin/collins-find
|
71
|
+
- bin/collins-log
|
72
|
+
- bin/collins-modify
|
73
|
+
- README.md
|
74
|
+
homepage: http://github.com/byxorna/collins-cli
|
75
|
+
licenses:
|
76
|
+
- Apache License 2.0
|
77
|
+
metadata: {}
|
78
|
+
post_install_message:
|
79
|
+
rdoc_options: []
|
80
|
+
require_paths:
|
81
|
+
- lib
|
82
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
83
|
+
requirements:
|
84
|
+
- - '>='
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: 1.9.2
|
87
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - '>='
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '0'
|
92
|
+
requirements: []
|
93
|
+
rubyforge_project:
|
94
|
+
rubygems_version: 2.0.14
|
95
|
+
signing_key:
|
96
|
+
specification_version: 4
|
97
|
+
summary: CLI utilities to interact with the Collins API
|
98
|
+
test_files: []
|