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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 087cbec35df74ca5deb78665d0d40547483968d3
4
- data.tar.gz: dcc8cc251bc58b3e6ca6df9881de6eeffef00caf
3
+ metadata.gz: 9d8cb9285558a8adc49d7da521c0241e1331eb08
4
+ data.tar.gz: 27559afb065f378b3e60aa010eb3271e40e53222
5
5
  SHA512:
6
- metadata.gz: 5d3b4860eede427118996a3740233e494df10b0bc46c94a2394f01d78fb1d20b3796a528d8353900bb8774f655f7bf9e86d24594c331b7fa56ac22b71471b319
7
- data.tar.gz: 19aa32592ffcf6cac34565ae87ca37bcd35c7ecc182138c2464bcceba65898258ac25fb11853f3d29fb627fe16c589fe5824bba081c7e60e6e70ff7857a6a4a8
6
+ metadata.gz: b96eda4857aaed7c4a92a704306ff7158f322f3708e24cb66fd15a9665977827d6620fec9244fff7496bf8217445144695d1058e935522504de95fc00f8d50f1
7
+ data.tar.gz: 050ca2a0dfddb04dd02c798bcd58fad07078e5445e37f0fb8337360928fb95ea2c7f3518fd293acbeec5da10011296b119b1b4516992adad21f8ec0e8e5b5b1a
data/README.md CHANGED
@@ -7,18 +7,34 @@ CLI scripts for interacting with Collins API
7
7
 
8
8
  ## Overview
9
9
 
10
- Main entry point is the ```collins``` binary.
10
+ ```collins-cli``` uses the ```collins_auth``` gem for authentication, so it relies on you either typing in your credentials every time, or setting up a ~/.collins.yml file. The base format for the config file is as follows:
11
11
 
