On 07/08/12 12:21, mfoj...@redhat.com wrote: > From: Michal Fojtik <mfoj...@redhat.com> > >
ACK and pushed on request from Michal > 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 >