From: Michal Fojtik <mfoj...@redhat.com>
Signed-off-by: Michal fojtik <mfoj...@redhat.com> --- client/bin/deltacloudc | 245 ------------------------------------ client/lib/plain_formatter.rb | 145 --------------------- client/tests/cmd.rb | 214 ------------------------------- client/tests/common.rb | 42 ------- clients/console/deltacloudc | 245 ++++++++++++++++++++++++++++++++++++ clients/console/plain_formatter.rb | 145 +++++++++++++++++++++ clients/console/tests/cmd.rb | 214 +++++++++++++++++++++++++++++++ clients/console/tests/common.rb | 42 +++++++ 8 files changed, 646 insertions(+), 646 deletions(-) delete mode 100755 client/bin/deltacloudc delete mode 100644 client/lib/plain_formatter.rb delete mode 100644 client/tests/cmd.rb delete mode 100644 client/tests/common.rb create mode 100755 clients/console/deltacloudc create mode 100644 clients/console/plain_formatter.rb create mode 100644 clients/console/tests/cmd.rb create mode 100644 clients/console/tests/common.rb diff --git a/client/bin/deltacloudc b/client/bin/deltacloudc deleted file mode 100755 index adc5d11..0000000 --- a/client/bin/deltacloudc +++ /dev/null @@ -1,245 +0,0 @@ -#!/usr/bin/env ruby -# -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. The -# ASF licenses this file to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance with the -# License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -require 'rubygems' -require 'optparse' -require 'uri' -require 'deltacloud' -require 'plain_formatter' - -include DeltaCloud::PlainFormatter - -options = { - :verbose => false -} - -@optparse = OptionParser.new do |opts| - -opts.banner = <<BANNER -Usage: -deltacloudc collection operation [options] - -URL format: -API_URL=http://[user]:[password]@[api_url][port][/uri] - OR -API_URL=http://[api_url][port][/uri] with -U and -P for credentials - -Examples: - - 1. To list collections for deltacloud api on port 3333 of server deltacloud.foo - - deltacloudc -l -u http://user:passw...@deltacloud.foo:3333/api - - 2. To list the operations for the 'images' collection: - - deltacloudc images -l -U username -P password -u http://deltacloud.foo:3333/api - - 3. To list all images (i.e. call the 'index' operation of 'images'): - - deltacloudc images index -u http://user:passw...@deltacloud.foo:3333/api - - 4. To get the details of image '5': - - deltacloudc images show -i 5 -U username -P password -u http://deltacloud.foo:3333/api - -Options: -BANNER - opts.on( '-i', '--id ID', 'ID for operation') { |id| options[:id] = id } - opts.on( '-d', '--image-id ID', 'Image ID') { |id| options[:image_id] = id } - opts.on( '-b', '--bucket-id ID', 'Bucket ID') {|id| options[:bucket_id] = id } - opts.on( '-r', '--bucket-location NAME', 'Bucket location') {|name| options[:bucket_location] = name } - opts.on( '-a', '--arch ARCH', 'Architecture (x86, x86_64)') { |id| options[:architecture] = id } - opts.on( '-p', '--hardware-profile HARDWARE_PROFILE', 'Hardware Profile') { |id| options[:hwp_id] = id } - opts.on( '-n', '--name NAME', 'Name (for instance eg.)') { |name| options[:name] = name } - opts.on( '-s', '--state STATE', 'Instance state (RUNNING, STOPPED)') { |state| options[:state] = state } - opts.on( '-u', '--url URL', 'API url ($API_URL variable)') { |url| options[:api_url] = url } - opts.on( '-U', '--user USER', 'API username ($API_USERNAME variable)') { |u| options[:api_user] = u } - opts.on( '-P', '--password PASSWORD', 'API password ($API_PASSWORD variable)') { |p| options[:api_password] = p } - opts.on( '-l', '--list', 'List collections/operations') { |id| options[:list] = true } - opts.on( '-h', '--help', 'Display this screen' ) { puts @optparse; Kernel.exit! } - opts.on( '-v', '--version', 'Display API version' ) { options[:version]=true } - opts.on( '-V', '--verbose', 'Print verbose messages' ) { options[:verbose]=true } - opts.on( '-f', '--file-path PATH', 'local path for new blob data') {|path| options[:file_path]=path } - opts.on( '-m', '--blob-metadata k1=v1, k2=v2', 'Comma seperated k=v pairs for blob metadata (for create operation)') do |meta| - blob_meta = {} - meta.gsub!(/ /,"") - meta.scan(/(\w+)=(\w+)/).map {|k,v| blob_meta[k] = v } - options[:blob_metadata] = blob_meta - end -end - -def invalid_usage(error_msg='') - puts "\n ERROR: #{error_msg} \n\n" - puts @optparse - exit(1) -end - -begin - @optparse.parse! -rescue Exception => e - invalid_usage(e.message) -end - -# First try to get API_URL from environment -options[:api_url] = ENV['API_URL'] if options[:api_url].nil? - -if(options[:api_url].nil?) - invalid_usage("You must supply the url to the deltacloud api; either use '-u' flag or set the 'API_URL' environment variable") -end - -url = URI.parse(options[:api_url]) -api_url = "http://#{url.host}#{url.port ? ":#{url.port}" : ''}#{url.path}" - -options[:collection] = ARGV[0] -options[:operation] = ARGV[1] - -# Connect to Deltacloud API and fetch all entry points -client = DeltaCloud.new(options[:api_user] || url.user || ENV['API_USER'], options[:api_password] || url.password || ENV['API_PASSWORD'], api_url) -collections = client.entry_points.keys - -# Exclude collection which don't have methods in client library yet -collections.delete(:instance_states) -#add blob collection if buckets is present -collections << :blob if collections.include?(:buckets) - -# If list parameter passed print out available collection -# with API documentation -if options[:list] and options[:collection].nil? - collections.each do |c| - puts sprintf("%-22s", c.to_s[0, 22]) - end - exit(0) -end - -# If collection parameter is present and user requested list -# print all operation defined for collection with API documentation -if options[:list] and options[:collection] - #deal with blobs again - bypass 'normal' docs procedure - if options[:collection] =~ /blob/i - puts "create \t\t: Create a new blob in a specified bucket (POST /api/buckets/:bucket)" - puts "destroy \t: Delete a specified blob in a specified bucket (DELETE /api/buckets/:bucket/:blob)" - puts "show \t\t: Get details of a specified blob in a specified bucket (GET /api/buckets/:bucket/:blob)" - puts "data \t\t: Get the contents of a specified blob - the blob data content itself" - exit(0) - end - doc = client.documentation(options[:collection]) - doc.operations.each do |c| - puts sprintf("%-20s: %s", c.operation, c.description) - end - exit(0) -end - -if options[:version] - puts "Deltacloud API(#{client.driver_name}) 0.1" - exit(0) -end - -# List items from collection (typically /instances) -# Do same if 'index' operation is set -if options[:collection] and ( options[:operation].nil? or options[:operation].eql?('index') ) - invalid_usage("Unknown collection: #{options[:collection]}") unless collections.include?(options[:collection].to_sym) -#cannot list blobs - can only show a specific blob - invalid_usage("You must specify a particular blob with -i and a particular bucket with -b") if options[:collection] =~ (/blob/i) - params = {} - params.merge!(:architecture => options[:architecture]) if options[:architecture] - params.merge!(:id => options[:id]) if options[:id] - params.merge!(:state => options[:state]) if options[:state] - client.send(options[:collection].to_s, params).each do |model| - puts format(model) - end - exit(0) -end - -if options[:collection] and options[:operation] - - invalid_usage("Unknown collection: #{options[:collection]}") unless collections.include?(options[:collection].to_sym) - - params = {} - params.merge!(:id => options[:id]) if options[:id] - - # If collection is set and requested operation is 'show' just 'singularize' - # collection name and print item with specified id (-i parameter) - #Blobs are a special case so deal with first - - if options[:collection] =~ (/blob/i) - invalid_usage("Please specify the bucket for this blob using the -b option") unless options[:bucket_id] - invalid_usage("Missing blob ID, please specify with -i option") unless options[:id] - params = {} - params.merge!(:id => options[:id], 'bucket' => options[:bucket_id]) - params.merge!('metadata'=>options[:blob_metadata]) unless options[:blob_metadata].nil? - case options[:operation] - when 'show' then puts format(client.send( options[:collection], params)) - when 'data' then puts client.blob_data(params) - when 'create' then - invalid_usage("Specify the location of the new blob data (full local path) using -f option") unless options[:file_path] - params.merge!('file_path'=>options[:file_path]) - blob = client.create_blob(params) - puts format(blob) - when 'destroy' then client.destroy_blob(params) - else invalid_usage("Please specify a valid operation for the blob collection - try -l to see available operations") - end - exit(0) - end - - if options[:operation].eql?('show') - invalid_usage("Missing ID, must be provided with --id") unless options[:id] - puts format(client.send(options[:collection].gsub(/s$/, ''), options[:id])) - exit(0) - end - - # If collection is set and requested operation is create new instance, - # --image-id, --hardware-profile and --name parameters are used - # Returns created instance in plain form - if options[:collection].eql?('instances') and options[:operation].eql?('create') - invalid_usage("Missing image-id") unless options[:image_id] - if options[:name] and ! client.feature?(:instances, :user_name) - invalid_usage("Driver does not support user-supplied name") - end - params.merge!(:name => options[:name]) if options[:name] - params.merge!(:image_id => options[:image_id]) if options[:image_id] - params.merge!(:hwp_id => options[:hwp_id]) if options[:hwp_id] - instance = client.create_instance(options[:image_id], params) - puts format(instance) - exit(0) - end - - #Create and Destroy a bucket - require the bucket id: - if options[:collection].eql?('buckets') - if options[:operation].eql?('create') - invalid_usage("Please specify an id for the new bucket with -i") unless options[:id] - bucket = client.create_bucket('id'=>options[:id], 'bucket_location'=>options[:bucket_location]) - puts format(bucket) - elsif options[:operation].eql?('destroy') - invalid_usage("Please specify the bucket you wish to destroy with -i") unless options[:id] - client.destroy_bucket('id'=>options[:id]) - else - invalid_usage("Please specify a valid operation on buckets - use -l to see valid operations") - end - exit(0) - end - - # All other operations above collections is done there: - if options[:collection].eql?('instances') - instance = client.instance(options[:id]) - instance.send("#{options[:operation]}!".to_s) - instance = client.instance(options[:id]) - puts format(instance) - exit(0) - end -end - -# If all above passed (eg. no parameters) -puts @optparse diff --git a/client/lib/plain_formatter.rb b/client/lib/plain_formatter.rb deleted file mode 100644 index 2e2ada3..0000000 --- a/client/lib/plain_formatter.rb +++ /dev/null @@ -1,145 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. The -# ASF licenses this file to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance with the -# License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -module DeltaCloud - module PlainFormatter - module FormatObject - - class Base - def initialize(obj) - @obj = obj - end - end - - class Key < Base - def format - sprintf("%-10s | %-60s", - @obj.id[0,10], - @obj.fingerprint - ) - end - end - - class Image < Base - def format - sprintf("%-10s | %-20s | %-6s | %-20s | %15s", - @obj.id[0,10], - @obj.name ? @obj.name[0, 20]: 'unknown', - @obj.architecture[0,6], - @obj.description[0,20], - @obj.owner_id[0,15] - ) - end - end - - class Realm < Base - def format - sprintf("%-10s | %-15s | %-5s | %10s GB", - @obj.id[0, 10], - @obj.name[0, 15], - @obj.state[0,5], - @obj.limit.to_s[0,10] - ) - end - end - - class HardwareProfile < Base - def format - architecture = @obj.architecture ? @obj.architecture.value[0,6] : 'opaque' - memory = @obj.memory ? @obj.memory.value.to_s[0,10] : 'opaque' - storage = @obj.storage ? @obj.storage.value.to_s[0,10] : 'opaque' - sprintf("%-15s | %-6s | %10s | %10s ", @obj.id[0, 15], - architecture , memory, storage) - end - end - - class Instance < Base - def format - sprintf("%-15s | %-15s | %-15s | %10s | %32s | %32s", - @obj.id ? @obj.id.to_s[0,15] : '-', - @obj.name ? @obj.name.to_s[0,15] : 'unknown', - @obj.image.name ? @obj.image.name.to_s[0,15] : 'unknown', - @obj.state ? @obj.state.to_s[0,10] : 'unknown', - @obj.public_addresses.collect { |a| a[:address] }.join(',')[0,32], - @obj.private_addresses.collect { |a| a[:address] }.join(',')[0,32] - ) - end - end - - class StorageVolume < Base - def format - sprintf("%-10s | %15s GB | %-10s | %-10s | %-15s", - @obj.id[0,10], - @obj.capacity ? @obj.capacity.to_s[0,15] : 'unknown', - @obj.device ? @obj.device[0,10] : 'unknown', - @obj.respond_to?('state') ? @obj.state[0,10] : 'unknown', - @obj.instance ? @obj.instance.name[0,15] : 'unknown' - ) - end - end - - class StorageSnapshot < Base - def format - sprintf("%-10s | %-15s | %-6s | %15s", - @obj.id[0,10], - @obj.storage_volume.respond_to?('name') ? @obj.storage_volume.name[0, 15] : 'unknown', - @obj.state ? @obj.state[0,10] : 'unknown', - @obj.created ? @obj.created[0,19] : 'unknown' - ) - end - end - - class Bucket < Base - def format - sprintf("%-s | %-s | %-s | %-s", - @obj.id, - @obj.name, - @obj.size ? @obj.size : "0", - @obj.instance_variables.include?("@blob_list") ? @obj.blob_list : "" - ) - end - end - - class Blob < Base - def format - sprintf("%-s | %-s | %-d | %-s | %-s | %-s " , - @obj.id, - @obj.bucket, - @obj.content_length, - @obj.content_type, - @obj.last_modified, - @obj.user_metadata - ) - end - end - - class Driver < Base - def format - sprintf("%-15s | %-15s | %-s", - @obj.id, - @obj.name, - @obj.url) - end - end - end - - def format(obj) - object_name = obj.class.name.classify.gsub(/^DeltaCloud::API::(\w+)::/, '') - format_class = DeltaCloud::PlainFormatter::FormatObject.const_get(object_name) - format_class.new(obj).format - end - - end -end diff --git a/client/tests/cmd.rb b/client/tests/cmd.rb deleted file mode 100644 index 90fb853..0000000 --- a/client/tests/cmd.rb +++ /dev/null @@ -1,214 +0,0 @@ -# Copyright (C) 2009-2011 Red Hat, Inc. -# -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. The -# ASF licenses this file to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance with the -# License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# - -require 'rubygems' -require 'shoulda' -require 'tests/common' - -include DeltaCloud::TestHelper - -class CommandLineTest < Test::Unit::TestCase - context "a command line client" do - - should "respond to --help argument" do - assert_nothing_raised do - base_client('--help') - end - end - - should "return API version with --version argument" do - assert_match /Deltacloud API\(mock\) (\d+)\.(\d+)/, client('--version') - end - - should "return list all collections with --list argument" do - output = nil - assert_nothing_raised do - output = client('--list') - end - assert_not_nil output - assert_match /images/, output - assert_match /instances/, output - assert_match /realms/, output - assert_match /hardware_profiles/, output - end - - should 'respond with proper error when accessing unknow collection' do - output = client('unknown_collection') - assert_match /ERROR: Unknown collection:/, output - end - - end -end - -class CmdRealmTest < Test::Unit::TestCase - context "a realms" do - - should "be listed using realms argument" do - output = nil - assert_nothing_raised do - output = client('realms') - end - assert_match /^us/m, output - assert_match /^eu/m, output - end - - should "be filtered using show --id argument" do - output = nil - assert_nothing_raised do - output = client('realms show --id us') - end - assert_match /^us/, output - assert_no_match /^eu/, output - end - - end -end - -class CmdHardwareProfilesTest < Test::Unit::TestCase - context "a hardware profiles" do - - should "be listed using hardware_profiles argument" do - output = nil - assert_nothing_raised do - output = client('hardware_profiles') - end - assert_no_warning output - assert_match /^m1-small/m, output - assert_match /^m1-large/m, output - assert_match /^m1-xlarge/m, output - assert_match /^opaque/m, output - end - - should "be filtered using show --id argument" do - output = nil - assert_nothing_raised do - output = client('hardware_profiles show --id m1-large') - end - assert_no_warning output - assert_match /^m1-large/, output - assert_no_match /^m1-small/, output - end - end -end - -class CmdImagesTest < Test::Unit::TestCase - - context "a images" do - - should "be listed using images argument" do - output = nil - assert_nothing_raised do - output = client('images') - end - assert_no_warning output - assert_match /^img2/m, output - assert_match /^img1/m, output - assert_match /^img3/m, output - end - - should "be filtered using show --id argument" do - output = nil - assert_nothing_raised do - output = client('images show --id img2') - end - assert_no_warning output - assert_match /^img2/m, output - assert_no_match /^img1/m, output - end - - should "be filtered using --arch argument" do - output = nil - assert_nothing_raised do - output = client('images --arch x86_64') - end - assert_no_warning output - assert_match /x86_64/, output - assert_no_match /i386/, output - end - - end - -end - -class CmdInstancesTest < Test::Unit::TestCase - - context 'an instances' do - - should 'be listed using instances argument' do - output = nil - assert_nothing_raised do - output = client('instances') - end - assert_no_warning output - assert_match /^inst1/, output - end - - should 'be filtered using --id argument' do - output = nil - assert_nothing_raised do - output = client('instances show --id inst0') - end - assert_no_warning output - assert_match /^inst0/m, output - assert_no_match /^inst1/m, output - end - - end - - context 'an instance' do - - should 'be created supplying --image-id argument and -p argument' do - output = nil - assert_nothing_raised do - output = client('instances create --image-id img1 -p m1-small') - end - assert_no_warning output - assert_match /^inst(\d+)/, output - @@created_instance_id = output.match(/^inst(\d+)/).to_a.first - end - - should 'be rebooted using reboot operation' do - output = nil - assert_nothing_raised do - output = client("instances reboot --id #{@@created_instance_id}") - end - assert_no_warning output - assert_match /#{@@created_instance_id}/, output - assert_match /RUNNING/, output - end - - should 'be stopped using stop operation' do - output = nil - assert_nothing_raised do - output = client("instances stop --id #{@@created_instance_id}") - end - assert_no_warning output - assert_match /#{@@created_instance_id}/, output - assert_match /STOPPED/, output - end - - should 'be destroyed using destroy operation' do - output = nil - assert_nothing_raised do - output = client("instances destroy --id #{@@created_instance_id}") - end - assert_no_warning output - end - - end -end diff --git a/client/tests/common.rb b/client/tests/common.rb deleted file mode 100644 index dc5b475..0000000 --- a/client/tests/common.rb +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright (C) 2009-2011 Red Hat, Inc. -# -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. The -# ASF licenses this file to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance with the -# License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# - -module DeltaCloud - module TestHelper - - include Test::Unit::Assertions - - API_URL = "http://localhost:3001/api" - API_USER = "mockuser" - API_PASWD = "mockpassword" - - def base_client(args) - `bin/deltacloudc #{args}` - end - - def client(args) - args = "-u http://mockuser:mockpassword@localhost:3001/api " + args - base_client(args) - end - - def assert_no_warning(output) - assert_no_match /\[WARNING\] Method unsupported by API: '(\w+)'/, output - end - - end -end diff --git a/clients/console/deltacloudc b/clients/console/deltacloudc new file mode 100755 index 0000000..adc5d11 --- /dev/null +++ b/clients/console/deltacloudc @@ -0,0 +1,245 @@ +#!/usr/bin/env ruby +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +require 'rubygems' +require 'optparse' +require 'uri' +require 'deltacloud' +require 'plain_formatter' + +include DeltaCloud::PlainFormatter + +options = { + :verbose => false +} + +@optparse = OptionParser.new do |opts| + +opts.banner = <<BANNER +Usage: +deltacloudc collection operation [options] + +URL format: +API_URL=http://[user]:[password]@[api_url][port][/uri] + OR +API_URL=http://[api_url][port][/uri] with -U and -P for credentials + +Examples: + + 1. To list collections for deltacloud api on port 3333 of server deltacloud.foo + + deltacloudc -l -u http://user:passw...@deltacloud.foo:3333/api + + 2. To list the operations for the 'images' collection: + + deltacloudc images -l -U username -P password -u http://deltacloud.foo:3333/api + + 3. To list all images (i.e. call the 'index' operation of 'images'): + + deltacloudc images index -u http://user:passw...@deltacloud.foo:3333/api + + 4. To get the details of image '5': + + deltacloudc images show -i 5 -U username -P password -u http://deltacloud.foo:3333/api + +Options: +BANNER + opts.on( '-i', '--id ID', 'ID for operation') { |id| options[:id] = id } + opts.on( '-d', '--image-id ID', 'Image ID') { |id| options[:image_id] = id } + opts.on( '-b', '--bucket-id ID', 'Bucket ID') {|id| options[:bucket_id] = id } + opts.on( '-r', '--bucket-location NAME', 'Bucket location') {|name| options[:bucket_location] = name } + opts.on( '-a', '--arch ARCH', 'Architecture (x86, x86_64)') { |id| options[:architecture] = id } + opts.on( '-p', '--hardware-profile HARDWARE_PROFILE', 'Hardware Profile') { |id| options[:hwp_id] = id } + opts.on( '-n', '--name NAME', 'Name (for instance eg.)') { |name| options[:name] = name } + opts.on( '-s', '--state STATE', 'Instance state (RUNNING, STOPPED)') { |state| options[:state] = state } + opts.on( '-u', '--url URL', 'API url ($API_URL variable)') { |url| options[:api_url] = url } + opts.on( '-U', '--user USER', 'API username ($API_USERNAME variable)') { |u| options[:api_user] = u } + opts.on( '-P', '--password PASSWORD', 'API password ($API_PASSWORD variable)') { |p| options[:api_password] = p } + opts.on( '-l', '--list', 'List collections/operations') { |id| options[:list] = true } + opts.on( '-h', '--help', 'Display this screen' ) { puts @optparse; Kernel.exit! } + opts.on( '-v', '--version', 'Display API version' ) { options[:version]=true } + opts.on( '-V', '--verbose', 'Print verbose messages' ) { options[:verbose]=true } + opts.on( '-f', '--file-path PATH', 'local path for new blob data') {|path| options[:file_path]=path } + opts.on( '-m', '--blob-metadata k1=v1, k2=v2', 'Comma seperated k=v pairs for blob metadata (for create operation)') do |meta| + blob_meta = {} + meta.gsub!(/ /,"") + meta.scan(/(\w+)=(\w+)/).map {|k,v| blob_meta[k] = v } + options[:blob_metadata] = blob_meta + end +end + +def invalid_usage(error_msg='') + puts "\n ERROR: #{error_msg} \n\n" + puts @optparse + exit(1) +end + +begin + @optparse.parse! +rescue Exception => e + invalid_usage(e.message) +end + +# First try to get API_URL from environment +options[:api_url] = ENV['API_URL'] if options[:api_url].nil? + +if(options[:api_url].nil?) + invalid_usage("You must supply the url to the deltacloud api; either use '-u' flag or set the 'API_URL' environment variable") +end + +url = URI.parse(options[:api_url]) +api_url = "http://#{url.host}#{url.port ? ":#{url.port}" : ''}#{url.path}" + +options[:collection] = ARGV[0] +options[:operation] = ARGV[1] + +# Connect to Deltacloud API and fetch all entry points +client = DeltaCloud.new(options[:api_user] || url.user || ENV['API_USER'], options[:api_password] || url.password || ENV['API_PASSWORD'], api_url) +collections = client.entry_points.keys + +# Exclude collection which don't have methods in client library yet +collections.delete(:instance_states) +#add blob collection if buckets is present +collections << :blob if collections.include?(:buckets) + +# If list parameter passed print out available collection +# with API documentation +if options[:list] and options[:collection].nil? + collections.each do |c| + puts sprintf("%-22s", c.to_s[0, 22]) + end + exit(0) +end + +# If collection parameter is present and user requested list +# print all operation defined for collection with API documentation +if options[:list] and options[:collection] + #deal with blobs again - bypass 'normal' docs procedure + if options[:collection] =~ /blob/i + puts "create \t\t: Create a new blob in a specified bucket (POST /api/buckets/:bucket)" + puts "destroy \t: Delete a specified blob in a specified bucket (DELETE /api/buckets/:bucket/:blob)" + puts "show \t\t: Get details of a specified blob in a specified bucket (GET /api/buckets/:bucket/:blob)" + puts "data \t\t: Get the contents of a specified blob - the blob data content itself" + exit(0) + end + doc = client.documentation(options[:collection]) + doc.operations.each do |c| + puts sprintf("%-20s: %s", c.operation, c.description) + end + exit(0) +end + +if options[:version] + puts "Deltacloud API(#{client.driver_name}) 0.1" + exit(0) +end + +# List items from collection (typically /instances) +# Do same if 'index' operation is set +if options[:collection] and ( options[:operation].nil? or options[:operation].eql?('index') ) + invalid_usage("Unknown collection: #{options[:collection]}") unless collections.include?(options[:collection].to_sym) +#cannot list blobs - can only show a specific blob + invalid_usage("You must specify a particular blob with -i and a particular bucket with -b") if options[:collection] =~ (/blob/i) + params = {} + params.merge!(:architecture => options[:architecture]) if options[:architecture] + params.merge!(:id => options[:id]) if options[:id] + params.merge!(:state => options[:state]) if options[:state] + client.send(options[:collection].to_s, params).each do |model| + puts format(model) + end + exit(0) +end + +if options[:collection] and options[:operation] + + invalid_usage("Unknown collection: #{options[:collection]}") unless collections.include?(options[:collection].to_sym) + + params = {} + params.merge!(:id => options[:id]) if options[:id] + + # If collection is set and requested operation is 'show' just 'singularize' + # collection name and print item with specified id (-i parameter) + #Blobs are a special case so deal with first - + if options[:collection] =~ (/blob/i) + invalid_usage("Please specify the bucket for this blob using the -b option") unless options[:bucket_id] + invalid_usage("Missing blob ID, please specify with -i option") unless options[:id] + params = {} + params.merge!(:id => options[:id], 'bucket' => options[:bucket_id]) + params.merge!('metadata'=>options[:blob_metadata]) unless options[:blob_metadata].nil? + case options[:operation] + when 'show' then puts format(client.send( options[:collection], params)) + when 'data' then puts client.blob_data(params) + when 'create' then + invalid_usage("Specify the location of the new blob data (full local path) using -f option") unless options[:file_path] + params.merge!('file_path'=>options[:file_path]) + blob = client.create_blob(params) + puts format(blob) + when 'destroy' then client.destroy_blob(params) + else invalid_usage("Please specify a valid operation for the blob collection - try -l to see available operations") + end + exit(0) + end + + if options[:operation].eql?('show') + invalid_usage("Missing ID, must be provided with --id") unless options[:id] + puts format(client.send(options[:collection].gsub(/s$/, ''), options[:id])) + exit(0) + end + + # If collection is set and requested operation is create new instance, + # --image-id, --hardware-profile and --name parameters are used + # Returns created instance in plain form + if options[:collection].eql?('instances') and options[:operation].eql?('create') + invalid_usage("Missing image-id") unless options[:image_id] + if options[:name] and ! client.feature?(:instances, :user_name) + invalid_usage("Driver does not support user-supplied name") + end + params.merge!(:name => options[:name]) if options[:name] + params.merge!(:image_id => options[:image_id]) if options[:image_id] + params.merge!(:hwp_id => options[:hwp_id]) if options[:hwp_id] + instance = client.create_instance(options[:image_id], params) + puts format(instance) + exit(0) + end + + #Create and Destroy a bucket - require the bucket id: + if options[:collection].eql?('buckets') + if options[:operation].eql?('create') + invalid_usage("Please specify an id for the new bucket with -i") unless options[:id] + bucket = client.create_bucket('id'=>options[:id], 'bucket_location'=>options[:bucket_location]) + puts format(bucket) + elsif options[:operation].eql?('destroy') + invalid_usage("Please specify the bucket you wish to destroy with -i") unless options[:id] + client.destroy_bucket('id'=>options[:id]) + else + invalid_usage("Please specify a valid operation on buckets - use -l to see valid operations") + end + exit(0) + end + + # All other operations above collections is done there: + if options[:collection].eql?('instances') + instance = client.instance(options[:id]) + instance.send("#{options[:operation]}!".to_s) + instance = client.instance(options[:id]) + puts format(instance) + exit(0) + end +end + +# If all above passed (eg. no parameters) +puts @optparse diff --git a/clients/console/plain_formatter.rb b/clients/console/plain_formatter.rb new file mode 100644 index 0000000..2e2ada3 --- /dev/null +++ b/clients/console/plain_formatter.rb @@ -0,0 +1,145 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +module DeltaCloud + module PlainFormatter + module FormatObject + + class Base + def initialize(obj) + @obj = obj + end + end + + class Key < Base + def format + sprintf("%-10s | %-60s", + @obj.id[0,10], + @obj.fingerprint + ) + end + end + + class Image < Base + def format + sprintf("%-10s | %-20s | %-6s | %-20s | %15s", + @obj.id[0,10], + @obj.name ? @obj.name[0, 20]: 'unknown', + @obj.architecture[0,6], + @obj.description[0,20], + @obj.owner_id[0,15] + ) + end + end + + class Realm < Base + def format + sprintf("%-10s | %-15s | %-5s | %10s GB", + @obj.id[0, 10], + @obj.name[0, 15], + @obj.state[0,5], + @obj.limit.to_s[0,10] + ) + end + end + + class HardwareProfile < Base + def format + architecture = @obj.architecture ? @obj.architecture.value[0,6] : 'opaque' + memory = @obj.memory ? @obj.memory.value.to_s[0,10] : 'opaque' + storage = @obj.storage ? @obj.storage.value.to_s[0,10] : 'opaque' + sprintf("%-15s | %-6s | %10s | %10s ", @obj.id[0, 15], + architecture , memory, storage) + end + end + + class Instance < Base + def format + sprintf("%-15s | %-15s | %-15s | %10s | %32s | %32s", + @obj.id ? @obj.id.to_s[0,15] : '-', + @obj.name ? @obj.name.to_s[0,15] : 'unknown', + @obj.image.name ? @obj.image.name.to_s[0,15] : 'unknown', + @obj.state ? @obj.state.to_s[0,10] : 'unknown', + @obj.public_addresses.collect { |a| a[:address] }.join(',')[0,32], + @obj.private_addresses.collect { |a| a[:address] }.join(',')[0,32] + ) + end + end + + class StorageVolume < Base + def format + sprintf("%-10s | %15s GB | %-10s | %-10s | %-15s", + @obj.id[0,10], + @obj.capacity ? @obj.capacity.to_s[0,15] : 'unknown', + @obj.device ? @obj.device[0,10] : 'unknown', + @obj.respond_to?('state') ? @obj.state[0,10] : 'unknown', + @obj.instance ? @obj.instance.name[0,15] : 'unknown' + ) + end + end + + class StorageSnapshot < Base + def format + sprintf("%-10s | %-15s | %-6s | %15s", + @obj.id[0,10], + @obj.storage_volume.respond_to?('name') ? @obj.storage_volume.name[0, 15] : 'unknown', + @obj.state ? @obj.state[0,10] : 'unknown', + @obj.created ? @obj.created[0,19] : 'unknown' + ) + end + end + + class Bucket < Base + def format + sprintf("%-s | %-s | %-s | %-s", + @obj.id, + @obj.name, + @obj.size ? @obj.size : "0", + @obj.instance_variables.include?("@blob_list") ? @obj.blob_list : "" + ) + end + end + + class Blob < Base + def format + sprintf("%-s | %-s | %-d | %-s | %-s | %-s " , + @obj.id, + @obj.bucket, + @obj.content_length, + @obj.content_type, + @obj.last_modified, + @obj.user_metadata + ) + end + end + + class Driver < Base + def format + sprintf("%-15s | %-15s | %-s", + @obj.id, + @obj.name, + @obj.url) + end + end + end + + def format(obj) + object_name = obj.class.name.classify.gsub(/^DeltaCloud::API::(\w+)::/, '') + format_class = DeltaCloud::PlainFormatter::FormatObject.const_get(object_name) + format_class.new(obj).format + end + + end +end diff --git a/clients/console/tests/cmd.rb b/clients/console/tests/cmd.rb new file mode 100644 index 0000000..90fb853 --- /dev/null +++ b/clients/console/tests/cmd.rb @@ -0,0 +1,214 @@ +# Copyright (C) 2009-2011 Red Hat, Inc. +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +require 'rubygems' +require 'shoulda' +require 'tests/common' + +include DeltaCloud::TestHelper + +class CommandLineTest < Test::Unit::TestCase + context "a command line client" do + + should "respond to --help argument" do + assert_nothing_raised do + base_client('--help') + end + end + + should "return API version with --version argument" do + assert_match /Deltacloud API\(mock\) (\d+)\.(\d+)/, client('--version') + end + + should "return list all collections with --list argument" do + output = nil + assert_nothing_raised do + output = client('--list') + end + assert_not_nil output + assert_match /images/, output + assert_match /instances/, output + assert_match /realms/, output + assert_match /hardware_profiles/, output + end + + should 'respond with proper error when accessing unknow collection' do + output = client('unknown_collection') + assert_match /ERROR: Unknown collection:/, output + end + + end +end + +class CmdRealmTest < Test::Unit::TestCase + context "a realms" do + + should "be listed using realms argument" do + output = nil + assert_nothing_raised do + output = client('realms') + end + assert_match /^us/m, output + assert_match /^eu/m, output + end + + should "be filtered using show --id argument" do + output = nil + assert_nothing_raised do + output = client('realms show --id us') + end + assert_match /^us/, output + assert_no_match /^eu/, output + end + + end +end + +class CmdHardwareProfilesTest < Test::Unit::TestCase + context "a hardware profiles" do + + should "be listed using hardware_profiles argument" do + output = nil + assert_nothing_raised do + output = client('hardware_profiles') + end + assert_no_warning output + assert_match /^m1-small/m, output + assert_match /^m1-large/m, output + assert_match /^m1-xlarge/m, output + assert_match /^opaque/m, output + end + + should "be filtered using show --id argument" do + output = nil + assert_nothing_raised do + output = client('hardware_profiles show --id m1-large') + end + assert_no_warning output + assert_match /^m1-large/, output + assert_no_match /^m1-small/, output + end + end +end + +class CmdImagesTest < Test::Unit::TestCase + + context "a images" do + + should "be listed using images argument" do + output = nil + assert_nothing_raised do + output = client('images') + end + assert_no_warning output + assert_match /^img2/m, output + assert_match /^img1/m, output + assert_match /^img3/m, output + end + + should "be filtered using show --id argument" do + output = nil + assert_nothing_raised do + output = client('images show --id img2') + end + assert_no_warning output + assert_match /^img2/m, output + assert_no_match /^img1/m, output + end + + should "be filtered using --arch argument" do + output = nil + assert_nothing_raised do + output = client('images --arch x86_64') + end + assert_no_warning output + assert_match /x86_64/, output + assert_no_match /i386/, output + end + + end + +end + +class CmdInstancesTest < Test::Unit::TestCase + + context 'an instances' do + + should 'be listed using instances argument' do + output = nil + assert_nothing_raised do + output = client('instances') + end + assert_no_warning output + assert_match /^inst1/, output + end + + should 'be filtered using --id argument' do + output = nil + assert_nothing_raised do + output = client('instances show --id inst0') + end + assert_no_warning output + assert_match /^inst0/m, output + assert_no_match /^inst1/m, output + end + + end + + context 'an instance' do + + should 'be created supplying --image-id argument and -p argument' do + output = nil + assert_nothing_raised do + output = client('instances create --image-id img1 -p m1-small') + end + assert_no_warning output + assert_match /^inst(\d+)/, output + @@created_instance_id = output.match(/^inst(\d+)/).to_a.first + end + + should 'be rebooted using reboot operation' do + output = nil + assert_nothing_raised do + output = client("instances reboot --id #{@@created_instance_id}") + end + assert_no_warning output + assert_match /#{@@created_instance_id}/, output + assert_match /RUNNING/, output + end + + should 'be stopped using stop operation' do + output = nil + assert_nothing_raised do + output = client("instances stop --id #{@@created_instance_id}") + end + assert_no_warning output + assert_match /#{@@created_instance_id}/, output + assert_match /STOPPED/, output + end + + should 'be destroyed using destroy operation' do + output = nil + assert_nothing_raised do + output = client("instances destroy --id #{@@created_instance_id}") + end + assert_no_warning output + end + + end +end diff --git a/clients/console/tests/common.rb b/clients/console/tests/common.rb new file mode 100644 index 0000000..dc5b475 --- /dev/null +++ b/clients/console/tests/common.rb @@ -0,0 +1,42 @@ +# Copyright (C) 2009-2011 Red Hat, Inc. +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +module DeltaCloud + module TestHelper + + include Test::Unit::Assertions + + API_URL = "http://localhost:3001/api" + API_USER = "mockuser" + API_PASWD = "mockpassword" + + def base_client(args) + `bin/deltacloudc #{args}` + end + + def client(args) + args = "-u http://mockuser:mockpassword@localhost:3001/api " + args + base_client(args) + end + + def assert_no_warning(output) + assert_no_match /\[WARNING\] Method unsupported by API: '(\w+)'/, output + end + + end +end -- 1.7.10.2