12
+ ---
13
+ host: https://collins.iata.company.com
14
+ username: myuser
15
+ # omit password to have collins auth prompt you
16
+ password: mypass
17
+
18
+ (see https://github.com/tumblr/collins/tree/master/support/ruby/collins-auth for more details)
19
+
20
+ Main entry point is the ```collins``` binary:
21
+
22
+ $ collins -h
12
23
  Usage: collins <command> [options]
13
24
  Available commands:
14
25
  query, find: Search for assets in Collins
15
26
  modify, set: Add and remove attributes, change statuses, and log to assets
16
27
  log: Display log messages on assets
17
- provision, action: Provision, control power status, allocate IPs, update IPMI info
28
+ provision: Provision assets
29
+ power: Control and show power status
30
+ ip, address, ipmi: Allocate IPs, update IPMI info
18
31
 
19
- ## Searching - collins-find
32
+ ## Find Assets - collins find
20
33
 
21
- Usage: collins-find [options] [hostnamepattern]
34
+ Use ```collins find``` to quickly construct complex queries of your assets in Collins. Bonus points for piping the output of ```collins find``` into another program.
35
+
36
+ $ collins find -h
37
+ Usage: collins find [options] [hostnamepattern]
22
38
  Query options:
23
39
  -t, --tag TAG[,...] Assets with tag[s] TAG
24
40
  -T, --type TYPE Only show assets with type TYPE
@@ -61,7 +77,9 @@ Main entry point is the ```collins``` binary.
61
77
  Query for all develnode6 nodes with a value for PUPPET_SERVER
62
78
  cf -n develnode6 -a puppet_server -H
63
79
 
64
- ## Logging - collins-log
80
+ ## View Logs - collins log
81
+
82
+ Pipe the output of ```collins find``` into ```collins log``` to pull recent logs, or tail logs. Very useful while watching provisioning. Reads asset tags from ARGF if ```--tags``` aren't provided.
65
83
 
66
84
  Usage: collins-log [options]
67
85
  -a, --all Show logs from ALL assets
@@ -87,9 +105,11 @@ Main entry point is the ```collins``` binary.
87
105
  Show last 10 logs for all web nodes that are provisioned having verification in the message
88
106
  cf -S provisioned -n webnode$ | collins-log -n10 -s debug | grep -i verification
89
107
 
90
- ## Modification - collins-modify
108
+ ## Modification - collins modify
91
109
 
92
- Usage: collins-modify [options]
110
+ Pipe the output of ```collins find``` into ```collins modify``` to change statuses, create and delete attributes, write log messages, etc. Reads asset tags from ARGF if ```--tags``` aren't provided.
111
+
112
+ Usage: collins modify [options]
93
113
  -a attribute:value, Set attribute=value. : between key and value. attribute will be uppercased.
94
114
  --set-attribute
95
115
  -d, --delete-attribute attribute Delete attribute.
@@ -116,33 +136,31 @@ Main entry point is the ```collins``` binary.
116
136
 
117
137
  Examples:
118
138
  Set an attribute on some hosts:
119
- collins-modify -t 001234,004567 -a my_attribute:true
139
+ collins modify -t 001234,004567 -a my_attribute:true
120
140
  Delete an attribute on some hosts:
121
- collins-modify -t 001234,004567 -d my_attribute
141
+ collins modify -t 001234,004567 -d my_attribute
122
142
  Delete and add attribute at same time:
123
- collins-modify -t 001234,004567 -a new_attr:test -d old_attr
143
+ collins modify -t 001234,004567 -a new_attr:test -d old_attr
124
144
  Set machine into maintenace noop:
125
- collins-modify -t 001234 -S maintenance:maint_noop -r "I do what I want"
145
+ collins modify -t 001234 -S maintenance:maint_noop -r "I do what I want"
126
146
  Set machine back to allocated:
127
- collins-modify -t 001234 -S allocated:running -r "Back to allocated"
147
+ collins modify -t 001234 -S allocated:running -r "Back to allocated"
128
148
  Set machine back to new without setting state:
129
- collins-modify -t 001234 -S new -r "Dunno why you would want this"
149
+ collins modify -t 001234 -S new -r "Dunno why you would want this"
130
150
  Create a log entry:
131
- collins-modify -t 001234 -l'computers are broken and everything is horrible' -Lwarning
151
+ collins modify -t 001234 -l'computers are broken and everything is horrible' -Lwarning
132
152
  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'
153
+ cf -n develnode | collins modify -d my_attribute
154
+ cf -n develnode -S allocated | collins modify -a collectd_version:5.2.1-52
155
+ echo -e "001234\n001235\n001236"| collins modify -a test_attribute:'hello world'
136
156
 
137
- ## Actions - collins-action
157
+ ## Provision - collins provision
158
+
159
+ Pipe the output of ```collins find``` into ```collins provision``` to provision assets. Reads asset tags from ARGF if ```--tags``` aren't provided.
160
+
161
+ $ collins provision -h
162
+ Usage: collins provision [options]
138
163
 
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
164
  -n, --nodeclass NODECLASS Nodeclass to provision as. (Required)
147
165
  -p, --pool POOL Provision with pool POOL.
148
166
  -r, --role ROLE Provision with primary role ROLE.
@@ -150,6 +168,25 @@ Main entry point is the ```collins``` binary.
150
168
  -s, --suffix SUFFIX Provision with suffix SUFFIX.
151
169
  -a, --activate Activate server on provision (useful with SL plugin) (Default: ignored)
152
170
  -b, --build-contact USER Build contact. (Default: gabe)
171
+
172
+ General:
173
+ -t, --tags TAG[,...] Tags to work on, comma separated
174
+ -C, --config CONFIG Use specific Collins config yaml for Collins::Client
175
+ -h, --help Help
176
+
177
+ Examples:
178
+ Provision some machines:
179
+ collins find -Sunallocated -arack_position:716|collins provision -P -napiwebnode6 -RALL
180
+
181
+ ## Power Management - collins power
182
+
183
+ Manage and show power states with ```collins power```
184
+
185
+ $ collins power -h
186
+ Usage: collins power [options]
187
+
188
+ -s, --status Show IPMI power status
189
+ -p, --power ACTION Perform IPMI power ACTION
153
190
 
154
191
  General:
155
192
  -t, --tags TAG[,...] Tags to work on, comma separated
@@ -157,17 +194,39 @@ Main entry point is the ```collins``` binary.
157
194
  -h, --help Help
158
195
 
159
196
  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
197
+ Reset some machines:
198
+ collins power -t 001234,003456,007895 -p reboot
166
199
 
167
- ## TODO
200
+ ## IPAM - collins ip
201
+
202
+ Allocate and delete addresses, and show what address pools are configured in Collins.
168
203
 
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
204
+ Usage: collins ipam [options]
205
+
206
+ -s, --show-pools Show IP pools
207
+ -H, --show-header Show header fields in --show-pools output
208
+ -a, --allocate POOL Allocate addresses in POOL
209
+ -n, --number [NUM] Allocate NUM addresses (Defaults to 1 if omitted)
210
+ -d, --delete [POOL] Delete addresses in POOL. Deletes ALL addresses if POOL is omitted
211
+
212
+ General:
213
+ -t, --tags TAG[,...] Tags to work on, comma separated
214
+ -C, --config CONFIG Use specific Collins config yaml for Collins::Client
215
+ -h, --help Help
216
+
217
+ Examples:
218
+ Show configured IP address pools:
219
+ collins ipam --show-pools -H
220
+ Allocate 2 IPs on each asset
221
+ collins ipam -t 001234,003456,007895 -a DEV_POOL -n2
222
+ Deallocate IPs in DEV_POOL pool on assets:
223
+ collins ipam -t 001234,003456,007895 -d DEV_POOL
224
+ Deallocate ALL IPs on assets:
225
+ collins ipam -t 001234,003456,007895 -d
226
+
227
+ ## TODO
170
228
 
171
- * Implement IP allocation in collins-action
172
- * Implement IPMI stuff in collins-action
173
- * Share code between binaries
229
+ * Implement IP allocation in collins-ipam
230
+ * Implement IPMI stuff in collins-ipmi
231
+ * Share code between binaries more
232
+ * Write some tests
data/bin/collins CHANGED
@@ -1,21 +1,40 @@
1
1
  #!/usr/bin/env ruby
2
+ require 'collins-cli'
2
3
 
3
4
  ALLOWED_ACTIONS = {
4
- :find => ['query','find'],
5
- :modify => ['modify','set'],
6
- :log => ['log'],
7
- :action => ['provision','action']
5
+ Collins::CLI::Find => ['query','find'],
6
+ Collins::CLI::Modify => ['modify','set'],
7
+ Collins::CLI::Log => ['log'],
8
+ Collins::CLI::Provision => ['provision'],
9
+ Collins::CLI::Power => ['power'],
10
+ Collins::CLI::IPAM => ['ipam','address','ipaddress'],
8
11
  }
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]
12
+
13
+ HELP_MESSAGE = "Usage: #{File.basename(File.realpath($0))} <command> [options]
14
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_
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: Provision assets
19
+ power: Control and show power status
20
+ ipam, address, ipaddress: Allocate and delete IPs, show IP pools"
21
+
22
+ abort HELP_MESSAGE if ARGV.empty?
23
+ action = ARGV.shift
24
+ targets = ALLOWED_ACTIONS.select {|k,v| v.any? {|handle| ! %r|^#{action}|.match(handle).nil? } }
25
+ target,_ = targets.first
26
+ if ['-h','--help'].include? action
27
+ puts HELP_MESSAGE
28
+ exit 0
29
+ elsif targets.empty? or target.nil?
30
+ abort ["Unknown action #{action}!","",HELP_MESSAGE].join("\n")
31
+ elsif targets.length > 1
32
+ abort ["Action #{action} was ambiguous! Please be more specific (i.e. type the whole action out)","",HELP_MESSAGE].join("\n")
33
+ end
34
+
35
+ begin
36
+ exit target.new.parse!.validate!.run!
37
+ rescue => e
38
+ abort e.message
39
+ #raise e
20
40
  end
21
- exec File.join(__dir__,"collins-#{target}"), *ARGV
@@ -0,0 +1,161 @@
1
+ require 'collins-cli'
2
+
3
+ #TODO: querying for :status or :state with -a status:maintenance doesnt play nice with -Smaintenance,allocated
4
+ #TODO: we construct the query for states and statuses only from the parameters to --status (ignoring any -a attributes)
5
+
6
+ module Collins::CLI
7
+ class Find
8
+ include Mixins
9
+ include Formatter # how to display assets
10
+
11
+ PROG_NAME = 'collins find'
12
+ QUERY_DEFAULTS = {
13
+ :operation => 'AND',
14
+ :size => 9999,
15
+ }
16
+ OPTION_DEFAULTS = {
17
+ :format => :table, # how to display the results
18
+ :separator => "\t",
19
+ :attributes => {}, # additional attributes to query for
20
+ :columns => [:tag, :hostname, :nodeclass, :status, :pool, :primary_role, :secondary_role],
21
+ :column_override => [], # if set, these are the columns to display
22
+ :timeout => 120,
23
+ :show_header => false, # if the header for columns should be displayed
24
+ :config => nil # collins config to give to setup_client
25
+ }
26
+
27
+ attr_reader :options, :query_opts, :search_attrs, :parser
28
+
29
+ def initialize
30
+ @parsed, @validated = false, false
31
+ @query_opts = QUERY_DEFAULTS.clone
32
+ @search_attrs = {}
33
+ @options = OPTION_DEFAULTS.clone
34
+ @parser = nil
35
+ end
36
+
37
+ def parse!(argv = ARGV)
38
+ raise "See --help for #{PROG_NAME} usage" if argv.empty?
39
+ @parser = OptionParser.new do |opts|
40
+ opts.banner = "Usage: #{PROG_NAME} [options] [hostnamepattern]"
41
+ opts.separator "Query options:"
42
+ opts.on('-t','--tag TAG[,...]',Array, "Assets with tag[s] TAG") {|v| search_attrs[:tag] = v}
43
+ opts.on('-T','--type TYPE',String, "Only show assets with type TYPE") {|v| search_attrs[:type] = v}
44
+ opts.on('-n','--nodeclass NODECLASS[,...]',Array, "Assets in nodeclass NODECLASS") {|v| search_attrs[:nodeclass] = v}
45
+ opts.on('-p','--pool POOL[,...]',Array, "Assets in pool POOL") {|v| search_attrs[:pool] = v}
46
+ opts.on('-s','--size SIZE',Integer, "Number of assets to return (Default: #{query_opts[:size]})") {|v| query_opts[:size] = v}
47
+ opts.on('-r','--role ROLE[,...]',Array,"Assets in primary role ROLE") {|v| search_attrs[:primary_role] = v}
48
+ opts.on('-R','--secondary-role ROLE[,...]',Array,"Assets in secondary role ROLE") {|v| search_attrs[:secondary_role] = v}
49
+ opts.on('-i','--ip-address IP[,...]',Array,"Assets with IP address[es]") {|v| search_attrs[:ip_address] = v}
50
+ opts.on('-S','--status STATUS[:STATE][,...]',Array,"Asset status (and optional state after :)") do |v|
51
+ # in order to know what state was paired with what status, lets store the original params
52
+ # so the query constructor can create the correct CQL query
53
+ options[:status_state] = v
54
+ search_attrs[:status], search_attrs[:state] = v.inject([[],[]]) do |memo,s|
55
+ status,state = s.split(':')
56
+ memo[0] << status.upcase if not status.nil? and not status.empty?
57
+ memo[1] << state.upcase if not state.nil? and not state.empty?
58
+ memo
59
+ end
60
+ end
61
+ opts.on('-a','--attribute attribute[:value[,...]]',String,"Arbitrary attributes and values to match in query. : between key and value") do |x|
62
+ x.split(',').each do |p|
63
+ a,v = p.split(':')
64
+ a = a.to_sym
65
+ if not search_attrs[a].nil? and not search_attrs[a].is_a? Array
66
+ # its a single value, turn it into an array
67
+ search_attrs[a] = [search_attrs[a]]
68
+ end
69
+ if search_attrs[a].is_a? Array
70
+ # already multivalue, append
71
+ search_attrs[a] << v
72
+ else
73
+ search_attrs[a] = v
74
+ end
75
+ end
76
+ end
77
+
78
+ opts.separator ""
79
+ opts.separator "Table formatting:"
80
+ opts.on('-H','--show-header',"Show header fields in output") {options[:show_header] = true}
81
+ 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)}
82
+ 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! }
83
+ opts.on('-f','--field-separator SEPARATOR',String,"Separator between columns in output (Default: #{options[:separator]})") {|v| options[:separator] = v}
84
+
85
+ opts.separator ""
86
+ opts.separator "Robot formatting:"
87
+ opts.on('-l','--link',"Output link to assets found in web UI") {options[:format] = :link}
88
+ opts.on('-j','--json',"Output results in JSON (NOTE: This probably wont be what you expected)") {options[:format] = :json}
89
+ opts.on('-y','--yaml',"Output results in YAML") {options[:format] = :yaml}
90
+
91
+ opts.separator ""
92
+ opts.separator "Extra options:"
93
+ opts.on('--expire SECONDS',Integer,"Timeout in seconds (0 == forever)") {|v| options[:timeout] = v}
94
+ opts.on('-C','--config CONFIG',String,'Use specific Collins config yaml for Collins::Client') {|v| options[:config] = v}
95
+ opts.on('-h','--help',"Help") {options[:mode] = :help}
96
+
97
+ opts.separator ""
98
+ opts.separator <<_EXAMPLES_
99
+ Examples:
100
+ Query for devnodes in DEVEL pool that are VMs
101
+ cf -n develnode -p DEVEL
102
+ Query for asset 001234, and show its system_password
103
+ cf -t 001234 -x system_password
104
+ Query for all decommissioned VM assets
105
+ cf -a is_vm:true -S decommissioned
106
+ Query for hosts matching hostname '^web6-'
107
+ cf ^web6-
108
+ Query for all develnode6 nodes with a value for PUPPET_SERVER
109
+ cf -n develnode6 -a puppet_server -H
110
+ _EXAMPLES_
111
+ end
112
+ @parser.parse!(argv)
113
+ # hostname is the final option, no flags
114
+ search_attrs[:hostname] = argv.shift
115
+ @parsed = true
116
+ self
117
+ end
118
+
119
+ def validate!
120
+ raise "Options not yet parsed with #parse!" unless @parsed
121
+ # fix bug where assets wont get found if they dont have that meta attribute
122
+ search_attrs.delete(:hostname) if search_attrs[:hostname].nil?
123
+ # for any search attributes, lets not pass arrays of 1 element
124
+ # as that will confuse as_query?
125
+ search_attrs.each do |k,v|
126
+ if v.is_a? Array
127
+ search_attrs[k] = v.first if v.length == 1
128
+ search_attrs[k] = nil if v.empty?
129
+ end
130
+ end
131
+
132
+ # merge search_attrs into query
133
+ if as_query?(search_attrs)
134
+ query_opts[:query] = convert_to_query(query_opts[:operation], search_attrs, options)
135
+ else
136
+ query_opts.merge!(search_attrs)
137
+ end
138
+ @validated = true
139
+ self
140
+ end
141
+
142
+ def run!
143
+ raise "Options not yet parsed with #parse!" unless @parsed
144
+ raise "Options not yet validated with #validate!" unless @validated
145
+ if options[:mode] == :help
146
+ puts parser
147
+ else
148
+ begin
149
+ assets = collins.find(query_opts)
150
+ rescue => e
151
+ raise "Error querying collins: #{e.message}"
152
+ end
153
+ format_assets(assets, options)
154
+ end
155
+ true
156
+ end
157
+
158
+ end
159
+ end
160
+
161
+
@@ -0,0 +1,74 @@
1
+ require 'collins-cli'
2
+
3
+ module Collins::CLI::Formatter
4
+ FORMATTING_DEFAULTS = {
5
+ :format => :table, # how to display the results
6
+ :separator => "\t",
7
+ :columns => [:tag, :hostname, :nodeclass, :status, :pool, :primary_role, :secondary_role],
8
+ :column_override => [], # if set, these are the columns to display
9
+ :show_header => false, # if the header for columns should be displayed
10
+ }
11
+ ADDRESS_POOL_COLUMNS = [:name, :network, :start_address, :specified_gateway, :gateway, :broadcast, :possible_addresses]
12
+
13
+ def format_pools(pools, opts = {})
14
+ if pools.length > 0
15
+ opts = FORMATTING_DEFAULTS.merge(opts)
16
+ # map the hashes into openstructs that will respond to #send(:name)
17
+ ostructs = pools.map { |p| OpenStruct.new(Hash[p.map {|k,v| [k.downcase,v]}]) }
18
+ display_as_table(ostructs, ADDRESS_POOL_COLUMNS, opts[:separator], opts[:show_header])
19
+ else
20
+ raise "No pools found"
21
+ end
22
+ end
23
+
24
+ def format_assets(assets, opts = {})
25
+ opts = FORMATTING_DEFAULTS.merge(opts)
26
+ if assets.length > 0
27
+ case opts[:format]
28
+ when :table
29
+ # if the user passed :column_override, respect that absolutely. otherwise, the columns to display
30
+ # should be opts[:columns] + any extra attributes queried for. this way ```cf -c hostname -a is_vm:true```
31
+ # wont return 2 columns; only the one you asked for
32
+ columns = if opts[:column_override].empty?
33
+ opts[:columns].concat(search_attrs.keys).compact.uniq
34
+ else
35
+ opts[:column_override]
36
+ end
37
+ display_as_table(assets,columns,opts[:separator],opts[:show_header])
38
+ when :link
39
+ display_as_link assets, collins
40
+ when :json,:yaml
41
+ display_as_robot_talk(assets,opts[:format])
42
+ else
43
+ raise "I don't know how to display assets in #{opts[:format]} format!"
44
+ end
45
+ else
46
+ raise "No assets found"
47
+ end
48
+ end
49
+
50
+ def display_as_robot_talk(assets, format = :json)
51
+ puts assets.send("to_#{format}".to_sym)
52
+ end
53
+ def display_as_table(assets, columns, separator, show_header = false)
54
+ # lets figure out how wide each column is, including header
55
+ column_width_pairs = columns.map do |column|
56
+ # grab all attributes == column and figure out max width
57
+ width = assets.map{|a| (column == :state) ? a.send(column).label.to_s.length : a.send(column).to_s.length}.max
58
+ width = [width, column.to_s.length].max if show_header
59
+ [column,width]
60
+ end
61
+ column_width_map = Hash[column_width_pairs]
62
+ if show_header
63
+ $stderr.puts column_width_map.map{|c,w| "%-#{w}s" % c}.join(separator)
64
+ end
65
+ assets.each do |a|
66
+ puts column_width_map.map {|c,w| v = (c == :state) ? a.send(c).label : a.send(c) ; "%-#{w}s" % v }.join(separator)
67
+ end
68
+ end
69
+ def display_as_link assets, client
70
+ assets.each do |a|
71
+ puts "#{client.host}/asset/#{a.tag}"
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,99 @@
1
+ require 'collins-cli'
2
+
3
+ module Collins::CLI
4
+ class IPAM
5
+ include Mixins
6
+ include Formatter
7
+ PROG_NAME = 'collins ipam'
8
+
9
+ DEFAULT_OPTIONS = {
10
+ :timeout => 120,
11
+ :mode => nil,
12
+ :show_header => false,
13
+ :num => 1,
14
+ :tags => [],
15
+ }
16
+
17
+ attr_reader :options, :parser
18
+
19
+ def initialize
20
+ @options = DEFAULT_OPTIONS.clone
21
+ @parsed, @validated = false, false
22
+ @parser = nil
23
+ end
24
+
25
+ def parse!(argv = ARGV)
26
+ @parser = OptionParser.new do |opts|
27
+ opts.banner = "Usage: #{PROG_NAME} [options]"
28
+ opts.separator ""
29
+ opts.on('-s','--show-pools',"Show IP pools") {|v| @options[:mode] = :show }
30
+ opts.on('-H','--show-header',"Show header fields in --show-pools output") {|v| @options[:show_header] = true }
31
+ opts.on('-a','--allocate POOL',String,"Allocate addresses in POOL") {|v| @options[:mode] = :allocate ; @options[:pool] = v }
32
+ opts.on('-n','--number [NUM]',Integer,"Allocate NUM addresses (Defaults to 1 if omitted)") {|v| @options[:num] = v || 1 }
33
+ opts.on('-d','--delete [POOL]',String,"Delete addresses in POOL. Deletes ALL addresses if POOL is omitted") {|v| @options[:mode] = :delete ; @options[:pool] = v }
34
+
35
+ opts.separator ""
36
+ opts.separator "General:"
37
+ opts.on('-t','--tags TAG[,...]',Array,"Tags to work on, comma separated") {|v| @options[:tags] = v.map(&:to_sym)}
38
+ opts.on('-C','--config CONFIG',String,'Use specific Collins config yaml for Collins::Client') {|v| @options[:config] = v}
39
+ opts.on('-h','--help',"Help") {@options[:mode] = :help}
40
+
41
+ opts.separator ""
42
+ opts.separator "Examples:"
43
+ opts.separator " Show configured IP address pools:"
44
+ opts.separator " #{PROG_NAME} --show-pools -H"
45
+ opts.separator " Allocate 2 IPs on each asset"
46
+ opts.separator " #{PROG_NAME} -t 001234,003456,007895 -a DEV_POOL -n2"
47
+ opts.separator " Deallocate IPs in DEV_POOL pool on assets:"
48
+ opts.separator " #{PROG_NAME} -t 001234,003456,007895 -d DEV_POOL"
49
+ opts.separator " Deallocate ALL IPs on assets:"
50
+ opts.separator " #{PROG_NAME} -t 001234,003456,007895 -d"
51
+ end
52
+ @parser.parse!(argv)
53
+
54
+ # only read tags from ARGF if we are going to do something with the tags
55
+ if [:allocate,:delete].include? options[:mode] && (options[:tags].nil? or options[:tags].empty?)
56
+ # read tags from stdin. first field on the line is the tag
57
+ input = ARGF.readlines
58
+ @options[:tags] = input.map{|l| l.split(/\s+/)[0] rescue nil}.compact.uniq
59
+ end
60
+ @parsed = true
61
+ self
62
+ end
63
+
64
+ def validate!
65
+ raise "You need to tell me to do something!" if @options[:mode].nil?
66
+ raise "No asset tags found via ARGF" if [:allocate,:delete].include?(options[:mode]) && (options[:tags].nil? or options[:tags].empty?)
67
+ @validated = true
68
+ self
69
+ end
70
+
71
+ def run!
72
+ raise "Options not yet parsed with #parse!" unless @parsed
73
+ raise "Options not yet validated with #validate!" unless @validated
74
+ success = true
75
+ case options[:mode]
76
+ when :help
77
+ puts parser
78
+ when :show
79
+ pools = collins.ipaddress_pools
80
+ format_pools(pools, :show_header => options[:show_header])
81
+ when :allocate
82
+ options[:tags].each do |t|
83
+ res = api_call("allocating #{options[:num]} IP in #{options[:pool]}",:ipaddress_allocate!,t,options[:pool],options[:num]) do |addresses|
84
+ "Allocated #{addresses.map(&:address).join(' ')}"
85
+ end
86
+ success = false unless res
87
+ end
88
+ when :delete
89
+ options[:tags].each do |t|
90
+ res = api_call("deleting all IPs#{" in #{options[:pool]}" unless options[:pool].nil?}",:ipaddress_delete!,t,options[:pool]) { |count| "Deleted #{count} IPs" }
91
+ success = false unless res
92
+ end
93
+ end
94
+ success
95
+ end
96
+
97
+ end
98
+ end
99
+