scli 0.0.3

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.
data/MIT-LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Sonian Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.org ADDED
@@ -0,0 +1,24 @@
1
+ * Scli
2
+ scli is a command line tool used to access and interface with IBM SmartCloud. It currently has support for creating, viewing and deleting instances, volumes, keys and addresses, etc.
3
+
4
+ ** Documentation
5
+ Please run `scli help`
6
+
7
+ ** Contributions
8
+ Please fork the project, make your commits and do a pull request.
9
+
10
+ ** Configuration
11
+ You can either set ENV var's (IBM_SC_USERNAME and IBM_SC_PASSWORD)
12
+
13
+ OR
14
+
15
+ ~/.scli/config.rb should contain:
16
+
17
+ ibm_username some@gmail.com
18
+ ibm_password mypassword
19
+
20
+ However if you run the tool without this file present it will prompt you for all the information.
21
+
22
+ ** License
23
+ Scli is released under the [[https://github.com/sensu/sensu/blob/master/MIT-LICENSE.txt][MIT license]]. Copyright (c) 2012 Sonian Inc
24
+
data/bin/scli ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ unless $:.include?(File.dirname(__FILE__) + '/../lib/')
4
+ $: << File.dirname(__FILE__) + '/../lib'
5
+ end
6
+
7
+ require 'scli'
8
+
9
+ Scli::Main.run
data/lib/formatters.rb ADDED
@@ -0,0 +1,146 @@
1
+ module Scli
2
+ def self.word_for_title(string)
3
+ string.gsub("_"," ").gsub(/\w+/) do |word|
4
+ word.capitalize
5
+ end
6
+ end
7
+
8
+ def self.format_table_titles(titles)
9
+ titles.collect do |title|
10
+ word_for_title(title.to_s).green
11
+ end
12
+ end
13
+
14
+ def self.format_state(state)
15
+ case state
16
+ when "Active", "Attached", "Available"
17
+ state.green
18
+ when "Requesting", "Provisioning", "New"
19
+ state.yellow
20
+ else
21
+ state.red
22
+ end
23
+ end
24
+
25
+ def self.format_is_default?(default)
26
+ default ? "True".green : "False".red
27
+ end
28
+
29
+ def self.format_owner(owner)
30
+ (owner.size > 8) ? "#{owner[0..6]}.." : owner
31
+ end
32
+
33
+ def self.format_name(name)
34
+ (name.size > 34) ? "#{name[0..32]}.." : name
35
+ end
36
+
37
+ def self.format_description(description)
38
+ (description.size > 14) ? "#{description[0..12]}.." : description
39
+ end
40
+
41
+ def self.format_capabilities(capable, full = false)
42
+ capabilities = capable.collect do |cap|
43
+ "#{cap['id']} => #{cap['entries']}" unless cap['entries'].size == 0 || cap.nil?
44
+ end
45
+ cap_print = capabilities.reject{|cap| cap.nil?}.join(",")
46
+ full ? cap_print : "#{cap_print[0..12]}.."
47
+ end
48
+
49
+ def self.format_ip(ip)
50
+ return "NA".red if ip.nil? || ip == ""
51
+ first_octet = ip.split(".").first
52
+ first_octet == "10" ? ip.cyan : ip.magenta
53
+ end
54
+
55
+ def self.format_instance_id(instance_id)
56
+ if instance_id.class == String
57
+ (instance_id.nil? || instance_id.to_s == "0") ? "Detached".red : instance_id.green
58
+ elsif instance_id.nil?
59
+ "NA".red
60
+ else
61
+ instance_id.join(",")
62
+ end
63
+ end
64
+
65
+ def self.format_type(instance_type)
66
+ instance_type.to_s.split(".").first
67
+ end
68
+
69
+ def self.format_image_instance_types(instance_types, single = false)
70
+ supported_types = []
71
+ instance_types.each do |it|
72
+ supported_types << ((single) ? it.id : format_type(it.id))
73
+ end
74
+ (single ? supported_types.join("\n") : supported_types.join(","))
75
+ end
76
+
77
+ def self.format_size(vol_size)
78
+ if vol_size.to_i > 1024
79
+ "#{vol_size.to_i / 1024}TB"
80
+ else
81
+ "#{vol_size}GB"
82
+ end
83
+ end
84
+
85
+ def self.format_location(location)
86
+ case location.to_i
87
+ when 41
88
+ "Raleigh"
89
+ when 61
90
+ "Ehningen"
91
+ when 82
92
+ "Boulder"
93
+ when 101
94
+ "Markham"
95
+ when 121
96
+ "Makuhari"
97
+ when 141
98
+ "Singapore"
99
+ else
100
+ location
101
+ end
102
+ end
103
+
104
+ def self.process_data_to_format(object, data_to_print, single = false)
105
+ print_array = []
106
+ data_to_print.each do |data|
107
+ print_array << case data.to_s
108
+ when /state/
109
+ format_state(object.send(data))
110
+ when /supported_instance_types/
111
+ single ? format_image_instance_types(object.send(data), true) : format_image_instance_types(object.send(data))
112
+ when /capabilities/
113
+ single ? format_capabilities(object.send(data), true) : format_capabilities(object.send(data))
114
+ when /default/
115
+ format_is_default?(object.send(data))
116
+ when /owner/
117
+ single ? object.send(data) : format_owner(object.send(data))
118
+ when /name/
119
+ format_name(object.send(data))
120
+ when /description/
121
+ single ? object.send(data) : format_description(object.send(data))
122
+ when "ip"
123
+ format_ip(object.send(data))
124
+ when /volume_ids/
125
+ object.send(data).join(",")
126
+ when /instance_id/
127
+ format_instance_id(object.send(data))
128
+ when /public_key/
129
+ format_description(object.send(data))
130
+ when /type/
131
+ if object.send(data).class == String
132
+ format_type(object.send(data))
133
+ else
134
+ object.send(data)
135
+ end
136
+ when /size/
137
+ format_size(object.send(data))
138
+ when /location/
139
+ format_location(object.send(data))
140
+ else
141
+ object.send(data)
142
+ end
143
+ end
144
+ print_array
145
+ end
146
+ end
data/lib/generic.rb ADDED
@@ -0,0 +1,99 @@
1
+ module Scli
2
+ class Compute
3
+ def initialize(options={})
4
+ cli_opts = Scli.options
5
+ cli_opts.merge!(options)
6
+ ibm_user = (Scli.env_populated?) ? ENV['IBM_SC_USERNAME'] : cli_opts[:ibm_username]
7
+ ibm_pass = (Scli.env_populated?) ? ENV['IBM_SC_PASSWORD'] : cli_opts[:ibm_password]
8
+ @fog_compute = Fog::Compute.new(:ibm_username => ibm_user, :ibm_password => ibm_pass, :provider => 'IBM')
9
+ end
10
+
11
+ def method_missing(method, *args, &block)
12
+ @fog_compute.send(method, *args, &block)
13
+ end
14
+ end
15
+
16
+ class Storage
17
+ def initialize(options={})
18
+ cli_opts = Scli.options
19
+ cli_opts.merge!(options)
20
+ ibm_user = (Scli.env_populated?) ? ENV['IBM_SC_USERNAME'] : cli_opts[:ibm_username]
21
+ ibm_pass = (Scli.env_populated?) ? ENV['IBM_SC_PASSWORD'] : cli_opts[:ibm_password]
22
+ @fog_storage = Fog::Storage.new(:ibm_username => ibm_user, :ibm_password => ibm_pass, :provider => 'IBM')
23
+ end
24
+
25
+ def method_missing(method, *args, &block)
26
+ @fog_storage.send(method, *args, &block)
27
+ end
28
+ end
29
+
30
+ def self.config_file_exists?(config_file_path)
31
+ File.exists?(File.expand_path(config_file_path))
32
+ end
33
+
34
+ def self.generate_config_file(config_file_path)
35
+ puts "Config file #{config_file_path} does not exist, let's create it."
36
+ puts "What is your IBM SC Username?"
37
+ ibm_username = $stdin.gets
38
+ puts "What is your IBM SC Password?"
39
+ ibm_password = $stdin.gets
40
+ Dir.mkdir(File.expand_path(File.dirname(config_file_path))) unless File.exists?(File.expand_path(File.dirname(config_file_path)))
41
+ ibm_config = File.open(File.expand_path(config_file_path), "w")
42
+ ibm_config.puts "ibm_username #{ibm_username}"
43
+ ibm_config.puts "ibm_password #{ibm_password}"
44
+ ibm_config.close
45
+ puts "Config file written."
46
+ end
47
+
48
+ def self.print_object(title, objects, data_to_print, options = {})
49
+ table_to_print = []
50
+ if objects.methods.include?(:each) # Its not a single object
51
+ title = title + "(#{objects.count})"
52
+ objects.each do |object|
53
+ table_to_print << process_data_to_format(object, data_to_print)
54
+ table_to_print << :separator if options[:separator]
55
+ end
56
+ else
57
+ table_to_print << process_data_to_format(objects, data_to_print, true)
58
+ end
59
+ table = Terminal::Table.new :title => title.cyan, :headings => format_table_titles(data_to_print), :rows => table_to_print
60
+ puts table
61
+ end
62
+
63
+ def self.print_volumes(volumes)
64
+ print_object("Volumes", volumes, [:id, :name, :size, :instance_id, :owner, :format, :location_id, :offering_id, :created_at, :state])
65
+ if volumes.class.to_s == "Fog::Storage::IBM::Volumes"
66
+ total_vol_size = 0
67
+ volumes.each do |vol|
68
+ next if vol.size.nil?
69
+ total_vol_size += vol.size.to_i
70
+ end
71
+ puts "Total #{(total_vol_size.to_i / 1024.0).round(2)}Tb of storage"
72
+ end
73
+ end
74
+
75
+ def self.print_servers(servers)
76
+ print_object("Servers", servers, [:id, :name, :ip, :owner, :vlan_id, :volume_ids, :instance_type, :launched_at, :location_id, :state])
77
+ end
78
+
79
+ def self.opt_merge(options, name, data)
80
+ options.merge!({name => data}) unless data.nil?
81
+ end
82
+
83
+ def self.env_populated?
84
+ (ENV['IBM_SC_USERNAME'].nil? || ENV['IBM_SC_PASSWORD'].nil?) ? false : (!ENV['IBM_SC_USERNAME'].empty? && !ENV['IBM_SC_PASSWORD'].empty?)
85
+ end
86
+
87
+ def self.read_config(config_file_path)
88
+ options = {}
89
+ if File.exists?(config_file_path)
90
+ config_file = File.open(config_file_path,'r')
91
+ config_file.each_line do |row|
92
+ option, data = row.split
93
+ options[option.to_sym] = data
94
+ end
95
+ end
96
+ options
97
+ end
98
+ end
99
+
@@ -0,0 +1,29 @@
1
+ module Scli
2
+ def self.attvol
3
+ cli = MyCLI.new
4
+ cli.parse_options
5
+ volume_id = cli.config[:volume_id]
6
+ instance_id = cli.config[:instance_id]
7
+ if volume_id.nil? || instance_id.nil?
8
+ puts "Instance and volume id's are required, please retry."
9
+ else
10
+ server = Scli::Compute.new.servers.get(instance_id)
11
+ volume = Scli::Storage.new.volumes.get(volume_id)
12
+ if server.nil?
13
+ puts "Could not find server: #{instance_id}"
14
+ elsif volume.nil?
15
+ puts "Could not find volume: #{volume_id}"
16
+ else
17
+ if server.attach(volume_id.to_i) # Note: attach() takes a string, not an object
18
+ print_volumes(volume)
19
+ puts "Is being attached to instance:".red
20
+ print_servers(server)
21
+ else
22
+ puts "Volume could not be attached for some reason..."
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ PLUGINS << ["attvol", "attach-volume", "Volume ID (Req -v), Instance ID (Req -i)", "scli attvol -i 123456 -v 23456"]
@@ -0,0 +1,22 @@
1
+ module Scli
2
+ def self.cadd
3
+ cli = MyCLI.new
4
+ cli.parse_options
5
+ offering_id = cli.config[:offering_id]
6
+ location_id = cli.config[:location_id]
7
+ vlan_id = cli.config[:vlan_id]
8
+ if offering_id.nil? || location_id.nil?
9
+ puts "No offering and or location id was found, please retry"
10
+ else
11
+ options = vlan_id.nil? ? {} : {:vlan_id => vlan_id}
12
+ response = Scli::Compute.new.create_address(location_id, offering_id, options)
13
+ if response.status == 200
14
+ puts "IP Created successfully: #{response.body.inspect}"
15
+ else
16
+ puts "Failed with #{response.status} error of: #{response.body.inspect}"
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ PLUGINS << ["cadd", "create-address", "Location ID (Req -l), Offering ID (Req -o), Vlan ID (Opt -V)", "scli cadd -o 20025212 -l 41 (-V XX)"]
@@ -0,0 +1,35 @@
1
+ module Scli
2
+ def self.cin
3
+ cli = MyCLI.new
4
+ cli.parse_options
5
+ name = cli.config[:name]
6
+ image_id = cli.config[:image_id]
7
+ instance_type = cli.config[:instance_type]
8
+ location_id = cli.config[:location_id]
9
+ #Optional args
10
+ opts = {}
11
+ opt_merge(opts, :key_name, cli.config[:key_id])
12
+ opt_merge(opts, :ip, cli.config[:address_id])
13
+ opt_merge(opts, :volume_id, cli.config[:volume_id])
14
+ opt_merge(opts, :vlan_id, cli.config[:vlan_id])
15
+ opt_merge(opts, :secondary_ip, cli.config[:secondary_address_id])
16
+ opt_merge(opts, :is_mini_ephemeral, cli.config[:mini_ephemeral])
17
+ opt_merge(opts, :configuration_data, cli.config[:configuration_data])
18
+ opt_merge(opts, :anti_collocation_instance, cli.config[:anti_colo])
19
+
20
+ if name.nil? || location_id.nil? || image_id.nil? || instance_type.nil?
21
+ puts "Missing one of the following (name, location_id, image_id OR instance_type)."
22
+ else
23
+ response = Scli::Compute.new.create_instance(name, image_id, instance_type, location_id, opts)
24
+ if response.status == 200
25
+ puts "Instance Created successfully: #{response.body.inspect}"
26
+ else
27
+ puts "Failed with #{response.status} error of: #{response.body.inspect}"
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ PLUGINS << :separator
34
+ PLUGINS << ["cin", "create-instance", "Name (Req -n), Image ID (Req -m), Instance Type (Req -t), Location ID (Req -l),\nKey name (Req if linux -k), Addr id eth0 (Opt -a), Addr id eth1 (Opt -A),\nVlan ID (Opt -V), Volume ID (Opt -v), Mini Ephemeral (Opt --mini),\nAnti Colo (Opt --anti-colo), Img Config (Opt --config-data)", "scli cin -n 'new_inst' -i 345678 -t 'COP32.1/2048/60' -l 41"]
35
+ PLUGINS << :separator
@@ -0,0 +1,21 @@
1
+ module Scli
2
+ def self.ckey
3
+ cli = MyCLI.new
4
+ cli.parse_options
5
+ name = cli.config[:name]
6
+ if name.nil?
7
+ puts "You must provide a name for the key."
8
+ else
9
+ response = Scli::Compute.new.create_key(name) #Note: This method can take a pub key too, not supported yet.
10
+ if response.status == 200
11
+ puts "Key Created successfully... Copy private key to a safe location."
12
+ puts "Key name: #{response.body['keyName']}"
13
+ puts "Key Contents:\n #{response.body['keyMaterial']}"
14
+ else
15
+ puts "Failed with #{response.status} error of: #{response.body.inspect}"
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ PLUGINS << ["ckey", "create-key", "Key Name (Req -n)", "scli ckey -n my_new_keypair"]
@@ -0,0 +1,31 @@
1
+ module Scli
2
+ def self.cvol
3
+ cli = MyCLI.new
4
+ cli.parse_options
5
+ offering_id = cli.config[:offering_id]
6
+ location_id = cli.config[:location_id]
7
+ format = cli.config[:format]
8
+ size = cli.config[:size]
9
+ name = cli.config[:name]
10
+
11
+ if offering_id.nil?
12
+ puts "You did not provide an offering id, using a generic id of 20035200."
13
+ offering_id = 20035200
14
+ end
15
+
16
+ if format.nil? || name.nil? || size.nil? || location_id.nil? || offering_id.nil?
17
+ puts "Missing offering_id or location_id or format or size or name, please retry."
18
+ else
19
+ if volume_format_valid?(format) && volume_size_valid?(size) && volume_offering_valid?(offering_id)
20
+ response = Scli::Storage.new.create_volume(name, offering_id, format, location_id, size)
21
+ if response.status == 200
22
+ puts "Volume Created successfully: #{response.body.inspect}"
23
+ else
24
+ puts "Failed with #{response.status} error of: #{response.body.inspect}"
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ PLUGINS << ["cvol", "create-volume", "Name (Req -n), Offering ID (Req -o), Format (Req -f), Location ID (Req -l), Size (Req -s)", "scli cvol -n 'new_vol' -o 342 -f EXT3 -l 41 -s 1024"]
@@ -0,0 +1,15 @@
1
+ module Scli
2
+ def self.daddoff
3
+ cli = MyCLI.new
4
+ cli.parse_options
5
+ addresses = Scli::Compute.new.list_address_offerings.body['addresses']
6
+ table_to_print = addresses.collect do |offering|
7
+ [offering['id'], offering['ipType'], offering['location'], offering['price']['rate'], offering['price']['countryCode'], offering['price']['currencyCode']]
8
+ end
9
+ table = Terminal::Table.new :title => "IP Address Offerings (#{addresses.count})".cyan, :headings => ["id".green, "IpType".green, "location".green, "price rate".green, "price country".green, "price currency".green], :rows => table_to_print
10
+ puts table
11
+ end
12
+ end
13
+
14
+ PLUGINS << ["daddoff", "describe-address-offerings", "", "scli daddoff, scli describe-address-offerings"]
15
+
@@ -0,0 +1,15 @@
1
+ module Scli
2
+ def self.dadd
3
+ cli = MyCLI.new
4
+ cli.parse_options
5
+ address_id = cli.config[:address_id] || ARGV[1]
6
+ if address_id.nil? || is_address_id?(address_id)
7
+ addresses = (address_id.nil?) ? Scli::Compute.new.addresses : Scli::Compute.new.addresses.get(address_id)
8
+ print_object("Addresses", addresses, [:id, :location, :ip, :state, :instance_id, :hostname, :mode, :owner])
9
+ else
10
+ puts "Got an invalid address id, please retry."
11
+ end
12
+ end
13
+ end
14
+
15
+ PLUGINS << ["dadd", "describe-address, describe-addresses", "Address ID (Opt -a)", "scli dadd -a 345313, scli describe-address 345313"]
@@ -0,0 +1,20 @@
1
+ module Scli
2
+ def self.dim
3
+ cli = MyCLI.new
4
+ cli.parse_options
5
+ image_id = cli.config[:image_id] || yield_regular_input(ARGV[1])
6
+ private_imgs = cli.config[:private]
7
+ if image_id.nil?
8
+ images = private_imgs ? Scli::Compute.new.images.reject{|img| img.visibility != "PRIVATE"} : Scli::Compute.new.images
9
+ print_object("Images", images, [:id, :name, :architecture, :platform, :visibility, :supported_instance_types, :description, :location, :owner, :created_at, :state])
10
+ else
11
+ if is_image_id?(image_id)
12
+ print_object("Image", Scli::Compute.new.images.get(image_id), [:id, :name, :architecture, :platform, :visibility, :supported_instance_types, :description, :location, :owner, :created_at, :state])
13
+ else
14
+ puts "Image id provided is invalid"
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ PLUGINS << ["dim", "describe-image, describe-images", "Image ID (Opt -m), Private Only (Opt -p)", "scli dim -m 34567, scli describe-image 34567"]
@@ -0,0 +1,35 @@
1
+ module Scli
2
+ def self.dint
3
+ instance_types = []
4
+
5
+ instance_types << "COP32.1/2048/60"
6
+ instance_types << "BRZ32.1/2048/60*175"
7
+ instance_types << "SLV32.2/4096/60*350"
8
+ instance_types << "GLD32.4/4096/60*350"
9
+
10
+ instance_types << "COP64.2/4096/60"
11
+ instance_types << "BRZ64.2/4096/60*500*350"
12
+ instance_types << "SLV64.4/8192/60*500*500"
13
+ instance_types << "GLD64.8/16384/60*500*500"
14
+ instance_types << "PLT64.16/16384/60*500*500*500*500"
15
+
16
+ table_to_print = []
17
+
18
+ instance_types.each do |inst_type|
19
+ name, ram, disks = inst_type.split("/")
20
+ color = name.slice!(0..2)
21
+ bits, cpus = name.split(".")
22
+ disk = disks.split("*")
23
+ total_ephemeral_size = disk.inject{|sum,x| sum.to_i + x.to_i }
24
+ total_ephemeral_non_root = (disk.count > 1) ? (disk.drop(1).inject{|sum,x| sum.to_i + x.to_i }) : 0
25
+ total_ephemeral_devices = (disk.count > 1) ? (disk.drop(1).count) : 0
26
+ table_to_print << [inst_type, color, bits, cpus, ram, total_ephemeral_size, total_ephemeral_non_root, total_ephemeral_devices]
27
+ end
28
+
29
+ table = Terminal::Table.new :title => "Instance Types".cyan, :headings => ["id".green, "color".green, "bits".green, "CPU(s)".green, "RAM".green, "Total Ephemeral".green, "Total Ephemeral (Non-Root)".green, "Ephemeral devices (Non-Root)".green], :rows => table_to_print
30
+ puts table
31
+ puts "Note: This table is not from an API, its simply a helper for the non-rememberable naming IBM gives their instances - List may update/change."
32
+ end
33
+ end
34
+
35
+ PLUGINS << ["dint", "describe-instance-types", "", "scli dint"]
@@ -0,0 +1,18 @@
1
+ module Scli
2
+ def self.din
3
+ cli = MyCLI.new
4
+ cli.parse_options
5
+ instance_id = cli.config[:instance_id] || ARGV[1]
6
+ if instance_id.nil?
7
+ print_servers(Scli::Compute.new.servers)
8
+ else
9
+ if is_instance_id?(instance_id)
10
+ print_servers(Scli::Compute.new.servers.get(instance_id))
11
+ else
12
+ puts "Instance id provided is invalid"
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ PLUGINS << ["din", "describe-instances, describe-instance", "Instance ID (Opt -i)", "scli din, scli din 123456"]
@@ -0,0 +1,11 @@
1
+ module Scli
2
+ def self.dkey
3
+ cli = MyCLI.new
4
+ cli.parse_options
5
+ key_id = cli.config[:key_id] || ARGV[1]
6
+ keys = key_id.nil? ? Scli::Compute.new.keys : Scli::Compute.new.keys.get(key_id)
7
+ print_object("Keys", keys, [:name, :default, :public_key, :instance_ids, :modified_at])
8
+ end
9
+ end
10
+
11
+ PLUGINS << ["dkey", "describe-key, describe-keys", "Key ID (Opt -k)", "scli dkey -k 345678, scli describe-key 345678"]
@@ -0,0 +1,11 @@
1
+ module Scli
2
+ def self.dloc
3
+ cli = MyCLI.new
4
+ cli.parse_options
5
+ location_id = cli.config[:location_id] || ARGV[1]
6
+ locations = location_id.nil? ? Scli::Compute.new.locations : Scli::Compute.new.locations.get(location_id)
7
+ print_object("Locations", locations, [:id, :name, :location, :capabilities, :description])
8
+ end
9
+ end
10
+
11
+ PLUGINS << ["dloc", "describe-location, describe-locations", "Location ID (Opt -l)", "scli dloc -l 345, scli describe-location 345"]
@@ -0,0 +1,18 @@
1
+ module Scli
2
+ def self.dvlan
3
+ cli = MyCLI.new
4
+ cli.parse_options
5
+ vlan_id = cli.config[:vlan_id] || ARGV[1]
6
+ if vlan_id.nil?
7
+ print_object("VLANs", Scli::Compute.new.vlans, [:id, :name, :location])
8
+ else
9
+ if is_vlan_id?(vlan_id)
10
+ print_object("VLANs", Scli::Compute.new.vlans.get(vlan_id), [:id, :name, :location])
11
+ else
12
+ puts "Got an invalid VLAN id, please retry."
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ PLUGINS << ["dvlan", "describe-vlan, describe-vlans", "Vlan ID (Opt -V)", "scli dvlan -V 345, scli describe-vlan 345"]
@@ -0,0 +1,14 @@
1
+ module Scli
2
+ def self.dvoloff
3
+ cli = MyCLI.new
4
+ cli.parse_options
5
+ offerings = Scli::Storage.new.list_offerings.body['volumes']
6
+ table_to_print = offerings.collect do |offering|
7
+ [offering['id'], offering['name'], format_location(offering['location']), offering['price']['rate'], offering['price']['currencyCode'], offering['price']['unitOfMeasure'], offering['formats'].collect{|format| format['id']}.join(","), offering['capacity']]
8
+ end
9
+ table = Terminal::Table.new :title => "Volume Offerings (#{offerings.count})".cyan, :headings => ["id".green, "Name".green, "location".green, "price rate".green, "price currency".green, "Price Measure".green, "Formats".green, "Capacity".green], :rows => table_to_print
10
+ puts table
11
+ end
12
+ end
13
+
14
+ PLUGINS << ["dvoloff", "describe-volume-offerings", "", "scli dvoloff"]
@@ -0,0 +1,18 @@
1
+ module Scli
2
+ def self.dvol
3
+ cli = MyCLI.new
4
+ cli.parse_options
5
+ volume_id = cli.config[:volume_id] || ARGV[1]
6
+ if volume_id.nil?
7
+ print_volumes(Scli::Storage.new.volumes)
8
+ else
9
+ if is_volume_id?(volume_id)
10
+ print_volumes(Scli::Storage.new.volumes.get(volume_id))
11
+ else
12
+ puts "Volume id provided is invalid"
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ PLUGINS << ["dvol", "describe-volume, describe-volumes", "Volume ID (Opt -v)", "scli dvol 23456"]
@@ -0,0 +1,29 @@
1
+ module Scli
2
+ def self.detvol
3
+ cli = MyCLI.new
4
+ cli.parse_options
5
+ volume_id = cli.config[:volume_id]
6
+ instance_id = cli.config[:instance_id]
7
+ if volume_id.nil? || instance_id.nil?
8
+ puts "Instance and volume id's are required, please retry."
9
+ else
10
+ server = Scli::Compute.new.servers.get(instance_id)
11
+ volume = Scli::Storage.new.volumes.get(volume_id)
12
+ if server.nil?
13
+ puts "Could not find server: #{instance_id}"
14
+ elsif volume.nil?
15
+ puts "Could not find volume: #{volume_id}"
16
+ else
17
+ if server.detach(volume_id) # Note: detach() takes a string, not an object
18
+ print_volumes(volume)
19
+ puts "Is being detached from instance:".red
20
+ print_servers(server)
21
+ else
22
+ puts "Volume could not be detached for some reason..."
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ PLUGINS << ["detvol", "detach-volume", "Volume ID (Req -v), Instance ID (Req -i)", "scli detvol -i 123456 -v 23456"]
@@ -0,0 +1,20 @@
1
+ module Scli
2
+ def self.gco
3
+ cli = MyCLI.new
4
+ cli.parse_options
5
+ instance_id = cli.config[:instance_id] || yield_regular_input(ARGV[1])
6
+ if instance_id.nil? || !is_instance_id?(instance_id)
7
+ puts "An invalid instance id was provided."
8
+ else
9
+ response = Scli::Compute.new.get_instance_logs(instance_id)
10
+ if response.status == 200
11
+ puts "Console log output:"
12
+ puts "#{response.body['logs'].join("\n")}"
13
+ else
14
+ puts "Failed with #{response.status} error of: #{response.body.inspect}"
15
+ end
16
+ end
17
+ end
18
+ end
19
+ PLUGINS << ["gco", "get-console-output", "Instance ID (Req -i)", "scli gco 123456"]
20
+
@@ -0,0 +1,23 @@
1
+ module Scli
2
+ def self.reboot
3
+ cli = MyCLI.new
4
+ cli.parse_options
5
+ instance_id = cli.config[:instance_id] || ARGV[1]
6
+ if instance_id.nil? || !is_instance_id?(instance_id)
7
+ puts "Instance id provided is invalid"
8
+ else
9
+ server = Scli::Compute.new.servers.get(instance_id)
10
+ if server.nil?
11
+ puts "Server could not be found, check instance_id and state to ensure it can be rebooted..."
12
+ else
13
+ if server.reboot
14
+ puts "Reboot successful..."
15
+ else
16
+ puts "Reboot failed, check instance_id and state to ensure it can be rebooted..."
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ PLUGINS << ["reboot", "", "Instance ID (Req -i)", "scli reboot 123456"]
@@ -0,0 +1,19 @@
1
+ module Scli
2
+ def self.tad
3
+ cli = MyCLI.new
4
+ cli.parse_options
5
+ address_id = cli.config[:address_id] || ARGV[1]
6
+ if address_id.nil? || !is_address_id?(address_id)
7
+ puts "Address id provided is invalid"
8
+ else
9
+ response = Scli::Compute.new.delete_address(address_id)
10
+ if response.status == 200
11
+ puts "Address successfully terminated: #{response.body.inspect}"
12
+ else
13
+ puts "Address terminated failed #{response.status} with #{response.body.inspect}"
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ PLUGINS << ["tad", "terminate-address", "Address ID (Req -a)", "scli tad 200001"]
@@ -0,0 +1,19 @@
1
+ module Scli
2
+ def self.tim
3
+ cli = MyCLI.new
4
+ cli.parse_options
5
+ image_id = cli.config[:image_id] || ARGV[1]
6
+ if image_id.nil? || !is_image_id?(image_id)
7
+ puts "Image id provided is invalid"
8
+ else
9
+ response = Scli::Compute.new.delete_image(image_id)
10
+ if response.status == 200
11
+ puts "Image successfully terminated: #{response.body.inspect}"
12
+ else
13
+ puts "Image terminated failed #{response.status} with #{response.body.inspect}"
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ PLUGINS << ["tim", "terminate-image", "Image ID (Req -m)", "scli tim 200001"]
@@ -0,0 +1,24 @@
1
+ module Scli
2
+ def self.tin
3
+ cli = MyCLI.new
4
+ cli.parse_options
5
+ instance_id = cli.config[:instance_id] || ARGV[1]
6
+ if instance_id.nil? || !is_instance_id?(instance_id)
7
+ puts "Instance id provided is invalid"
8
+ else
9
+ server = Scli::Compute.new.servers.get(instance_id)
10
+ if server.nil?
11
+ puts "Could not find server #{instance_id}, please check instance_id and state and retry."
12
+ else
13
+ if server.destroy
14
+ print_server(server)
15
+ puts "Is being destroyed...".red
16
+ else
17
+ puts "Could not destroy server #{instance_id}, please check instance_id and state and retry."
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ PLUGINS << ["tin", "terminate-instance", "Instance ID (Req -i)", "scli tin 123456"]
@@ -0,0 +1,19 @@
1
+ module Scli
2
+ def self.tkey
3
+ cli = MyCLI.new
4
+ cli.parse_options
5
+ name = cli.config[:name] || yield_regular_input(ARGV[1])
6
+ if name.nil?
7
+ puts "Key name not provided"
8
+ else
9
+ response = Scli::Compute.new.delete_key(name)
10
+ if response.status == 200
11
+ puts "Key successfully terminated: #{response.body.inspect}"
12
+ else
13
+ puts "Key termination failed #{response.status} with #{response.body.inspect}"
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ PLUGINS << ["tkey", "terminate-key", "Name (Req -n)", "scli tkey 200001"]
@@ -0,0 +1,24 @@
1
+ module Scli
2
+ def self.delvol
3
+ cli = MyCLI.new
4
+ cli.parse_options
5
+ volume_id = cli.config[:volume_id] || ARGV[1]
6
+ if volume_id.nil? || !is_volume_id?(volume_id)
7
+ puts "Volume ID is either missing or invalid, please retry."
8
+ else
9
+ volume = Scli::Storage.new.volumes.get(volume_id)
10
+ if volume.nil?
11
+ puts "Could not find volume: #{volume_id}"
12
+ else
13
+ if volume.destroy
14
+ print_volume(volume)
15
+ puts "Is being destroyed...".red
16
+ else
17
+ puts "Volume could not be destroyed for some reason..."
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ PLUGINS << ["delvol", "delete-volume,terminate-volume", "Volume ID (Req -v)", "scli delvol -v 23456, scli delvol 23456"]
data/lib/scli.rb ADDED
@@ -0,0 +1,174 @@
1
+ require 'rubygems'
2
+ require 'mixlib/cli'
3
+ require 'fog'
4
+ require 'colored'
5
+ require 'terminal-table'
6
+ require 'validators'
7
+ require 'formatters'
8
+ require 'generic'
9
+
10
+ PLUGINS = []
11
+
12
+ class MyCLI
13
+ include Mixlib::CLI
14
+
15
+ option :config_file,
16
+ :short => "-C CONFIG",
17
+ :long => "--config CONFIG",
18
+ :default => '~/.scli/config.rb',
19
+ :description => "The configuration file to use"
20
+
21
+ option :address_id,
22
+ :short => "-a ADDRESS",
23
+ :long => "--address-id ADDRESS",
24
+ :description => "IP Address ID to use"
25
+
26
+ option :instance_id,
27
+ :short => "-i INSTANCE",
28
+ :long => "--instance-id INSTANCE",
29
+ :description => "Instance ID to use"
30
+
31
+ option :volume_id,
32
+ :short => "-v VOLUME",
33
+ :long => "--volume-id VOLUME",
34
+ :description => "Volume ID to use"
35
+
36
+ option :instance_type,
37
+ :short => "-t TYPE",
38
+ :long => "--instance-type TYPE",
39
+ :description => "Instance Type (e.g. COP32.1/2048/60)"
40
+
41
+ option :image_id,
42
+ :short => "-m IMAGE",
43
+ :long => "--image-id IMAGE",
44
+ :description => "Image ID to use"
45
+
46
+ option :key_id,
47
+ :short => "-k IMAGE",
48
+ :long => "--key-id KEY",
49
+ :description => "Key ID to use"
50
+
51
+ option :offering_id,
52
+ :short => "-o OFFERING",
53
+ :long => "--offering-id OFFERING",
54
+ :description => "Offering ID to use"
55
+
56
+ option :vlan_id,
57
+ :short => "-V VLAN",
58
+ :long => "--vlan-id VLAN",
59
+ :description => "Vlan ID to use"
60
+
61
+ option :location_id,
62
+ :short => "-l LOCATION",
63
+ :long => "--location-id LOCATION",
64
+ :description => "Location ID to use"
65
+
66
+ option :name,
67
+ :short => "-n NAME",
68
+ :long => "--name NAME",
69
+ :description => "Name to use"
70
+
71
+ option :size,
72
+ :short => "-s SIZE",
73
+ :long => "--size SIZE",
74
+ :description => "Size to use"
75
+
76
+ option :format,
77
+ :short => "-f FORMAT",
78
+ :long => "--format FORMAT",
79
+ :description => "Format to use (ext3, raw)"
80
+
81
+ option :private,
82
+ :short => "-p",
83
+ :long => "--private",
84
+ :boolean => true,
85
+ :description => "Only show private images, etc"
86
+
87
+ option :mini_ephemeral,
88
+ :long => "--mini",
89
+ :boolean => true,
90
+ :description => "(Create instance only) Set mini ephemeral)"
91
+
92
+ option :configuration_data,
93
+ :long => "--config-data DATA",
94
+ :description => "(Create instance only) Extra config data required by image"
95
+
96
+ option :anti_colo,
97
+ :long => "--anti-colo INST_ID",
98
+ :description => "(Create instance only) Instance ID to anti-colo against"
99
+
100
+ option :secondary_address_id,
101
+ :short => "-A SADDRESS",
102
+ :long => "--secondary-address-id SADDRESS",
103
+ :description => "(Create instance only) IP To use for eth1"
104
+
105
+ option :help,
106
+ :short => "-h",
107
+ :long => "--help",
108
+ :description => "SCLI Help",
109
+ :on => :tail,
110
+ :boolean => true,
111
+ :show_options => true,
112
+ :exit => 0
113
+
114
+ end
115
+
116
+ module Scli
117
+ def self.options
118
+ cli = MyCLI.new
119
+ cli.parse_options
120
+ cli.config.merge!(read_config(File.expand_path(cli.config[:config_file])) || {})
121
+ end
122
+ end
123
+
124
+ module Scli
125
+ class Main
126
+ def self.run
127
+ cli = MyCLI.new
128
+ cli.parse_options
129
+
130
+ Scli.generate_config_file(cli.config[:config_file]) unless Scli.config_file_exists?(cli.config[:config_file]) || Scli.env_populated?
131
+
132
+ Dir.glob("#{File.dirname(__FILE__)}/plugins/*").each do |plugin|
133
+ require plugin
134
+ end
135
+
136
+ help_table = Terminal::Table.new :title => "Help", :headings => ["Command", "Aliases", "Arguments", "Examples"], :rows => PLUGINS
137
+
138
+ #
139
+ # every array in plugins contains the main command [0], comma seperated command aliases [1], options [2] and examples [3]. So we match on [0] or [1] below.
140
+ #
141
+ begin
142
+ plugin_executed = false
143
+ PLUGINS.each do |plugin|
144
+ commands = [plugin[0]] + plugin[1].split(",")
145
+ if commands.include?(ARGV[0])
146
+ Scli.send(plugin[0])
147
+ plugin_executed = true
148
+ end
149
+ end
150
+
151
+ unless plugin_executed || !ARGV[0] == "help"
152
+ puts help_table
153
+ puts "\n Run scli -h to get CLI Args"
154
+ end
155
+
156
+ rescue Excon::Errors::InternalServerError => e
157
+ puts "Got an internal server error while trying to talk to IBM: #{e.response.body}"
158
+ rescue Excon::Errors::PreconditionFailed => e
159
+ puts "A precondition failed while trying to do our API Request: #{e.response.body}"
160
+ rescue Excon::Errors::SocketError => e
161
+ puts "Could not connect to IBM: #{e}"
162
+ rescue Excon::Errors::Unauthorized => e
163
+ puts "You were not authorized to access a resource, Are you sure its owned by the account in your config file? -- #{e.response.body}"
164
+ rescue Exception => e
165
+ if e.methods.include?(:response)
166
+ puts "Fog API Took an exception while speaking to IBM: #{e.response.body}"
167
+ else
168
+ puts "Took an exception, You probably put an invalid instance, volume or address id in as a command line argument, Check to make sure it really exists and retry."
169
+ puts "#{e.backtrace.join("\n")} -- #{e.message}"
170
+ end
171
+ end
172
+ end
173
+ end
174
+ end
data/lib/validators.rb ADDED
@@ -0,0 +1,56 @@
1
+ module Scli
2
+ def self.yield_regular_input(argument)
3
+ return nil if argument.nil?
4
+ (argument[0] == "-") ? nil : argument
5
+ end
6
+
7
+ def self.is_address_id?(address_id)
8
+ address_id.to_i.to_s.size >= 6
9
+ end
10
+
11
+ def self.is_volume_id?(volume_id)
12
+ volume_id.to_i.to_s.size >= 5
13
+ end
14
+
15
+ def self.is_vlan_id?(vlan_id)
16
+ vlan_id.to_i.to_s.size == 3
17
+ end
18
+
19
+ def self.is_image_id?(image_id)
20
+ image_id.to_i.to_s.size >= 8
21
+ end
22
+
23
+ def self.is_instance_id?(instance_id)
24
+ instance_id.to_i.to_s.size >= 6
25
+ end
26
+
27
+ def self.volume_format_valid?(format)
28
+ valid_formats = ["EXT3", "RAW"]
29
+ if valid_formats.include?(format)
30
+ true
31
+ else
32
+ puts "The format you provided is invalid, it must be one of #{valid_formats.join(",")}"
33
+ false
34
+ end
35
+ end
36
+
37
+ def self.volume_size_valid?(size)
38
+ valid_sizes = [60, 256, 512, 1024, 2048, 4112, 8224, 10240]
39
+ if valid_sizes.include?(size.to_i)
40
+ true
41
+ else
42
+ puts "The size you provided is invalid, it must be one of #{valid_sizes.join(",")}"
43
+ false
44
+ end
45
+ end
46
+
47
+ def self.volume_offering_valid?(offering_id)
48
+ unless offering_id.to_s == "20035200"
49
+ puts "=" * 60
50
+ puts "WARN: IBM has many offering ID's for volumes, however most do not support large blocks and they seem to be migrating away from using them."
51
+ puts "WARN: Every volume created in the WebUI uses a offering id of 20035200, so you probably want to use that."
52
+ puts "=" * 60
53
+ end
54
+ true
55
+ end
56
+ end
data/scli.gemspec ADDED
@@ -0,0 +1,22 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'scli'
3
+ s.version = '0.0.3'
4
+ s.date = '2012-06-30'
5
+ s.platform = Gem::Platform::RUBY
6
+ s.authors = ['Josh Pasqualetto']
7
+ s.email = ['josh.pasqualetto@sonian.net']
8
+ s.homepage = 'https://github.com/sonian/scli'
9
+ s.summary = 'CLI Interface to IBM SmartCloud API'
10
+ s.description = 'A command line tool to interface with IBM SmartCloud: create, view & delete instances, keys, volumes, addresses, etc'
11
+ s.license = 'MIT'
12
+ s.has_rdoc = false
13
+
14
+ s.add_dependency('fog', '~> 1.3.1')
15
+ s.add_dependency('mixlib-cli', '~> 1.2.2')
16
+ s.add_dependency('terminal-table', '~> 1.4.5')
17
+ s.add_dependency('colored', '1.2')
18
+
19
+ s.files = Dir.glob('{bin,lib}/**/*') + %w[scli.gemspec README.org MIT-LICENSE.txt]
20
+ s.executables = Dir.glob('bin/**/*').map { |file| File.basename(file) }
21
+ s.require_paths = ['lib']
22
+ end
metadata ADDED
@@ -0,0 +1,143 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: scli
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Josh Pasqualetto
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-06-30 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: fog
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 1.3.1
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 1.3.1
30
+ - !ruby/object:Gem::Dependency
31
+ name: mixlib-cli
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: 1.2.2
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 1.2.2
46
+ - !ruby/object:Gem::Dependency
47
+ name: terminal-table
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: 1.4.5
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 1.4.5
62
+ - !ruby/object:Gem::Dependency
63
+ name: colored
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - '='
68
+ - !ruby/object:Gem::Version
69
+ version: '1.2'
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - '='
76
+ - !ruby/object:Gem::Version
77
+ version: '1.2'
78
+ description: ! 'A command line tool to interface with IBM SmartCloud: create, view
79
+ & delete instances, keys, volumes, addresses, etc'
80
+ email:
81
+ - josh.pasqualetto@sonian.net
82
+ executables:
83
+ - scli
84
+ extensions: []
85
+ extra_rdoc_files: []
86
+ files:
87
+ - bin/scli
88
+ - lib/formatters.rb
89
+ - lib/generic.rb
90
+ - lib/plugins/attach-volume.rb
91
+ - lib/plugins/create-address.rb
92
+ - lib/plugins/create-instance.rb
93
+ - lib/plugins/create-key.rb
94
+ - lib/plugins/create-volume.rb
95
+ - lib/plugins/describe-address-offering.rb
96
+ - lib/plugins/describe-address.rb
97
+ - lib/plugins/describe-image.rb
98
+ - lib/plugins/describe-instance-types.rb
99
+ - lib/plugins/describe-instance.rb
100
+ - lib/plugins/describe-key.rb
101
+ - lib/plugins/describe-location.rb
102
+ - lib/plugins/describe-vlan.rb
103
+ - lib/plugins/describe-volume-offering.rb
104
+ - lib/plugins/describe-volume.rb
105
+ - lib/plugins/detach-volume.rb
106
+ - lib/plugins/get-console-output.rb
107
+ - lib/plugins/reboot.rb
108
+ - lib/plugins/terminate-address.rb
109
+ - lib/plugins/terminate-image.rb
110
+ - lib/plugins/terminate-instance.rb
111
+ - lib/plugins/terminate-key.rb
112
+ - lib/plugins/terminate-volume.rb
113
+ - lib/scli.rb
114
+ - lib/validators.rb
115
+ - scli.gemspec
116
+ - README.org
117
+ - MIT-LICENSE.txt
118
+ homepage: https://github.com/sonian/scli
119
+ licenses:
120
+ - MIT
121
+ post_install_message:
122
+ rdoc_options: []
123
+ require_paths:
124
+ - lib
125
+ required_ruby_version: !ruby/object:Gem::Requirement
126
+ none: false
127
+ requirements:
128
+ - - ! '>='
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ required_rubygems_version: !ruby/object:Gem::Requirement
132
+ none: false
133
+ requirements:
134
+ - - ! '>='
135
+ - !ruby/object:Gem::Version
136
+ version: '0'
137
+ requirements: []
138
+ rubyforge_project:
139
+ rubygems_version: 1.8.24
140
+ signing_key:
141
+ specification_version: 3
142
+ summary: CLI Interface to IBM SmartCloud API
143
+ test_files: []