aws-2.10.2/0000755000175000017500000000000012511515441011101 5ustar tnnntnnnaws-2.10.2/metadata.yml0000644000175000017500000000765512511515441013421 0ustar tnnntnnn--- !ruby/object:Gem::Specification name: aws version: !ruby/object:Gem::Version version: 2.10.2 platform: ruby authors: - Travis Reeder - Chad Arimura - RightScale autorequire: bindir: bin cert_chain: [] date: 2014-06-27 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: uuidtools requirement: !ruby/object:Gem::Requirement requirements: - - '>=' - !ruby/object:Gem::Version version: '0' type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - '>=' - !ruby/object:Gem::Version version: '0' - !ruby/object:Gem::Dependency name: xml-simple requirement: !ruby/object:Gem::Requirement requirements: - - '>=' - !ruby/object:Gem::Version version: '0' type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - '>=' - !ruby/object:Gem::Version version: '0' - !ruby/object:Gem::Dependency name: http_connection requirement: !ruby/object:Gem::Requirement requirements: - - '>=' - !ruby/object:Gem::Version version: '0' type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - '>=' - !ruby/object:Gem::Version version: '0' description: AWS Ruby Library for interfacing with Amazon Web Services including EC2, S3, SQS, SimpleDB and most of their other services as well. By http://www.appoxy.com email: - travis@appoxy.com executables: [] extensions: [] extra_rdoc_files: [] files: - .gitignore - Gemfile - README.markdown - Rakefile - aws.gemspec - lib/acf/acf_interface.rb - lib/aws.rb - lib/awsbase/aws_response_array.rb - lib/awsbase/awsbase.rb - lib/awsbase/benchmark_fix.rb - lib/awsbase/errors.rb - lib/awsbase/parsers.rb - lib/awsbase/require_relative.rb - lib/awsbase/utils.rb - lib/ec2/ec2.rb - lib/ec2/mon_interface.rb - lib/elb/elb_interface.rb - lib/iam/iam.rb - lib/rds/rds.rb - lib/right_aws.rb - lib/s3/bucket.rb - lib/s3/grantee.rb - lib/s3/key.rb - lib/s3/s3.rb - lib/s3/s3_interface.rb - lib/sdb/active_sdb.rb - lib/sdb/sdb_interface.rb - lib/ses/ses.rb - lib/sqs/sqs.rb - lib/sqs/sqs_interface.rb - lib/version.rb - test/acf/test_acf.rb - test/acf/test_helper.rb - test/ec2/test_ec2.rb - test/ec2/test_helper.rb - test/ec2/test_mon.rb - test/elb/test_elb.rb - test/http_connection.rb - test/iam/test_iam.rb - test/rds/test_rds.rb - test/s3/s3_test_base.rb - test/s3/test_helper.rb - test/s3/test_s3.rb - test/s3/test_s3_class.rb - test/s3/test_s3_rights.rb - test/s3/test_s3_stubbed.rb - test/sdb/test_helper.rb - test/sdb/test_sdb.rb - test/sdb/unicode.txt - test/ses/test_ses.rb - test/sqs/test_helper.rb - test/sqs/test_sqs.rb - test/test_credentials.rb homepage: http://github.com/appoxy/aws/ licenses: [] metadata: {} post_install_message: rdoc_options: [] require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement requirements: - - '>=' - !ruby/object:Gem::Version version: '1.8' required_rubygems_version: !ruby/object:Gem::Requirement requirements: - - '>=' - !ruby/object:Gem::Version version: 1.3.6 requirements: [] rubyforge_project: rubygems_version: 2.2.2 signing_key: specification_version: 4 summary: AWS Ruby Library for interfacing with Amazon Web Services including EC2, S3, SQS, SimpleDB and most of their other services as well. By http://www.appoxy.com test_files: - test/acf/test_acf.rb - test/acf/test_helper.rb - test/ec2/test_ec2.rb - test/ec2/test_helper.rb - test/ec2/test_mon.rb - test/elb/test_elb.rb - test/http_connection.rb - test/iam/test_iam.rb - test/rds/test_rds.rb - test/s3/s3_test_base.rb - test/s3/test_helper.rb - test/s3/test_s3.rb - test/s3/test_s3_class.rb - test/s3/test_s3_rights.rb - test/s3/test_s3_stubbed.rb - test/sdb/test_helper.rb - test/sdb/test_sdb.rb - test/sdb/unicode.txt - test/ses/test_ses.rb - test/sqs/test_helper.rb - test/sqs/test_sqs.rb - test/test_credentials.rb aws-2.10.2/test/0000755000175000017500000000000012511515441012060 5ustar tnnntnnnaws-2.10.2/test/rds/0000755000175000017500000000000012511515441012650 5ustar tnnntnnnaws-2.10.2/test/rds/test_rds.rb0000644000175000017500000001414512511515441015031 0ustar tnnntnnnrequire 'test/unit' require File.dirname(__FILE__) + '/../../lib/aws' require 'rds/rds' require 'pp' require File.dirname(__FILE__) + '/../test_credentials.rb' class TestRds < Test::Unit::TestCase # Some of RightEc2 instance methods concerning instance launching and image registration # are not tested here due to their potentially risk. def setup TestCredentials.get_credentials @rds = Aws::Rds.new(TestCredentials.aws_access_key_id, TestCredentials.aws_secret_access_key) @identifier = 'test-db-instance1b' # deleting this one.... #@identifier2 = 'my-db-instance2' end def test_00_describe_db_instances_empty instances = @rds.describe_db_instances # puts "instances_result=" + instances_result.inspect # instances = instances_result["DescribeDBInstancesResult"]["DBInstances"]["DBInstance"] puts "instances count = " + instances.count.to_s puts 'instances=' + instances.inspect assert instances.size == 0 end def test_01_create_db_instance begin db_instance3 = @rds.create_db_instance('bad_test_key', "db.m1.small", 5, "master", "masterpass") rescue => ex #puts "msg=" + ex.message #puts "response=" + ex.response assert ex.message[0, "InvalidParameterValue".size] == "InvalidParameterValue" end db_instance = @rds.create_db_instance(@identifier, "db.m1.small", 5, "master", "masterpass") assert db_instance[:db_instance_status] == "creating" start = Time.now tries=0 catch (:done) do while tries < 100 instances = @rds.describe_db_instances #puts "INSTANCES -----> " + instances.inspect instances.each do |i| db_status = i[:db_instance_status] puts 'i=' + db_status.to_s next unless i[:db_instance_identifier] == @identifier throw :done if db_status == "available" puts "Database not ready yet.... attempt #{tries.to_s} of 100, db state --> #{i[:db_instance_status].to_s}" tries += 1 sleep 5 end end end puts "Duration to start db instance: #{Time.now-start}" end def test_02_describe_db_instances instances = @rds.describe_db_instances # puts "instances_result=" + instances_result.inspect # instances = instances_result["DescribeDBInstancesResult"]["DBInstances"]["DBInstance"] puts "instances count = " + instances.count.to_s puts 'instances=' + instances.inspect assert instances.size > 0 i_describe = nil instances.each do |rdi| puts 'rdi=' + rdi.inspect i_describe = rdi if rdi[:db_instance_identifier] == @identifier end assert i_describe puts 'response_metadata=' + instances.response_metadata.inspect assert instances.response_metadata assert instances.response_metadata[:request_id] end def test_03_describe_security_groups security_groups = @rds.describe_db_security_groups() puts "security_groups=" + security_groups.inspect default_present = false assert security_groups.is_a?(Array) security_groups.each do |security_group| security_group.inspect if security_group[:db_security_group_name] == "default" default_present=true end assert security_group[:ec2_security_groups].is_a? Array end assert default_present end def test_04_authorize_security_groups_ingress # Create # security_groups = @rds.describe_db_security_groups # @security_info = security_groups[0] @rds.authorize_db_security_group_ingress_range("default", "122.122.122.122/12") # Check security_groups = @rds.describe_db_security_groups @security_info = security_groups[0] ip_found = @security_info.inspect.include? "122.122.122.122/12" assert ip_found end def test_05_delete_db_instance @rds.delete_db_instance(@identifier) # todo: can't delete unless it's in "available" state #@rds.delete_db_instance(@identifier2) sleep 3 instances = @rds.describe_db_instances(:db_instance_identifier=>@identifier) #puts "instances_result=" + instances_result.inspect instances.each do |i| next unless i[:db_instance_identifier] == @identifier db_status = i[:db_instance_status] puts "Trying to delete and getting i[DBInstanceStatus] -----------> " + db_status @rds.delete_db_instance(i[:db_instance_identifier]) if db_status == "available" assert db_status == "deleting" end sleep 2 end def test_06_create_security_groups group_present=false @rds.create_db_security_group("new_sample_group", "new_sample_group_description") security_groups = @rds.describe_db_security_groups security_groups.each do |security_group| if (security_group[:db_security_group_name]=="new_sample_group")&&(security_group[:db_security_group_description]=="new_sample_group_description") group_present = true end end assert group_present end def test_07_revoking_security_groups_ingress # sleep 15 @rds.revoke_db_security_group_ingress("default", "122.122.122.122/12") sleep 2 security_groups = @rds.describe_db_security_groups revoking = security_groups[0].inspect.include? "revoking" assert revoking end def test_08_delete_security_group group_present=false @rds.delete_db_security_group("new_sample_group") sleep 2 security_groups = @rds.describe_db_security_groups security_groups.each do |security_group| if (security_group[:db_security_group_name]=="new_sample_group") group_present=true end end assert !group_present end endaws-2.10.2/test/sdb/0000755000175000017500000000000012511515441012630 5ustar tnnntnnnaws-2.10.2/test/sdb/test_helper.rb0000644000175000017500000000010612511515441015470 0ustar tnnntnnnrequire 'test/unit' require File.dirname(__FILE__) + '/../../lib/aws' aws-2.10.2/test/sdb/test_sdb.rb0000644000175000017500000001614312511515441014771 0ustar tnnntnnnrequire File.dirname(__FILE__) + '/test_helper.rb' require File.dirname(__FILE__) + '/../test_credentials.rb' class TestSdb < Test::Unit::TestCase def setup TestCredentials.get_credentials STDOUT.sync = true @domain = 'right_sdb_awesome_test_domain' @item = 'toys' @attr = {'Jon' => %w{beer car}} # Interface instance @sdb = Aws::SdbInterface.new(TestCredentials.aws_access_key_id, TestCredentials.aws_secret_access_key) end SDB_DELAY = 2 def wait(delay, msg='') print "waiting #{delay} seconds #{msg}" while delay>0 do delay -= 1 print '.' sleep 1 end puts end #--------------------------- # Aws::SdbInterface #--------------------------- def test_00_delete_domain # delete the domain to reset all the things assert @sdb.delete_domain(@domain), 'delete_domain fail' wait SDB_DELAY, 'after domain deletion' end def test_01_create_domain # check that domain does not exist assert !@sdb.list_domains[:domains].include?(@domain) # create domain assert @sdb.create_domain(@domain), 'create_domain fail' wait SDB_DELAY, 'after domain creation' # check that we have received new domain from Amazin assert @sdb.list_domains[:domains].include?(@domain) end def test_02_put_attributes # put attributes assert @sdb.put_attributes(@domain, @item, @attr) wait SDB_DELAY, 'after putting attributes' end def test_03_get_attributes # get attributes values = @sdb.get_attributes(@domain, @item)[:attributes]['Jon'].to_a.sort # compare to original list assert_equal values, @attr['Jon'].sort end def test_04_add_attributes # add new attribute new_value = 'girls' @sdb.put_attributes @domain, @item, {'Jon' => new_value} wait SDB_DELAY, 'after putting attributes' # get attributes ('girls' must be added to already existent attributes) values = @sdb.get_attributes(@domain, @item)[:attributes]['Jon'].to_a.sort assert_equal values, (@attr['Jon'] << new_value).sort end def test_05_replace_attributes # replace attributes @sdb.put_attributes @domain, @item, {'Jon' => 'pub'}, :replace wait SDB_DELAY, 'after replacing attributes' # get attributes (all must be removed except of 'pub') values = @sdb.get_attributes(@domain, @item)[:attributes]['Jon'] assert_equal values, ['pub'] end def test_06_delete_attribute # add value 'girls' and 'vodka' to 'Jon' @sdb.put_attributes @domain, @item, {'Jon' => ['girls', 'vodka']} wait SDB_DELAY, 'after adding attributes' # get attributes ('girls' and 'vodka' must be added 'pub') values = @sdb.get_attributes(@domain, @item)[:attributes]['Jon'].to_a.sort assert_equal values, ['girls', 'pub', 'vodka'] # delete a single value 'girls' from attribute 'Jon' @sdb.delete_attributes @domain, @item, 'Jon' => ['girls'] wait SDB_DELAY, 'after the deletion of attribute' # get attributes ('girls' must be removed) values = @sdb.get_attributes(@domain, @item)[:attributes]['Jon'] assert_equal values, ['pub', 'vodka'] # delete all values from attribute 'Jon' @sdb.delete_attributes @domain, @item, ['Jon'] wait SDB_DELAY, 'after the deletion of attributes' # get attributes (values must be empty) values = @sdb.get_attributes(@domain, @item)[:attributes]['Jon'] assert_equal values, nil end def test_07_delete_item @sdb.put_attributes @domain, @item, {'Volodya' => ['girls', 'vodka']} wait SDB_DELAY, 'after adding attributes' # get attributes ('girls' and 'vodka' must be there) values = @sdb.get_attributes(@domain, @item)[:attributes]['Volodya'].to_a.sort assert_equal ['girls', 'vodka'], values # delete an item @sdb.delete_attributes @domain, @item sleep 1 # get attributes (values must be empty) values = @sdb.get_attributes(@domain, @item)[:attributes]['Volodya'] assert_nil values end def test_08_batch_put_and_delete_attributes items = [] 10.times do |i| items << Aws::SdbInterface::Item.new("#{@item}_#{i}", {:name=>"name_#{i}"}, true) end @sdb.batch_put_attributes @domain, items sleep 1 @sdb.batch_delete_attributes @domain, items.collect { |x| x.item_name } end def test_11_signature_version_2 sdb = Aws::SdbInterface.new(TestCredentials.aws_access_key_id, TestCredentials.aws_secret_access_key, :signature_version => '2') domains = nil assert_nothing_thrown "Failed to use signature V2" do domains = sdb.list_domains end assert domains end def test_12_unicode # This was creating a bad signature s = '' File.open("unicode.txt", "r") { |f| s = f.read } # s = s.force_encoding("UTF-8") puts 's=' + s.inspect puts "encoding? " + s.encoding.name # s = s.encode("ASCII") # todo: I'm thinking just iterate through characters and swap out ones that aren't in ascii range. @sdb.put_attributes @domain, @item, {"badname"=>[s]} sleep 1 value = @sdb.get_attributes(@domain, @item)[:attributes]['badname'][0] puts 'value=' + value.inspect # assert value == s # NOT WORKING, not even sure this is a valid test though end def test_15_array_of_attrs item = 'multiples' assert_nothing_thrown "Failed to put multiple attrs" do @sdb.put_attributes(@domain, item, {:one=>1, :two=>2, :three=>3}) end end def test_16_zero_len_attrs item = 'zeroes' assert_nothing_thrown "Failed to put zero-length attributes" do @sdb.put_attributes(@domain, item, {:one=>"", :two=>"", :three=>""}) end end def test_17_nil_attrs item = 'nils' res = nil assert_nothing_thrown do @sdb.put_attributes(@domain, item, {:one=>nil, :two=>nil, :three=>'chunder'}) end sleep 1 assert_nothing_thrown do res = @sdb.get_attributes(@domain, item) end assert_nil(res[:attributes]['one'][0]) assert_nil(res[:attributes]['two'][0]) assert_not_nil(res[:attributes]['three'][0]) end def test_18_url_escape item = 'urlescapes' content = {:a=>"one & two & three", :b=>"one ? two / three"} @sdb.put_attributes(@domain, item, content) res = @sdb.get_attributes(@domain, item) assert_equal(content[:a], res[:attributes]['a'][0]) assert_equal(content[:b], res[:attributes]['b'][0]) end def test_19_put_attrs_by_post item = 'reqgirth' i = 0 sa = "" while (i < 64) do sa += "aaaaaaaa" i += 1 end @sdb.put_attributes(@domain, item, {:a => sa, :b => sa, :c => sa, :d => sa, :e => sa}) end # from https://github.com/appoxy/aws/issues#issue/15 def test_21_query_with_single_quotes sel = "select * from `#{@domain}` where one=#{@sdb.escape("ain't good")}" puts sel @sdb.select(sel) sel = "select * from `#{@domain}` where one=#{@sdb.escape('ain\t good')}" puts sel @sdb.select(sel) end # Keep this test last, because it deletes the domain... def test_40_delete_domain assert @sdb.delete_domain(@domain), 'delete_domain fail' wait SDB_DELAY, 'after domain deletion' # check that domain does not exist assert !@sdb.list_domains[:domains].include?(@domain) end endaws-2.10.2/test/sdb/unicode.txt0000644000175000017500000000007512511515441015021 0ustar tnnntnnnHelmut Fischer GmbH Institut für Elektronik und Messtechnik aws-2.10.2/test/acf/0000755000175000017500000000000012511515441012611 5ustar tnnntnnnaws-2.10.2/test/acf/test_helper.rb0000644000175000017500000000011412511515441015450 0ustar tnnntnnnrequire 'test/unit' require File.dirname(__FILE__) + '/../../lib/right_aws' aws-2.10.2/test/acf/test_acf.rb0000644000175000017500000001076112511515441014733 0ustar tnnntnnnrequire File.dirname(__FILE__) + '/test_helper.rb' require File.dirname(__FILE__) + '/../test_credentials.rb' class TestAcf < Test::Unit::TestCase RIGHT_OBJECT_TEXT = 'Right test message' STDOUT.sync = true def setup TestCredentials.get_credentials @acf= Aws::AcfInterface.new(TestCredentials.aws_access_key_id, TestCredentials.aws_secret_access_key) @s3 = Aws::S3.new(TestCredentials.aws_access_key_id, TestCredentials.aws_secret_access_key) @bucket_name = "right-acf-awesome-test-bucket-0001" @bucket_domain = "#{@bucket_name}.s3.amazonaws.com" end def test_01_list_distributions_part1 distributions = nil assert_nothing_raised(Aws::AwsError) do distributions = @acf.list_distributions end assert distributions.is_a?(Array) end def test_02_try_to_create_for_bad_bucket # a bucket does not exist assert_raise(Aws::AwsError) do @acf.create_distribution("right-cloudfront-awesome-test-bucket-not-exist", "Mustn't to be born", true) end # a bucket is not a domain naming complied guy bucket_name = 'right_cloudfront_awesome_test_bucket_BAD' @s3.bucket(bucket_name, :create) assert_raise(Aws::AwsError) do @acf.create_distribution(bucket_name, "Mustn't to be born", true) end end def test_03_create comment = 'WooHoo!!!' # create a test bucket @s3.bucket(@bucket_name, :create) # create a distribution distribution = @acf.create_distribution(@bucket_domain, comment, true) assert_equal comment, distribution[:comment] assert distribution[:cnames].size == 0 assert distribution[:enabled] end def test_04_list_distributions_part2 distributions = @acf.list_distributions assert distributions.size > 0 end def get_test_distribution @acf.list_distributions.select{ |d| d[:origin] == @bucket_domain }.first end def test_05_get_distribution old = get_test_distribution assert_nothing_raised do @acf.get_distribution(old[:aws_id]) end end def test_06_get_and_set_config config = nil old = get_test_distribution assert_nothing_raised do config = @acf.get_distribution_config(old[:aws_id]) end # change a config config[:enabled] = false config[:cnames] << 'x1.myawesomesite.com' config[:cnames] << 'x2.myawesomesite.com' # set config set_config_result = nil assert_nothing_raised do set_config_result = @acf.set_distribution_config(old[:aws_id], config) end assert set_config_result # reget the config and check new_config = nil assert_nothing_raised do new_config = @acf.get_distribution_config(old[:aws_id]) end assert !new_config[:enabled] assert_equal new_config[:cnames].sort, ['x1.myawesomesite.com', 'x2.myawesomesite.com'] assert_not_equal config[:e_tag], new_config[:e_tag] # try to update the old config again (must fail because ETAG has changed) assert_raise(Aws::AwsError) do @acf.set_distribution_config(old[:aws_id], config) end end def test_07_caching # enable caching @acf.params[:cache] = true # list distributions @acf.list_distributions # list the distributions again - cache should hit assert_raise(Aws::AwsNoChange) do @acf.list_distributions end # disable caching @acf.params[:cache] = true end def test_08_delete_distribution # we need ETAG so use get_distribution distribution = @acf.get_distribution(get_test_distribution[:aws_id]) # try to delete a distribution # should fail because if distribution[:status] == 'InProgress' # should fail because the distribution is not deployed yet assert_raise(Aws::AwsError) do @acf.delete_distribution(distribution[:aws_id], distribution[:e_tag]) end # wait for a deployed state print "waiting up to 5 min while the distribution is being deployed: " 100.times do print '.' distribution = @acf.get_distribution(distribution[:aws_id]) if distribution[:status] == 'Deployed' print ' done' break end sleep 3 end puts end # only disabled and deployed distribution can be deleted assert_equal 'Deployed', distribution[:status] assert !distribution[:enabled] # delete the distribution assert_nothing_raised do @acf.delete_distribution(distribution[:aws_id], distribution[:e_tag]) end end def test_09_drop_bucket assert @s3.bucket(@bucket_name).delete end end aws-2.10.2/test/elb/0000755000175000017500000000000012511515441012622 5ustar tnnntnnnaws-2.10.2/test/elb/test_elb.rb0000644000175000017500000000210212511515441014743 0ustar tnnntnnnrequire 'test/unit' require File.dirname(__FILE__) + '/../../lib/aws' require 'pp' require File.dirname(__FILE__) + '/../test_credentials.rb' class TestElb < Test::Unit::TestCase # Some of RightEc2 instance methods concerning instance launching and image registration # are not tested here due to their potentially risk. def setup TestCredentials.get_credentials @ec2 = Aws::Ec2.new(TestCredentials.aws_access_key_id, TestCredentials.aws_secret_access_key) @elb = Aws::Elb.new(TestCredentials.aws_access_key_id, TestCredentials.aws_secret_access_key) @key = 'right_ec2_awesome_test_key' @group = 'right_ec2_awesome_test_security_group' end def test_01_create_elb end def test_02_register_instances end def test_03_deregister_instances end def test_04_describe_elb desc = @elb.describe_load_balancers puts desc.inspect end def test_06_describe_instance_health end def test_15_delete_elb end end aws-2.10.2/test/ec2/0000755000175000017500000000000012511515441012531 5ustar tnnntnnnaws-2.10.2/test/ec2/test_mon.rb0000644000175000017500000000115112511515441014704 0ustar tnnntnnnrequire File.dirname(__FILE__) + '/test_helper.rb' require File.dirname(__FILE__) + '/../test_credentials.rb' require 'pp' class TestEc2 < Test::Unit::TestCase # Some of RightEc2 instance methods concerning instance launching and image registration # are not tested here due to their potentially risk. def setup TestCredentials.get_credentials @ec2 = Aws::Ec2.new(TestCredentials.aws_access_key_id, TestCredentials.aws_secret_access_key) @key = 'right_ec2_awesome_test_key' @group = 'right_ec2_awesome_test_security_group' end end aws-2.10.2/test/ec2/test_ec2.rb0000644000175000017500000002321712511515441014573 0ustar tnnntnnnrequire File.dirname(__FILE__) + '/test_helper.rb' require 'pp' require File.dirname(__FILE__) + '/../test_credentials.rb' class TestEc2 < Test::Unit::TestCase # Some of RightEc2 instance methods concerning instance launching and image registration # are not tested here due to their potentially risk. def setup TestCredentials.get_credentials @ec2 = Aws::Ec2.new(TestCredentials.aws_access_key_id, TestCredentials.aws_secret_access_key) @key = 'right_ec2_awesome_test_key' @group = 'right_ec2_awesome_test_security_group' end def test_001_describe_availability_zones TestCredentials.get_credentials @ec2 = Aws::Ec2.new(TestCredentials.aws_access_key_id, TestCredentials.aws_secret_access_key) zones = @ec2.describe_availability_zones puts zones.inspect assert zones.is_a? Array assert zones.size > 3 zones.each do |z| puts z[:zone_name] end assert zones[0][:zone_name] == "us-east-1a" end def test_01_create_describe_key_pairs new_key = @ec2.create_key_pair(@key) assert new_key[:aws_material][/BEGIN RSA PRIVATE KEY/], "New key material is absent" keys = @ec2.describe_key_pairs assert keys.map { |key| key[:aws_key_name] }.include?(@key), "#{@key} must exist" end def test_02_create_security_group assert @ec2.create_security_group(@group, 'My awesone test group'), 'Create_security_group fail' group = @ec2.describe_security_groups([@group])[0] assert_equal @group, group[:aws_group_name], 'Group must be created but does not exist' end def test_03_perms_add assert @ec2.authorize_security_group_named_ingress(@group, TestCredentials.account_number, 'default') assert @ec2.authorize_security_group_IP_ingress(@group, 80, 80, 'udp', '192.168.1.0/8') end def test_04_check_new_perms_exist assert_equal 2, @ec2.describe_security_groups([@group])[0][:aws_perms].size end def test_05_perms_remove assert @ec2.revoke_security_group_IP_ingress(@group, 80, 80, 'udp', '192.168.1.0/8') assert @ec2.revoke_security_group_named_ingress(@group, TestCredentials.account_number, 'default') end def test_06_describe_images images = describe_images # unknown image assert_raise(Aws::AwsError) { @ec2.describe_images(['ami-ABCDEFGH']) } end def test_07_describe_instanses assert @ec2.describe_instances # unknown image assert_raise(Aws::AwsError) { @ec2.describe_instances(['i-ABCDEFGH']) } end def test_08_delete_security_group assert @ec2.delete_security_group(@group), 'Delete_security_group fail' end def test_09_delete_key_pair assert @ec2.delete_key_pair(@key), 'Delete_key_pair fail' ## Hmmm... Amazon does not through the exception any more. It now just returns a 'true' if the key does not exist any more... ## # key must be deleted already ## assert_raise(Aws::AwsError) { @ec2.delete_key_pair(@key) } end def test_10_signature_version_0 ec2 = Aws::Ec2.new(TestCredentials.aws_access_key_id, TestCredentials.aws_secret_access_key, :signature_version => '0') images = ec2.describe_images assert images.size>0, 'Amazon must have at least some public images' # check that the request has correct signature version assert ec2.last_request.path.include?('SignatureVersion=0') end def test_11_regions regions = nil assert_nothing_raised do regions = @ec2.describe_regions end # check we got more that 0 regions assert regions.size > 0 # check an access to regions regions.each do |region| regional_ec2 = Aws::Ec2.new(TestCredentials.aws_access_key_id, TestCredentials.aws_secret_access_key, :region => region) # do we have a correct endpoint server? assert_equal "#{region}.ec2.amazonaws.com", regional_ec2.params[:server] # get a list of images from every region images = nil assert_nothing_raised do images = regional_ec2.describe_regions end # every region must have images assert images.size > 0 end end def test_12_endpoint_url ec2 = Aws::Ec2.new(TestCredentials.aws_access_key_id, TestCredentials.aws_secret_access_key, :endpoint_url => 'a://b.c:0/d/', :region => 'z') # :endpoint_url has a priority hence :region should be ommitted assert_equal 'a', ec2.params[:protocol] assert_equal 'b.c', ec2.params[:server] assert_equal '/d/', ec2.params[:service] assert_equal 0, ec2.params[:port] assert_nil ec2.params[:region] end def test_13a_create_describe_delete_tag images = describe_images resource_id = images.first[:aws_id] assert @ec2.create_tag(resource_id, 'testkey', 'testvalue').inspect, "Could not add a tag to #{resource_id}" assert_equal( [{:aws_resource_id=>resource_id, :aws_resource_type=>"image", :aws_key=>"testkey", :aws_value=>"testvalue"}], @ec2.describe_tags('Filter.1.Name' => 'resource-id', 'Filter.1.Value.1' => resource_id) ) assert_equal( [], @ec2.describe_tags('Filter.1.Name' => 'resource-id', 'Filter.1.Value.1' => '__blah__') ) assert @ec2.delete_tag(resource_id, 'testkey').inspect, "Could not delete tag 'testkey' from #{resource_id}" sleep 1 # :( assert_equal( [], @ec2.describe_tags('Filter.1.Name' => 'resource-id', 'Filter.1.Value.1' => resource_id) ) end def test_13b_create_describe_delete_tag_by_value images = describe_images resource_id = images.first[:aws_id] assert @ec2.create_tag(resource_id, 'testkey', 'testvalue').inspect, "Could not add a tag to #{resource_id}" assert_equal( [{:aws_resource_id=>resource_id, :aws_resource_type=>"image", :aws_key=>"testkey", :aws_value=>"testvalue"}], @ec2.describe_tags('Filter.1.Name' => 'resource-id', 'Filter.1.Value.1' => resource_id, 'Filter.2.Name' => 'key', 'Filter.2.Value.1' => 'testkey') ) assert_equal( [], @ec2.describe_tags('Filter.1.Name' => 'resource-id', 'Filter.1.Value.1' => resource_id, 'Filter.2.Name' => 'key', 'Filter.2.Value.1' => '__blah__') ) assert @ec2.delete_tag(resource_id, 'testkey', 'testvalue').inspect, "Could not delete tag 'testkey' from #{resource_id}" sleep 1 # :( assert_equal( [], @ec2.describe_tags('Filter.1.Name' => 'resource-id', 'Filter.1.Value.1' => resource_id, 'Filter.2.Name' => 'key', 'Filter.2.Value.1' => 'testkey') ) end def test_13c_delete_tag_with_empty_or_nil_value images = describe_images resource_id = images.first[:aws_id] assert @ec2.create_tag(resource_id, 'testkey', 'testvalue').inspect, "Could not add a tag to #{resource_id}" assert_equal( [{:aws_resource_id=>resource_id, :aws_resource_type=>"image", :aws_key=>"testkey", :aws_value=>"testvalue"}], @ec2.describe_tags('Filter.1.Name' => 'resource-id', 'Filter.1.Value.1' => resource_id) ) # Delete a tag with an empty string value... assert @ec2.delete_tag(resource_id, 'testkey', '').inspect, "Could not delete tag 'testkey' from #{resource_id}" sleep 1 # :( # ... does nothing assert_equal( [{:aws_resource_id=>resource_id, :aws_resource_type=>"image", :aws_key=>"testkey", :aws_value=>"testvalue"}], @ec2.describe_tags('Filter.1.Name' => 'resource-id', 'Filter.1.Value.1' => resource_id) ) # Delete a tag with value = nil... assert @ec2.delete_tag(resource_id, 'testkey', nil).inspect, "Could not delete tag 'testkey' from #{resource_id}" sleep 1 # :( # ... deletes all tags with the given key assert_equal( [], @ec2.describe_tags('Filter.1.Name' => 'resource-id', 'Filter.1.Value.1' => resource_id) ) end def test_14_vpc @vpc = @ec2.create_vpc("172.16.0.0/16").first assert_equal "172.16.0.0/16", @vpc[:cidr_block] assert @vpc.key?(:state) vpcs = @ec2.describe_vpcs assert vpcs.find { |v| v[:vpc_id] == @vpc[:vpc_id] } vpcs = @ec2.describe_vpcs(@vpc[:vpc_id]) assert_equal 1, vpcs.size assert_equal @vpc[:vpc_id], vpcs.first[:vpc_id] @subnet = @ec2.create_subnet(@vpc[:vpc_id], "172.16.3.0/24", "us-east-1b").first assert @subnet[:subnet_id] assert_equal @vpc[:vpc_id], @subnet[:vpc_id] assert_equal "172.16.3.0/24", @subnet[:cidr_block] assert_equal "251", @subnet[:available_ip_address_count] assert_equal "us-east-1b", @subnet[:availability_zone] assert @subnet.key?(:state) subnets = @ec2.describe_subnets assert subnets.find { |s| s[:subnet_id] = @subnet[:subnet_id] } subnets = @ec2.describe_subnets('Filter.1.Name' => 'vpc-id', 'Filter.1.Value' => @vpc[:vpc_id]) assert_equal 1, subnets.size assert_equal @subnet[:subnet_id], subnets.first[:subnet_id] assert @ec2.delete_subnet(@subnet[:subnet_id]) @subnet = nil assert @ec2.delete_vpc(@vpc[:vpc_id]) @vpc = nil ensure @ec2.delete_subnet(@subnet[:subnet_id]) if @subnet @ec2.delete_vpc(@vpc[:vpc_id]) if @vpc end private # Memoize the images to speed up the tests def describe_images @@images ||= @ec2.describe_images assert @@images.size>0, 'Amazon must have at least some public images' @@images end end aws-2.10.2/test/ec2/test_helper.rb0000644000175000017500000000010612511515441015371 0ustar tnnntnnnrequire 'test/unit' require File.dirname(__FILE__) + '/../../lib/aws' aws-2.10.2/test/http_connection.rb0000644000175000017500000000460112511515441015604 0ustar tnnntnnn=begin Copyright (c) 2007 RightScale, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. =end # Stub extension/redefinition of RightHttpConnection for testing purposes. require 'net/http' require 'rubygems' require 'right_http_connection' module Net class HTTPResponse alias_method :real_body, :body def setmsg(msg) @mymsg = msg end def body # defined?() helps us to get rid of a bunch of 'warnings' (defined?(@mymsg) && @mymsg) ? @mymsg : real_body end end end module Rightscale class HttpConnection @@response_stack = [] alias_method :real_request, :request def request(request_params, &block) if(@@response_stack.length == 0) return real_request(request_params, &block) end if(block) # Do something special else next_response = HttpConnection::pop() classname = Net::HTTPResponse::CODE_TO_OBJ["#{next_response[:code]}"] response = classname.new("1.1", next_response[:code], next_response[:msg]) if(next_response[:msg]) response.setmsg(next_response[:msg]) end response end end def self.reset @@response_stack = [] end def self.push(code, msg=nil) response = {:code => code, :msg => msg} @@response_stack << response end def self.pop @@response_stack.pop end def self.length @@response_stack.length end end end aws-2.10.2/test/ses/0000755000175000017500000000000012511515441012652 5ustar tnnntnnnaws-2.10.2/test/ses/test_ses.rb0000644000175000017500000000175012511515441015033 0ustar tnnntnnnrequire 'test/unit' require File.dirname(__FILE__) + '/../../lib/aws' require 'pp' require File.dirname(__FILE__) + '/../test_credentials.rb' class TestSes < Test::Unit::TestCase def setup TestCredentials.get_credentials @ses = Aws::Ses.new(TestCredentials.aws_access_key_id, TestCredentials.aws_secret_access_key) end def test_01_get_send_quota ret = @ses.get_send_quota p ret assert_true(ret.size == 0) end def test_02_get_send_statistics ret = @ses.get_send_quota p ret end def test_10_list_verified_email_addresses ret = @ses.get_send_quota p ret assert_true(ret.size == 0) end def test_20_send_email ret = @ses.get_send_quota p ret assert_true(ret.size == 0) end def test_30_send_raw_email ret = @ses.get_send_quota p ret assert_true(ret.size == 0) end def test_40_verify_email_address ret = @ses.get_send_quota p ret assert_true(ret.size == 0) end end aws-2.10.2/test/s3/0000755000175000017500000000000012511515441012405 5ustar tnnntnnnaws-2.10.2/test/s3/s3_test_base.rb0000644000175000017500000000144212511515441015311 0ustar tnnntnnnclass S3TestBase < Test::Unit::TestCase RIGHT_OBJECT_TEXT = 'Right test message' def setup TestCredentials.get_credentials @s3 = Aws::S3Interface.new(TestCredentials.aws_access_key_id, TestCredentials.aws_secret_access_key) @bucket = TestCredentials.config['amazon']['my_prefix'] + '_awesome_test_bucket_000A1' @bucket2 = TestCredentials.config['amazon']['my_prefix'] + '_awesome_test_bucket_000A2' @key1 = 'test/woohoo1/' @key2 = 'test1/key/woohoo2' @key3 = 'test2/A%B@C_D&E?F+G=H"I' @key1_copy = 'test/woohoo1_2' @key1_new_name = 'test/woohoo1_3' @key2_new_name = 'test1/key/woohoo2_new' @s = Aws::S3.new(TestCredentials.aws_access_key_id, TestCredentials.aws_secret_access_key) end def teardown end endaws-2.10.2/test/s3/test_s3_stubbed.rb0000644000175000017500000000603212511515441016027 0ustar tnnntnnnrequire File.dirname(__FILE__) + '/test_helper.rb' require File.dirname(__FILE__) + '/../test_credentials.rb' class TestS3Stubbed < Test::Unit::TestCase RIGHT_OBJECT_TEXT = 'Right test message' def setup TestCredentials.get_credentials @s3 = Aws::S3Interface.new(TestCredentials.aws_access_key_id, TestCredentials.aws_secret_access_key) @bucket = 'right_s3_awesome_test_bucket' @key1 = 'test/woohoo1' @key2 = 'test1/key/woohoo2' @s = Aws::S3.new(TestCredentials.aws_access_key_id, TestCredentials.aws_secret_access_key) Rightscale::HttpConnection.reset end # Non-remote tests: these use the stub version of Rightscale::HTTPConnection def test_101_create_bucket Rightscale::HttpConnection.push(409, 'The named bucket you tried to create already exists') Rightscale::HttpConnection.push(500, 'We encountered an internal error. Please try again.') Rightscale::HttpConnection.push(500, 'We encountered an internal error. Please try again.') assert_raise Aws::AwsError do @s3.create_bucket(@bucket) end end def test_102_list_all_my_buckets_failure Rightscale::HttpConnection.push(401, 'Unauthorized') assert_raise Aws::AwsError do @s3.list_all_my_buckets end end def test_103_list_empty_bucket Rightscale::HttpConnection.push(403, 'Access Denied') assert_raise Aws::AwsError do @s3.list_bucket(@bucket) end end def test_104_put Rightscale::HttpConnection.push(400, 'Your proposed upload exceeds the maximum allowed object size.') Rightscale::HttpConnection.push(400, 'The Content-MD5 you specified was an invalid.') Rightscale::HttpConnection.push(409, 'Please try again') assert_raise Aws::AwsError do assert @s3.put(@bucket, @key1, RIGHT_OBJECT_TEXT, 'x-amz-meta-family'=>'Woohoo1!'), 'Put bucket fail' end assert_raise Aws::AwsError do assert @s3.put(@bucket, @key2, RIGHT_OBJECT_TEXT, 'x-amz-meta-family'=>'Woohoo2!'), 'Put bucket fail' end end def test_105_get_and_get_object Rightscale::HttpConnection.push(404, 'not found') assert_raise(Rightscale::AwsError) { @s3.get(@bucket, 'undefined/key') } end def test_106_head Rightscale::HttpConnection.push(404, 'Good Luck!') assert_raise Aws::AwsError do @s3.head(@bucket,@key1) end end def test_109_delete_bucket Rightscale::HttpConnection.push(403, 'Good Luck!') assert_raise(Rightscale::AwsError) { @s3.delete_bucket(@bucket) } end def test_115_copy_key Rightscale::HttpConnection.push(500, 'not found') #--- test COPY # copy a key assert_raise Aws::AwsError do @s3.copy(@bucket, @key1, @bucket, @key1_copy) end end def test_116_move_key # move a key Rightscale::HttpConnection.push(413, 'not found') assert @s3.move(@bucket, @key1, @bucket, @key1_new_name) end def test_117_rename_key # rename a key Rightscale::HttpConnection.push(500, 'not found') assert @s3.rename(@bucket, @key2, @key2_new_name) end end aws-2.10.2/test/s3/test_helper.rb0000644000175000017500000000010712511515441015246 0ustar tnnntnnnrequire 'test/unit' require File.dirname(__FILE__) + '/../../lib/aws' aws-2.10.2/test/s3/test_s3_class.rb0000644000175000017500000001521512511515441015507 0ustar tnnntnnn# encoding: utf-8 require File.dirname(__FILE__) + '/test_helper.rb' require_relative 's3_test_base' require File.dirname(__FILE__) + '/../test_credentials.rb' class TestS3Class < S3TestBase #--------------------------- # Aws::S3 classes #--------------------------- def test_20_s3 # create bucket bucket = @s.bucket(@bucket, true) assert bucket # check that the bucket exists assert @s.buckets.map { |b| b.name }.include?(@bucket) # delete bucket assert bucket.clear assert bucket.delete end def test_21_bucket_create_put_get_key bucket = Aws::S3::Bucket.create(@s, @bucket, true) # check that the bucket exists assert @s.buckets.map { |b| b.name }.include?(@bucket) assert bucket.keys.empty?, "keys are not empty: " + bucket.keys.inspect # put data assert bucket.put(@key3, RIGHT_OBJECT_TEXT, {'family'=>'123456'}) # get data and compare assert_equal RIGHT_OBJECT_TEXT, bucket.get(@key3) # get key object key = bucket.key(@key3, true) assert_equal Aws::S3::Key, key.class assert key.exists? assert_equal '123456', key.meta_headers['family'] end def test_22_bucket_put_big_with_multibyte_chars bucket = Aws::S3::Bucket.create(@s, @bucket, true) super_big_string = "" 10000.times { |i| super_big_string << "abcde Café" } # this string has multibye values just to mess things up abit. puts 'String made, putting...' puts "#{super_big_string.size} - #{super_big_string.bytesize}" assert bucket.put("super_big", super_big_string), 'Put bucket fail' got = bucket.get("super_big") puts 'got.class=' + got.class.name assert_equal(super_big_string, got, "not the same yo") end def test_23_put_strange_things bucket = Aws::S3::Bucket.create(@s, @bucket, true) # this is kinda bad, you put a nil, but get an empty string back assert bucket.put("strange", nil), 'Put bucket fail' got = bucket.get("strange") assert_equal("", got) x = "\xE2\x80\x99s Café" puts "#{x.size} - #{x.bytesize}" assert bucket.put("multibye", x) end def test_30_keys bucket = Aws::S3::Bucket.create(@s, @bucket, false) # create first key key3 = Aws::S3::Key.create(bucket, @key3) key3.refresh assert key3.exists? assert_equal '123456', key3.meta_headers['family'] # create second key key2 = Aws::S3::Key.create(bucket, @key2) assert !key2.refresh assert !key2.exists? assert_raise(Aws::AwsError) { key2.head } # store key key2.meta_headers = {'family'=>'111222333'} assert key2.put(RIGHT_OBJECT_TEXT) # make sure that the key exists assert key2.refresh assert key2.exists? assert key2.head # get its data assert_equal RIGHT_OBJECT_TEXT, key2.get # drop key assert key2.delete assert !key2.exists? end def test_31_rename_key bucket = Aws::S3::Bucket.create(@s, @bucket, false) # -- 1 -- (key based rename) # create a key key = bucket.key('test/copy/1') key.put(RIGHT_OBJECT_TEXT) original_key = key.clone assert key.exists?, "'test/copy/1' should exist" # rename it key.rename('test/copy/2') assert_equal 'test/copy/2', key.name assert key.exists?, "'test/copy/2' should exist" # the original key should not exist assert !original_key.exists?, "'test/copy/1' should not exist" # -- 2 -- (bucket based rename) bucket.rename_key('test/copy/2', 'test/copy/3') assert bucket.key('test/copy/3').exists?, "'test/copy/3' should exist" assert !bucket.key('test/copy/2').exists?, "'test/copy/2' should not exist" end def test_32_copy_key bucket = Aws::S3::Bucket.create(@s, @bucket, false) # -- 1 -- (key based copy) # create a key key = bucket.key('test/copy/10') key.put(RIGHT_OBJECT_TEXT) # make copy new_key = key.copy('test/copy/11') # make sure both the keys exist and have a correct data assert key.exists?, "'test/copy/10' should exist" assert new_key.exists?, "'test/copy/11' should exist" assert_equal RIGHT_OBJECT_TEXT, key.get assert_equal RIGHT_OBJECT_TEXT, new_key.get # -- 2 -- (bucket based copy) bucket.copy_key('test/copy/11', 'test/copy/12') assert bucket.key('test/copy/11').exists?, "'test/copy/11' should exist" assert bucket.key('test/copy/12').exists?, "'test/copy/12' should exist" assert_equal RIGHT_OBJECT_TEXT, bucket.key('test/copy/11').get assert_equal RIGHT_OBJECT_TEXT, bucket.key('test/copy/12').get end def test_33_move_key bucket = Aws::S3::Bucket.create(@s, @bucket, false) # -- 1 -- (key based copy) # create a key key = bucket.key('test/copy/20') key.put(RIGHT_OBJECT_TEXT) # move new_key = key.move('test/copy/21') # make sure both the keys exist and have a correct data assert !key.exists?, "'test/copy/20' should not exist" assert new_key.exists?, "'test/copy/21' should exist" assert_equal RIGHT_OBJECT_TEXT, new_key.get # -- 2 -- (bucket based copy) bucket.copy_key('test/copy/21', 'test/copy/22') assert bucket.key('test/copy/21').exists?, "'test/copy/21' should not exist" assert bucket.key('test/copy/22').exists?, "'test/copy/22' should exist" assert_equal RIGHT_OBJECT_TEXT, bucket.key('test/copy/22').get end def test_40_save_meta bucket = Aws::S3::Bucket.create(@s, @bucket, false) # create a key key = bucket.key('test/copy/30') key.put(RIGHT_OBJECT_TEXT) assert key.meta_headers.empty? # store some meta keys meta = {'family' => 'oops', 'race' => 'troll'} assert_equal meta, key.save_meta(meta) # reload meta assert_equal meta, key.reload_meta end def test_60_clear_delete bucket = Aws::S3::Bucket.create(@s, @bucket, false) # add another key bucket.put(@key2, RIGHT_OBJECT_TEXT) # delete 'folder' assert_equal 1, bucket.delete_folder(@key1).size # delete assert_raise(Aws::AwsError) { bucket.delete } assert bucket.delete(true) end # No streaming test should be captured in # test_21_bucket_create_put_get_key def test_61_get_bucket_key_via_streaming bucket = Aws::S3::Bucket.create(@s, @bucket, true) # check that the bucket exists assert @s.buckets.map { |b| b.name }.include?(@bucket) # put data assert bucket.put(@key2, RIGHT_OBJECT_TEXT, {'family'=>'123456_61'}) # get data and compare via streaming # stream data from S3 data = "" bucket.get(@key2) do |chunk| data += chunk end assert_equal RIGHT_OBJECT_TEXT, data # get key object key = bucket.key(@key2, true) assert_equal Aws::S3::Key, key.class assert key.exists? assert_equal '123456_61', key.meta_headers['family'] end end aws-2.10.2/test/s3/test_s3.rb0000644000175000017500000001773712511515441014335 0ustar tnnntnnnrequire File.dirname(__FILE__) + '/test_helper.rb' require_relative 's3_test_base' require File.dirname(__FILE__) + '/../test_credentials.rb' class TestS3 < S3TestBase #--------------------------- # Aws::S3Interface #--------------------------- def test_01_create_bucket assert @s3.create_bucket(@bucket), 'Create_bucket fail' end def test_02_list_all_my_buckets assert @s3.list_all_my_buckets.map { |bucket| bucket[:name] }.include?(@bucket), "#{@bucket} must exist in bucket list" end def test_03_list_empty_bucket assert_equal 0, @s3.list_bucket(@bucket).size, "#{@bucket} isn't empty, arrgh!" end def test_04_put assert @s3.put(@bucket, @key1, RIGHT_OBJECT_TEXT, 'x-amz-meta-family'=>'Woohoo1!'), 'Put bucket fail' assert @s3.put(@bucket, @key2, RIGHT_OBJECT_TEXT, 'x-amz-meta-family'=>'Woohoo2!'), 'Put bucket fail' assert @s3.put(@bucket, @key3, RIGHT_OBJECT_TEXT, 'x-amz-meta-family'=>'Woohoo3!'), 'Put bucket fail' end def test_04a_put super_big_string = "" 1000000.times {|i| super_big_string << "abcde" } assert @s3.put(@bucket, "super_big", super_big_string), 'Put bucket fail' end def test_05_get_and_get_object assert_raise(Aws::AwsError) { @s3.get(@bucket, 'undefined/key') } data1 = @s3.get(@bucket, @key1) assert_equal RIGHT_OBJECT_TEXT, data1[:object], "Object text must be equal to '#{RIGHT_OBJECT_TEXT}'" assert_equal RIGHT_OBJECT_TEXT, @s3.get_object(@bucket, @key1), "Get_object text must return '#{RIGHT_OBJECT_TEXT}'" assert_equal 'Woohoo1!', data1[:headers]['x-amz-meta-family'], "x-amz-meta-family header must be equal to 'Woohoo1!'" assert_equal RIGHT_OBJECT_TEXT, @s3.get_object(@bucket, @key3), "Get_object text must return '#{RIGHT_OBJECT_TEXT}'" end def test_06_head assert_equal 'Woohoo1!', @s3.head(@bucket, @key1)['x-amz-meta-family'], "x-amz-meta-family header must be equal to 'Woohoo1!'" end def test_07_streaming_get resp = String.new assert_raise(Aws::AwsError) do @s3.get(@bucket, 'undefined/key') do |chunk| resp += chunk end end resp = String.new data1 = @s3.get(@bucket, @key1) do |chunk| resp += chunk end assert_equal RIGHT_OBJECT_TEXT, resp, "Object text must be equal to '#{RIGHT_OBJECT_TEXT}'" assert_equal @s3.get_object(@bucket, @key1), resp, "Streaming iface must return same as non-streaming" assert_equal 'Woohoo1!', data1[:headers]['x-amz-meta-family'], "x-amz-meta-family header must be equal to 'Woohoo1!'" end def test_08_keys keys = @s3.list_bucket(@bucket).map { |b| b[:key] } assert_equal keys.size, 3, "There should be 3 keys" assert(keys.include?(@key1)) assert(keys.include?(@key2)) assert(keys.include?(@key3)) end def test_09_copy_key #--- test COPY # copy a key assert @s3.copy(@bucket, @key1, @bucket, @key1_copy) # check it was copied well assert_equal RIGHT_OBJECT_TEXT, @s3.get_object(@bucket, @key1_copy), "copied object must have the same data" # check meta-headers were copied headers = @s3.head(@bucket, @key1_copy) assert_equal 'Woohoo1!', headers['x-amz-meta-family'], "x-amz-meta-family header must be equal to 'Woohoo1!'" #--- test REPLACE assert @s3.copy(@bucket, @key1, @bucket, @key1_copy, :replace, 'x-amz-meta-family' => 'oooops!') # check it was copied well assert_equal RIGHT_OBJECT_TEXT, @s3.get_object(@bucket, @key1_copy), "copied object must have the same data" # check meta-headers were overwrittenn headers = @s3.head(@bucket, @key1_copy) assert_equal 'oooops!', headers['x-amz-meta-family'], "x-amz-meta-family header must be equal to 'oooops!'" end def test_10_move_key # move a key assert @s3.move(@bucket, @key1, @bucket, @key1_new_name) # check it's data was moved correctly assert_equal RIGHT_OBJECT_TEXT, @s3.get_object(@bucket, @key1_new_name), "moved object must have the same data" # check meta-headers were moved headers = @s3.head(@bucket, @key1_new_name) assert_equal 'Woohoo1!', headers['x-amz-meta-family'], "x-amz-meta-family header must be equal to 'Woohoo1!'" # check the original key is not exists any more keys = @s3.list_bucket(@bucket).map { |b| b[:key] } assert(!keys.include?(@key1)) end def test_11_rename_key # rename a key assert @s3.rename(@bucket, @key2, @key2_new_name) # check the new key data assert_equal RIGHT_OBJECT_TEXT, @s3.get_object(@bucket, @key2_new_name), "moved object must have the same data" # check meta-headers headers = @s3.head(@bucket, @key2_new_name) assert_equal 'Woohoo2!', headers['x-amz-meta-family'], "x-amz-meta-family header must be equal to 'Woohoo2!'" # check the original key is not exists any more keys = @s3.list_bucket(@bucket).map { |b| b[:key] } assert(!keys.include?(@key2)) end def test_12_retrieve_object assert_raise(Aws::AwsError) { @s3.retrieve_object(:bucket => @bucket, :key => 'undefined/key') } data1 = @s3.retrieve_object(:bucket => @bucket, :key => @key1_new_name) assert_equal RIGHT_OBJECT_TEXT, data1[:object], "Object text must be equal to '#{RIGHT_OBJECT_TEXT}'" assert_equal 'Woohoo1!', data1[:headers]['x-amz-meta-family'], "x-amz-meta-family header must be equal to 'Woohoo1!'" end def test_13_delete_folder assert_equal 1, @s3.delete_folder(@bucket, 'test').size, "Only one key(#{@key1}) must be deleted!" end def test_14_multipart_upload segmented_object = TestCredentials.config['amazon']['my_prefix']+"segmented" uploadId = @s3.initiate_multipart(@bucket, segmented_object) assert(uploadId.instance_of?(String)) part1_etag = @s3.upload_part(@bucket, segmented_object, uploadId, "1", File.open(TestCredentials.config['amazon']['multipart_segment1'])) assert(part1_etag.instance_of?(String)) part2_etag = @s3.upload_part(@bucket, segmented_object, uploadId, "2", File.open(TestCredentials.config['amazon']['multipart_segment2'])) assert(part2_etag.instance_of?(String)) parts = @s3.list_parts(@bucket, segmented_object, uploadId) part_etags = parts[:parts].collect{|part| part[:etag].gsub!("\"", "")} assert(part_etags.include?(part1_etag)) assert(part_etags.include?(part2_etag)) assert(@s3.complete_multipart(@bucket, segmented_object, uploadId, {"1"=>part1_etag, "2"=>part2_etag})) object_data = @s3.head(@bucket, segmented_object) combined_size = File.size(TestCredentials.config['amazon']['multipart_segment1'])+ File.size(TestCredentials.config['amazon']['multipart_segment2']) assert_equal object_data["content-length"].to_i, combined_size end # idle timeout is 20 seconds # https://forums.aws.amazon.com/thread.jspa?threadID=58038 def test_15_idle_timeout @s3 = Aws::S3Interface.new(TestCredentials.aws_access_key_id, TestCredentials.aws_secret_access_key, :connection_mode=>:single) # Disable connection retrying Aws::AWSErrorHandler.close_on_error = false assert @s3.put(@bucket, @key1, RIGHT_OBJECT_TEXT, 'x-amz-meta-family'=>'Woohoo1!'), 'Put bucket fail' sleep 300 assert_raises Aws::AwsError do @s3.put(@bucket, @key2, RIGHT_OBJECT_TEXT, 'x-amz-meta-family'=>'Woohoo1!') end # now try again with retry mode @s3 = Aws::S3Interface.new(TestCredentials.aws_access_key_id, TestCredentials.aws_secret_access_key, :connection_mode=>:single) Aws::AWSErrorHandler.close_on_error = true assert @s3.put(@bucket, @key1, RIGHT_OBJECT_TEXT, 'x-amz-meta-family'=>'Woohoo1!'), 'Put bucket fail' sleep 30 assert @s3.put(@bucket, @key2, RIGHT_OBJECT_TEXT, 'x-amz-meta-family'=>'Woohoo1!'), 'Put bucket fail' end def test_99_delete_bucket assert_raise(Aws::AwsError) { @s3.delete_bucket(@bucket) } assert @s3.clear_bucket(@bucket), 'Clear_bucket fail' assert_equal 0, @s3.list_bucket(@bucket).size, 'Bucket must be empty' assert @s3.delete_bucket(@bucket) assert !@s3.list_all_my_buckets.map { |bucket| bucket[:name] }.include?(@bucket), "#{@bucket} must not exist" end end aws-2.10.2/test/s3/test_s3_rights.rb0000644000175000017500000001054312511515441015701 0ustar tnnntnnnrequire File.dirname(__FILE__) + '/test_helper.rb' require_relative 's3_test_base' require File.dirname(__FILE__) + '/../test_credentials.rb' class TestS3Rights < S3TestBase # Grantees def test_30_create_bucket bucket = @s.bucket(@bucket, true, 'public-read') assert bucket end def test_31_list_grantees bucket = Aws::S3::Bucket.create(@s, @bucket, false) # get grantees list grantees = bucket.grantees # check that the grantees count equal to 2 (root, AllUsers) assert_equal 2, grantees.size end def test_32_grant_revoke_drop bucket = Aws::S3::Bucket.create(@s, @bucket, false) # Take 'AllUsers' grantee grantee = Aws::S3::Grantee.new(bucket, 'http://acs.amazonaws.com/groups/global/AllUsers') # Check exists? assert grantee.exists? # Add grant as String assert grantee.grant('WRITE') # Add grants as Array assert grantee.grant(['READ_ACP', 'WRITE_ACP']) # Check perms count assert_equal 4, grantee.perms.size # revoke 'WRITE_ACP' assert grantee.revoke('WRITE_ACP') # Check manual perm removal method grantee.perms -= ['READ_ACP'] grantee.apply assert_equal 2, grantee.perms.size # Check grantee removal if it has no permissions assert grantee.perms = [] assert grantee.apply assert !grantee.exists? # Check multiple perms assignment assert grantee.grant('FULL_CONTROL', 'READ', 'WRITE') assert_equal ['FULL_CONTROL', 'READ', 'WRITE'].sort, grantee.perms.sort # Check multiple perms removal assert grantee.revoke('FULL_CONTROL', 'WRITE') assert_equal ['READ'], grantee.perms # check 'Drop' method assert grantee.drop assert !grantee.exists? assert_equal 1, bucket.grantees.size # Delete bucket bucket.delete(true) end def test_33_key_grantees # Create bucket bucket = @s.bucket(@bucket, true) # Create key key = bucket.key(@key1) assert key.put(RIGHT_OBJECT_TEXT, 'public-read') # Get grantees list (must be == 2) grantees = key.grantees assert grantees assert_equal 2, grantees.size # Take one of grantees and give him 'Write' perms grantee = grantees[0] assert grantee.grant('WRITE') # Drop grantee assert grantee.drop # Drop bucket bucket.delete(true) end def test_34_bucket_create_put_with_perms bucket = Aws::S3::Bucket.create(@s, @bucket, true) # check that the bucket exists assert @s.buckets.map { |b| b.name }.include?(@bucket) assert bucket.keys.empty? # put data (with canned ACL) assert bucket.put(@key1, RIGHT_OBJECT_TEXT, {'family'=>'123456'}, "public-read") # get data and compare assert_equal RIGHT_OBJECT_TEXT, bucket.get(@key1) # get key object key = bucket.key(@key1, true) assert_equal Aws::S3::Key, key.class assert key.exists? assert_equal '123456', key.meta_headers['family'] end def test_35_key_put_with_perms bucket = Aws::S3::Bucket.create(@s, @bucket, false) # create first key key1 = Aws::S3::Key.create(bucket, @key1) key1.refresh assert key1.exists? assert key1.put(RIGHT_OBJECT_TEXT, "public-read") # get its data assert_equal RIGHT_OBJECT_TEXT, key1.get # drop key assert key1.delete assert !key1.exists? end def test_36_set_amazon_problems original_problems = Aws::S3Interface.amazon_problems assert(original_problems.length > 0) Aws::S3Interface.amazon_problems= original_problems << "A New Problem" new_problems = Aws::S3Interface.amazon_problems assert_equal(new_problems, original_problems) Aws::S3Interface.amazon_problems= nil assert_nil(Aws::S3Interface.amazon_problems) end def test_37_access_logging bucket = Aws::S3::Bucket.create(@s, @bucket, false) targetbucket = Aws::S3::Bucket.create(@s, @bucket2, true) # Take 'AllUsers' grantee grantee = Aws::S3::Grantee.new(targetbucket, 'http://acs.amazonaws.com/groups/s3/LogDelivery') assert grantee.grant(['READ_ACP', 'WRITE']) assert bucket.enable_logging(:targetbucket => targetbucket, :targetprefix => "loggylogs/") assert_equal(bucket.logging_info, {:enabled => true, :targetbucket => @bucket2, :targetprefix => "loggylogs/"}) assert bucket.disable_logging # check 'Drop' method assert grantee.drop # Delete bucket bucket.delete(true) targetbucket.delete(true) end endaws-2.10.2/test/sqs/0000755000175000017500000000000012511515441012666 5ustar tnnntnnnaws-2.10.2/test/sqs/test_helper.rb0000644000175000017500000000011412511515441015525 0ustar tnnntnnnrequire 'test/unit' require File.dirname(__FILE__) + '/../../lib/right_aws' aws-2.10.2/test/sqs/test_sqs.rb0000644000175000017500000001647612511515441015076 0ustar tnnntnnnrequire File.dirname(__FILE__) + '/test_helper.rb' require File.dirname(__FILE__) + '/../test_credentials.rb' class TestSqs < Test::Unit::TestCase GRANTEE_EMAIL_ADDRESS = 'fester@example.com' RIGHT_MESSAGE_TEXT = 'Right test message' def setup TestCredentials.get_credentials @sqs = Aws::SqsInterface.new(TestCredentials.aws_access_key_id, TestCredentials.aws_secret_access_key) @queue_name = 'right_sqs_test_gen2_queue' # for classes @s = Aws::Sqs.new(TestCredentials.aws_access_key_id, TestCredentials.aws_secret_access_key) end # Wait for the queue to appear in the queues list. # Amazon needs some time to after the queue creation to place # it to the accessible queues list. If we dont want to get # the additional faults then wait a bit... def wait_for_queue_url(queue_name) queue_url = nil until queue_url queue_url = @sqs.queue_url_by_name(queue_name) unless queue_url print '-' STDOUT.flush sleep 1 end end queue_url end def test_on_exception_no_method_error assert_raise Aws::ResourceNotFoundError do Aws::Sqs::Queue.new(@s, "some_nonexistant_queue") end end #--------------------------- # Rightscale::SqsInterface #--------------------------- def test_01_create_queue queue_url = @sqs.create_queue @queue_name assert queue_url[/http.*#{@queue_name}/], 'New queue creation failed' end def test_02_list_queues wait_for_queue_url(@queue_name) queues = @sqs.list_queues('right_') assert queues.size>0, 'Must more that 0 queues in list' end def test_03_set_and_get_queue_attributes queue_url = @sqs.queue_url_by_name(@queue_name) assert queue_url[/http.*#{@queue_name}/], "#{@queue_name} must exist!" assert @sqs.set_queue_attributes(queue_url, 'VisibilityTimeout', 111), 'Set_queue_attributes fail' sleep 20 # Amazon needs some time to change attribute assert_equal '111', @sqs.get_queue_attributes(queue_url)['VisibilityTimeout'], 'New VisibilityTimeout must be equal to 111' end def test_06_send_message queue_url = @sqs.queue_url_by_name(@queue_name) # send 5 messages for the tests below assert @sqs.send_message(queue_url, RIGHT_MESSAGE_TEXT) assert @sqs.send_message(queue_url, RIGHT_MESSAGE_TEXT) assert @sqs.send_message(queue_url, RIGHT_MESSAGE_TEXT) assert @sqs.send_message(queue_url, RIGHT_MESSAGE_TEXT) assert @sqs.send_message(queue_url, RIGHT_MESSAGE_TEXT) sleep 2 end def test_07_get_queue_length queue_url = @sqs.queue_url_by_name(@queue_name) assert_equal 5, @sqs.get_queue_length(queue_url), 'Queue must have 5 messages' end def test_08_receive_message queue_url = @sqs.queue_url_by_name(@queue_name) r_message = @sqs.receive_message(queue_url, 1)[0] assert r_message, "Receive returned no message(s), but this is not necessarily incorrect" assert_equal RIGHT_MESSAGE_TEXT, r_message['Body'], 'Receive message got wrong message text' end def test_09_delete_message queue_url = @sqs.queue_url_by_name(@queue_name) message = @sqs.receive_message(queue_url)[0] assert @sqs.delete_message(queue_url, message['ReceiptHandle']), 'Delete_message fail' assert @sqs.pop_message(queue_url), 'Pop_message fail' end def test_10_clear_and_delete_queue queue_url = @sqs.queue_url_by_name(@queue_name) assert @sqs.delete_queue(queue_url) end #--------------------------- # Aws::Sqs classes #--------------------------- def test_20_sqs_create_delete_queue assert @s, 'Aws::Sqs must exist' # get queues list queues_size = @s.queues.size # create new queue queue = @s.queue("#{@queue_name}_20", true) # check that it is created assert queue.is_a?(Aws::Sqs::Queue) wait_for_queue_url(@queue_name) # check that amount of queues has increased assert_equal queues_size + 1, @s.queues.size # delete queue assert queue.delete end def test_21_queue_create # create new queue queue = Aws::Sqs::Queue.create(@s, "#{@queue_name}_21", true) # check that it is created assert queue.is_a?(Aws::Sqs::Queue) wait_for_queue_url(@queue_name) end def test_22_queue_attributes queue = Aws::Sqs::Queue.create(@s, "#{@queue_name}_21", false) # get a list of attrinutes attributes = queue.get_attribute assert attributes.is_a?(Hash) && attributes.size>0 # get attribute value and increase it by 10 v = (queue.get_attribute('VisibilityTimeout').to_i + 10).to_s # set attribute assert queue.set_attribute('VisibilityTimeout', v) # wait a bit sleep 20 # check that attribute has changed assert_equal v, queue.get_attribute('VisibilityTimeout') # get queue visibility timeout assert_equal v, queue.visibility # change it queue.visibility = queue.visibility.to_i + 10 # make sure that it is changed assert v.to_i + 10, queue.visibility end def test_24_send_size queue = Aws::Sqs::Queue.create(@s, "#{@queue_name}_24", true) # send 5 messages assert queue.push('a1') assert queue.push('a2') assert queue.push('a3') assert queue.push('a4') assert queue.push('a5') sleep 10 # check queue size assert_equal 5, queue.size # send one more assert queue.push('a6') sleep 10 # check queue size again assert_equal 6, queue.size end def test_25_message_receive_pop_delete queue = Aws::Sqs::Queue.create(@s, "#{@queue_name}_24", false) # get queue size size = queue.size # get first message m1 = queue.receive(10) assert m1.is_a?(Aws::Sqs::Message) # pop second message m2 = queue.pop assert m2.is_a?(Aws::Sqs::Message) # make sure that queue size has decreased assert_equal size-1, queue.size # delete messsage assert m1.delete # make sure that queue size has decreased again assert_equal size-2, queue.size end def test_26 queue = Aws::Sqs::Queue.create(@s, "#{@queue_name}_24", false) # lock message queue.receive(100) # clear queue assert queue.clear # queue size is greater than zero assert queue.size>0 end def test_27_set_amazon_problems original_problems = Aws::SqsInterface.amazon_problems assert(original_problems.length > 0) Aws::SqsInterface.amazon_problems= original_problems << "A New Problem" new_problems = Aws::SqsInterface.amazon_problems assert_equal(new_problems, original_problems) Aws::SqsInterface.amazon_problems= nil assert_nil(Aws::SqsInterface.amazon_problems) end def test_28_check_threading_model assert(!@sqs.multi_thread) newsqs = Aws::SqsInterface.new(TestCredentials.aws_access_key_id, TestCredentials.aws_secret_access_key, {:multi_thread => true}) assert(newsqs.multi_thread) end # Note that just like the other tests, this one does not clean up # after itself (queue does not get deleted) def test_29_change_message_visibility queue = Aws::Sqs::Queue.create(@s, "#{@queue_name}_29", true, 10) # Be sure the queue is empty. Useful when rerunning the tests. queue.clear queue.send_message(RIGHT_MESSAGE_TEXT) m = nil # Wait for the message to be delivered while m.nil? m = queue.receive end m.visibility = 60 sleep(20) # Had the visibility timeout been not updated, the message would be available # at this point. assert queue.receive.nil? end end aws-2.10.2/test/test_credentials.rb0000644000175000017500000000224512511515441015744 0ustar tnnntnnnclass TestCredentials @@aws_access_key_id = nil @@aws_secret_access_key = nil @@account_number = nil @@config = nil def self.config @@config end def self.aws_access_key_id @@aws_access_key_id end def self.aws_access_key_id=(newval) @@aws_access_key_id = newval end def self.account_number @@account_number end def self.account_number=(newval) @@account_number = newval end def self.aws_secret_access_key @@aws_secret_access_key end def self.aws_secret_access_key=(newval) @@aws_secret_access_key = newval end require 'yaml' def self.get_credentials #Dir.chdir do begin Dir.chdir(File.expand_path("~/.test_configs")) do credentials = YAML::load(File.open("aws.yml")) @@config = credentials self.aws_access_key_id = credentials["amazon"]["access_key"] self.aws_secret_access_key = credentials["amazon"]["secret_key"] end rescue Exception => e puts "#{e.message}" raise e end #end end end aws-2.10.2/test/iam/0000755000175000017500000000000012511515441012626 5ustar tnnntnnnaws-2.10.2/test/iam/test_iam.rb0000644000175000017500000000154012511515441014760 0ustar tnnntnnnrequire 'test/unit' require File.dirname(__FILE__) + '/../../lib/aws' require 'pp' require File.dirname(__FILE__) + '/../test_credentials.rb' class TestIam < Test::Unit::TestCase def setup TestCredentials.get_credentials @iam = Aws::Iam.new(TestCredentials.aws_access_key_id, TestCredentials.aws_secret_access_key) end def test_01_list_server_certificates ret = @iam.list_server_certificates p ret assert_true(ret.size == 0) end def test_02_upload_server_certificate ret = @iam.upload_server_certificate("test_cert", IO.read('x').strip, IO.read('y').strip, :certificate_chain=>IO.read('z').strip) p ret end end aws-2.10.2/.gitignore0000644000175000017500000000005112511515441013065 0ustar tnnntnnn/.project pkg .idea Gemfile.lock .bundle aws-2.10.2/aws.gemspec0000644000175000017500000000222012511515441013234 0ustar tnnntnnn require File.expand_path('../lib/version', __FILE__) Gem::Specification.new do |gem| gem.authors = ["Travis Reeder", "Chad Arimura", "RightScale"] gem.email = ["travis@appoxy.com"] gem.description = "AWS Ruby Library for interfacing with Amazon Web Services including EC2, S3, SQS, SimpleDB and most of their other services as well. By http://www.appoxy.com" gem.summary = "AWS Ruby Library for interfacing with Amazon Web Services including EC2, S3, SQS, SimpleDB and most of their other services as well. By http://www.appoxy.com" gem.homepage = "http://github.com/appoxy/aws/" gem.files = `git ls-files`.split($\) gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) gem.name = "aws" gem.require_paths = ["lib"] gem.version = Aws::VERSION gem.required_rubygems_version = ">= 1.3.6" gem.required_ruby_version = Gem::Requirement.new(">= 1.8") gem.add_runtime_dependency "uuidtools", ">= 0" gem.add_runtime_dependency "xml-simple", ">= 0" gem.add_runtime_dependency "http_connection", ">= 0" end aws-2.10.2/Gemfile0000644000175000017500000000004712511515441012375 0ustar tnnntnnnsource "https://rubygems.org" gemspec aws-2.10.2/lib/0000755000175000017500000000000012511515441011647 5ustar tnnntnnnaws-2.10.2/lib/version.rb0000644000175000017500000000004412511515441013657 0ustar tnnntnnnmodule Aws VERSION = "2.10.2" end aws-2.10.2/lib/awsbase/0000755000175000017500000000000012511515441013274 5ustar tnnntnnnaws-2.10.2/lib/awsbase/benchmark_fix.rb0000644000175000017500000000266712511515441016434 0ustar tnnntnnn# # Copyright (c) 2007-2008 RightScale Inc # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # # # A hack because there's a bug in add! in Benchmark::Tms module Benchmark #:nodoc: class Tms #:nodoc: def add!(&blk) t = Benchmark::measure(&blk) @utime = utime + t.utime @stime = stime + t.stime @cutime = cutime + t.cutime @cstime = cstime + t.cstime @real = real + t.real self end end end aws-2.10.2/lib/awsbase/awsbase.rb0000644000175000017500000006003512511515441015252 0ustar tnnntnnn# # Copyright (c) 2007-2008 RightScale Inc # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # # Test module Aws require 'digest/md5' require 'pp' require 'cgi' require 'uri' require 'xmlsimple' require 'net/http' require_relative 'utils' require_relative 'errors' require_relative 'parsers' class AwsBenchmarkingBlock #:nodoc: attr_accessor :xml, :service def initialize # Benchmark::Tms instance for service (Ec2, S3, or SQS) access benchmarking. @service = Benchmark::Tms.new() # Benchmark::Tms instance for XML parsing benchmarking. @xml = Benchmark::Tms.new() end end class AwsNoChange < RuntimeError end class AwsBase # Amazon HTTP Error handling # Text, if found in an error message returned by AWS, indicates that this may be a transient # error. Transient errors are automatically retried with exponential back-off. AMAZON_PROBLEMS = ['internal service error', 'is currently unavailable', 'no response from', 'Please try again', 'InternalError', 'ServiceUnavailable', #from SQS docs 'Unavailable', 'This application is not currently available', 'InsufficientInstanceCapacity' ] @@amazon_problems = AMAZON_PROBLEMS # Returns a list of Amazon service responses which are known to be transient problems. # We have to re-request if we get any of them, because the problem will probably disappear. # By default this method returns the same value as the AMAZON_PROBLEMS const. def self.amazon_problems @@amazon_problems end # Sets the list of Amazon side problems. Use in conjunction with the # getter to append problems. def self.amazon_problems=(problems_list) @@amazon_problems = problems_list end end module AwsBaseInterface DEFAULT_SIGNATURE_VERSION = '2' module ClassMethods def self.bench @@bench end def self.bench_xml @@bench.xml end def self.bench_s3 @@bench.service end end @@caching = false def self.caching @@caching end def self.caching=(caching) @@caching = caching end # Current aws_access_key_id attr_reader :aws_access_key_id # Last HTTP request object attr_reader :last_request # Last HTTP response object attr_reader :last_response # Last AWS errors list (used by AWSErrorHandler) attr_accessor :last_errors # Last AWS request id (used by AWSErrorHandler) attr_accessor :last_request_id # Logger object attr_accessor :logger # Initial params hash attr_accessor :params # RightHttpConnection instance # there's a method now to get this since it could be per thread or what have you # attr_reader :connection # Cache attr_reader :cache # Signature version (all services except s3) attr_reader :signature_version def init(service_info, aws_access_key_id, aws_secret_access_key, params={}) #:nodoc: @params = params if Aws::Utils.blank?(aws_access_key_id) || Aws::Utils.blank?(aws_secret_access_key) raise AwsError.new("AWS access keys are required to operate on #{service_info[:name]}") end @aws_access_key_id = aws_access_key_id @aws_secret_access_key = aws_secret_access_key # if the endpoint was explicitly defined - then use it if @params[:endpoint_url] @params[:server] = URI.parse(@params[:endpoint_url]).host @params[:port] = URI.parse(@params[:endpoint_url]).port @params[:service] = URI.parse(@params[:endpoint_url]).path @params[:protocol] = URI.parse(@params[:endpoint_url]).scheme @params[:api_version] ||= service_info[:api_version] @params[:region] = nil else @params[:server] ||= service_info[:default_host] @params[:server] = "#{@params[:region]}.#{@params[:server]}" if @params[:region] @params[:port] ||= service_info[:default_port] @params[:service] ||= service_info[:default_service] @params[:protocol] ||= service_info[:default_protocol] @params[:api_version] ||= service_info[:api_version] end if !@params[:multi_thread].nil? && @params[:connection_mode].nil? # user defined this @params[:connection_mode] = @params[:multi_thread] ? :per_thread : :single end # @params[:multi_thread] ||= defined?(AWS_DAEMON) @params[:connection_mode] ||= :default @params[:connection_mode] = :per_request if @params[:connection_mode] == :default @logger = @params[:logger] @logger = Rails.logger if !@logger && defined?(Rails) && defined?(Rails.logger) @logger = ::Rails.logger if !@logger && defined?(::Rails.logger) if !@logger @logger = Logger.new(STDOUT) @logger.level = Logger::INFO end @logger.debug "New #{self.class.name} using #{@params[:connection_mode].to_s}-connection mode" @error_handler = nil @cache = {} @signature_version = (params[:signature_version] || service_info[:signature_version] || DEFAULT_SIGNATURE_VERSION).to_s end def signed_service_params(aws_secret_access_key, service_hash, http_verb=nil, host=nil, service=nil) case signature_version.to_s when '0' then Utils::sign_request_v0(aws_secret_access_key, service_hash) when '1' then Utils::sign_request_v1(aws_secret_access_key, service_hash) when '2' then Utils::sign_request_v2(aws_secret_access_key, service_hash, http_verb, host, service) else raise AwsError.new("Unknown signature version (#{signature_version.to_s}) requested") end end def generate_request(action, params={}) generate_request2(@aws_access_key_id, @aws_secret_access_key, action, @params[:api_version], @params, params) end # FROM SDB def generate_request2(aws_access_key, aws_secret_key, action, api_version, lib_params, user_params={}, options={}) #:nodoc: # remove empty params from request user_params.delete_if { |key, value| value.nil? } # user_params.each_pair do |k,v| # user_params[k] = v.force_encoding("UTF-8") # end #params_string = params.to_a.collect{|key,val| key + "=#{CGI::escape(val.to_s)}" }.join("&") # prepare service data service = lib_params[:service] now = Time.now.getutc service_hash = {"Action" => action, "AWSAccessKeyId" => aws_access_key} service_hash.update("Version" => api_version) if api_version service_hash.update(user_params) headers = {} if signature_version == '3' service_hash["Timestamp"] = now.iso8601 service_params = escape_params(service_hash) signature, algorithm = Aws::Utils.signature_version3(aws_secret_key, now) headers['X-Amzn-Authorization'] = "AWS3-HTTPS AWSAccessKeyId=#{aws_access_key}, Algorithm=#{algorithm.upcase}, Signature=#{signature}" headers['Date'] = now.httpdate else # puts 'service=' + service.to_s service_params = signed_service_params(aws_secret_key, service_hash, :get, lib_params[:server], lib_params[:service]) end # # use POST method if the length of the query string is too large # see http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/MakingRESTRequests.html if service_params.size > 2000 if signature_version == '2' # resign the request because HTTP verb is included into signature service_params = signed_service_params(aws_secret_key, service_hash, :post, lib_params[:server], service) end request = Net::HTTP::Post.new(service) request.body = service_params request['Content-Type'] = 'application/x-www-form-urlencoded; charset=utf-8' else request = Net::HTTP::Get.new("#{service}?#{service_params}") end headers.each_pair do |k, v| request[k] = v end # puts "header=" + request['X-Amzn-Authorization'] #puts "\n\n --------------- QUERY REQUEST TO AWS -------------- \n\n" #puts "#{@params[:service]}?#{service_params}\n\n" # prepare output hash {:request => request, :server => lib_params[:server], :port => lib_params[:port], :protocol => lib_params[:protocol]} end def escape_params(service_hash) canonical_string = service_hash.keys.sort.map do |key| "#{Aws::Utils.amz_escape(key)}=#{Aws::Utils.amz_escape(service_hash[key])}" end.join('&') canonical_string end def get_conn(connection_name, lib_params, logger) # thread = lib_params[:multi_thread] ? Thread.current : Thread.main # thread[connection_name] ||= Rightscale::HttpConnection.new(:exception => Aws::AwsError, :logger => logger) # conn = thread[connection_name] # return conn http_conn = nil conn_mode = lib_params[:connection_mode] params = {:exception => AwsError, :logger => logger} # Adds all parameters accepted by Rightscale::HttpConnection#new [:user_agent, :ca_file, :http_connection_retry_count, :http_connection_open_timeout, :http_connection_read_timeout, :http_connection_retry_delay ].each do |key| params[key] = lib_params[key] if lib_params.has_key?(key) end if conn_mode == :per_request http_conn = Rightscale::HttpConnection.new(params) elsif conn_mode == :per_thread || conn_mode == :single thread = conn_mode == :per_thread ? Thread.current : Thread.main thread[connection_name] ||= Rightscale::HttpConnection.new(params) http_conn = thread[connection_name] # ret = request_info_impl(http_conn, bench, request, parser, &block) end return http_conn end def close_conn(conn_name) conn_mode = @params[:connection_mode] if conn_mode == :per_thread || conn_mode == :single thread = conn_mode == :per_thread ? Thread.current : Thread.main if !thread[conn_name].nil? thread[conn_name].finish thread[conn_name] = nil end end end def connection get_conn(self.class.connection_name, self.params, self.logger) end def close_connection close_conn(self.class.connection_name) end def request_info2(request, parser, lib_params, connection_name, logger, bench, options={}, &block) #:nodoc: ret = nil # puts 'OPTIONS=' + options.inspect http_conn = get_conn(connection_name, lib_params, logger) begin # todo: this QueryTimeout retry should go into a SimpleDbErrorHandler, not here retry_count = 1 count = 0 while count <= retry_count puts 'RETRYING QUERY due to QueryTimeout...' if count > 0 begin ret = request_info_impl(http_conn, bench, request, parser, options, &block) rescue Aws::AwsError => ex if !ex.include?(/QueryTimeout/) || count == retry_count raise ex end end break if ret count += 1 end ensure http_conn.finish if http_conn && lib_params[:connection_mode] == :per_request end ret end # This is the latest and greatest now. Service must have connection_name defined. def request_info3(service_interface, request, parser, options, &block) request_info2(request, parser, service_interface.params, service_interface.class.connection_name, service_interface.logger, service_interface.class.bench, options, &block) end # This is the direction we should head instead of writing our own parsers for everything, much simpler # params: # - :group_tags => hash of indirection to eliminate, see: http://xml-simple.rubyforge.org/ # - :force_array => true for all or an array of tag names to force # - :pull_out_array => an array of levels to dig into when generating return value (see rds.rb for example) def request_info_xml_simple(connection_name, lib_params, request, logger, params = {}) connection = get_conn(connection_name, lib_params, logger) begin @last_request = request[:request] @last_response = nil response = connection.request(request) # puts "response=" + response.body # benchblock.service.add!{ response = connection.request(request) } # check response for errors... @last_response = response if response.is_a?(Net::HTTPSuccess) @error_handler = nil # benchblock.xml.add! { parser.parse(response) } # return parser.result force_array = params[:force_array] || false # Force_array and group_tags don't work nice together so going to force array manually xml_simple_options = {"KeyToSymbol" => false, 'ForceArray' => false} xml_simple_options["GroupTags"] = params[:group_tags] if params[:group_tags] # { 'GroupTags' => { 'searchpath' => 'dir' } # 'ForceArray' => %r(_list$) parsed = XmlSimple.xml_in(response.body, xml_simple_options) # todo: we may want to consider stripping off a couple of layers when doing this, for instance: # # # # .... # Strip it off and only return an array or hash of 's (hash by identifier). # would have to be able to make the RequestId available somehow though, perhaps some special array subclass which included that? unless force_array.is_a? Array force_array = [] end parsed = symbolize(parsed, force_array) # puts 'parsed=' + parsed.inspect if params[:pull_out_array] ret = Aws::AwsResponseArray.new(parsed[:response_metadata]) level_hash = parsed params[:pull_out_array].each do |x| level_hash = level_hash[x] end if level_hash.is_a? Hash # When there's only one ret << level_hash else # should be array # puts 'level_hash=' + level_hash.inspect level_hash.each do |x| ret << x end end elsif params[:pull_out_single] # returns a single object ret = AwsResponseObjectHash.new(parsed[:response_metadata]) level_hash = parsed params[:pull_out_single].each do |x| level_hash = level_hash[x] end ret.merge!(level_hash) else ret = parsed end return ret else @error_handler = AWSErrorHandler.new(self, nil, :errors_list => self.class.amazon_problems) unless @error_handler check_result = @error_handler.check(request) if check_result @error_handler = nil return check_result end request_text_data = "#{request[:server]}:#{request[:port]}#{request[:request].path}" raise AwsError2.new(@last_response.code, @last_request_id, request_text_data, @last_response.body) end ensure connection.finish if connection && lib_params[:connection_mode] == :per_request end end # This is the latest and greatest now. Service must have connection_name defined. def request_info_xml_simple3(service_interface, request, options) request_info_xml_simple(service_interface.class.connection_name, service_interface.params, request, service_interface.logger, options) end def symbolize(hash, force_array) ret = {} hash.keys.each do |key| val = hash[key] if val.is_a? Hash val = symbolize(val, force_array) if force_array.include? key val = [val] end elsif val.is_a? Array val = val.collect { |x| symbolize(x, force_array) } end ret[Aws::Utils.underscore(key).to_sym] = val end ret end # Returns +true+ if the describe_xxx responses are being cached def caching? @params.key?(:cache) ? @params[:cache] : @@caching end # Check if the aws function response hits the cache or not. # If the cache hits: # - raises an +AwsNoChange+ exception if +do_raise+ == +:raise+. # - returnes parsed response from the cache if it exists or +true+ otherwise. # If the cache miss or the caching is off then returns +false+. def cache_hits?(function, response, do_raise=:raise) result = false if caching? function = function.to_sym # get rid of requestId (this bad boy was added for API 2008-08-08+ and it is uniq for every response) response = response.sub(%r{.+?}, '') response_md5 =Digest::MD5.hexdigest(response).to_s # check for changes unless @cache[function] && @cache[function][:response_md5] == response_md5 # well, the response is new, reset cache data update_cache(function, {:response_md5 => response_md5, :timestamp => Time.now, :hits => 0, :parsed => nil}) else # aha, cache hits, update the data and throw an exception if needed @cache[function][:hits] += 1 if do_raise == :raise raise(AwsNoChange, "Cache hit: #{function} response has not changed since "+ "#{@cache[function][:timestamp].strftime('%Y-%m-%d %H:%M:%S')}, "+ "hits: #{@cache[function][:hits]}.") else result = @cache[function][:parsed] || true end end end result end def update_cache(function, hash) (@cache[function.to_sym] ||= {}).merge!(hash) if caching? end def on_exception(options={:raise => true, :log => true}) # :nodoc: raise if $!.is_a?(AwsNoChange) AwsError::on_aws_exception(self, options) end # Return +true+ if this instance works in multi_thread mode and +false+ otherwise. def multi_thread @params[:multi_thread] end def request_info_impl(connection, benchblock, request, parser, options={}, &block) #:nodoc: connection = connection @last_request = request[:request] @last_response = nil response =nil blockexception = nil # puts 'OPTIONS2=' + options.inspect if (block != nil) # TRB 9/17/07 Careful - because we are passing in blocks, we get a situation where # an exception may get thrown in the block body (which is high-level # code either here or in the application) but gets caught in the # low-level code of HttpConnection. The solution is not to let any # exception escape the block that we pass to HttpConnection::request. # Exceptions can originate from code directly in the block, or from user # code called in the other block which is passed to response.read_body. benchblock.service.add! do responsehdr = connection.request(request) do |response| ######### begin @last_response = response if response.is_a?(Net::HTTPSuccess) @error_handler = nil response.read_body(&block) else @error_handler = AWSErrorHandler.new(self, parser, :errors_list => self.class.amazon_problems) unless @error_handler check_result = @error_handler.check(request, options) if check_result @error_handler = nil return check_result end request_text_data = "#{request[:server]}:#{request[:port]}#{request[:request].path}" raise AwsError.new(@last_errors, @last_response.code, @last_request_id, request_text_data) end rescue Exception => e blockexception = e end end ######### #OK, now we are out of the block passed to the lower level if (blockexception) raise blockexception end benchblock.xml.add! do parser.parse(responsehdr) end return parser.result end else benchblock.service.add! { response = connection.request(request) } # check response for errors... @last_response = response if response.is_a?(Net::HTTPSuccess) @error_handler = nil benchblock.xml.add! { parser.parse(response) } return parser.result else @error_handler = AWSErrorHandler.new(self, parser, :errors_list => self.class.amazon_problems) unless @error_handler check_result = @error_handler.check(request, options) if check_result @error_handler = nil return check_result end request_text_data = "#{request[:server]}:#{request[:port]}#{request[:request].path}" raise AwsError.new(@last_errors, @last_response.code, @last_request_id, request_text_data) end end rescue @error_handler = nil raise end def request_cache_or_info(method, link, parser_class, benchblock, use_cache=true) #:nodoc: # We do not want to break the logic of parsing hence will use a dummy parser to process all the standard # steps (errors checking etc). The dummy parser does nothig - just returns back the params it received. # If the caching is enabled and hit then throw AwsNoChange. # P.S. caching works for the whole images list only! (when the list param is blank) # check cache response, params = request_info(link, RightDummyParser.new) cache_hits?(method.to_sym, response.body) if use_cache parser = parser_class.new(:logger => @logger) benchblock.xml.add! { parser.parse(response, params) } result = block_given? ? yield(parser) : parser.result # update parsed data update_cache(method.to_sym, :parsed => result) if use_cache result end # Returns Amazons request ID for the latest request def last_request_id @last_response && @last_response.body.to_s[%r{(.+?)}] && $1 end def hash_params(prefix, list) #:nodoc: groups = {} list.each_index { |i| groups.update("#{prefix}.#{i+1}" => list[i]) } if list return groups end end end aws-2.10.2/lib/awsbase/parsers.rb0000644000175000017500000001367412511515441015313 0ustar tnnntnnnmodule Aws #----------------------------------------------------------------- class RightSaxParserCallback #:nodoc: def self.include_callback include XML::SaxParser::Callbacks end def initialize(right_aws_parser) @right_aws_parser = right_aws_parser end def on_start_element(name, attr_hash) @right_aws_parser.tag_start(name, attr_hash) end def on_characters(chars) @right_aws_parser.text(chars) end def on_end_element(name) @right_aws_parser.tag_end(name) end def on_start_document; end def on_comment(msg) ; end def on_processing_instruction(target, data) ; end def on_cdata_block(cdata) ; end def on_end_document; end end class AwsParser #:nodoc: # default parsing library DEFAULT_XML_LIBRARY = 'rexml' # a list of supported parsers @@supported_xml_libs = [DEFAULT_XML_LIBRARY, 'libxml'] @@xml_lib = DEFAULT_XML_LIBRARY # xml library name: 'rexml' | 'libxml' def self.xml_lib @@xml_lib end def self.xml_lib=(new_lib_name) @@xml_lib = new_lib_name end attr_accessor :result attr_reader :xmlpath attr_accessor :xml_lib def initialize(params={}) @xmlpath = '' @result = false @text = '' @xml_lib = params[:xml_lib] || @@xml_lib @logger = params[:logger] reset end def tag_start(name, attributes) @text = '' tagstart(name, attributes) @xmlpath += @xmlpath.empty? ? name : "/#{name}" end def tag_end(name) if @xmlpath =~ /^(.*?)\/?#{name}$/ @xmlpath = $1 end tagend(name) end def text(text) @text += text tagtext(text) end # Parser method. # Params: # xml_text - xml message text(String) or Net:HTTPxxx instance (response) # params[:xml_lib] - library name: 'rexml' | 'libxml' def parse(xml_text, params={}) # Get response body unless xml_text.is_a?(String) xml_text = xml_text.body.respond_to?(:force_encoding) ? xml_text.body.force_encoding("UTF-8") : xml_text.body end @xml_lib = params[:xml_lib] || @xml_lib # check that we had no problems with this library otherwise use default @xml_lib = DEFAULT_XML_LIBRARY unless @@supported_xml_libs.include?(@xml_lib) # load xml library if @xml_lib=='libxml' && !defined?(XML::SaxParser) begin require 'xml/libxml' # is it new ? - Setup SaxParserCallback if XML::Parser::VERSION >= '0.5.1.0' RightSaxParserCallback.include_callback end rescue LoadError => e @@supported_xml_libs.delete(@xml_lib) @xml_lib = DEFAULT_XML_LIBRARY if @logger @logger.error e.inspect @logger.error e.backtrace @logger.info "Can not load 'libxml' library. '#{DEFAULT_XML_LIBRARY}' is used for parsing." end end end # Parse the xml text case @xml_lib when 'libxml' xml = XML::SaxParser.string(xml_text) # check libxml-ruby version if XML::Parser::VERSION >= '0.5.1.0' xml.callbacks = RightSaxParserCallback.new(self) else xml.on_start_element { |name, attr_hash| self.tag_start(name, attr_hash) } xml.on_characters { |text| self.text(text) } xml.on_end_element { |name| self.tag_end(name) } end xml.parse else REXML::Document.parse_stream(xml_text, self) end end # Parser must have a lots of methods # (see /usr/lib/ruby/1.8/rexml/parsers/streamparser.rb) # We dont need most of them in AwsParser and method_missing helps us # to skip their definition def method_missing(method, *params) # if the method is one of known - just skip it ... return if [:comment, :attlistdecl, :notationdecl, :elementdecl, :entitydecl, :cdata, :xmldecl, :attlistdecl, :instruction, :doctype].include?(method) # ... else - call super to raise an exception super(method, params) end # the functions to be overriden by children (if nessesery) def reset; end def tagstart(name, attributes) ; end def tagend(name) ; end def tagtext(text) ; end end #----------------------------------------------------------------- # PARSERS: Errors #----------------------------------------------------------------- # # TemporaryRedirect # Please re-send this request to the specified temporary endpoint. Continue to use the original request endpoint for future requests. # FD8D5026D1C5ABA3 # bucket-for-k.s3-external-3.amazonaws.com # ItJy8xPFPli1fq/JR3DzQd3iDvFCRqi1LTRmunEdM1Uf6ZtW2r2kfGPWhRE1vtaU # bucket-for-k # class RightErrorResponseParser < AwsParser #:nodoc: attr_accessor :errors # array of hashes: error/message attr_accessor :requestID # attr_accessor :endpoint, :host_id, :bucket def parse(response) super end def tagend(name) case name when 'RequestID'; @requestID = @text when 'Code'; @code = @text when 'Message'; @message = @text when 'Endpoint' ; @endpoint = @text when 'HostId' ; @host_id = @text when 'Bucket' ; @bucket = @text when 'Error'; @errors << [@code, @message] end end def reset @errors = [] end end # Dummy parser - does nothing # Returns the original params back class RightDummyParser # :nodoc: attr_accessor :result def parse(response, params={}) @result = [response, params] end end class RightHttp2xxParser < AwsParser # :nodoc: def parse(response) @result = response.is_a?(Net::HTTPSuccess) end end end aws-2.10.2/lib/awsbase/utils.rb0000644000175000017500000002431012511515441014761 0ustar tnnntnnnmodule Aws class Utils #:nodoc: @@digest1 = OpenSSL::Digest.new("sha1") @@digest256 = nil if OpenSSL::OPENSSL_VERSION_NUMBER > 0x00908000 @@digest256 = OpenSSL::Digest.new("sha256") rescue nil # Some installation may not support sha256 end def self.sign(aws_secret_access_key, auth_string) Base64.encode64(OpenSSL::HMAC.digest(@@digest1, aws_secret_access_key, auth_string)).strip end # Set a timestamp and a signature version def self.fix_service_params(service_hash, signature) service_hash["Timestamp"] ||= Time.now.utc.strftime("%Y-%m-%dT%H:%M:%S.000Z") unless service_hash["Expires"] service_hash["SignatureVersion"] = signature service_hash end # Signature Version 0 # A deprecated guy (should work till septemper 2009) def self.sign_request_v0(aws_secret_access_key, service_hash) fix_service_params(service_hash, '0') string_to_sign = "#{service_hash['Action']}#{service_hash['Timestamp'] || service_hash['Expires']}" service_hash['Signature'] = Utils::sign(aws_secret_access_key, string_to_sign) service_hash.to_a.collect { |key, val| "#{amz_escape(key)}=#{amz_escape(val.to_s)}" }.join("&") end # Signature Version 1 # Another deprecated guy (should work till septemper 2009) def self.sign_request_v1(aws_secret_access_key, service_hash) fix_service_params(service_hash, '1') string_to_sign = service_hash.sort { |a, b| (a[0].to_s.downcase)<=>(b[0].to_s.downcase) }.to_s service_hash['Signature'] = Utils::sign(aws_secret_access_key, string_to_sign) service_hash.to_a.collect { |key, val| "#{amz_escape(key)}=#{amz_escape(val.to_s)}" }.join("&") end # Signature Version 2 # EC2, SQS and SDB requests must be signed by this guy. # See: http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/index.html?REST_RESTAuth.html # http://developer.amazonwebservices.com/connect/entry.jspa?externalID=1928 def self.sign_request_v2(aws_secret_access_key, service_hash, http_verb, host, uri) fix_service_params(service_hash, '2') # select a signing method (make an old openssl working with sha1) # make 'HmacSHA256' to be a default one service_hash['SignatureMethod'] = 'HmacSHA256' unless ['HmacSHA256', 'HmacSHA1'].include?(service_hash['SignatureMethod']) service_hash['SignatureMethod'] = 'HmacSHA1' unless @@digest256 # select a digest digest = (service_hash['SignatureMethod'] == 'HmacSHA256' ? @@digest256 : @@digest1) # form string to sign canonical_string = service_hash.keys.sort.map do |key| "#{amz_escape(key)}=#{amz_escape(service_hash[key])}" end.join('&') string_to_sign = "#{http_verb.to_s.upcase}\n#{host.downcase}\n#{uri}\n#{canonical_string}" # sign the string signature = escape_sig(Base64.encode64(OpenSSL::HMAC.digest(digest, aws_secret_access_key, string_to_sign)).strip) ret = "#{canonical_string}&Signature=#{signature}" # puts 'full=' + ret.inspect ret end # New signature for ses def self.signature_version3(aws_secret_key, now) algorithm = @@digest256 ? 'HmacSHA256' : 'HmacSHA1' # select a digest digest = (algorithm == 'HmacSHA256' ? @@digest256 : @@digest1) signature = (Base64.encode64(OpenSSL::HMAC.digest(digest, aws_secret_key, now.httpdate)).strip) return signature, algorithm end HEX = [ "%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07", "%08", "%09", "%0A", "%0B", "%0C", "%0D", "%0E", "%0F", "%10", "%11", "%12", "%13", "%14", "%15", "%16", "%17", "%18", "%19", "%1A", "%1B", "%1C", "%1D", "%1E", "%1F", "%20", "%21", "%22", "%23", "%24", "%25", "%26", "%27", "%28", "%29", "%2A", "%2B", "%2C", "%2D", "%2E", "%2F", "%30", "%31", "%32", "%33", "%34", "%35", "%36", "%37", "%38", "%39", "%3A", "%3B", "%3C", "%3D", "%3E", "%3F", "%40", "%41", "%42", "%43", "%44", "%45", "%46", "%47", "%48", "%49", "%4A", "%4B", "%4C", "%4D", "%4E", "%4F", "%50", "%51", "%52", "%53", "%54", "%55", "%56", "%57", "%58", "%59", "%5A", "%5B", "%5C", "%5D", "%5E", "%5F", "%60", "%61", "%62", "%63", "%64", "%65", "%66", "%67", "%68", "%69", "%6A", "%6B", "%6C", "%6D", "%6E", "%6F", "%70", "%71", "%72", "%73", "%74", "%75", "%76", "%77", "%78", "%79", "%7A", "%7B", "%7C", "%7D", "%7E", "%7F", "%80", "%81", "%82", "%83", "%84", "%85", "%86", "%87", "%88", "%89", "%8A", "%8B", "%8C", "%8D", "%8E", "%8F", "%90", "%91", "%92", "%93", "%94", "%95", "%96", "%97", "%98", "%99", "%9A", "%9B", "%9C", "%9D", "%9E", "%9F", "%A0", "%A1", "%A2", "%A3", "%A4", "%A5", "%A6", "%A7", "%A8", "%A9", "%AA", "%AB", "%AC", "%AD", "%AE", "%AF", "%B0", "%B1", "%B2", "%B3", "%B4", "%B5", "%B6", "%B7", "%B8", "%B9", "%BA", "%BB", "%BC", "%BD", "%BE", "%BF", "%C0", "%C1", "%C2", "%C3", "%C4", "%C5", "%C6", "%C7", "%C8", "%C9", "%CA", "%CB", "%CC", "%CD", "%CE", "%CF", "%D0", "%D1", "%D2", "%D3", "%D4", "%D5", "%D6", "%D7", "%D8", "%D9", "%DA", "%DB", "%DC", "%DD", "%DE", "%DF", "%E0", "%E1", "%E2", "%E3", "%E4", "%E5", "%E6", "%E7", "%E8", "%E9", "%EA", "%EB", "%EC", "%ED", "%EE", "%EF", "%F0", "%F1", "%F2", "%F3", "%F4", "%F5", "%F6", "%F7", "%F8", "%F9", "%FA", "%FB", "%FC", "%FD", "%FE", "%FF" ] TO_REMEMBER = 'AZaz09 -_.!~*\'()' ASCII = {} # {'A'=>65, 'Z'=>90, 'a'=>97, 'z'=>122, '0'=>48, '9'=>57, ' '=>32, '-'=>45, '_'=>95, '.'=>} TO_REMEMBER.each_byte do |b| ASCII[b.chr] = b.chr.unpack("c")[0] end # puts 'ascii=' + ASCII.inspect # Escape a string accordingly Amazon rulles # http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/index.html?REST_RESTAuth.html def self.amz_escape(param) param = param.to_s # param = param.force_encoding("UTF-8") # e = "x" # escape2(param.to_s) # puts 'ESCAPED=' + e.inspect #return CGI.escape(param.to_s).gsub("%7E", "~").gsub("+", "%20") # from: http://umlaut.rubyforge.org/svn/trunk/lib/aws_product_sign.rb #param.to_s.gsub(/([^a-zA-Z0-9._~-]+)/n) do # '%' + $1.unpack('H2' * $1.size).join('%').upcase #end # puts 'e in=' + e.inspect # converter = Iconv.new('ASCII', 'UTF-8') # e = converter.iconv(e) #.unpack('U*').select{ |cp| cp < 127 }.pack('U*') # puts 'e out=' + e.inspect e2 = CGI.escape(param) e2 = e2.gsub("%7E", "~") e2 = e2.gsub("+", "%20") e2 = e2.gsub("*", "%2A") # puts 'E2=' + e2.inspect # puts e == e2.to_s e2 end # From: https://github.com/tomandersen/aws/commit/04b494b1ac440db1cd3b4ff3c84d0b2745d25250 # Not used yet, but worth trying it out. def self.perhaps_a_better_escape(key) # EG: CGI escape is str.gsub(/[^a-zA-Z0-9_\-.]/n){ sprintf("%%%02X", $&.unpack("C")[0]) }, but we can leave in / and some others for easier reading # escape all characters except a-Z, 0-9, and - _ . ! ~ *' ( ) / key.gsub(/[^a-zA-Z0-9\-_.!~*'()\/]/n) { sprintf("%%%02X", $&.unpack("C")[0]) } end def self.escape2(s) # home grown ret = "" s.unpack("U*") do |ch| # puts 'ch=' + ch.inspect if ASCII['A'] <= ch && ch <= ASCII['Z'] # A to Z ret << ch elsif ASCII['a'] <= ch && ch <= ASCII['z'] # a to z ret << ch elsif ASCII['0'] <= ch && ch <= ASCII['9'] # 0 to 9 ret << ch elsif ch == ASCII[' '] # space ret << "%20" # "+" elsif ch == ASCII['-'] || ch == ASCII['_'] || ch == ASCII['.'] || ch == ASCII['~'] ret << ch elsif ch <= 0x007f # other ascii ret << HEX[ch] elsif ch <= 0x07FF # non-ascii ret << HEX[0xc0 | (ch >> 6)] ret << HEX[0x80 | (ch & 0x3F)] else ret << HEX[0xe0 | (ch >> 12)] ret << HEX[0x80 | ((ch >> 6) & 0x3F)] ret << HEX[0x80 | (ch & 0x3F)] end end ret end def self.escape_sig(raw) e = CGI.escape(raw) end # From Amazon's SQS Dev Guide, a brief description of how to escape: # "URL encode the computed signature and other query parameters as specified in # RFC1738, section 2.2. In addition, because the + character is interpreted as a blank space # by Sun Java classes that perform URL decoding, make sure to encode the + character # although it is not required by RFC1738." # Avoid using CGI::escape to escape URIs. # CGI::escape will escape characters in the protocol, host, and port # sections of the URI. Only target chars in the query # string should be escaped. def self.URLencode(raw) e = URI.escape(raw) e.gsub(/\+/, "%2b") end def self.allow_only(allowed_keys, params) bogus_args = [] params.keys.each { |p| bogus_args.push(p) unless allowed_keys.include?(p) } raise AwsError.new("The following arguments were given but are not legal for the function call #{caller_method}: #{bogus_args.inspect}") if bogus_args.length > 0 end def self.mandatory_arguments(required_args, params) rargs = required_args.dup params.keys.each { |p| rargs.delete(p) } raise AwsError.new("The following mandatory arguments were not provided to #{caller_method}: #{rargs.inspect}") if rargs.length > 0 end def self.caller_method caller[1]=~/`(.*?)'/ $1 end def self.blank?(obj) case obj when NilClass, FalseClass true when TrueClass, Numeric false when Array, Hash obj.empty? when String obj.empty? || obj.strip.empty? else # "", " ", nil, [], and {} are blank if obj.respond_to?(:empty?) && obj.respond_to?(:strip) obj.empty? or obj.strip.empty? elsif obj.respond_to?(:empty?) obj.empty? else !obj end end end def self.underscore(camel_cased_word) camel_cased_word.to_s.gsub(/::/, '/'). gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2'). gsub(/([a-z\d])([A-Z])/,'\1_\2'). tr("-", "_"). downcase end end end aws-2.10.2/lib/awsbase/errors.rb0000644000175000017500000002604712511515441015146 0ustar tnnntnnnmodule Aws class ResourceNotFoundError < StandardError end # Exception class to signal any Amazon errors. All errors occuring during calls to Amazon's # web services raise this type of error. # Attribute inherited by RuntimeError: # message - the text of the error, generally as returned by AWS in its XML response. class AwsError < RuntimeError # either an array of errors where each item is itself an array of [code, message]), # or an error string if the error was raised manually, as in AwsError.new('err_text') attr_reader :errors # Request id (if exists) attr_reader :request_id # Response HTTP error code attr_reader :http_code # Raw request text data to AWS attr_reader :request_data attr_reader :response def initialize(errors=nil, http_code=nil, request_id=nil, request_data=nil, response=nil) @errors = errors @request_id = request_id @http_code = http_code @request_data = request_data @response = response msg = @errors.is_a?(Array) ? @errors.map { |code, m| "#{code}: #{m}" }.join("; ") : @errors.to_s msg += "\nREQUEST=#{@request_data} " unless @request_data.nil? msg += "\nREQUEST ID=#{@request_id} " unless @request_id.nil? super(msg) end # Does any of the error messages include the regexp +pattern+? # Used to determine whether to retry request. def include?(pattern_or_string) if pattern_or_string.is_a?(String) puts 'convert to pattern' pattern_or_string = /#{pattern_or_string}/ end if @errors.is_a?(Array) @errors.each { |code, msg| return true if code =~ pattern_or_string } else return true if @errors_str =~ pattern_or_string end false end # Generic handler for AwsErrors. +aws+ is the Aws::S3, Aws::EC2, or Aws::SQS # object that caused the exception (it must provide last_request and last_response). Supported # boolean options are: # * :log print a message into the log using aws.logger to access the Logger # * :puts do a "puts" of the error # * :raise re-raise the error after logging def self.on_aws_exception(aws, options={:raise=>true, :log=>true}) # Only log & notify if not user error if !options[:raise] || system_error?($!) error_text = "#{$!.inspect}\n#{$@}.join('\n')}" puts error_text if options[:puts] # Log the error if options[:log] request = aws.last_request ? aws.last_request.path : '-none-' response = aws.last_response ? "#{aws.last_response.code} -- #{aws.last_response.message} -- #{aws.last_response.body}" : '-none-' @response = response aws.logger.error error_text aws.logger.error "Request was: #{request}" aws.logger.error "Response was: #{response}" end end raise if options[:raise] # re-raise an exception return nil end # True if e is an AWS system error, i.e. something that is for sure not the caller's fault. # Used to force logging. def self.system_error?(e) !e.is_a?(self) || e.message =~ /InternalError|InsufficientInstanceCapacity|Unavailable/ end end # Simplified version class AwsError2 < RuntimeError # Request id (if exists) attr_reader :request_id # Response HTTP error code attr_reader :http_code # Raw request text data to AWS attr_reader :request_data attr_reader :response attr_reader :errors def initialize(http_code=nil, request_id=nil, request_data=nil, response=nil) @request_id = request_id @http_code = http_code @request_data = request_data @response = response # puts '@response=' + @response.inspect if @response ref = XmlSimple.xml_in(@response, {"ForceArray"=>false}) # puts "refxml=" + ref.inspect msg = "#{ref['Error']['Code']}: #{ref['Error']['Message']}" else msg = "#{@http_code}: REQUEST(#{@request_data})" end msg += "\nREQUEST ID=#{@request_id} " unless @request_id.nil? super(msg) end end class AWSErrorHandler # 0-100 (%) DEFAULT_CLOSE_ON_4XX_PROBABILITY = 10 @@reiteration_start_delay = 0.2 def self.reiteration_start_delay @@reiteration_start_delay end def self.reiteration_start_delay=(reiteration_start_delay) @@reiteration_start_delay = reiteration_start_delay end @@reiteration_time = 5 def self.reiteration_time @@reiteration_time end def self.reiteration_time=(reiteration_time) @@reiteration_time = reiteration_time end @@close_on_error = true def self.close_on_error @@close_on_error end def self.close_on_error=(close_on_error) @@close_on_error = close_on_error end @@close_on_4xx_probability = DEFAULT_CLOSE_ON_4XX_PROBABILITY def self.close_on_4xx_probability @@close_on_4xx_probability end def self.close_on_4xx_probability=(close_on_4xx_probability) @@close_on_4xx_probability = close_on_4xx_probability end # params: # :reiteration_time # :errors_list # :close_on_error = true | false # :close_on_4xx_probability = 1-100 def initialize(aws, parser, params={}) #:nodoc: @aws = aws # Link to RightEc2 | RightSqs | RightS3 instance @parser = parser # parser to parse Amazon response @started_at = Time.now @stop_at = @started_at + (params[:reiteration_time] || @@reiteration_time) @errors_list = params[:errors_list] || [] @reiteration_delay = @@reiteration_start_delay @retries = 0 # close current HTTP(S) connection on 5xx, errors from list and 4xx errors @close_on_error = params[:close_on_error].nil? ? @@close_on_error : params[:close_on_error] @close_on_4xx_probability = params[:close_on_4xx_probability] || @@close_on_4xx_probability end # Returns false if def check(request, options={}) #:nodoc: result = false error_found = false redirect_detected = false error_match = nil last_errors_text = '' response = @aws.last_response # log error request_text_data = "#{request[:server]}:#{request[:port]}#{request[:request].path}" # is this a redirect? # yes! if response.is_a?(Net::HTTPRedirection) redirect_detected = true else # no, it's an error ... @aws.logger.debug("##### #{@aws.class.name} returned an error: #{response.code} #{response.message}\n#{response.body} #####") @aws.logger.debug("##### #{@aws.class.name} request: #{request_text_data} ####") end # Check response body: if it is an Amazon XML document or not: if redirect_detected || (response.body && response.body[/<\?xml/]) # ... it is a xml document @aws.class.bench_xml.add! do error_parser = RightErrorResponseParser.new error_parser.parse(response) @aws.last_errors = error_parser.errors @aws.last_request_id = error_parser.requestID last_errors_text = @aws.last_errors.flatten.join("\n") # on redirect : if redirect_detected location = response['location'] || "#{request[:protocol]}://#{error_parser.instance_variable_get(:'@endpoint')}" # ... log information and ... @aws.logger.info("##### #{@aws.class.name} redirect requested: #{response.code} #{response.message} #####") @aws.logger.info("##### New location: #{location} #####") # ... fix the connection data request[:server] = URI.parse(location).host request[:protocol] = URI.parse(location).scheme request[:port] = URI.parse(location).port end end else # ... it is not a xml document(probably just a html page?) @aws.last_errors = [[response.code, "#{response.message} (#{request_text_data})"]] @aws.last_request_id = '-undefined-' last_errors_text = response.message end # now - check the error unless redirect_detected @errors_list.each do |error_to_find| if last_errors_text[/#{error_to_find}/i] error_found = true error_match = error_to_find @aws.logger.warn("##### Retry is needed, error pattern match: #{error_to_find} #####") break end end end # check the time has gone from the first error come if redirect_detected || error_found # Close the connection to the server and recreate a new one. # It may have a chance that one server is a semi-down and reconnection # will help us to connect to the other server if !redirect_detected && @close_on_error @aws.connection.finish "#{self.class.name}: error match to pattern '#{error_match}'" end # puts 'OPTIONS3=' + options.inspect if options[:retries].nil? || @retries < options[:retries] if (Time.now < @stop_at) @retries += 1 unless redirect_detected @aws.logger.warn("##### Retry ##{@retries} is being performed. Sleeping for #{@reiteration_delay} sec. Whole time: #{Time.now-@started_at} sec ####") sleep @reiteration_delay @reiteration_delay *= 2 # Always make sure that the fp is set to point to the beginning(?) # of the File/IO. TODO: it assumes that offset is 0, which is bad. if (request[:request].body_stream && request[:request].body_stream.respond_to?(:pos)) begin request[:request].body_stream.pos = 0 rescue Exception => e @logger.warn("Retry may fail due to unable to reset the file pointer" + " -- #{self.class.name} : #{e.inspect}") end end else @aws.logger.info("##### Retry ##{@retries} is being performed due to a redirect. ####") end result = @aws.request_info(request, @parser, options) else @aws.logger.warn("##### Ooops, time is over... ####") end else @aws.logger.info("##### Stopped retrying because retries=#{@retries} and max=#{options[:retries]} ####") end # aha, this is unhandled error: elsif @close_on_error # Is this a 5xx error ? if @aws.last_response.code.to_s[/^5\d\d$/] @aws.connection.finish "#{self.class.name}: code: #{@aws.last_response.code}: '#{@aws.last_response.message}'" # Is this a 4xx error ? elsif @aws.last_response.code.to_s[/^4\d\d$/] && @close_on_4xx_probability > rand(100) @aws.connection.finish "#{self.class.name}: code: #{@aws.last_response.code}: '#{@aws.last_response.message}', " + "probability: #{@close_on_4xx_probability}%" end end result end end end aws-2.10.2/lib/awsbase/aws_response_array.rb0000644000175000017500000000130212511515441017523 0ustar tnnntnnnmodule Aws # This class is a special array to hold a bit of extra information about a response like: # # 4f1fae46-bf3d-11de-a88b-7b5b3d23b3a7 # # # Which can be accessed directly from the array using array.response_metadata # class AwsResponseArray < Array attr_accessor :response_metadata def initialize(response_metadata) @response_metadata = response_metadata end end # Used when pulling out a single response object class AwsResponseObjectHash < Hash attr_accessor :response_metadata def initialize(response_metadata) @response_metadata = response_metadata end end endaws-2.10.2/lib/awsbase/require_relative.rb0000644000175000017500000000111112511515441017162 0ustar tnnntnnn# require_relative was introduced in 1.9.2. This makes it # available to younger rubies. It trys hard to not re-require # files. unless Kernel.respond_to?(:require_relative) module Kernel def require_relative(path) desired_path = File.expand_path('../' + path.to_str, caller[0]) shortest = desired_path $:.each do |path| path += '/' if desired_path.index(path) == 0 candidate = desired_path.sub(path, '') shortest = candidate if candidate.size < shortest.size end end require shortest end end end aws-2.10.2/lib/rds/0000755000175000017500000000000012511515441012437 5ustar tnnntnnnaws-2.10.2/lib/rds/rds.rb0000644000175000017500000002022312511515441013553 0ustar tnnntnnnmodule Aws require 'xmlsimple' # API Reference: http://docs.amazonwebservices.com/AmazonRDS/latest/APIReference/ class Rds < AwsBase include AwsBaseInterface # Amazon API version being used API_VERSION = nil DEFAULT_HOST = "rds.amazonaws.com" DEFAULT_PATH = '/' DEFAULT_PROTOCOL = 'https' DEFAULT_PORT = 443 @@api = ENV['RDS_API_VERSION'] || API_VERSION def self.connection_name :rds_connection end def self.api @@api end @@bench = AwsBenchmarkingBlock.new def self.bench @@bench end def self.bench_xml @@bench.xml end def self.bench_ec2 @@bench.service end def initialize(aws_access_key_id=nil, aws_secret_access_key=nil, params={}) uri = ENV['RDS_URL'] ? URI.parse(ENV['RDS_URL']) : nil init({:name => 'RDS', :default_host => uri ? uri.host : DEFAULT_HOST, :default_port => uri ? uri.port : DEFAULT_PORT, :default_service => uri ? uri.path : DEFAULT_PATH, :default_protocol => uri ? uri.scheme : DEFAULT_PROTOCOL, :api_version => API_VERSION}, aws_access_key_id || ENV['AWS_ACCESS_KEY_ID'], aws_secret_access_key|| ENV['AWS_SECRET_ACCESS_KEY'], params) end def do_request(action, params, options={}) link = generate_request(action, params) resp = request_info_xml_simple(self.class.connection_name, @params, link, @logger, :group_tags =>{"DBInstances" =>"DBInstance", "DBParameterGroups"=>"DBParameterGroup", "DBSecurityGroups" =>"DBSecurityGroup", "EC2SecurityGroups"=>"EC2SecurityGroup", "IPRanges" =>"IPRange"}, :force_array =>["DBInstances", "DBParameterGroups", "DBSecurityGroups", "EC2SecurityGroups", "IPRanges"], :pull_out_array =>options[:pull_out_array], :pull_out_single=>options[:pull_out_single], :wrapper =>options[:wrapper]) end #----------------------------------------------------------------- # REQUESTS #----------------------------------------------------------------- # # identifier: db instance identifier. Must be unique per account per zone. # instance_class: db.m1.small | db.m1.large | db.m1.xlarge | db.m2.2xlarge | db.m2.4xlarge # See this for other values: http://docs.amazonwebservices.com/AmazonRDS/latest/APIReference/ # # options: # db_name: if you want a database created at the same time as the instance, specify :db_name option. # availability_zone: default is random zone. def create_db_instance(identifier, instance_class, allocated_storage, master_username, master_password, options={}) params = {} params['DBInstanceIdentifier'] = identifier params['DBInstanceClass'] = instance_class params['AllocatedStorage'] = allocated_storage params['MasterUsername'] = master_username params['MasterUserPassword'] = master_password params['Engine'] = options[:engine] || "MySQL5.1" params['DBName'] = options[:db_name] if options[:db_name] params['AvailabilityZone'] = options[:availability_zone] if options[:availability_zone] params['PreferredMaintenanceWindow'] = options[:preferred_maintenance_window] if options[:preferred_maintenance_window] params['BackupRetentionPeriod'] = options[:preferred_retention_period] if options[:preferred_retention_period] params['PreferredBackupWindow'] = options[:preferred_backup_window] if options[:preferred_backup_window] @logger.info("Creating DB Instance called #{identifier}") link = do_request("CreateDBInstance", params, :pull_out_single=>[:create_db_instance_result, :db_instance]) rescue Exception on_exception end # options: # DBInstanceIdentifier # MaxRecords # Marker # # Returns array of instances as hashes. # Response metadata can be retreived by calling array.response_metadata on the returned array. def describe_db_instances(options={}) params = {} params['DBInstanceIdentifier'] = options[:db_instance_identifier] if options[:db_instance_identifier] params['MaxRecords'] = options[:max_records] if options[:max_records] params['Marker'] = options[:marker] if options[:marker] resp = do_request("DescribeDBInstances", params, :pull_out_array=>[:describe_db_instances_result, :db_instances]) rescue Exception on_exception end # identifier: identifier of db instance to delete. # final_snapshot_identifier: if specified, RDS will crate a final snapshot before deleting so you can restore it later. def delete_db_instance(identifier, final_snapshot_identifier=nil) @logger.info("Deleting DB Instance - " + identifier.to_s) params = {} params['DBInstanceIdentifier'] = identifier if final_snapshot_identifier params['FinalDBSnapshotIdentifier'] = final_snapshot_identifier else params['SkipFinalSnapshot'] = true end link = do_request("DeleteDBInstance", params, :pull_out_single=>[:delete_db_instance_result, :db_instance]) rescue Exception on_exception end def create_db_security_group(group_name, description, options={}) params = {} params['DBSecurityGroupName'] = group_name params['DBSecurityGroupDescription'] = description link = do_request("CreateDBSecurityGroup", params, :pull_out_single => [:create_db_security_group_result, :db_security_group]) rescue Exception on_exception end def delete_db_security_group(group_name, options={}) params = {} params['DBSecurityGroupName'] = group_name link = do_request("DeleteDBSecurityGroup", params) rescue Exception on_exception end def describe_db_security_groups(options={}) params = {} params['DBSecurityGroupName'] = options[:DBSecurityGroupName] if options[:DBSecurityGroupName] params['MaxRecords'] = options[:MaxRecords] if options[:MaxRecords] link = do_request("DescribeDBSecurityGroups", params, :pull_out_array=>[:describe_db_security_groups_result, :db_security_groups], :wrapper=>:db_security_group) rescue Exception on_exception end def authorize_db_security_group_ingress_ec2group(group_name, ec2_group_name, ec2_group_owner_id, options={}) params = {} params['DBSecurityGroupName'] = group_name params['EC2SecurityGroupOwnerId'] = ec2_group_owner_id params['EC2SecurityGroupName'] = ec2_group_name link = do_request("AuthorizeDBSecurityGroupIngress", params) rescue Exception on_exception end def authorize_db_security_group_ingress_range(group_name, ip_range, options={}) params = {} params['DBSecurityGroupName'] = group_name params['CIDRIP'] = ip_range link = do_request("AuthorizeDBSecurityGroupIngress", params) rescue Exception on_exception end def revoke_db_security_group_ingress(group_name, ip_range, options={}) params = {} params['DBSecurityGroupName'] = group_name params['CIDRIP'] = ip_range link = do_request("RevokeDBSecurityGroupIngress", params) rescue Exception on_exception end end endaws-2.10.2/lib/sdb/0000755000175000017500000000000012511515441012417 5ustar tnnntnnnaws-2.10.2/lib/sdb/active_sdb.rb0000644000175000017500000013343212511515441015055 0ustar tnnntnnnputs 'WARNING: ActiveSdb is deprecated, please use SimpleRecord at https://github.com/appoxy/simple_record/ instead.' # Copyright (c) 2008 RightScale Inc # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # begin require 'uuidtools' rescue LoadError => e STDERR.puts("RightSDB requires the uuidtools gem. Run \'gem install uuidtools\' and try again.") exit end module Aws # = Aws::ActiveSdb -- RightScale SDB interface (alpha release) # The Aws::ActiveSdb class provides a complete interface to Amazon's Simple # Database Service. # # ActiveSdb is in alpha and does not load by default with the rest of Aws. You must use an additional require statement to load the ActiveSdb class. For example: # # require 'right_aws' # require 'sdb/active_sdb' # # Additionally, the ActiveSdb class requires the 'uuidtools' gem; this gem is not normally required by Aws and is not installed as a # dependency of Aws. # # Simple ActiveSdb usage example: # # class Client < Aws::ActiveSdb::Base # end # # # connect to SDB # Aws::ActiveSdb.establish_connection # # # create domain # Client.create_domain # # # create initial DB # Client.create 'name' => 'Bush', 'country' => 'USA', 'gender' => 'male', 'expiration' => '2009', 'post' => 'president' # Client.create 'name' => 'Putin', 'country' => 'Russia', 'gender' => 'male', 'expiration' => '2008', 'post' => 'president' # Client.create 'name' => 'Medvedev', 'country' => 'Russia', 'gender' => 'male', 'expiration' => '2012', 'post' => 'president' # Client.create 'name' => 'Mary', 'country' => 'USA', 'gender' => 'female', 'hobby' => ['patchwork', 'bundle jumping'] # Client.create 'name' => 'Mary', 'country' => 'Russia', 'gender' => 'female', 'hobby' => ['flowers', 'cats', 'cooking'] # sandy_id = Client.create('name' => 'Sandy', 'country' => 'Russia', 'gender' => 'female', 'hobby' => ['flowers', 'cats', 'cooking']).id # # # find all Bushes in USA # Client.find(:all, :conditions => ["['name'=?] intersection ['country'=?]",'Bush','USA']).each do |client| # client.reload # puts client.attributes.inspect # end # # # find all Maries through the world # Client.find_all_by_name_and_gender('Mary','female').each do |mary| # mary.reload # puts "#{mary[:name]}, gender: #{mary[:gender]}, hobbies: #{mary[:hobby].join(',')}" # end # # # find new russian president # medvedev = Client.find_by_post_and_country_and_expiration('president','Russia','2012') # if medvedev # medvedev.reload # medvedev.save_attributes('age' => '42', 'hobby' => 'Gazprom') # end # # # retire old president # Client.find_by_name('Putin').delete # # # Sandy disappointed in 'cooking' and decided to hide her 'gender' and 'country' () # sandy = Client.find(sandy_id) # sandy.reload # sandy.delete_values('hobby' => ['cooking'] ) # sandy.delete_attributes('country', 'gender') # # # remove domain # Client.delete_domain # class ActiveSdb module ActiveSdbConnect def connection @connection || raise(ActiveSdbError.new('Connection to SDB is not established')) end # Create a new handle to an Sdb account. All handles share the same per process or per thread # HTTP connection to Amazon Sdb. Each handle is for a specific account. # The +params+ are passed through as-is to Aws::SdbInterface.new # Params: # { :server => 'sdb.amazonaws.com' # Amazon service host: 'sdb.amazonaws.com'(default) # :port => 443 # Amazon service port: 80 or 443(default) # :protocol => 'https' # Amazon service protocol: 'http' or 'https'(default) # :signature_version => '2' # The signature version : '0', '1' or '2' (default) # DEPRECATED :multi_thread => true|false # Multi-threaded (connection per each thread): true or false(default) # :connection_mode => :default # options are :default (will use best known option, may change in the future) # :per_request (opens and closes a connection on every request to SDB) # :single (same as old multi_thread=>false) # :per_thread (same as old multi_thread=>true) # :pool (uses a connection pool with a maximum number of connections - NOT IMPLEMENTED YET) # :logger => Logger Object # Logger instance: logs to STDOUT if omitted # :nil_representation => 'mynil'} # interpret Ruby nil as this string value; i.e. use this string in SDB to represent Ruby nils (default is the string 'nil') # :service_endpoint => '/' # Set this to /mdb/request.mgwsi for usage with M/DB def establish_connection(aws_access_key_id=nil, aws_secret_access_key=nil, params={}) @connection = Aws::SdbInterface.new(aws_access_key_id, aws_secret_access_key, params) end def close_connection @connection.close_connection unless @connection.nil? end end class ActiveSdbError < RuntimeError end class << self include ActiveSdbConnect # Retreive a list of domains. # # put Aws::ActiveSdb.domains #=> ['co-workers','family','friends','clients'] # def domains connection.list_domains[:domains] end # Create new domain. # Raises no errors if the domain already exists. # # Aws::ActiveSdb.create_domain('alpha') #=> {:request_id=>"6fc652a0-0000-41d5-91f4-3ed390a3d3b2", :box_usage=>"0.0055590278"} # def create_domain(domain_name) connection.create_domain(domain_name) end # Remove domain from SDB. # Raises no errors if the domain does not exist. # # Aws::ActiveSdb.create_domain('alpha') #=> {:request_id=>"6fc652a0-0000-41c6-91f4-3ed390a3d3b2", :box_usage=>"0.0055590001"} # def delete_domain(domain_name) connection.delete_domain(domain_name) end end class Base class << self include ActiveSdbConnect # next_token value returned by last find: is useful to continue finding attr_accessor :next_token # Returns a Aws::SdbInterface object # # class A < Aws::ActiveSdb::Base # end # # class B < Aws::ActiveSdb::Base # end # # class C < Aws::ActiveSdb::Base # end # # Aws::ActiveSdb.establish_connection 'key_id_1', 'secret_key_1' # # C.establish_connection 'key_id_2', 'secret_key_2' # # # A and B uses the default connection, C - uses its own # puts A.connection #=> # # puts B.connection #=> # # puts C.connection #=> # # def connection @connection || ActiveSdb::connection end @domain = nil # Current domain name. # # # if 'ActiveSupport' is not loaded then class name converted to downcase # class Client < Aws::ActiveSdb::Base # end # puts Client.domain #=> 'client' # # # if 'ActiveSupport' is loaded then class name being tableized # require 'activesupport' # class Client < Aws::ActiveSdb::Base # end # puts Client.domain #=> 'clients' # # # Explicit domain name definition # class Client < Aws::ActiveSdb::Base # set_domain_name :foreign_clients # end # puts Client.domain #=> 'foreign_clients' # def domain unless @domain if defined? ActiveSupport::CoreExtensions::String::Inflections @domain = name.tableize else @domain = name.downcase end end @domain end # Change the default domain name to user defined. # # class Client < Aws::ActiveSdb::Base # set_domain_name :foreign_clients # end # def set_domain_name(domain) @domain = domain.to_s end # Create domain at SDB. # Raises no errors if the domain already exists. # # class Client < Aws::ActiveSdb::Base # end # Client.create_domain #=> {:request_id=>"6fc652a0-0000-41d5-91f4-3ed390a3d3b2", :box_usage=>"0.0055590278"} # def create_domain connection.create_domain(domain) end # Remove domain from SDB. # Raises no errors if the domain does not exist. # # class Client < Aws::ActiveSdb::Base # end # Client.delete_domain #=> {:request_id=>"e14d90d3-0000-4898-9995-0de28cdda270", :box_usage=>"0.0055590278"} # def delete_domain connection.delete_domain(domain) end # # See select(), original find with QUERY syntax is deprecated so now find and select are synonyms. # def find(*args) options = args.last.is_a?(Hash) ? args.pop : {} case args.first when nil then raise "Invalid parameters passed to find: nil." when :all then sql_select(options)[:items] when :first then sql_select(options.merge(:limit => 1))[:items].first when :count then res = sql_select(options.merge(:count => true))[:count] res else res = select_from_ids(args, options) return res[:single] if res[:single] return res[:items] end end # # Same as find, but will return SimpleDB metadata like :request_id and :box_usage # def find_with_metadata(*args) options = args.last.is_a?(Hash) ? args.pop : {} case args.first when nil then raise "Invalid parameters passed to find: nil." when :all then sql_select(options) when :first then sql_select(options.merge(:limit => 1)) when :count then res = sql_select(options.merge(:count => true)) res else select_from_ids args, options end end # Perform a SQL-like select request. # # Single record: # # Client.select(:first) # Client.select(:first, :conditions=> [ "name=? AND wife=?", "Jon", "Sandy"]) # Client.select(:first, :conditions=> { :name=>"Jon", :wife=>"Sandy" }, :select => :girfriends) # # Bunch of records: # # Client.select(:all) # Client.select(:all, :limit => 10) # Client.select(:all, :conditions=> [ "name=? AND 'girlfriend'=?", "Jon", "Judy"]) # Client.select(:all, :conditions=> { :name=>"Sandy" }, :limit => 3) # # Records by ids: # # Client.select('1') # Client.select('1234987b4583475347523948') # Client.select('1','2','3','4', :conditions=> ["toys=?", "beer"]) # # Find helpers: Aws::ActiveSdb::Base.select_by_... and Aws::ActiveSdb::Base.select_all_by_... # # Client.select_by_name('Matias Rust') # Client.select_by_name_and_city('Putin','Moscow') # Client.select_by_name_and_city_and_post('Medvedev','Moscow','president') # # Client.select_all_by_author('G.Bush jr') # Client.select_all_by_age_and_gender_and_ethnicity('34','male','russian') # Client.select_all_by_gender_and_country('male', 'Russia', :order => 'name') # # Continue listing: # # # initial listing # Client.select(:all, :limit => 10) # # continue listing # begin # Client.select(:all, :limit => 10, :next_token => Client.next_token) # end while Client.next_token # # Sort oder: # If :order=>'attribute' option is specified then result response (ordered by 'attribute') will contain only items where attribute is defined (is not null). # # Client.select(:all) # returns all records # Client.select(:all, :order => 'gender') # returns all records ordered by gender where gender attribute exists # Client.select(:all, :order => 'name desc') # returns all records ordered by name in desc order where name attribute exists # # see http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/index.html?UsingSelect.html # def select(*args) find(*args) end def generate_id # :nodoc: UUIDTools::UUID.timestamp_create().to_s end protected # Select def select_from_ids(args, options) # :nodoc: cond = [] # detect amount of records requested bunch_of_records_requested = args.size > 1 || args.first.is_a?(Array) # flatten ids args = args.to_a.flatten args.each { |id| cond << "itemName() = #{self.connection.escape(id)}" } ids_cond = "(#{cond.join(' OR ')})" # user defined :conditions to string (if it was defined) options[:conditions] = build_conditions(options[:conditions]) # join ids condition and user defined conditions options[:conditions] = Aws::Utils.blank?(options[:conditions]) ? ids_cond : "(#{options[:conditions]}) AND #{ids_cond}" #puts 'options=' + options.inspect result = sql_select(options) #puts 'select_from_ids result=' + result.inspect # if one record was requested then return it unless bunch_of_records_requested record = result[:items].first # railse if nothing was found raise ActiveSdbError.new("Couldn't find #{name} with ID #{args}") unless record result[:single] = record else # if a bunch of records was requested then return check that we found all of them # and return as an array unless args.size == result[:items].size # todo: might make sense to return the array but with nil values in the slots where an item wasn't found? id_list = args.map{|i| "'#{i}'"}.join(',') raise ActiveSdbError.new("Couldn't find all #{name} with IDs (#{id_list}) (found #{result[:items].size} results, but was looking for #{args.size})") else result end end result end def sql_select(options) # :nodoc: count = options[:count] || false #puts 'count? ' + count.to_s @next_token = options[:next_token] @consistent_read = options[:consistent_read] select_expression = build_select(options) # request items query_result = self.connection.select(select_expression, @next_token, @consistent_read) # puts 'QR=' + query_result.inspect ret = {} if count ret[:count] = query_result.delete(:items)[0]["Domain"]["Count"][0].to_i ret.merge!(query_result) return ret end @next_token = query_result[:next_token] items = query_result.delete(:items).map do |hash| id, attributes = hash.shift new_item = self.new( ) new_item.initialize_from_db(attributes.merge({ 'id' => id })) new_item.mark_as_old new_item end ret[:items] = items ret.merge!(query_result) ret end # select_by helpers def select_all_by_(format_str, args, options) # :nodoc: fields = format_str.to_s.sub(/^select_(all_)?by_/, '').split('_and_') conditions = fields.map { |field| "#{field}=?" }.join(' AND ') options[:conditions] = [conditions, *args] find(:all, options) end def select_by_(format_str, args, options) # :nodoc: options[:limit] = 1 select_all_by_(format_str, args, options).first end # Query # Returns an array of query attributes. # Query_expression must be a well formated SDB query string: # query_attributes("['title' starts-with 'O\\'Reily'] intersection ['year' = '2007']") #=> ["title", "year"] def query_attributes(query_expression) # :nodoc: attrs = [] array = query_expression.scan(/['"](.*?[^\\])['"]/).flatten until array.empty? do attrs << array.shift # skip it's value array.shift # end attrs end # Returns an array of [attribute_name, 'asc'|'desc'] def sort_options(sort_string) sort_string[/['"]?(\w+)['"]? *(asc|desc)?/i] [$1, ($2 || 'asc')] end # Perform a query request. # # Options # :query_expression nil | string | array # :max_number_of_items nil | integer # :next_token nil | string # :sort_option nil | string "name desc|asc" # def query(options) # :nodoc: @next_token = options[:next_token] @consistent_read = options[:consistent_read] query_expression = build_conditions(options[:query_expression]) # add sort_options to the query_expression if options[:sort_option] sort_by, sort_order = sort_options(options[:sort_option]) sort_query_expression = "['#{sort_by}' starts-with '']" sort_by_expression = " sort '#{sort_by}' #{sort_order}" # make query_expression to be a string (it may be null) query_expression = query_expression.to_s # quote from Amazon: # The sort attribute must be present in at least one of the predicates of the query expression. if Aws::Utils.blank?(query_expression) query_expression = sort_query_expression elsif !query_attributes(query_expression).include?(sort_by) query_expression += " intersection #{sort_query_expression}" end query_expression += sort_by_expression end # request items query_result = self.connection.query(domain, query_expression, options[:max_number_of_items], @next_token, @consistent_read) @next_token = query_result[:next_token] items = query_result[:items].map do |name| new_item = self.new('id' => name) new_item.mark_as_old reload_if_exists(record) if options[:auto_load] new_item end items end # reload a record unless it is nil def reload_if_exists(record) # :nodoc: record && record.reload end def reload_all_records(*list) # :nodoc: list.flatten.each { |record| reload_if_exists(record) } end def find_every(options) # :nodoc: records = query( :query_expression => options[:conditions], :max_number_of_items => options[:limit], :next_token => options[:next_token], :sort_option => options[:sort] || options[:order], :consistent_read => options[:consistent_read] ) options[:auto_load] ? reload_all_records(records) : records end def find_initial(options) # :nodoc: options[:limit] = 1 record = find_every(options).first options[:auto_load] ? reload_all_records(record).first : record end def find_from_ids(args, options) # :nodoc: cond = [] # detect amount of records requested bunch_of_records_requested = args.size > 1 || args.first.is_a?(Array) # flatten ids args = args.to_a.flatten args.each { |id| cond << "'id'=#{self.connection.escape(id)}" } ids_cond = "[#{cond.join(' OR ')}]" # user defined :conditions to string (if it was defined) options[:conditions] = build_conditions(options[:conditions]) # join ids condition and user defined conditions options[:conditions] = Aws::Utils.blank?(options[:conditions]) ? ids_cond : "#{options[:conditions]} intersection #{ids_cond}" result = find_every(options) # if one record was requested then return it unless bunch_of_records_requested record = result.first # railse if nothing was found raise ActiveSdbError.new("Couldn't find #{name} with ID #{args}") unless record options[:auto_load] ? reload_all_records(record).first : record else # if a bunch of records was requested then return check that we found all of them # and return as an array unless args.size == result.size id_list = args.map{|i| "'#{i}'"}.join(',') raise ActiveSdbError.new("Couldn't find all #{name} with IDs (#{id_list}) (found #{result.size} results, but was looking for #{args.size})") else options[:auto_load] ? reload_all_records(result) : result end end end # find_by helpers def find_all_by_(format_str, args, options) # :nodoc: fields = format_str.to_s.sub(/^find_(all_)?by_/, '').split('_and_') conditions = fields.map { |field| "['#{field}'=?]" }.join(' intersection ') options[:conditions] = [conditions, *args] find(:all, options) end def find_by_(format_str, args, options) # :nodoc: options[:limit] = 1 find_all_by_(format_str, args, options).first end # Misc def method_missing(method, *args) # :nodoc: if method.to_s[/^(find_all_by_|find_by_|select_all_by_|select_by_)/] # get rid of the find ones, only select now to_send_to = $1 attributes = method.to_s[$1.length..method.to_s.length] # puts 'attributes=' + attributes if to_send_to[0...4] == "find" to_send_to = "select" + to_send_to[4..to_send_to.length] # puts 'CONVERTED ' + $1 + " to " + to_send_to end options = args.last.is_a?(Hash) ? args.pop : {} __send__(to_send_to, attributes, args, options) else super(method, *args) end end def build_select(options) # :nodoc: select = options[:select] || '*' select = options[:count] ? "count(*)" : select #puts 'select=' + select.to_s from = options[:from] || domain conditions = options[:conditions] ? " WHERE #{build_conditions(options[:conditions])}" : '' order = options[:order] ? " ORDER BY #{options[:order]}" : '' limit = options[:limit] ? " LIMIT #{options[:limit]}" : '' # mix sort by argument (it must present in response) unless Aws::Utils.blank?(order) sort_by, sort_order = sort_options(options[:order]) conditions << (Aws::Utils.blank?(conditions) ? " WHERE " : " AND ") << "(#{sort_by} IS NOT NULL)" end "SELECT #{select} FROM `#{from}`#{conditions}#{order}#{limit}" end def build_conditions(conditions) # :nodoc: case when conditions.is_a?(Array) then connection.query_expression_from_array(conditions) when conditions.is_a?(Hash) then connection.query_expression_from_hash(conditions) else conditions end end end public # instance attributes attr_accessor :attributes # item name attr_accessor :id # Create new Item instance. # +attrs+ is a hash: { attribute1 => values1, ..., attributeN => valuesN }. # # item = Client.new('name' => 'Jon', 'toys' => ['girls', 'beer', 'pub']) # puts item.inspect #=> #["Jon"], "toys"=>["girls", "beer", "pub"]}> # item.save #=> {"name"=>["Jon"], "id"=>"c03edb7e-e45c-11dc-bede-001bfc466dd7", "toys"=>["girls", "beer", "pub"]} # puts item.inspect #=> #["Jon"], "id"=>"c03edb7e-e45c-11dc-bede-001bfc466dd7", "toys"=>["girls", "beer", "pub"]}> # def initialize(attrs={}) @attributes = uniq_values(attrs) @new_record = true end # This is to separate initialization from user vs coming from db (ie: find()) def initialize_from_db(attrs={}) initialize(attrs) end # Create and save new Item instance. # +Attributes+ is a hash: { attribute1 => values1, ..., attributeN => valuesN }. # # item = Client.create('name' => 'Cat', 'toys' => ['Jons socks', 'mice', 'clew']) # puts item.inspect #=> #["Cat"], "id"=>"2937601a-e45d-11dc-a75f-001bfc466dd7", "toys"=>["Jons socks", "mice", "clew"]}> # def self.create(attributes={}) item = self.new(attributes) item.save item end # Returns an item id. Same as: item['id'] or item.attributes['id'] def id @attributes['id'] end # Sets an item id. def id=(id) @attributes['id'] = id.to_s end # Returns a hash of all the attributes. # # puts item.attributes.inspect #=> {"name"=>["Cat"], "id"=>"2937601a-e45d-11dc-a75f-001bfc466dd7", "toys"=>["Jons socks", "clew", "mice"]} # def attributes @attributes.dup end # Allows one to set all the attributes at once by passing in a hash with keys matching the attribute names. # if '+id+' attribute is not set in new attributes has then it being derived from old attributes. # # puts item.attributes.inspect #=> {"name"=>["Cat"], "id"=>"2937601a-e45d-11dc-a75f-001bfc466dd7", "toys"=>["Jons socks", "clew", "mice"]} # # set new attributes ('id' is missed) # item.attributes = { 'name'=>'Dog', 'toys'=>['bones','cats'] } # puts item.attributes.inspect #=> {"name"=>["Dog"], "id"=>"2937601a-e45d-11dc-a75f-001bfc466dd7", "toys"=>["bones", "cats"]} # # set new attributes ('id' is set) # item.attributes = { 'id' => 'blah-blah', 'name'=>'Birds', 'toys'=>['seeds','dogs tail'] } # puts item.attributes.inspect #=> {"name"=>["Birds"], "id"=>"blah-blah", "toys"=>["seeds", "dogs tail"]} # def attributes=(attrs) old_id = @attributes['id'] @attributes = uniq_values(attrs) if Aws::Utils.blank?(@attributes['id']) && !Aws::Utils.blank?(old_id) @attributes['id'] = old_id end self.attributes end def connection self.class.connection end # Item domain name. def domain self.class.domain end # Returns the values of the attribute identified by +attribute+. # # puts item['Cat'].inspect #=> ["Jons socks", "clew", "mice"] # def [](attribute) @attributes[attribute.to_s] end # Updates the attribute identified by +attribute+ with the specified +values+. # # puts item['Cat'].inspect #=> ["Jons socks", "clew", "mice"] # item['Cat'] = ["Whiskas", "chicken"] # puts item['Cat'].inspect #=> ["Whiskas", "chicken"] # def []=(attribute, values) attribute = attribute.to_s @attributes[attribute] = attribute == 'id' ? values.to_s : values.is_a?(Array) ? values.uniq : [values] end # Reload attributes from SDB. Replaces in-memory attributes. # # item = Client.find_by_name('Cat') #=> #"2937601a-e45d-11dc-a75f-001bfc466dd7"}, @new_record=false> # item.reload #=> #"2937601a-e45d-11dc-a75f-001bfc466dd7", "name"=>["Cat"], "toys"=>["Jons socks", "clew", "mice"]}, @new_record=false> # def reload raise_on_id_absence old_id = id attrs = connection.get_attributes(domain, id)[:attributes] @attributes = {} unless attrs.nil? || attrs.empty? attrs.each { |attribute, values| @attributes[attribute] = values } @attributes['id'] = old_id end mark_as_old @attributes end # Reload a set of attributes from SDB. Adds the loaded list to in-memory data. # +attrs_list+ is an array or comma separated list of attributes names. # Returns a hash of loaded attributes. # # This is not the best method to get a bunch of attributes because # a web service call is being performed for every attribute. # # item = Client.find_by_name('Cat') # item.reload_attributes('toys', 'name') #=> {"name"=>["Cat"], "toys"=>["Jons socks", "clew", "mice"]} # def reload_attributes(*attrs_list) raise_on_id_absence attrs_list = attrs_list.flatten.map{ |attribute| attribute.to_s } attrs_list.delete('id') result = {} attrs_list.flatten.uniq.each do |attribute| attribute = attribute.to_s values = connection.get_attributes(domain, id, attribute)[:attributes][attribute] unless Aws::Utils.blank?(values) @attributes[attribute] = result[attribute] = values else @attributes.delete(attribute) end end mark_as_old result end # Stores in-memory attributes to SDB. # Adds the attributes values to already stored at SDB. # Returns a hash of stored attributes. # # sandy = Client.new(:name => 'Sandy') #=> #["Sandy"]}, @new_record=true> # sandy['toys'] = 'boys' # sandy.put # sandy['toys'] = 'patchwork' # sandy.put # sandy['toys'] = 'kids' # sandy.put # puts sandy.attributes.inspect #=> {"name"=>["Sandy"], "id"=>"b2832ce2-e461-11dc-b13c-001bfc466dd7", "toys"=>["kids"]} # sandy.reload #=> {"name"=>["Sandy"], "id"=>"b2832ce2-e461-11dc-b13c-001bfc466dd7", "toys"=>["boys", "kids", "patchwork"]} # # compare to +save+ method def put @attributes = uniq_values(@attributes) prepare_for_update attrs = @attributes.dup attrs.delete('id') connection.put_attributes(domain, id, attrs) unless Aws::Utils.blank?(attrs) connection.put_attributes(domain, id, { 'id' => id }, :replace) mark_as_old @attributes end # Stores specified attributes. # +attrs+ is a hash: { attribute1 => values1, ..., attributeN => valuesN }. # Returns a hash of saved attributes. # # see to +put+ method def put_attributes(attrs) attrs = uniq_values(attrs) prepare_for_update # if 'id' is present in attrs hash: # replace internal 'id' attribute and remove it from the attributes to be sent @attributes['id'] = attrs['id'] unless Aws::Utils.blank?(attrs['id']) attrs.delete('id') # add new values to all attributes from list connection.put_attributes(domain, id, attrs) unless Aws::Utils.blank?(attrs) connection.put_attributes(domain, id, { 'id' => id }, :replace) attrs.each do |attribute, values| @attributes[attribute] ||= [] @attributes[attribute] += values @attributes[attribute].uniq! end mark_as_old attributes end # Store in-memory attributes to SDB. # Replaces the attributes values already stored at SDB by in-memory data. # Returns a hash of stored attributes. # # sandy = Client.new(:name => 'Sandy') #=> #["Sandy"]}, @new_record=true> # sandy['toys'] = 'boys' # sandy.save # sandy['toys'] = 'patchwork' # sandy.save # sandy['toys'] = 'kids' # sandy.save # puts sandy.attributes.inspect #=> {"name"=>["Sandy"], "id"=>"b2832ce2-e461-11dc-b13c-001bfc466dd7", "toys"=>["kids"]} # sandy.reload #=> {"name"=>["Sandy"], "id"=>"b2832ce2-e461-11dc-b13c-001bfc466dd7", "toys"=>["kids"]} # # Options: # - :except => Array of attributes to NOT save # # compare to +put+ method def save(options={}) pre_save2 atts_to_save = @attributes.dup #puts 'atts_to_save=' + atts_to_save.inspect #options = params.first.is_a?(Hash) ? params.pop : {} if options[:except] options[:except].each do |e| atts_to_save.delete(e).inspect end end if options[:dirty] # Only used in simple_record right now # only save if the attribute is dirty dirty_atts = options[:dirty_atts] atts_to_save.delete_if { |key, value| !dirty_atts.has_key?(key) } end #puts 'atts_to_save2=' + atts_to_save.inspect connection.put_attributes(domain, id, atts_to_save, :replace) apres_save2 @attributes end def pre_save2 @attributes = uniq_values(@attributes) prepare_for_update end def apres_save2 mark_as_old end # Replaces the attributes at SDB by the given values. # +Attrs+ is a hash: { attribute1 => values1, ..., attributeN => valuesN }. # The other in-memory attributes are not being saved. # Returns a hash of stored attributes. # # see +save+ method def save_attributes(attrs) prepare_for_update attrs = uniq_values(attrs) # if 'id' is present in attrs hash then replace internal 'id' attribute unless Aws::Utils.blank?(attrs['id']) @attributes['id'] = attrs['id'] else attrs['id'] = id end connection.put_attributes(domain, id, attrs, :replace) unless attrs.blank? attrs.each { |attribute, values| attrs[attribute] = values } mark_as_old attrs end # Remove specified values from corresponding attributes. # +attrs+ is a hash: { attribute1 => values1, ..., attributeN => valuesN }. # # sandy = Client.find_by_name 'Sandy' # sandy.reload # puts sandy.inspect #=> #["Sandy"], "id"=>"b2832ce2-e461-11dc-b13c-001bfc466dd7", "toys"=>["boys", "kids", "patchwork"]}> # puts sandy.delete_values('toys' => 'patchwork') #=> { 'toys' => ['patchwork'] } # puts sandy.inspect #=> #["Sandy"], "id"=>"b2832ce2-e461-11dc-b13c-001bfc466dd7", "toys"=>["boys", "kids"]}> # def delete_values(attrs) raise_on_id_absence attrs = uniq_values(attrs) attrs.delete('id') unless attrs.nil? || attrs.empty? connection.delete_attributes(domain, id, attrs) attrs.each do |attribute, values| # remove the values from the attribute if @attributes[attribute] @attributes[attribute] -= values else # if the attribute is unknown remove it from a resulting list of fixed attributes attrs.delete(attribute) end end end attrs end # Removes specified attributes from the item. # +attrs_list+ is an array or comma separated list of attributes names. # Returns the list of deleted attributes. # # sandy = Client.find_by_name 'Sandy' # sandy.reload # puts sandy.inspect #=> #["Sandy"], "id"=>"b2832ce2-e461-11dc-b13c-001bfc466dd7", "toys"=>["boys", "kids", "patchwork"}> # puts sandy.delete_attributes('toys') #=> ['toys'] # puts sandy.inspect #=> #["Sandy"], "id"=>"b2832ce2-e461-11dc-b13c-001bfc466dd7"}> # def delete_attributes(*attrs_list) raise_on_id_absence attrs_list = attrs_list.flatten.map{ |attribute| attribute.to_s } attrs_list.delete('id') unless attrs_list.empty? connection.delete_attributes(domain, id, attrs_list) attrs_list.each { |attribute| @attributes.delete(attribute) } end attrs_list end # Delete the Item entirely from SDB. # # sandy = Client.find_by_name 'Sandy' # sandy.reload # sandy.inspect #=> #["Sandy"], "id"=>"b2832ce2-e461-11dc-b13c-001bfc466dd7", "toys"=>["boys", "kids", "patchwork"}> # puts sandy.delete # sandy.reload # puts sandy.inspect #=> # # def delete raise_on_id_absence connection.delete_attributes(domain, id) end # Item ID def to_s @id end # Returns true if this object hasn‘t been saved yet. def new_record? @new_record end def mark_as_old # :nodoc: @new_record = false end private def raise_on_id_absence raise ActiveSdbError.new('Unknown record id') unless id end def prepare_for_update @attributes['id'] = self.class.generate_id if Aws::Utils.blank?(@attributes['id']) end def uniq_values(attributes=nil) # :nodoc: attrs = {} attributes.each do |attribute, values| attribute = attribute.to_s newval = attribute == 'id' ? values.to_s : values.is_a?(Array) ? values.uniq : [values] attrs[attribute] = newval if Aws::Utils.blank?(newval) # puts "VALUE IS BLANK " + attribute.to_s + " val=" + values.inspect attrs.delete(attribute) end end attrs end end end end aws-2.10.2/lib/sdb/sdb_interface.rb0000644000175000017500000010710212511515441015535 0ustar tnnntnnn# # Copyright (c) 2008 RightScale Inc # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # module Aws class SdbInterface < AwsBase include AwsBaseInterface DEFAULT_HOST = 'sdb.amazonaws.com' DEFAULT_PORT = 443 DEFAULT_PROTOCOL = 'https' DEFAULT_SERVICE = '/' API_VERSION = '2009-04-15' DEFAULT_NIL_REPRESENTATION = 'nil' def self.connection_name :s3_connection end @@bench = AwsBenchmarkingBlock.new def self.bench @@bench end def self.bench_xml; @@bench.xml; end def self.bench_sdb; @@bench.service; end attr_reader :last_query_expression # Creates new RightSdb instance. # # Params: # { :server => 'sdb.amazonaws.com' # Amazon service host: 'sdb.amazonaws.com'(default) # :port => 443 # Amazon service port: 80(default) or 443 # :protocol => 'https' # Amazon service protocol: 'http'(default) or 'https' # :signature_version => '2' # The signature version : '0', '1' or '2' (default) # DEPRECATED :multi_thread => true|false # Multi-threaded (connection per each thread): true or false(default) # :connection_mode => :default # options are :default (will use best known option, may change in the future) # :per_request (opens and closes a connection on every request to SDB) # :single - one connection shared across app (same as old multi_thread=>false) # :per_thread - one connection per ruby thread (same as old multi_thread=>true) # :pool (uses a connection pool with a maximum number of connections - NOT IMPLEMENTED YET) # :logger => Logger Object # Logger instance: logs to STDOUT if omitted # :nil_representation => 'mynil'} # interpret Ruby nil as this string value; i.e. use this string in SDB to represent Ruby nils (default is the string 'nil') # :service => '/' # Set this to /mdb/request.mgwsi for usage with M/DB # # # Example: # # sdb = Aws::SdbInterface.new('1E3GDYEOGFJPIT7XXXXXX','hgTHt68JY07JKUY08ftHYtERkjgtfERn57XXXXXX', {:connection_mode => :per_request, :logger => Logger.new('/tmp/x.log')}) #=> # # # see: http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/ # def initialize(aws_access_key_id=nil, aws_secret_access_key=nil, params={}) @nil_rep = params[:nil_representation] ? params[:nil_representation] : DEFAULT_NIL_REPRESENTATION params.delete(:nil_representation) init({:name => 'SDB', :default_host => ENV['SDB_URL'] ? URI.parse(ENV['SDB_URL']).host : DEFAULT_HOST, :default_port => ENV['SDB_URL'] ? URI.parse(ENV['SDB_URL']).port : DEFAULT_PORT, :default_protocol => ENV['SDB_URL'] ? URI.parse(ENV['SDB_URL']).scheme : DEFAULT_PROTOCOL, :default_service => ENV['SDB_URL'] ? URI.parse(ENV['SDB_URL']).path : DEFAULT_SERVICE}, # :service_endpoint => ENV['SDB_URL'] ? URI.parse(ENV['SDB_URL']).path : DEFAULT_ENDPOINT }, aws_access_key_id || ENV['AWS_ACCESS_KEY_ID'], aws_secret_access_key || ENV['AWS_SECRET_ACCESS_KEY'], params) end #----------------------------------------------------------------- # Requests #----------------------------------------------------------------- def generate_request(action, params={}, options={}) #:nodoc: generate_request2(@aws_access_key_id, @aws_secret_access_key, action, API_VERSION, @params, params, options) end # Sends request to Amazon and parses the response # Raises AwsError if any banana happened def request_info(request, parser, options={}) #:nodoc: # request_info2(request, parser, :sdb_connection) request_info2(request, parser, @params, :sdb_connection, @logger, @@bench, options) end # Prepare attributes for putting. # (used by put_attributes) def pack_attributes(attributes, replace = false, key_prefix = "") #:nodoc: result = {} if attributes idx = 0 skip_values = attributes.is_a?(Array) attributes.each do |attribute, values| # set replacement attribute result["#{key_prefix}Attribute.#{idx}.Replace"] = 'true' if replace # pack Name/Value unless values.nil? Array(values).each do |value| result["#{key_prefix}Attribute.#{idx}.Name"] = attribute result["#{key_prefix}Attribute.#{idx}.Value"] = ruby_to_sdb(value) unless skip_values idx += 1 end else result["#{key_prefix}Attribute.#{idx}.Name"] = attribute result["#{key_prefix}Attribute.#{idx}.Value"] = ruby_to_sdb(nil) unless skip_values idx += 1 end end end result end # Use this helper to manually escape the fields in the query expressions. # To escape the single quotes and backslashes and to wrap the string into the single quotes. # # see: http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/SDB_API.html # def escape(value) # %Q{'#{value.to_s.gsub(/(['\\])/) { "\\#{$1}" }}'} if value %{'#{value.to_s.gsub(/(['\\])/){"#{$1}#{$1}"}}'} if value end # Convert a Ruby language value to a SDB value by replacing Ruby nil with the user's chosen string representation of nil. # Non-nil values are unaffected by this filter. def ruby_to_sdb(value) # puts "value #{value} is frozen? #{value.frozen?}" # value.nil? ? @nil_rep : ((value.frozen? || !value.is_a?(String)) ? value : value.force_encoding("UTF-8")) value.nil? ? @nil_rep : value end # Convert a SDB value to a Ruby language value by replacing the user's chosen string representation of nil with Ruby nil. # Values are unaffected by this filter unless they match the nil representation exactly. def sdb_to_ruby(value) value.eql?(@nil_rep) ? nil : value end # Convert select and query_with_attributes responses to a Ruby language values by replacing the user's chosen string representation of nil with Ruby nil. # (This method affects on a passed response value) def select_response_to_ruby(response) #:nodoc: response[:items].each_with_index do |item, idx| item.each do |key, attributes| attributes.each do |name, values| values.collect! { |value| sdb_to_ruby(value) } end end end response end # Create query expression from an array. # (similar to ActiveRecord::Base#find using :conditions => ['query', param1, .., paramN]) # def query_expression_from_array(params) #:nodoc: return '' if Aws::Utils.blank?(params) query = params[0].to_s i = 1 query.gsub(/(\\)?(\?)/) do if $1 # if escaped '\?' is found - replace it by '?' without backslash "?" else # well, if no backslash precedes '?' then replace it by next param from the list case params[i] when Array ret = "(#{params[i].map{|p| escape(p)}.join(",")})" else ret = escape(params[i]) end i +=1 ret end end end def query_expression_from_hash(hash) return '' if Aws::Utils.blank?(hash) expression = [] hash.each do |key, value| expression << "#{key}=#{escape(value)}" end expression.join(' AND ') end # Retrieve a list of SDB domains from Amazon. # # Returns a hash: # { :domains => [domain1, ..., domainN], # :next_token => string || nil, # :box_usage => string, # :request_id => string } # # Example: # # sdb = Aws::SdbInterface.new # sdb.list_domains #=> { :box_usage => "0.0000071759", # :request_id => "976709f9-0111-2345-92cb-9ce90acd0982", # :domains => ["toys", "dolls"]} # # If a block is given, this method yields to it. If the block returns true, list_domains will continue looping the request. If the block returns false, # list_domains will end. # # sdb.list_domains(10) do |result| # list by 10 domains per iteration # puts result.inspect # true # end # # see: http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/SDB_API_ListDomains.html # def list_domains(max_number_of_domains = nil, next_token = nil) request_params = {'MaxNumberOfDomains' => max_number_of_domains, 'NextToken' => next_token} link = generate_request("ListDomains", request_params) result = request_info(link, QSdbListDomainParser.new) # return result if no block given return result unless block_given? # loop if block if given begin # the block must return true if it wanna continue break unless yield(result) && result[:next_token] # make new request request_params['NextToken'] = result[:next_token] link = generate_request("ListDomains", request_params) result = request_info(link, QSdbListDomainParser.new) end while true rescue Exception on_exception end # Retrieve a list of SDB domains from Amazon. # # Returns a hash: # { :domains => [domain1, ..., domainN], # :next_token => string || nil, # :box_usage => string, # :request_id => string } # # Example: # # sdb = Aws::SdbInterface.new # sdb.list_domains #=> { :box_usage => "0.0000071759", # :request_id => "976709f9-0111-2345-92cb-9ce90acd0982", # :domains => ["toys", "dolls"]} # # If a block is given, this method yields to it. If the block returns true, list_domains will continue looping the request. If the block returns false, # list_domains will end. # # sdb.list_domains(10) do |result| # list by 10 domains per iteration # puts result.inspect # true # end # # see: http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/SDB_API_ListDomains.html # def domain_metadata(domain_name) link = generate_request("DomainMetadata", 'DomainName' => domain_name) result = request_info(link, QSdbDomainMetadataParser.new) return result rescue Exception on_exception end # Create new SDB domain at Amazon. # # Returns a hash: { :box_usage, :request_id } on success or an exception on error. # (Amazon raises no errors if the domain already exists). # # Example: # # sdb = Aws::SdbInterface.new # sdb.create_domain('toys') # => { :box_usage => "0.0000071759", # :request_id => "976709f9-0111-2345-92cb-9ce90acd0982" } # # see: http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/SDB_API_CreateDomain.html def create_domain(domain_name) link = generate_request("CreateDomain", 'DomainName' => domain_name) request_info(link, QSdbSimpleParser.new) rescue Exception on_exception end # Delete SDB domain at Amazon. # # Returns a hash: { :box_usage, :request_id } on success or an exception on error. # (Amazon raises no errors if the domain does not exist). # # Example: # # sdb = Aws::SdbInterface.new # sdb.delete_domain('toys') # => { :box_usage => "0.0000071759", # :request_id => "976709f9-0111-2345-92cb-9ce90acd0982" } # # see: http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/SDB_API_DeleteDomain.html # def delete_domain(domain_name) link = generate_request("DeleteDomain", 'DomainName' => domain_name) request_info(link, QSdbSimpleParser.new) rescue Exception on_exception end # Add/Replace item attributes. # # Params: # domain_name = DomainName # item_name = ItemName # attributes = { # 'nameA' => [valueA1,..., valueAN], # ... # 'nameZ' => [valueZ1,..., valueZN] # } # replace = :replace | any other value to skip replacement # options: # :create_domain => If true and domain does not exist, it will be created. Default is false. # # Returns a hash: { :box_usage, :request_id } on success or an exception on error. # (Amazon raises no errors if the attribute was not overridden, as when the :replace param is unset). # # Example: # # sdb = Aws::SdbInterface.new # sdb.create_domain 'family' # # attributes = {} # # create attributes for Jon and Silvia # attributes['Jon'] = %w{ car beer } # attributes['Silvia'] = %w{ beetle rolling_pin kids } # sdb.put_attributes 'family', 'toys', attributes #=> ok # # now: Jon=>[car, beer], Silvia=>[beetle, rolling_pin, kids] # # # add attributes to Jon # attributes.delete('Silvia') # attributes['Jon'] = %w{ girls pub } # sdb.put_attributes 'family', 'toys', attributes #=> ok # # now: Jon=>[car, beer, girls, pub], Silvia=>[beetle, rolling_pin, kids] # # # replace attributes for Jon and add to a cat (the cat had no attributes before) # attributes['Jon'] = %w{ vacuum_cleaner hammer spade } # attributes['cat'] = %w{ mouse clew Jons_socks } # sdb.put_attributes 'family', 'toys', attributes, :replace #=> ok # # now: Jon=>[vacuum_cleaner, hammer, spade], Silvia=>[beetle, rolling_pin, kids], cat=>[mouse, clew, Jons_socks] # # see: http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/SDB_API_PutAttributes.html # def put_attributes(domain_name, item_name, attributes, replace = false, options={}) params = {'DomainName' => domain_name, 'ItemName' => item_name}.merge(pack_attributes(attributes, replace)) logger.debug 'PUT=' + params.inspect link = generate_request("PutAttributes", params) begin request_info(link, QSdbSimpleParser.new, options) rescue Aws::AwsError => ex # puts "RESCUED in put_attributes: " + $! if options[:create_domain] && create_domain_if_not_exist(ex, domain_name) options.delete(:create_domain) put_attributes(domain_name, item_name, attributes, replace, options) else raise ex end end rescue Exception on_exception end def create_domain_if_not_exist(ex, domain_name) if ex.message().index("NoSuchDomain") puts "Creating domain: #{domain_name}" create_domain(domain_name) return true end return false end # # items is an array of Aws::SdbInterface::Item.new(o.id, o.attributes, true) def batch_put_attributes(domain_name, items, options={}) params = {'DomainName' => domain_name} i = 0 items.each do |item| prefix = "Item." + i.to_s + "." params[prefix + "ItemName"] = item.item_name params.merge!(pack_attributes(item.attributes, item.replace, prefix)) i += 1 end link = generate_request("BatchPutAttributes", params) begin request_info(link, QSdbSimpleParser.new, options) rescue Aws::AwsError => ex # puts "RESCUED in batch_put_attributes: " + $! if options[:create_domain] && create_domain_if_not_exist(ex, domain_name) options.delete(:create_domain) batch_put_attributes(domain_name, items, options) else raise ex end end rescue Exception on_exception end # # items is an array item_name's or Aws::SdbInterface::Item.new(o.id, o.attributes, true) def batch_delete_attributes(domain_name, items) params = {'DomainName' => domain_name} i = 0 items.each do |item| prefix = "Item." + i.to_s + "." if item.is_a?(String) params[prefix + "ItemName"] = item else params[prefix + "ItemName"] = item.item_name params.merge!(pack_attributes(item.attributes, item.replace, prefix)) end i += 1 end link = generate_request("BatchDeleteAttributes", params) request_info(link, QSdbSimpleParser.new) rescue Exception on_exception end # Retrieve SDB item's attribute(s). # # Returns a hash: # { :box_usage => string, # :request_id => string, # :attributes => { 'nameA' => [valueA1,..., valueAN], # ... , # 'nameZ' => [valueZ1,..., valueZN] } } # # Example: # # request all attributes # sdb.get_attributes('family', 'toys') # => { :attributes => {"cat" => ["clew", "Jons_socks", "mouse"] }, # "Silvia" => ["beetle", "rolling_pin", "kids"], # "Jon" => ["vacuum_cleaner", "hammer", "spade"]}, # :box_usage => "0.0000093222", # :request_id => "81273d21-000-1111-b3f9-512d91d29ac8" } # # # request cat's attributes only # sdb.get_attributes('family', 'toys', 'cat') # => { :attributes => {"cat" => ["clew", "Jons_socks", "mouse"] }, # :box_usage => "0.0000093222", # :request_id => "81273d21-001-1111-b3f9-512d91d29ac8" } # # see: http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/SDB_API_GetAttributes.html # def get_attributes(domain_name, item_name, attribute_name=nil, consistent_read = nil) link = generate_request("GetAttributes", 'DomainName' => domain_name, 'ItemName' => item_name, 'AttributeName' => attribute_name, 'ConsistentRead' => consistent_read) res = request_info(link, QSdbGetAttributesParser.new) res[:attributes].each_value do |values| values.collect! { |e| sdb_to_ruby(e) } end res rescue Exception on_exception end # Delete value, attribute or item. # # Example: # # delete 'vodka' and 'girls' from 'Jon' and 'mice' from 'cat'. # sdb.delete_attributes 'family', 'toys', { 'Jon' => ['vodka', 'girls'], 'cat' => ['mice'] } # # # delete the all the values from attributes (i.e. delete the attributes) # sdb.delete_attributes 'family', 'toys', { 'Jon' => [], 'cat' => [] } # # or # sdb.delete_attributes 'family', 'toys', [ 'Jon', 'cat' ] # # # delete all the attributes from item 'toys' (i.e. delete the item) # sdb.delete_attributes 'family', 'toys' # # see http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/SDB_API_DeleteAttributes.html # def delete_attributes(domain_name, item_name, attributes = nil) params = {'DomainName' => domain_name, 'ItemName' => item_name}.merge(pack_attributes(attributes)) link = generate_request("DeleteAttributes", params) request_info(link, QSdbSimpleParser.new) rescue Exception on_exception end # QUERY: # Perform a query on SDB. # # Returns a hash: # { :box_usage => string, # :request_id => string, # :next_token => string, # :items => [ItemName1,..., ItemNameN] } # # Example: # # query = "['cat' = 'clew']" # sdb.query('family', query) #=> hash of data # sdb.query('family', query, 10) #=> hash of data with max of 10 items # # If a block is given, query will iteratively yield results to it as long as the block continues to return true. # # # List 10 items per iteration. Don't # # forget to escape single quotes and backslashes and wrap all the items in single quotes. # query = "['cat'='clew'] union ['dog'='Jon\\'s boot']" # sdb.query('family', query, 10) do |result| # puts result.inspect # true # end # # # Same query using automatic escaping...to use the auto escape, pass the query and its params as an array: # query = [ "['cat'=?] union ['dog'=?]", "clew", "Jon's boot" ] # sdb.query('family', query) # # query = [ "['cat'=?] union ['dog'=?] sort 'cat' desc", "clew", "Jon's boot" ] # sdb.query('family', query) # # see: http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/SDB_API_Query.html # http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/index.html?SortingData.html # def query(domain_name, query_expression = nil, max_number_of_items = nil, next_token = nil, consistent_read = nil) query_expression = query_expression_from_array(query_expression) if query_expression.is_a?(Array) @last_query_expression = query_expression # request_params = {'DomainName' => domain_name, 'QueryExpression' => query_expression, 'MaxNumberOfItems' => max_number_of_items, 'NextToken' => next_token, 'ConsistentRead' => consistent_read} link = generate_request("Query", request_params) result = request_info(link, QSdbQueryParser.new) # return result if no block given return result unless block_given? # loop if block if given begin # the block must return true if it wanna continue break unless yield(result) && result[:next_token] # make new request request_params['NextToken'] = result[:next_token] link = generate_request("Query", request_params) result = request_info(link, QSdbQueryParser.new) end while true rescue Exception on_exception end # Perform a query and fetch specified attributes. # If attributes are not specified then fetches the whole list of attributes. # # # Returns a hash: # { :box_usage => string, # :request_id => string, # :next_token => string, # :items => [ { ItemName1 => { attribute1 => value1, ... attributeM => valueM } }, # { ItemName2 => {...}}, ... ] # # Example: # # sdb.query_with_attributes(domain, ['hobby', 'country'], "['gender'='female'] intersection ['name' starts-with ''] sort 'name'") #=> # { :request_id => "06057228-70d0-4487-89fb-fd9c028580d3", # :items => # [ { "035f1ba8-dbd8-11dd-80bd-001bfc466dd7"=> # { "hobby" => ["cooking", "flowers", "cats"], # "country" => ["Russia"]}}, # { "0327614a-dbd8-11dd-80bd-001bfc466dd7"=> # { "hobby" => ["patchwork", "bundle jumping"], # "country" => ["USA"]}}, ... ], # :box_usage=>"0.0000504786"} # # sdb.query_with_attributes(domain, [], "['gender'='female'] intersection ['name' starts-with ''] sort 'name'") #=> # { :request_id => "75bb19db-a529-4f69-b86f-5e3800f79a45", # :items => # [ { "035f1ba8-dbd8-11dd-80bd-001bfc466dd7"=> # { "hobby" => ["cooking", "flowers", "cats"], # "name" => ["Mary"], # "country" => ["Russia"], # "gender" => ["female"], # "id" => ["035f1ba8-dbd8-11dd-80bd-001bfc466dd7"]}}, # { "0327614a-dbd8-11dd-80bd-001bfc466dd7"=> # { "hobby" => ["patchwork", "bundle jumping"], # "name" => ["Mary"], # "country" => ["USA"], # "gender" => ["female"], # "id" => ["0327614a-dbd8-11dd-80bd-001bfc466dd7"]}}, ... ], # :box_usage=>"0.0000506668"} # # see: http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/index.html?SDB_API_QueryWithAttributes.html # def query_with_attributes(domain_name, attributes=[], query_expression = nil, max_number_of_items = nil, next_token = nil, consistent_read = nil) attributes = attributes.to_a query_expression = query_expression_from_array(query_expression) if query_expression.is_a?(Array) @last_query_expression = query_expression # request_params = {'DomainName' => domain_name, 'QueryExpression' => query_expression, 'MaxNumberOfItems' => max_number_of_items, 'NextToken' => next_token, 'ConsistentRead' => consistent_read} attributes.each_with_index do |attribute, idx| request_params["AttributeName.#{idx+1}"] = attribute end link = generate_request("QueryWithAttributes", request_params) result = select_response_to_ruby(request_info(link, QSdbQueryWithAttributesParser.new)) # return result if no block given return result unless block_given? # loop if block if given begin # the block must return true if it wanna continue break unless yield(result) && result[:next_token] # make new request request_params['NextToken'] = result[:next_token] link = generate_request("QueryWithAttributes", request_params) result = select_response_to_ruby(request_info(link, QSdbQueryWithAttributesParser.new)) end while true rescue Exception on_exception end # Perform SQL-like select and fetch attributes. # Attribute values must be quoted with a single or double quote. If a quote appears within the attribute value, it must be escaped with the same quote symbol as shown in the following example. # (Use array to pass select_expression params to avoid manual escaping). # # sdb.select(["select * from my_domain where gender=?", 'female']) #=> # {:request_id =>"8241b843-0fb9-4d66-9100-effae12249ec", # :items => # [ { "035f1ba8-dbd8-11dd-80bd-001bfc466dd7"=> # {"hobby" => ["cooking", "flowers", "cats"], # "name" => ["Mary"], # "country" => ["Russia"], # "gender" => ["female"], # "id" => ["035f1ba8-dbd8-11dd-80bd-001bfc466dd7"]}}, # { "0327614a-dbd8-11dd-80bd-001bfc466dd7"=> # {"hobby" => ["patchwork", "bundle jumping"], # "name" => ["Mary"], # "country" => ["USA"], # "gender" => ["female"], # "id" => ["0327614a-dbd8-11dd-80bd-001bfc466dd7"]}}, ... ] # :box_usage =>"0.0000506197"} # # sdb.select('select country, name from my_domain') #=> # {:request_id=>"b1600198-c317-413f-a8dc-4e7f864a940a", # :items=> # [ { "035f1ba8-dbd8-11dd-80bd-001bfc466dd7"=> {"name"=>["Mary"], "country"=>["Russia"]} }, # { "376d2e00-75b0-11dd-9557-001bfc466dd7"=> {"name"=>["Putin"], "country"=>["Russia"]} }, # { "0327614a-dbd8-11dd-80bd-001bfc466dd7"=> {"name"=>["Mary"], "country"=>["USA"]} }, # { "372ebbd4-75b0-11dd-9557-001bfc466dd7"=> {"name"=>["Bush"], "country"=>["USA"]} }, # { "37a4e552-75b0-11dd-9557-001bfc466dd7"=> {"name"=>["Medvedev"], "country"=>["Russia"]} }, # { "38278dfe-75b0-11dd-9557-001bfc466dd7"=> {"name"=>["Mary"], "country"=>["Russia"]} }, # { "37df6c36-75b0-11dd-9557-001bfc466dd7"=> {"name"=>["Mary"], "country"=>["USA"]} } ], # :box_usage=>"0.0000777663"} # # options: # :next_token # :consistent_read # :retries => maximum number of times to retry this query on an error response. # # see: http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/index.html?SDB_API_Select.html # http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/index.html?UsingSelect.html # http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/index.html?SDBLimits.html # def select(select_expression, next_token = nil, consistent_read = nil) select_expression = query_expression_from_array(select_expression) if select_expression.is_a?(Array) @last_query_expression = select_expression options = {} if next_token.is_a?(Hash) options = next_token next_token = options[:next_token] consistent_read = options[:consistent_read] end # request_params = {'SelectExpression' => select_expression, 'NextToken' => next_token, 'ConsistentRead' => consistent_read} link = generate_request("Select", request_params, options) result = select_response_to_ruby(request_info(link, QSdbSelectParser.new, options)) return result unless block_given? # loop if block if given begin # the block must return true if it wanna continue break unless yield(result) && result[:next_token] # make new request request_params['NextToken'] = result[:next_token] link = generate_request("Select", request_params) result = select_response_to_ruby(request_info(link, QSdbSelectParser.new, options)) end while true rescue Exception on_exception end class Item attr_accessor :item_name, :attributes, :replace def initialize(item_name, attributes, replace = false) @item_name = item_name @attributes = attributes @replace = replace end end #----------------------------------------------------------------- # PARSERS: #----------------------------------------------------------------- class QSdbListDomainParser < AwsParser #:nodoc: def reset @result = {:domains => []} end def tagend(name) case name when 'NextToken' then @result[:next_token] = @text when 'DomainName' then @result[:domains] << @text when 'BoxUsage' then @result[:box_usage] = @text when 'RequestId' then @result[:request_id] = @text end end end class QSdbDomainMetadataParser < AwsParser #:nodoc: def reset @result = {} end def tagend(name) case name when 'Timestamp' then @result[:timestamp] = @text when 'ItemCount' then @result[:item_count] = @text.to_i when 'AttributeValueCount' then @result[:attribute_value_count] = @text.to_i when 'AttributeNameCount' then @result[:attribute_name_acount] = @text.to_i when 'ItemNamesSizeBytes' then @result[:item_names_size_bytes] = @text.to_i when 'AttributeValuesSizeBytes' then @result[:attributes_values_size_bytes] = @text.to_i when 'AttributeNamesSizeBytes' then @result[:attributes_names_size_bytes] = @text.to_i end end end class QSdbSimpleParser < AwsParser #:nodoc: def reset @result = {} end def tagend(name) case name when 'BoxUsage' then @result[:box_usage] = @text when 'RequestId' then @result[:request_id] = @text end end end class QSdbGetAttributesParser < AwsParser #:nodoc: def reset @last_attribute_name = nil @result = {:attributes => {}} end def tagend(name) case name when 'Name' then @last_attribute_name = @text when 'Value' then (@result[:attributes][@last_attribute_name] ||= []) << @text when 'BoxUsage' then @result[:box_usage] = @text when 'RequestId' then @result[:request_id] = @text end end end class QSdbQueryParser < AwsParser #:nodoc: def reset @result = {:items => []} end def tagend(name) case name when 'ItemName' then @result[:items] << @text when 'BoxUsage' then @result[:box_usage] = @text when 'RequestId' then @result[:request_id] = @text when 'NextToken' then @result[:next_token] = @text end end end class QSdbQueryWithAttributesParser < AwsParser #:nodoc: def reset @result = {:items => []} end def tagend(name) case name when 'Name' case @xmlpath when 'QueryWithAttributesResponse/QueryWithAttributesResult/Item' @item = @text @result[:items] << {@item => {}} when 'QueryWithAttributesResponse/QueryWithAttributesResult/Item/Attribute' @attribute = @text @result[:items].last[@item][@attribute] ||= [] end when 'RequestId' then @result[:request_id] = @text when 'BoxUsage' then @result[:box_usage] = @text when 'NextToken' then @result[:next_token] = @text when 'Value' then @result[:items].last[@item][@attribute] << @text end end end class QSdbSelectParser < AwsParser #:nodoc: def reset @result = {:items => []} end def tagend(name) case name when 'Name' case @xmlpath when 'SelectResponse/SelectResult/Item' @item = @text @result[:items] << {@item => {}} when 'SelectResponse/SelectResult/Item/Attribute' @attribute = @text @result[:items].last[@item][@attribute] ||= [] end when 'RequestId' then @result[:request_id] = @text when 'BoxUsage' then @result[:box_usage] = @text when 'NextToken' then @result[:next_token] = @text when 'Value' then @result[:items].last[@item][@attribute] << @text end end end end end aws-2.10.2/lib/acf/0000755000175000017500000000000012511515441012400 5ustar tnnntnnnaws-2.10.2/lib/acf/acf_interface.rb0000644000175000017500000004374612511515441015514 0ustar tnnntnnn# # Copyright (c) 2008 RightScale Inc # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # module Aws # = Aws::AcfInterface -- RightScale Amazon's CloudFront interface # The AcfInterface class provides a complete interface to Amazon's # CloudFront service. # # For explanations of the semantics of each call, please refer to # Amazon's documentation at # http://developer.amazonwebservices.com/connect/kbcategory.jspa?categoryID=211 # # Example: # # acf = Aws::AcfInterface.new('1E3GDYEOGFJPIT7XXXXXX','hgTHt68JY07JKUY08ftHYtERkjgtfERn57XXXXXX') # # list = acf.list_distributions #=> # [{:status => "Deployed", # :domain_name => "d74zzrxmpmygb.6hops.net", # :aws_id => "E4U91HCJHGXVC", # :origin => "my-bucket.s3.amazonaws.com", # :cnames => ["x1.my-awesome-site.net", "x1.my-awesome-site.net"] # :comment => "My comments", # :last_modified_time => Wed Sep 10 17:00:04 UTC 2008 }, ..., {...} ] # # distibution = list.first # # info = acf.get_distribution(distibution[:aws_id]) #=> # {:enabled => true, # :caller_reference => "200809102100536497863003", # :e_tag => "E39OHHU1ON65SI", # :status => "Deployed", # :domain_name => "d3dxv71tbbt6cd.6hops.net", # :cnames => ["web1.my-awesome-site.net", "web2.my-awesome-site.net"] # :aws_id => "E2REJM3VUN5RSI", # :comment => "Woo-Hoo!", # :origin => "my-bucket.s3.amazonaws.com", # :last_modified_time => Wed Sep 10 17:00:54 UTC 2008 } # # config = acf.get_distribution_config(distibution[:aws_id]) #=> # {:enabled => true, # :caller_reference => "200809102100536497863003", # :e_tag => "E39OHHU1ON65SI", # :cnames => ["web1.my-awesome-site.net", "web2.my-awesome-site.net"] # :comment => "Woo-Hoo!", # :origin => "my-bucket.s3.amazonaws.com"} # # config[:comment] = 'Olah-lah!' # config[:enabled] = false # config[:cnames] << "web3.my-awesome-site.net" # # acf.set_distribution_config(distibution[:aws_id], config) #=> true # class AcfInterface < AwsBase include AwsBaseInterface API_VERSION = "2010-08-01" DEFAULT_HOST = 'cloudfront.amazonaws.com' DEFAULT_PORT = 443 DEFAULT_PROTOCOL = 'https' DEFAULT_PATH = '/' def self.connection_name :acf_connection end @@bench = AwsBenchmarkingBlock.new def self.bench @@bench end def self.bench_xml @@bench.xml end def self.bench_service @@bench.service end # Create a new handle to a CloudFront account. All handles share the same per process or per thread # HTTP connection to CloudFront. Each handle is for a specific account. The params have the # following options: # * :server: CloudFront service host, default: DEFAULT_HOST # * :port: CloudFront service port, default: DEFAULT_PORT # * :protocol: 'http' or 'https', default: DEFAULT_PROTOCOL # * :multi_thread: true=HTTP connection per thread, false=per process # * :logger: for log messages, default: Rails.logger else STDOUT # * :cache: true/false: caching for list_distributions method, default: false. # # acf = Aws::AcfInterface.new('1E3GDYEOGFJPIT7XXXXXX','hgTHt68JY07JKUY08ftHYtERkjgtfERn57XXXXXX', # {:multi_thread => true, :logger => Logger.new('/tmp/x.log')}) #=> # # def initialize(aws_access_key_id=nil, aws_secret_access_key=nil, params={}) init({:name => 'ACF', :default_host => ENV['ACF_URL'] ? URI.parse(ENV['ACF_URL']).host : DEFAULT_HOST, :default_port => ENV['ACF_URL'] ? URI.parse(ENV['ACF_URL']).port : DEFAULT_PORT, :default_service => ENV['ACF_URL'] ? URI.parse(ENV['ACF_URL']).path : DEFAULT_PATH, :default_protocol => ENV['ACF_URL'] ? URI.parse(ENV['ACF_URL']).scheme : DEFAULT_PROTOCOL}, aws_access_key_id || ENV['AWS_ACCESS_KEY_ID'], aws_secret_access_key || ENV['AWS_SECRET_ACCESS_KEY'], params) end #----------------------------------------------------------------- # Requests #----------------------------------------------------------------- # Generates request hash for REST API. def generate_request(method, path, body=nil, headers={}) # :nodoc: headers['content-type'] ||= 'text/xml' if body headers['date'] = Time.now.httpdate # Auth signature = Utils::sign(@aws_secret_access_key, headers['date']) headers['Authorization'] = "AWS #{@aws_access_key_id}:#{signature}" # Request path = "#{@params[:default_service]}/#{API_VERSION}/#{path}" request = Net::HTTP.const_get(method.capitalize).new(path) request.body = body if body # Set request headers headers.each { |key, value| request[key.to_s] = value } # prepare output hash {:request => request, :server => @params[:server], :port => @params[:port], :protocol => @params[:protocol]} end # Sends request to Amazon and parses the response. # Raises AwsError if any banana happened. # todo: remove this and switch to using request_info2 def request_info(request, parser, options={}, &block) # :nodoc: conn = get_conn(self.class.connection_name, @params, @logger) request_info_impl(conn, @@bench, request, parser, options, &block) end #----------------------------------------------------------------- # Helpers: #----------------------------------------------------------------- def self.escape(text) # :nodoc: REXML::Text::normalize(text) end def self.unescape(text) # :nodoc: REXML::Text::unnormalize(text) end def xmlns # :nodoc: %Q{"http://#{@params[:server]}/doc/#{API_VERSION}/"} end def generate_call_reference # :nodoc: result = Time.now.strftime('%Y%m%d%H%M%S') 10.times { result << rand(10).to_s } result end def merge_headers(hash) # :nodoc: hash[:location] = @last_response['Location'] if @last_response['Location'] hash[:e_tag] = @last_response['ETag'] if @last_response['ETag'] hash end #----------------------------------------------------------------- # API Calls: #----------------------------------------------------------------- # List distributions. # Returns an array of distributions or Aws::AwsError exception. # # acf.list_distributions #=> # [{:status => "Deployed", # :domain_name => "d74zzrxmpmygb.6hops.net", # :aws_id => "E4U91HCJHGXVC", # :cnames => ["web1.my-awesome-site.net", "web2.my-awesome-site.net"] # :origin => "my-bucket.s3.amazonaws.com", # :comment => "My comments", # :last_modified_time => Wed Sep 10 17:00:04 UTC 2008 }, ..., {...} ] # def list_distributions request_hash = generate_request('GET', 'distribution') request_cache_or_info :list_distributions, request_hash, AcfDistributionListParser, @@bench end def list_streaming_distributions request_hash = generate_request('GET', 'streaming-distribution') request_cache_or_info :list_streaming_distributions, request_hash, AcfStreamingDistributionListParser, @@bench end # Create a new distribution. # Returns the just created distribution or Aws::AwsError exception. # # acf.create_distribution('bucket-for-k-dzreyev.s3.amazonaws.com', 'Woo-Hoo!', true, ['web1.my-awesome-site.net'] ) #=> # {:comment => "Woo-Hoo!", # :enabled => true, # :location => "https://cloudfront.amazonaws.com/2008-06-30/distribution/E2REJM3VUN5RSI", # :status => "InProgress", # :aws_id => "E2REJM3VUN5RSI", # :domain_name => "d3dxv71tbbt6cd.6hops.net", # :origin => "my-bucket.s3.amazonaws.com", # :cnames => ["web1.my-awesome-site.net"] # :last_modified_time => Wed Sep 10 17:00:54 UTC 2008, # :caller_reference => "200809102100536497863003"} # def create_distribution(origin, comment='', enabled=true, cnames=[], caller_reference=nil, default_root_object=nil) body = distribution_config_for(origin, comment, enabled, cnames, caller_reference, false, default_root_object) request_hash = generate_request('POST', 'distribution', body.strip) merge_headers(request_info(request_hash, AcfDistributionParser.new)) end def create_streaming_distribution(origin, comment='', enabled=true, cnames=[], caller_reference=nil, default_root_object=nil) body = distribution_config_for(origin, comment, enabled, cnames, caller_reference, true, default_root_object) request_hash = generate_request('POST', 'streaming-distribution', body.strip) merge_headers(request_info(request_hash, AcfDistributionParser.new)) end def distribution_config_for(origin, comment='', enabled=true, cnames=[], caller_reference=nil, streaming = false, default_root_object=nil) rootElement = streaming ? "StreamingDistributionConfig" : "DistributionConfig" # join CNAMES cnames_str = '' unless cnames.nil? || cnames.empty? cnames.to_a.each { |cname| cnames_str += "\n #{cname}" } end caller_reference ||= generate_call_reference root_ob = default_root_object ? "#{config[:default_root_object]}" : "" body = <<-EOXML <#{rootElement} xmlns=#{xmlns}> #{origin} #{caller_reference} #{cnames_str.lstrip} #{AcfInterface::escape(comment.to_s)} #{enabled} #{root_ob} EOXML end # Get a distribution's information. # Returns a distribution's information or Aws::AwsError exception. # # acf.get_distribution('E2REJM3VUN5RSI') #=> # {:enabled => true, # :caller_reference => "200809102100536497863003", # :e_tag => "E39OHHU1ON65SI", # :status => "Deployed", # :domain_name => "d3dxv71tbbt6cd.6hops.net", # :cnames => ["web1.my-awesome-site.net", "web2.my-awesome-site.net"] # :aws_id => "E2REJM3VUN5RSI", # :comment => "Woo-Hoo!", # :origin => "my-bucket.s3.amazonaws.com", # :last_modified_time => Wed Sep 10 17:00:54 UTC 2008 } # def get_distribution(aws_id) request_hash = generate_request('GET', "distribution/#{aws_id}") merge_headers(request_info(request_hash, AcfDistributionParser.new)) end def get_streaming_distribution(aws_id) request_hash = generate_request('GET', "streaming-distribution/#{aws_id}") merge_headers(request_info(request_hash, AcfDistributionParser.new)) end # Get a distribution's configuration. # Returns a distribution's configuration or Aws::AwsError exception. # # acf.get_distribution_config('E2REJM3VUN5RSI') #=> # {:enabled => true, # :caller_reference => "200809102100536497863003", # :e_tag => "E39OHHU1ON65SI", # :cnames => ["web1.my-awesome-site.net", "web2.my-awesome-site.net"] # :comment => "Woo-Hoo!", # :origin => "my-bucket.s3.amazonaws.com"} # def get_distribution_config(aws_id) request_hash = generate_request('GET', "distribution/#{aws_id}/config") merge_headers(request_info(request_hash, AcfDistributionConfigParser.new)) end # Set a distribution's configuration # (the :origin and the :caller_reference cannot be changed). # Returns +true+ on success or Aws::AwsError exception. # # config = acf.get_distribution_config('E2REJM3VUN5RSI') #=> # {:enabled => true, # :caller_reference => "200809102100536497863003", # :e_tag => "E39OHHU1ON65SI", # :cnames => ["web1.my-awesome-site.net", "web2.my-awesome-site.net"] # :comment => "Woo-Hoo!", # :origin => "my-bucket.s3.amazonaws.com", # :default_root_object => # } # config[:comment] = 'Olah-lah!' # config[:enabled] = false # acf.set_distribution_config('E2REJM3VUN5RSI', config) #=> true # def set_distribution_config(aws_id, config) body = distribution_config_for(config[:origin], config[:comment], config[:enabled], config[:cnames], config[:caller_reference], false) request_hash = generate_request('PUT', "distribution/#{aws_id}/config", body.strip, 'If-Match' => config[:e_tag]) request_info(request_hash, RightHttp2xxParser.new) end def set_streaming_distribution_config(aws_id, config) body = distribution_config_for(config[:origin], config[:comment], config[:enabled], config[:cnames], config[:caller_reference], true) request_hash = generate_request('PUT', "streaming-distribution/#{aws_id}/config", body.strip, 'If-Match' => config[:e_tag]) request_info(request_hash, RightHttp2xxParser.new) end # Delete a distribution. The enabled distribution cannot be deleted. # Returns +true+ on success or Aws::AwsError exception. # # acf.delete_distribution('E2REJM3VUN5RSI', 'E39OHHU1ON65SI') #=> true # def delete_distribution(aws_id, e_tag) request_hash = generate_request('DELETE', "distribution/#{aws_id}", nil, 'If-Match' => e_tag) request_info(request_hash, RightHttp2xxParser.new) end def delete_streaming_distribution(aws_id, e_tag) request_hash = generate_request('DELETE', "streaming-distribution/#{aws_id}", nil, 'If-Match' => e_tag) request_info(request_hash, RightHttp2xxParser.new) end #----------------------------------------------------------------- # PARSERS: #----------------------------------------------------------------- # Parses attributes common to many CF distribution API calls class AcfBaseDistributionParser < AwsParser # :nodoc: def reset @distribution = {:cnames => []} @result = [] end def tagend(name) case name when 'Id' then @distribution[:aws_id] = @text when 'Status' then @distribution[:status] = @text when 'LastModifiedTime' then @distribution[:last_modified_time] = Time.parse(@text) when 'DomainName' then @distribution[:domain_name] = @text when 'Origin' then @distribution[:origin] = @text when 'CallerReference' then @distribution[:caller_reference] = @text when 'Comment' then @distribution[:comment] = AcfInterface::unescape(@text) when 'Enabled' then @distribution[:enabled] = @text == 'true' ? true : false when 'CNAME' then @distribution[:cnames] << @text end end end class AcfDistributionParser < AcfBaseDistributionParser # :nodoc: def tagend(name) super @result = @distribution end end class AcfDistributionListParser < AcfBaseDistributionParser # :nodoc: def tagstart(name, attributes) @distribution = {:cnames => []} if name == 'DistributionSummary' end def tagend(name) super(name) case name when 'DistributionSummary' then @result << @distribution end end end class AcfDistributionConfigParser < AwsParser # :nodoc: def reset @result = {:cnames => []} end def tagend(name) case name when 'Origin' then @result[:origin] = @text when 'CallerReference' then @result[:caller_reference] = @text when 'Comment' then @result[:comment] = AcfInterface::unescape(@text) when 'Enabled' then @result[:enabled] = @text == 'true' ? true : false when 'CNAME' then @result[:cnames] << @text end end end class AcfStreamingDistributionListParser < AcfBaseDistributionParser # :nodoc: def tagstart(name, attributes) @distribution = {:cnames => []} if name == 'StreamingDistributionSummary' end def tagend(name) super(name) case name when 'StreamingDistributionSummary' then @result << @distribution end end end end end aws-2.10.2/lib/elb/0000755000175000017500000000000012511515441012411 5ustar tnnntnnnaws-2.10.2/lib/elb/elb_interface.rb0000644000175000017500000003745312511515441015534 0ustar tnnntnnnmodule Aws require 'xmlsimple' class Elb < AwsBase include AwsBaseInterface #Amazon ELB API version being used API_VERSION = "2012-06-01" DEFAULT_HOST = "elasticloadbalancing.amazonaws.com" DEFAULT_PATH = '/' DEFAULT_PROTOCOL = 'https' DEFAULT_PORT = 443 def self.connection_name :elb_connection end @@bench = AwsBenchmarkingBlock.new def self.bench @@bench end def self.bench_xml @@bench.xml end def self.bench_ec2 @@bench.service end # Current API version (sometimes we have to check it outside the GEM). @@api = ENV['ELB_API_VERSION'] || API_VERSION def self.api @@api end def initialize(aws_access_key_id=nil, aws_secret_access_key=nil, params={}) init({:name => 'ELB', :default_host => ENV['ELB_URL'] ? URI.parse(ENV['ELB_URL']).host : DEFAULT_HOST, :default_port => ENV['ELB_URL'] ? URI.parse(ENV['ELB_URL']).port : DEFAULT_PORT, :default_service => ENV['ELB_URL'] ? URI.parse(ENV['ELB_URL']).path : DEFAULT_PATH, :default_protocol => ENV['ELB_URL'] ? URI.parse(ENV['ELB_URL']).scheme : DEFAULT_PROTOCOL, :api_version => API_VERSION}, aws_access_key_id || ENV['AWS_ACCESS_KEY_ID'], aws_secret_access_key|| ENV['AWS_SECRET_ACCESS_KEY'], params) end # Sends request to Amazon and parses the response # Raises AwsError if any banana happened def request_info(request, parser, options={}) request_info2(request, parser, @params, self.class.connection_name, @logger, @@bench, options) end # todo: convert to xml-simple version and get rid of parser below def do_request(action, params, options={}) link = generate_request(action, params) resp = request_info_xml_simple(self.class.connection_name, @params, link, @logger, :group_tags =>{"LoadBalancersDescriptions"=>"LoadBalancersDescription", "DBParameterGroups" =>"DBParameterGroup", "DBSecurityGroups" =>"DBSecurityGroup", "EC2SecurityGroups" =>"EC2SecurityGroup", "IPRanges" =>"IPRange"}, :force_array =>["DBInstances", "DBParameterGroups", "DBSecurityGroups", "EC2SecurityGroups", "IPRanges"], :pull_out_array =>options[:pull_out_array], :pull_out_single=>options[:pull_out_single], :wrapper =>options[:wrapper]) end #----------------------------------------------------------------- # REQUESTS #----------------------------------------------------------------- # # name: name of load balancer # availability_zones: array of zones # listeners: array of hashes containing :load_balancer_port, :instance_port, :protocol # eg: {:load_balancer_port=>80, :instance_port=>8080, :protocol=>"HTTP"} def create_load_balancer(name, availability_zones, listeners) params = hash_params('AvailabilityZones.member', availability_zones) i = 1 listeners.each do |l| params["Listeners.member.#{i}.Protocol"] = "#{l[:protocol]}" params["Listeners.member.#{i}.LoadBalancerPort"] = "#{l[:load_balancer_port]}" params["Listeners.member.#{i}.InstancePort"] = "#{l[:instance_port]}" i += 1 end params['LoadBalancerName'] = name @logger.info("Creating LoadBalancer called #{params['LoadBalancerName']}") link = generate_request("CreateLoadBalancer", params) resp = request_info(link, QElbCreateParser.new(:logger => @logger)) rescue Exception on_exception end # # name: name of load balancer # listeners: array of hashes containing :load_balancer_port, :instance_port, :protocol # eg: {:load_balancer_port=>80, :instance_port=>8080, :protocol=>"HTTP"} # def create_load_balancer_listeners(name, listeners) params = {} params['LoadBalancerName'] = name i = 1 listeners.each do |l| params["Listeners.member.#{i}.Protocol"] = "#{l[:protocol]}" params["Listeners.member.#{i}.LoadBalancerPort"] = "#{l[:load_balancer_port]}" params["Listeners.member.#{i}.InstancePort"] = "#{l[:instance_port]}" i += 1 end @logger.info("Creating Listeners for LoadBalancer #{name}") link = generate_request("CreateLoadBalancerListeners", params) resp = request_info(link, QElbEmptyResponseParser.new(:logger => @logger)) rescue Exception on_exception end # # name: name of load balancer # ports: array of client port number(s) of the load balancer listener(s) to be removed. # def delete_load_balancer_listeners(name, ports) params = {} params['LoadBalancerName'] = name i = 1 ports.each do |l| params["LoadBalancerPorts.member.#{i}"] = "#{l}" i += 1 end @logger.info("Deleting Listeners for LoadBalancer #{name}") link = generate_request("DeleteLoadBalancerListeners", params) resp = request_info(link, QElbEmptyResponseParser.new(:logger => @logger)) rescue Exception on_exception end # name: name of load balancer # instance_ids: array of instance_id's to add to load balancer def register_instances_with_load_balancer(name, instance_ids) params = {} params['LoadBalancerName'] = name i = 1 instance_ids.each do |l| params["Instances.member.#{i}.InstanceId"] = "#{l}" i += 1 end @logger.info("Registering Instances #{instance_ids.join(',')} with Load Balancer '#{name}'") link = generate_request("RegisterInstancesWithLoadBalancer", params) resp = request_info(link, QElbRegisterInstancesParser.new(:logger => @logger)) rescue Exception on_exception end def deregister_instances_from_load_balancer(name, instance_ids) params = {} params['LoadBalancerName'] = name i = 1 instance_ids.each do |l| params["Instances.member.#{i}.InstanceId"] = "#{l}" i += 1 end @logger.info("Deregistering Instances #{instance_ids.join(',')} from Load Balancer '#{name}'") link = generate_request("DeregisterInstancesFromLoadBalancer", params) # Same response as register I believe resp = request_info(link, QElbRegisterInstancesParser.new(:logger => @logger)) rescue Exception on_exception end # name: name of load balancer # healthy_threshold: number of successful probes required to enter Healthy state. # unhealthy_threshold: number of unsuccessful probes required to enter Unhealthy state. # interval: interval between probes (seconds) # target: probe target (protocol:port[/path]) # timeout: probe response timeout def configure_health_check(name, healthy_threshold, unhealthy_threshold, interval, target, timeout ) params = {} params['LoadBalancerName'] = name params['HealthCheck.HealthyThreshold'] = "#{healthy_threshold}" params['HealthCheck.UnhealthyThreshold'] = "#{unhealthy_threshold}" params['HealthCheck.Interval'] = "#{interval}" params['HealthCheck.Target'] = target params['HealthCheck.Timeout'] = "#{timeout}" @logger.info("Configuring health check for Load Balancer '#{name}'") link = generate_request("ConfigureHealthCheck", params) resp = request_info(link, QElbConfigureHealthCheckParser.new(:logger => @logger)) rescue Exception on_exception end def describe_load_balancers(lparams={}) @logger.info("Describing Load Balancers") params = {} params.update(hash_params('LoadBalancerNames.member', lparams[:names])) if lparams[:names] link = generate_request("DescribeLoadBalancers", params) resp = request_info(link, QElbDescribeLoadBalancersParser.new(:logger => @logger)) rescue Exception on_exception end def describe_instance_health(name, instance_ids=[]) instance_ids = [instance_ids] if instance_ids.is_a?(String) # @logger.info("Describing Instance Health") params = {} params['LoadBalancerName'] = name i = 1 instance_ids.each do |l| params["Instances.member.#{i}.InstanceId"] = "#{l}" i += 1 end @logger.info("Describing Instances Health #{instance_ids.join(',')} with Load Balancer '#{name}'") link = generate_request("DescribeInstanceHealth", params) resp = request_info(link, QElbDescribeInstancesHealthParser.new(:logger => @logger)) rescue Exception on_exception end def delete_load_balancer(name) @logger.info("Deleting Load Balancer - " + name.to_s) params = {} params['LoadBalancerName'] = name link = generate_request("DeleteLoadBalancer", params) resp = request_info(link, QElbEmptyResponseParser.new(:logger => @logger)) rescue Exception on_exception end #----------------------------------------------------------------- # PARSERS: Instances #----------------------------------------------------------------- class QElbCreateParser < AwsParser def reset @result = {} end def tagend(name) case name when 'DNSName' then @result[:dns_name] = @text end end end class QElbDescribeLoadBalancersParser < AwsParser def reset @result = [] end def tagstart(name, attributes) # puts 'tagstart ' + name + ' -- ' + @xmlpath if (name == 'member' && @xmlpath == 'DescribeLoadBalancersResponse/DescribeLoadBalancersResult/LoadBalancerDescriptions/member/ListenerDescriptions') @listener = {} end if (name == 'member' && @xmlpath == 'DescribeLoadBalancersResponse/DescribeLoadBalancersResult/LoadBalancerDescriptions/member/AvailabilityZones') @availability_zone = {} end if (name == 'member' && @xmlpath == 'DescribeLoadBalancersResponse/DescribeLoadBalancersResult/LoadBalancerDescriptions/member/Instances') @instance = {} end if (name == 'member' && @xmlpath == 'DescribeLoadBalancersResponse/DescribeLoadBalancersResult/LoadBalancerDescriptions') @member = {:listeners=>[], :availability_zones=>[], :health_check=>{}, :instances=>[]} end end def tagend(name) case name when 'LoadBalancerName' then @member[:load_balancer_name] = @text @member[:name] = @text when 'CreatedTime' then @member[:created_time] = Time.parse(@text) @member[:created] = @member[:created_time] when 'DNSName' then @member[:dns_name] = @text # Instances when 'InstanceId' then @instance[:instance_id] = @text # Listeners when 'Protocol' then @listener[:protocol] = @text when 'LoadBalancerPort' then @listener[:load_balancer_port] = @text.to_i when 'InstancePort' then @listener[:instance_port] = @text.to_i # HEALTH CHECK STUFF when 'Interval' then @member[:health_check][:interval] = @text.to_i when 'Target' then @member[:health_check][:target] = @text when 'HealthyThreshold' then @member[:health_check][:healthy_threshold] = @text.to_i when 'Timeout' then @member[:health_check][:timeout] = @text.to_i when 'UnhealthyThreshold' then @member[:health_check][:unhealthy_threshold] = @text.to_i # AvailabilityZones when 'member' then if @xmlpath == 'DescribeLoadBalancersResponse/DescribeLoadBalancersResult/LoadBalancerDescriptions/member/ListenerDescriptions' @member[:listeners] << @listener elsif @xmlpath == 'DescribeLoadBalancersResponse/DescribeLoadBalancersResult/LoadBalancerDescriptions/member/AvailabilityZones' @availability_zone = @text @member[:availability_zones] << @availability_zone elsif @xmlpath == 'DescribeLoadBalancersResponse/DescribeLoadBalancersResult/LoadBalancerDescriptions/member/Instances' @member[:instances] << @instance elsif @xmlpath == 'DescribeLoadBalancersResponse/DescribeLoadBalancersResult/LoadBalancerDescriptions' @result << @member end end end end class QElbRegisterInstancesParser < AwsParser def reset @result = [] end def tagstart(name, attributes) # puts 'tagstart ' + name + ' -- ' + @xmlpath if (name == 'member' && (@xmlpath == 'RegisterInstancesWithLoadBalancerResponse/RegisterInstancesWithLoadBalancerResult/Instances' || @xmlpath == 'DeregisterInstancesFromLoadBalancerResponse/DeregisterInstancesFromLoadBalancerResult/Instances') ) @member = {} end end def tagend(name) case name when 'InstanceId' then @member[:instance_id] = @text when 'member' then @result << @member end end # end class QElbConfigureHealthCheckParser < AwsParser def reset @result = {} end def tagstart(name, attributes) if name == 'HealthCheck' @result[:health_check] = {} end end def tagend(name) case name when 'HealthyThreshold' then @result[:health_check][:healthy_threshold] = @text.to_i when 'UnhealthyThreshold' then @result[:health_check][:unhealthy_threshold] = @text.to_i when 'Interval' then @result[:health_check][:interval] = @text.to_i when 'Target' then @result[:health_check][:target] = @text when 'Timeout' then @result[:health_check][:timeout] = @text.to_i end end # end class QElbDescribeInstancesHealthParser < AwsParser def reset @result = [] end def tagstart(name, attributes) # puts 'tagstart ' + name + ' -- ' + @xmlpath if (name == 'member' && @xmlpath == 'DescribeInstanceHealthResponse/DescribeInstanceHealthResult/InstanceStates') @member = {} end end def tagend(name) case name when 'Description' then @member[:description] = @text when 'State' then @member[:state] = @text when 'InstanceId' then @member[:instance_id] = @text when 'ReasonCode' then @member[:reason_code] = @text when 'member' then @result << @member end end # end class QElbEmptyResponseParser < AwsParser def reset @result = true end end end end aws-2.10.2/lib/ec2/0000755000175000017500000000000012511515441012320 5ustar tnnntnnnaws-2.10.2/lib/ec2/mon_interface.rb0000644000175000017500000001720712511515441015465 0ustar tnnntnnnmodule Aws # This is the interface for Amazon CloudWatch. class Mon < Aws::AwsBase include Aws::AwsBaseInterface #Amazon EC2 API version being used API_VERSION = "2009-05-15" DEFAULT_HOST = "monitoring.amazonaws.com" DEFAULT_PATH = '/' DEFAULT_PROTOCOL = 'https' DEFAULT_PORT = 443 # Available measures for EC2 instances: # NetworkIn NetworkOut DiskReadOps DiskWriteOps DiskReadBytes DiskWriteBytes CPUUtilization measures =%w(NetworkIn NetworkOut DiskReadOps DiskWriteOps DiskReadBytes DiskWriteBytes CPUUtilization) def self.connection_name :mon_connection end @@bench = Aws::AwsBenchmarkingBlock.new def self.bench @@bench end def self.bench_xml @@bench.xml end def self.bench_ec2 @@bench.service end # Current API version (sometimes we have to check it outside the GEM). @@api = ENV['EC2_API_VERSION'] || API_VERSION def self.api @@api end def initialize(aws_access_key_id=nil, aws_secret_access_key=nil, params={}) init({:name => 'MON', :default_host => ENV['MON_URL'] ? URI.parse(ENV['MON_URL']).host : DEFAULT_HOST, :default_port => ENV['MON_URL'] ? URI.parse(ENV['MON_URL']).port : DEFAULT_PORT, :default_service => ENV['MON_URL'] ? URI.parse(ENV['MON_URL']).path : DEFAULT_PATH, :default_protocol => ENV['MON_URL'] ? URI.parse(ENV['MON_URL']).scheme : DEFAULT_PROTOCOL, :api_version => API_VERSION}, aws_access_key_id || ENV['AWS_ACCESS_KEY_ID'], aws_secret_access_key|| ENV['AWS_SECRET_ACCESS_KEY'], params) end def generate_request(action, params={}) service_hash = {"Action" => action, "AWSAccessKeyId" => @aws_access_key_id, "Version" => @@api} service_hash.update(params) service_params = signed_service_params(@aws_secret_access_key, service_hash, :get, @params[:server], @params[:service]) # use POST method if the length of the query string is too large if service_params.size > 2000 if signature_version == '2' # resign the request because HTTP verb is included into signature service_params = signed_service_params(@aws_secret_access_key, service_hash, :post, @params[:server], @params[:service]) end request = Net::HTTP::Post.new(service) request.body = service_params request['Content-Type'] = 'application/x-www-form-urlencoded' else request = Net::HTTP::Get.new("#{@params[:service]}?#{service_params}") end #puts "\n\n --------------- QUERY REQUEST TO AWS -------------- \n\n" #puts "#{@params[:service]}?#{service_params}\n\n" # prepare output hash {:request => request, :server => @params[:server], :port => @params[:port], :protocol => @params[:protocol]} end # Sends request to Amazon and parses the response # Raises AwsError if any banana happened # todo: remove this and switch to using request_info2 def request_info(request, parser, options={}) conn = get_conn(self.class.connection_name, @params, @logger) request_info_impl(conn, @@bench, request, parser, options) end #----------------------------------------------------------------- # REQUESTS #----------------------------------------------------------------- def list_metrics(options={}) next_token = options[:next_token] || nil params = {} params['NextToken'] = next_token unless next_token.nil? @logger.info("list Metrics ") link = generate_request("ListMetrics", params) resp = request_info(link, QMonListMetrics.new(:logger => @logger)) rescue Exception on_exception end # measureName: CPUUtilization (Units: Percent), NetworkIn (Units: Bytes), NetworkOut (Units: Bytes), DiskWriteOps (Units: Count) # DiskReadBytes (Units: Bytes), DiskReadOps (Units: Count), DiskWriteBytes (Units: Bytes) # stats: array containing one or more of Minimum, Maximum, Sum, Average, Samples # start_time : Timestamp to start # end_time: Timestamp to end # unit: Either Seconds, Percent, Bytes, Bits, Count, Bytes, Bits/Second, Count/Second, and None # # Optional parameters: # period: Integer 60 or multiple of 60 # dimensions: Hash containing keys ImageId, AutoScalingGroupName, InstanceId, InstanceType # customUnit: nil. not supported currently. # namespace: AWS/EC2 def get_metric_statistics (measure_name, stats, start_time, end_time, unit, options={}) period = options[:period] || 60 dimensions = options[:dimensions] || nil custom_unit = options[:custom_unit] || nil namespace = options[:namespace] || "AWS/EC2" params = {} params['MeasureName'] = measure_name i =1 stats.each do |s| params['Statistics.member.'+i.to_s] = s i = i+1 end params['Period'] = period if (dimensions != nil) i = 1 dimensions.each do |k, v| params['Dimensions.member.'+i.to_s+".Name."+i.to_s] = k params['Dimensions.member.'+i.to_s+".Value."+i.to_s] = v i = i+1 end end params['StartTime'] = start_time params['EndTime'] = end_time params['Unit'] = unit #params['CustomUnit'] = customUnit always nil params['Namespace'] = namespace link = generate_request("GetMetricStatistics", params) resp = request_info(link, QMonGetMetricStatistics.new(:logger => @logger)) rescue Exception on_exception end #----------------------------------------------------------------- # PARSERS: Instances #----------------------------------------------------------------- class QMonGetMetricStatistics < Aws::AwsParser def reset @result = [] end def tagstart(name, attributes) @metric = {} if name == 'member' end def tagend(name) case name when 'Timestamp' then @metric[:timestamp] = @text when 'Samples' then @metric[:samples] = @text when 'Unit' then @metric[:unit] = @text when 'Average' then @metric[:average] = @text when 'Minimum' then @metric[:minimum] = @text when 'Maximum' then @metric[:maximum] = @text when 'Sum' then @metric[:sum] = @text when 'Value' then @metric[:value] = @text when 'member' then @result << @metric end end end class QMonListMetrics < Aws::AwsParser def reset @result = [] @namespace = "" @measure_name = "" end def tagstart(name, attributes) @metric = {} if name == 'member' end def tagend(name) case name when 'MeasureName' then @measure_name = @text when 'Namespace' then @namespace = @text when 'Name' then @metric[:name] = @text when 'Value' then @metric[:value] = @text when 'member' then @metric[:namespace] = @namespace @metric[:measure_name] = @measure_name @result << @metric end end end end end aws-2.10.2/lib/ec2/ec2.rb0000644000175000017500000032373212511515441013330 0ustar tnnntnnn# -*- coding: utf-8 -*- # # Copyright (c) 2007-2008 RightScale Inc # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # module Aws # = Aws::EC2 -- RightScale Amazon EC2 interface # The Aws::EC2 class provides a complete interface to Amazon's # Elastic Compute Cloud service, as well as the associated EBS (Elastic Block # Store). # For explanations of the semantics # of each call, please refer to Amazon's documentation at # http://developer.amazonwebservices.com/connect/kbcategory.jspa?categoryID=87 # # Examples: # # Create an EC2 interface handle: # # @ec2 = Aws::Ec2.new(aws_access_key_id, # aws_secret_access_key) # Create a new SSH key pair: # @key = 'right_ec2_awesome_test_key' # new_key = @ec2.create_key_pair(@key) # keys = @ec2.describe_key_pairs # # Create a security group: # @group = 'right_ec2_awesome_test_security_group' # @ec2.create_security_group(@group,'My awesome test group') # group = @ec2.describe_security_groups([@group])[0] # # Configure a security group: # @ec2.authorize_security_group_named_ingress(@group, account_number, 'default') # @ec2.authorize_security_group_IP_ingress(@group, 80,80,'udp','192.168.1.0/8') # # Describe the available images: # images = @ec2.describe_images # # Launch an instance: # ec2.run_instances('ami-9a9e7bf3', 1, 1, ['default'], @key, 'SomeImportantUserData', 'public') # # # Describe running instances: # @ec2.describe_instances # # Error handling: all operations raise an Aws::AwsError in case # of problems. Note that transient errors are automatically retried. class Ec2 < AwsBase include AwsBaseInterface # Amazon EC2 API version being used API_VERSION = "2010-08-31" DEFAULT_HOST = "ec2.amazonaws.com" DEFAULT_PATH = '/' DEFAULT_PROTOCOL = 'https' DEFAULT_PORT = 443 # Default addressing type (public=NAT, direct=no-NAT) used when launching instances. DEFAULT_ADDRESSING_TYPE = 'public' DNS_ADDRESSING_SET = ['public', 'direct'] # Amazon EC2 Instance Types : http://www.amazon.com/b?ie=UTF8&node=370375011 # Default EC2 instance type (platform) DEFAULT_INSTANCE_TYPE = 'm1.small' INSTANCE_TYPES = ['t1.micro', 'm1.small', 'c1.medium', 'm1.large', 'm1.xlarge', 'c1.xlarge'] def self.connection_name :ec2_connection end @@bench = AwsBenchmarkingBlock.new def self.bench @@bench end def self.bench_xml @@bench.xml end def self.bench_ec2 @@bench.service end # Current API version (sometimes we have to check it outside the GEM). @@api = ENV['EC2_API_VERSION'] || API_VERSION def self.api @@api end # Create a new handle to an EC2 account. All handles share the same per process or per thread # HTTP connection to Amazon EC2. Each handle is for a specific account. The params have the # following options: # * :endpoint_url a fully qualified url to Amazon API endpoint (this overwrites: :server, :port, :service, :protocol and :region). Example: 'https://eu-west-1.ec2.amazonaws.com/' # * :server: EC2 service host, default: DEFAULT_HOST # * :region: EC2 region (North America by default) # * :port: EC2 service port, default: DEFAULT_PORT # * :protocol: 'http' or 'https', default: DEFAULT_PROTOCOL # * :multi_thread: true=HTTP connection per thread, false=per process # * :logger: for log messages, default: Rails.logger else STDOUT # * :signature_version: The signature version : '0' or '1'(default) # * :cache: true/false: caching for: ec2_describe_images, describe_instances, # describe_images_by_owner, describe_images_by_executable_by, describe_availability_zones, # describe_security_groups, describe_key_pairs, describe_addresses, # describe_volumes, describe_snapshots methods, default: false. # def initialize(aws_access_key_id=nil, aws_secret_access_key=nil, params={}) init({:name => 'EC2', :default_host => ENV['EC2_URL'] ? URI.parse(ENV['EC2_URL']).host : DEFAULT_HOST, :default_port => ENV['EC2_URL'] ? URI.parse(ENV['EC2_URL']).port : DEFAULT_PORT, :default_service => ENV['EC2_URL'] ? URI.parse(ENV['EC2_URL']).path : DEFAULT_PATH, :default_protocol => ENV['EC2_URL'] ? URI.parse(ENV['EC2_URL']).scheme : DEFAULT_PROTOCOL, :api_version => API_VERSION}, aws_access_key_id || ENV['AWS_ACCESS_KEY_ID'], aws_secret_access_key|| ENV['AWS_SECRET_ACCESS_KEY'], params) # EC2 doesn't really define any transient errors to retry, and in fact, # when they return a 503 it is usually for 'request limit exceeded' which # we most certainly should not retry. So let's pare down the list of # retryable errors to InternalError only (see AwsBase for the default # list) amazon_problems = ['InternalError'] end def generate_request(action, params={}) #:nodoc: service_hash = {"Action" => action, "AWSAccessKeyId" => @aws_access_key_id, "Version" => @@api} service_hash.update(params) service_params = signed_service_params(@aws_secret_access_key, service_hash, :get, @params[:server], @params[:service]) # use POST method if the length of the query string is too large if service_params.size > 2000 if signature_version == '2' # resign the request because HTTP verb is included into signature service_params = signed_service_params(@aws_secret_access_key, service_hash, :post, @params[:server], @params[:service]) end request = Net::HTTP::Post.new(@params[:service]) request.body = service_params request['Content-Type'] = 'application/x-www-form-urlencoded' else request = Net::HTTP::Get.new("#{@params[:service]}?#{service_params}") end # prepare output hash {:request => request, :server => @params[:server], :port => @params[:port], :protocol => @params[:protocol]} end # Sends request to Amazon and parses the response # Raises AwsError if any banana happened # todo: remove this and switch to using request_info2 def request_info(request, parser, options={}) #:nodoc: conn = get_conn(self.class.connection_name, @params, @logger) request_info_impl(conn, @@bench, request, parser, options) end def hash_params(prefix, list) #:nodoc: groups = {} list.each_index { |i| groups.update("#{prefix}.#{i+1}"=>list[i]) } if list return groups end def hash_params_with_suffix(prefix, suffix, list) #:nodoc: groups = {} list.each_index { |i| groups.update("#{prefix}.#{i+1}.suffix"=>list[i]) } return groups end #----------------------------------------------------------------- # Images #----------------------------------------------------------------- # params: # { 'ImageId' => ['id1', ..., 'idN'], # 'Owner' => ['self', ..., 'userN'], # 'ExecutableBy' => ['self', 'all', ..., 'userN'] # } def ec2_describe_images(params={}, image_type=nil, cache_for=nil) #:nodoc: request_hash = {} params.each do |list_by, list| request_hash.merge! hash_params(list_by, list.to_a) end if image_type request_hash['Filter.1.Name'] = "image-type" request_hash['Filter.1.Value.1'] = image_type end link = generate_request("DescribeImages", request_hash) request_cache_or_info cache_for, link, QEc2DescribeImagesParser, @@bench, cache_for rescue Exception on_exception end # Retrieve a list of images. Returns array of hashes describing the images or an exception: # +image_type+ = 'machine' || 'kernel' || 'ramdisk' # # ec2.describe_images #=> # [{:aws_owner => "522821470517", # :aws_id => "ami-e4b6538d", # :aws_state => "available", # :aws_location => "marcins_cool_public_images/ubuntu-6.10.manifest.xml", # :aws_is_public => true, # :aws_architecture => "i386", # :aws_image_type => "machine"}, # {...}, # {...} ] # # If +list+ param is set, then retrieve information about the listed images only: # # ec2.describe_images(['ami-e4b6538d']) #=> # [{:aws_owner => "522821470517", # :aws_id => "ami-e4b6538d", # :aws_state => "available", # :aws_location => "marcins_cool_public_images/ubuntu-6.10.manifest.xml", # :aws_is_public => true, # :aws_architecture => "i386", # :aws_image_type => "machine"}] # def describe_images(list=[], image_type=nil) list = list.to_a cache_for = list.empty? && !image_type ? :describe_images : nil ec2_describe_images({'ImageId' => list}, image_type, cache_for) end # # Example: # # ec2.describe_images_by_owner('522821470517') # ec2.describe_images_by_owner('self') # def describe_images_by_owner(list=['self'], image_type=nil) list = list.to_a cache_for = list==['self'] && !image_type ? :describe_images_by_owner : nil ec2_describe_images({'Owner' => list}, image_type, cache_for) end # # Example: # # ec2.describe_images_by_executable_by('522821470517') # ec2.describe_images_by_executable_by('self') # ec2.describe_images_by_executable_by('all') # def describe_images_by_executable_by(list=['self'], image_type=nil) list = list.to_a cache_for = list==['self'] && !image_type ? :describe_images_by_executable_by : nil ec2_describe_images({'ExecutableBy' => list}, image_type, cache_for) end # Register new image at Amazon. # Returns new image id or an exception. # # ec2.register_image('bucket/key/manifest') #=> 'ami-e444444d' # def register_image(image_location) link = generate_request("RegisterImage", 'ImageLocation' => image_location.to_s) request_info(link, QEc2RegisterImageParser.new(:logger => @logger)) rescue Exception on_exception end # Deregister image at Amazon. Returns +true+ or an exception. # # ec2.deregister_image('ami-e444444d') #=> true # def deregister_image(image_id) link = generate_request("DeregisterImage", 'ImageId' => image_id.to_s) request_info(link, RightBoolResponseParser.new(:logger => @logger)) rescue Exception on_exception end # Describe image attributes. Currently 'launchPermission', 'productCodes', 'kernel', 'ramdisk' and 'blockDeviceMapping' are supported. # # ec2.describe_image_attribute('ami-e444444d') #=> {:groups=>["all"], :users=>["000000000777"]} # def describe_image_attribute(image_id, attribute='launchPermission') link = generate_request("DescribeImageAttribute", 'ImageId' => image_id, 'Attribute' => attribute) request_info(link, QEc2DescribeImageAttributeParser.new(:logger => @logger)) rescue Exception on_exception end # Reset image attribute. Currently, only 'launchPermission' is supported. Returns +true+ or an exception. # # ec2.reset_image_attribute('ami-e444444d') #=> true # def reset_image_attribute(image_id, attribute='launchPermission') link = generate_request("ResetImageAttribute", 'ImageId' => image_id, 'Attribute' => attribute) request_info(link, RightBoolResponseParser.new(:logger => @logger)) rescue Exception on_exception end # Modify an image's attributes. It is recommended that you use # modify_image_launch_perm_add_users, modify_image_launch_perm_remove_users, etc. # instead of modify_image_attribute because the signature of # modify_image_attribute may change with EC2 service changes. # # operation_type : currently, only 'Add' & 'Remove' are supported. # vars: # :user_group : currently, only 'all' is supported. # :user_id # :product_code # :description def modify_image_attribute(image_id, operation_type = nil, vars = {}) params = {'ImageId' => image_id } params.update(hash_params_with_suffix("LaunchPermission.#{operation_type}", 'UserId', vars[:user_id].to_a)) if vars[:user_id] params.update(hash_params_with_suffix("LaunchPermission.#{operation_type}", 'Group', vars[:user_group].to_a)) if vars[:user_group] params.update(hash_params('ProductCode', vars[:product_code])) if vars[:product_code] params.update('Description.Value' => vars[:description].to_s) if vars[:description] link = generate_request("ModifyImageAttribute", params) request_info(link, RightBoolResponseParser.new(:logger => @logger)) rescue Exception on_exception end # Grant image launch permissions to users. # Parameter +userId+ is a list of user AWS account ids. # Returns +true+ or an exception. # # ec2.modify_image_launch_perm_add_users('ami-e444444d',['000000000777','000000000778']) #=> true def modify_image_launch_perm_add_users(image_id, user_id=[]) modify_image_attribute(image_id, 'Add', :user_id => user_id.to_a) end # Revokes image launch permissions for users. +userId+ is a list of users AWS accounts ids. Returns +true+ or an exception. # # ec2.modify_image_launch_perm_remove_users('ami-e444444d',['000000000777','000000000778']) #=> true # def modify_image_launch_perm_remove_users(image_id, user_id=[]) modify_image_attribute(image_id, 'Remove', :user_id => user_id.to_a) end # Add image launch permissions for users groups (currently only 'all' is supported, which gives public launch permissions). # Returns +true+ or an exception. # # ec2.modify_image_launch_perm_add_groups('ami-e444444d') #=> true # def modify_image_launch_perm_add_groups(image_id, user_group=['all']) modify_image_attribute(image_id, 'Add', :user_group => user_group.to_a) end # Remove image launch permissions for users groups (currently only 'all' is supported, which gives public launch permissions). # # ec2.modify_image_launch_perm_remove_groups('ami-e444444d') #=> true # def modify_image_launch_perm_remove_groups(image_id, user_group=['all']) modify_image_attribute(image_id, 'Remove', :user_group => user_group.to_a) end # Add product code to image # # ec2.modify_image_product_code('ami-e444444d','0ABCDEF') #=> true # def modify_image_product_code(image_id, product_code=[]) modify_image_attribute(image_id, nil, :product_code => product_code.to_a) end # Change image description # # ec2.modify_image_description('ami-e444444d','My new AMI') #=> true # def modify_image_description(image_id, description='') modify_image_attribute(image_id, nil, :description => description) end #----------------------------------------------------------------- # Instances #----------------------------------------------------------------- def get_desc_instances(instances) # :nodoc: result = [] instances.each do |reservation| reservation[:instances_set].each do |instance| # Parse and remove timestamp from the reason string. The timestamp is of # the request, not when EC2 took action, thus confusing & useless... instance[:aws_reason] = instance[:aws_reason].sub(/\(\d[^)]*GMT\) */, '') instance[:aws_owner] = reservation[:aws_owner] instance[:aws_reservation_id] = reservation[:aws_reservation_id] instance[:aws_groups] = reservation[:aws_groups] result << instance end end result rescue Exception on_exception end # Retrieve information about EC2 instances. If +list+ is omitted then returns the # list of all instances. # # ec2.describe_instances #=> # [{:aws_image_id => "ami-e444444d", # :aws_reason => "", # :aws_state_code => "16", # :aws_owner => "000000000888", # :aws_instance_id => "i-123f1234", # :aws_reservation_id => "r-aabbccdd", # :aws_state => "running", # :dns_name => "domU-12-34-67-89-01-C9.usma2.compute.amazonaws.com", # :ssh_key_name => "staging", # :aws_groups => ["default"], # :private_dns_name => "domU-12-34-67-89-01-C9.usma2.compute.amazonaws.com", # :aws_instance_type => "m1.small", # :aws_launch_time => "2008-1-1T00:00:00.000Z"}, # :aws_availability_zone => "us-east-1b", # :aws_kernel_id => "aki-ba3adfd3", # :aws_ramdisk_id => "ari-badbad00", # :monitoring_state => ..., # ..., {...}] # def describe_instances(list=[]) link = generate_request("DescribeInstances", hash_params('InstanceId', list.to_a)) request_cache_or_info(:describe_instances, link, QEc2DescribeInstancesParser, @@bench, list.nil? || list.empty?) do |parser| get_desc_instances(parser.result) end rescue Exception on_exception end # Return the product code attached to instance or +nil+ otherwise. # # ec2.confirm_product_instance('ami-e444444d','12345678') #=> nil # ec2.confirm_product_instance('ami-e444444d','00001111') #=> "000000000888" # def confirm_product_instance(instance, product_code) link = generate_request("ConfirmProductInstance", {'ProductCode' => product_code, 'InstanceId' => instance}) request_info(link, QEc2ConfirmProductInstanceParser.new(:logger => @logger)) end # DEPRECATED, USE launch_instances instead. # # Launch new EC2 instances. Returns a list of launched instances or an exception. # # ec2.run_instances('ami-e444444d',1,1,['my_awesome_group'],'my_awesome_key', 'Woohoo!!!', 'public') #=> # [{:aws_image_id => "ami-e444444d", # :aws_reason => "", # :aws_state_code => "0", # :aws_owner => "000000000888", # :aws_instance_id => "i-123f1234", # :aws_reservation_id => "r-aabbccdd", # :aws_state => "pending", # :dns_name => "", # :ssh_key_name => "my_awesome_key", # :aws_groups => ["my_awesome_group"], # :private_dns_name => "", # :aws_instance_type => "m1.small", # :aws_launch_time => "2008-1-1T00:00:00.000Z" # :aws_ramdisk_id => "ari-8605e0ef" # :aws_kernel_id => "aki-9905e0f0", # :ami_launch_index => "0", # :aws_availability_zone => "us-east-1b" # }] # def run_instances(image_id, min_count, max_count, group_ids, key_name, user_data='', addressing_type = nil, instance_type = nil, kernel_id = nil, ramdisk_id = nil, availability_zone = nil, block_device_mappings = nil) launch_instances(image_id, {:min_count => min_count, :max_count => max_count, :user_data => user_data, :group_ids => group_ids, :key_name => key_name, :instance_type => instance_type, :addressing_type => addressing_type, :kernel_id => kernel_id, :ramdisk_id => ramdisk_id, :availability_zone => availability_zone, :block_device_mappings => block_device_mappings }) end # Launch new EC2 instances. Returns a list of launched instances or an exception. # # +lparams+ keys (default values in parenthesis): # :min_count fixnum, (1) # :max_count fixnum, (1) # :group_ids array or string ([] == 'default') # :instance_type string (DEFAULT_INSTACE_TYPE) # :addressing_type string (DEFAULT_ADDRESSING_TYPE # :key_name string # :kernel_id string # :ramdisk_id string # :availability_zone string # :block_device_mappings string # :user_data string # :monitoring_enabled boolean (default=false) # # ec2.launch_instances('ami-e444444d', :group_ids => 'my_awesome_group', # :user_data => "Woohoo!!!", # :addressing_type => "public", # :key_name => "my_awesome_key", # :availability_zone => "us-east-1c") #=> # [{:aws_image_id => "ami-e444444d", # :aws_reason => "", # :aws_state_code => "0", # :aws_owner => "000000000888", # :aws_instance_id => "i-123f1234", # :aws_reservation_id => "r-aabbccdd", # :aws_state => "pending", # :dns_name => "", # :ssh_key_name => "my_awesome_key", # :aws_groups => ["my_awesome_group"], # :private_dns_name => "", # :aws_instance_type => "m1.small", # :aws_launch_time => "2008-1-1T00:00:00.000Z", # :aws_ramdisk_id => "ari-8605e0ef" # :aws_kernel_id => "aki-9905e0f0", # :ami_launch_index => "0", # :aws_availability_zone => "us-east-1c" # }] # def launch_instances(image_id, options={}) @logger.info("Launching instance of image #{image_id}, " + "key: #{options[:key_name]}, groups: #{(options[:group_ids]).to_a.join(',')}") # careful: keyName and securityGroups may be nil params = hash_params('SecurityGroup', options[:group_ids].to_a) params.update({'ImageId' => image_id, 'MinCount' => (options[:min_count] || 1).to_s, 'MaxCount' => (options[:max_count] || 1).to_s, 'AddressingType' => options[:addressing_type] || DEFAULT_ADDRESSING_TYPE, 'InstanceType' => options[:instance_type] || DEFAULT_INSTANCE_TYPE}) # optional params params.merge!(hash_params('SecurityGroupId',options[:SecurityGroupId].to_a)) unless Aws::Utils.blank?(options[:SecurityGroupId]) params['KeyName'] = options[:key_name] unless Aws::Utils.blank?(options[:key_name]) params['KernelId'] = options[:kernel_id] unless Aws::Utils.blank?(options[:kernel_id]) params['RamdiskId'] = options[:ramdisk_id] unless Aws::Utils.blank?(options[:ramdisk_id]) params['Placement.AvailabilityZone'] = options[:availability_zone] unless Aws::Utils.blank?(options[:availability_zone]) params['BlockDeviceMappings'] = options[:block_device_mappings] unless Aws::Utils.blank?(options[:block_device_mappings]) params['Monitoring.Enabled'] = options[:monitoring_enabled] unless Aws::Utils.blank?(options[:monitoring_enabled]) params['SubnetId'] = options[:subnet_id] unless Aws::Utils.blank?(options[:subnet_id]) params['AdditionalInfo'] = options[:additional_info] unless Aws::Utils.blank?(options[:additional_info]) params['DisableApiTermination'] = options[:disable_api_termination].to_s unless options[:disable_api_termination].nil? params['InstanceInitiatedShutdownBehavior'] = options[:instance_initiated_shutdown_behavior] unless Aws::Utils.blank?(options[:instance_initiated_shutdown_behavior]) unless Aws::Utils.blank?(options[:user_data]) options[:user_data].strip! # Do not use CGI::escape(encode64(...)) as it is done in Amazons EC2 library. # Amazon 169.254.169.254 does not like escaped symbols! # And it doesn't like "\n" inside of encoded string! Grrr.... # Otherwise, some of UserData symbols will be lost... params['UserData'] = Base64.encode64(options[:user_data]).delete("\n").strip unless Aws::Utils.blank?(options[:user_data]) end unless Aws::Utils.blank?(options[:block_device_mappings]) options[:block_device_mappings].size.times do |n| if options[:block_device_mappings][n][:virtual_name] params["BlockDeviceMapping.#{n+1}.VirtualName"] = options[:block_device_mappings][n][:virtual_name] end if options[:block_device_mappings][n][:device_name] params["BlockDeviceMapping.#{n+1}.DeviceName"] = options[:block_device_mappings][n][:device_name] end if options[:block_device_mappings][n][:ebs_snapshot_id] params["BlockDeviceMapping.#{n+1}.Ebs.SnapshotId"] = options[:block_device_mappings][n][:ebs_snapshot_id] end end end link = generate_request("RunInstances", params) #debugger instances = request_info(link, QEc2DescribeInstancesParser.new(:logger => @logger)) get_desc_instances(instances) rescue Exception on_exception end def monitor_instances(list=[]) link = generate_request("MonitorInstances", hash_params('InstanceId', list.to_a)) request_info(link, QEc2TerminateInstancesParser.new(:logger => @logger)) rescue Exception on_exception end # Terminates EC2 instances. Returns a list of termination params or an exception. # # ec2.terminate_instances(['i-f222222d','i-f222222e']) #=> # [{:aws_shutdown_state => "shutting-down", # :aws_instance_id => "i-f222222d", # :aws_shutdown_state_code => 32, # :aws_prev_state => "running", # :aws_prev_state_code => 16}, # {:aws_shutdown_state => "shutting-down", # :aws_instance_id => "i-f222222e", # :aws_shutdown_state_code => 32, # :aws_prev_state => "running", # :aws_prev_state_code => 16}] # def terminate_instances(list=[]) link = generate_request("TerminateInstances", hash_params('InstanceId', list.to_a)) request_info(link, QEc2TerminateInstancesParser.new(:logger => @logger)) rescue Exception on_exception end # Stop EBS-backed EC2 instances. Returns a list of instance state changes or an exception. # # ec2.stop_instances(['i-f222222d', 'i-f222222e']) #=> # [{:aws_instance_id => "i-f222222d", # :aws_current_state_code => 64, # :aws_current_state => "stopping", # :aws_prev_state_code => 16, # :aws_prev_state => "running"}, # {:aws_instance_id => "i-f222222e", # :aws_current_state_code => 64, # :aws_current_state => "stopping", # :aws_prev_state_code => 16, # :aws_prev_state => "running"}] # def stop_instances(list=[]) link = generate_request("StopInstances", hash_params('InstanceId', list.to_a)) request_info(link, QEc2StopInstancesParser.new(:logger => @logger)) rescue Exception on_exception end # Start EBS-backed EC2 instances. Returns a list of instance state changes or an exception. # # ec2.start_instances(['i-f222222d', 'i-f222222e']) #=> # [{:aws_instance_id => "i-f222222d", # :aws_current_state_code => 0, # :aws_current_state => "pending", # :aws_prev_state_code => 80, # :aws_prev_state => "stopped"}, # {:aws_instance_id => "i-f222222e", # :aws_current_state_code => 0, # :aws_current_state => "pending", # :aws_prev_state_code => 80, # :aws_prev_state => "stopped"}] # def start_instances(list=[]) link = generate_request("StartInstances", hash_params('InstanceId', list.to_a)) request_info(link, QEc2StartInstancesParser.new(:logger => @logger)) rescue Exception on_exception end # Retreive EC2 instance OS logs. Returns a hash of data or an exception. # # ec2.get_console_output('i-f222222d') => # {:aws_instance_id => 'i-f222222d', # :aws_timestamp => "2007-05-23T14:36:07.000-07:00", # :timestamp => Wed May 23 21:36:07 UTC 2007, # Time instance # :aws_output => "Linux version 2.6.16-xenU (builder@patchbat.amazonsa) (gcc version 4.0.1 20050727 ..." def get_console_output(instance_id) link = generate_request("GetConsoleOutput", {'InstanceId.1' => instance_id}) request_info(link, QEc2GetConsoleOutputParser.new(:logger => @logger)) rescue Exception on_exception end # Reboot an EC2 instance. Returns +true+ or an exception. # # ec2.reboot_instances(['i-f222222d','i-f222222e']) #=> true # def reboot_instances(list) link = generate_request("RebootInstances", hash_params('InstanceId', list.to_a)) request_info(link, RightBoolResponseParser.new(:logger => @logger)) rescue Exception on_exception end #----------------------------------------------------------------- # Instances: Windows addons #----------------------------------------------------------------- # Get initial Windows Server setup password from an instance console output. # # my_awesome_key = ec2.create_key_pair('my_awesome_key') #=> # {:aws_key_name => "my_awesome_key", # :aws_fingerprint => "01:02:03:f4:25:e6:97:e8:9b:02:1a:26:32:4e:58:6b:7a:8c:9f:03", # :aws_material => "-----BEGIN RSA PRIVATE KEY-----\nMIIEpQIBAAK...Q8MDrCbuQ=\n-----END RSA PRIVATE KEY-----"} # # my_awesome_instance = ec2.run_instances('ami-a000000a',1,1,['my_awesome_group'],'my_awesome_key', 'WindowsInstance!!!') #=> # [{:aws_image_id => "ami-a000000a", # :aws_instance_id => "i-12345678", # ... # :aws_availability_zone => "us-east-1b" # }] # # # wait until instance enters 'operational' state and get it's initial password # # puts ec2.get_initial_password(my_awesome_instance[:aws_instance_id], my_awesome_key[:aws_material]) #=> "MhjWcgZuY6" # def get_initial_password(instance_id, private_key) console_output = get_console_output(instance_id) crypted_password = console_output[:aws_output][%r{(.+)}m] && $1 unless crypted_password raise AwsError.new("Initial password was not found in console output for #{instance_id}") else OpenSSL::PKey::RSA.new(private_key).private_decrypt(Base64.decode64(crypted_password)) end rescue Exception on_exception end # Bundle a Windows image. # Internally, it queues the bundling task and shuts down the instance. # It then takes a snapshot of the Windows volume bundles it, and uploads it to # S3. After bundling completes, Aws::Ec2#register_image may be used to # register the new Windows AMI for subsequent launches. # # ec2.bundle_instance('i-e3e24e8a', 'my-awesome-bucket', 'my-win-image-1') #=> # [{:aws_update_time => "2008-10-16T13:58:25.000Z", # :s3_bucket => "kd-win-1", # :s3_prefix => "win2pr", # :aws_state => "pending", # :aws_id => "bun-26a7424f", # :aws_instance_id => "i-878a25ee", # :aws_start_time => "2008-10-16T13:58:02.000Z"}] # def bundle_instance(instance_id, s3_bucket, s3_prefix, s3_owner_aws_access_key_id=nil, s3_owner_aws_secret_access_key=nil, s3_expires = S3Interface::DEFAULT_EXPIRES_AFTER, s3_upload_policy='ec2-bundle-read') # S3 access and signatures s3_owner_aws_access_key_id ||= @aws_access_key_id s3_owner_aws_secret_access_key ||= @aws_secret_access_key s3_expires = Time.now.utc + s3_expires if s3_expires.is_a?(Fixnum) && (s3_expires < S3Interface::ONE_YEAR_IN_SECONDS) # policy policy = {'expiration' => s3_expires.strftime('%Y-%m-%dT%H:%M:%SZ'), 'conditions' => [{'bucket' => s3_bucket}, {'acl' => s3_upload_policy}, ['starts-with', '$key', s3_prefix]]}.to_json policy64 = Base64.encode64(policy).gsub("\n", "") signed_policy64 = Utils.sign(s3_owner_aws_secret_access_key, policy64) # fill request params params = {'InstanceId' => instance_id, 'Storage.S3.AWSAccessKeyId' => s3_owner_aws_access_key_id, 'Storage.S3.UploadPolicy' => policy64, 'Storage.S3.UploadPolicySignature' => signed_policy64, 'Storage.S3.Bucket' => s3_bucket, 'Storage.S3.Prefix' => s3_prefix, } link = generate_request("BundleInstance", params) request_info(link, QEc2BundleInstanceParser.new) rescue Exception on_exception end # Describe the status of the Windows AMI bundlings. # If +list+ is omitted the returns the whole list of tasks. # # ec2.describe_bundle_tasks(['bun-4fa74226']) #=> # [{:s3_bucket => "my-awesome-bucket" # :aws_id => "bun-0fa70206", # :s3_prefix => "win1pr", # :aws_start_time => "2008-10-14T16:27:57.000Z", # :aws_update_time => "2008-10-14T16:37:10.000Z", # :aws_error_code => "Client.S3Error", # :aws_error_message => # "AccessDenied(403)- Invalid according to Policy: Policy Condition failed: [\"eq\", \"$acl\", \"aws-exec-read\"]", # :aws_state => "failed", # :aws_instance_id => "i-e3e24e8a"}] # def describe_bundle_tasks(list=[]) link = generate_request("DescribeBundleTasks", hash_params('BundleId', list.to_a)) request_info(link, QEc2DescribeBundleTasksParser.new) rescue Exception on_exception end # Cancel an in‐progress or pending bundle task by id. # # ec2.cancel_bundle_task('bun-73a7421a') #=> # [{:s3_bucket => "my-awesome-bucket" # :aws_id => "bun-0fa70206", # :s3_prefix => "win02", # :aws_start_time => "2008-10-14T13:00:29.000Z", # :aws_error_message => "User has requested bundling operation cancellation", # :aws_state => "failed", # :aws_update_time => "2008-10-14T13:01:31.000Z", # :aws_error_code => "Client.Cancelled", # :aws_instance_id => "i-e3e24e8a"} # def cancel_bundle_task(bundle_id) link = generate_request("CancelBundleTask", {'BundleId' => bundle_id}) request_info(link, QEc2BundleInstanceParser.new) rescue Exception on_exception end #----------------------------------------------------------------- # Security groups #----------------------------------------------------------------- # Retrieve Security Group information. If +list+ is omitted the returns the whole list of groups. # # ec2.describe_security_groups #=> # [{:aws_group_name => "default-1", # :aws_owner => "000000000888", # :aws_description => "a default security group", # :aws_perms => # [ {:protocol => "tcp", :from_port=>"1000", :to_port=>"2000", # :ip_ranges=>[{cidr_ip=>"10.1.2.3/32"}, {cidr_ip=>"192.168.1.10/24"}], # :groups => [{:owner=>"123456789012", :group_name="default"}] }, # # {:protocol ="icmp", :from_port="-1", :to_port=>"-1", # :ip_ranges=>[{:cidr_ip=>"0.0.0.0/0"}], # :groups=>[] }, # # {:protocol=>"udp", :from_port=>"0", :to_port=>"65535", # :ip_ranges=>[], # :groups=>[{:owner=>"123456789012", :group_name=>"newgroup"}, {:owner=>"123456789012", :group_name=>"default"}], # # {:protocol=>"tcp", :from_port="22", :to_port=>"22", # :ip_ranges=>[{:cidr_ip=>"0.0.0.0/0"}], # :groups=>[{:owner=>"", :group_name=>"default"}] }, # # ..., {...} # ] # def describe_security_groups(list=[]) link = generate_request("DescribeSecurityGroups", hash_params('GroupName', list.to_a)) request_cache_or_info(:describe_security_groups, link, QEc2DescribeSecurityGroupsParser, @@bench, list.nil? || list.empty?) do |parser| result = [] parser.result.each do |item| perms = [] item.ipPermissions.each do |perm| current = {:from_port => perm.fromPort, :to_port => perm.toPort, :protocol => perm.ipProtocol, :groups => [], :ip_ranges => []} perm.groups.each do |ngroup| current[:groups] << {:group_name => ngroup.groupName, :owner => ngroup.userId} end perm.ipRanges.each do |cidr_ip| current[:ip_ranges] << {:cidr_ip => cidr_ip.cidrIp} end perms << current end result << {:aws_owner => item.ownerId, :aws_group_name => item.groupName, :aws_description => item.groupDescription, :aws_perms => perms} end result end rescue Exception on_exception end # Create new Security Group. Returns +true+ or an exception. # # ec2.create_security_group('default-1',"Default allowing SSH, HTTP, and HTTPS ingress") #=> true # def create_security_group(name, description) # EC2 doesn't like an empty description... description = " " if Aws::Utils.blank?(description) link = generate_request("CreateSecurityGroup", 'GroupName' => name.to_s, 'GroupDescription' => description.to_s) request_info(link, RightBoolResponseParser.new(:logger => @logger)) rescue Exception on_exception end # Remove Security Group. Returns +true+ or an exception. # # ec2.delete_security_group('default-1') #=> true # def delete_security_group(name) link = generate_request("DeleteSecurityGroup", 'GroupName' => name.to_s) request_info(link, RightBoolResponseParser.new(:logger => @logger)) rescue Exception on_exception end # Authorize OR Revoke ingress for security group, depending on the value of the 'action' parameter. # If you 'authorize' then you allow instances that are member of some other # security groups, or some range of ip addresses to open connections to instances in # my group. Can specify an array of ip addresses, source groups or mix of both in a single rule: # # ec2.manage_security_group_ingress('authorize', 'new_firewall', 80, 80, 'tcp', ['192.168.0.1/32', '10.0.0.1/24'], # [{'group_name'=>'default', 'owner'=>'297467797945'}, {'group_name'=>'test', 'owner'=>'123456789012'}]) # # ec2.manage_security_group_ingress('new_firewall', 0, 1000, 'udp', 'revoke', [], # [{'group_name'=>'default', 'owner'=>'123456789012'}]) # # ec2.manage_security_group_ingress('new_firewall', 0, 1000, 'udp', 'authorize', ['0.0.0.0/0']) # # Similarly, if you specify 'revoke' as the action parameter then you will remove the specified # source ip addresses or source groups from access to instances in the named group: # def manage_security_group_ingress(name, from_port, to_port, protocol, action, source_ip_ranges, source_groups = []) call_params = { 'GroupName' => name.to_s, 'IpPermissions.1.IpProtocol' => protocol.to_s, 'IpPermissions.1.FromPort' => from_port.to_s, 'IpPermissions.1.ToPort' => to_port.to_s } source_ip_ranges.each_index do |i| call_params.merge!({"IpPermissions.1.IpRanges.#{i+1}.CidrIp" => source_ip_ranges[i].to_s}) end source_groups.each_index do |i| call_params.merge!({"IpPermissions.1.Groups.#{i+1}.GroupName" => source_groups[i]['group_name'].to_s, "IpPermissions.1.Groups.#{i+1}.UserId"=> source_groups[i]['owner'].to_s.gsub(/-/,'')}) end unless ['Authorize', 'Revoke'].include?(action.capitalize) raise AwsError.new("Invalid action #{action} - must be one of \'Authorize\' or \'Revoke\'") end link = generate_request("#{action.capitalize}SecurityGroupIngress", call_params) request_info(link, RightBoolResponseParser.new(:logger => @logger)) rescue Exception on_exception end # Authorize named ingress for security group. Allows instances that are member of someone # else's security group to open connections to instances in my group. # # ec2.authorize_security_group_named_ingress('my_awesome_group', '7011-0219-8268', 'their_group_name') #=> true # def authorize_security_group_named_ingress(name, owner, group) link = generate_request("AuthorizeSecurityGroupIngress", 'GroupName' => name.to_s, 'SourceSecurityGroupName' => group.to_s, 'SourceSecurityGroupOwnerId' => owner.to_s.gsub(/-/, '')) request_info(link, RightBoolResponseParser.new(:logger => @logger)) rescue Exception on_exception end # Revoke named ingress for security group. # # ec2.revoke_security_group_named_ingress('my_awesome_group', aws_user_id, 'another_group_name') #=> true # def revoke_security_group_named_ingress(name, owner, group) link = generate_request("RevokeSecurityGroupIngress", 'GroupName' => name.to_s, 'SourceSecurityGroupName' => group.to_s, 'SourceSecurityGroupOwnerId' => owner.to_s.gsub(/-/, '')) request_info(link, RightBoolResponseParser.new(:logger => @logger)) rescue Exception on_exception end # Add permission to a security group. Returns +true+ or an exception. +protocol+ is one of :'tcp'|'udp'|'icmp'. # # ec2.authorize_security_group_IP_ingress('my_awesome_group', 80, 82, 'udp', '192.168.1.0/8') #=> true # ec2.authorize_security_group_IP_ingress('my_awesome_group', -1, -1, 'icmp') #=> true # def authorize_security_group_IP_ingress(name, from_port, to_port, protocol='tcp', cidr_ip='0.0.0.0/0') link = generate_request("AuthorizeSecurityGroupIngress", 'GroupName' => name.to_s, 'IpProtocol' => protocol.to_s, 'FromPort' => from_port.to_s, 'ToPort' => to_port.to_s, 'CidrIp' => cidr_ip.to_s) request_info(link, RightBoolResponseParser.new(:logger => @logger)) rescue Exception on_exception end # Remove permission from a security group. Returns +true+ or an exception. +protocol+ is one of :'tcp'|'udp'|'icmp' ('tcp' is default). # # ec2.revoke_security_group_IP_ingress('my_awesome_group', 80, 82, 'udp', '192.168.1.0/8') #=> true # def revoke_security_group_IP_ingress(name, from_port, to_port, protocol='tcp', cidr_ip='0.0.0.0/0') link = generate_request("RevokeSecurityGroupIngress", 'GroupName' => name.to_s, 'IpProtocol' => protocol.to_s, 'FromPort' => from_port.to_s, 'ToPort' => to_port.to_s, 'CidrIp' => cidr_ip.to_s) request_info(link, RightBoolResponseParser.new(:logger => @logger)) rescue Exception on_exception end #----------------------------------------------------------------- # Keys #----------------------------------------------------------------- # Retrieve a list of SSH keys. Returns an array of keys or an exception. Each key is # represented as a two-element hash. # # ec2.describe_key_pairs #=> # [{:aws_fingerprint=> "01:02:03:f4:25:e6:97:e8:9b:02:1a:26:32:4e:58:6b:7a:8c:9f:03", :aws_key_name=>"key-1"}, # {:aws_fingerprint=> "1e:29:30:47:58:6d:7b:8c:9f:08:11:20:3c:44:52:69:74:80:97:08", :aws_key_name=>"key-2"}, # ..., {...} ] # def describe_key_pairs(list=[]) link = generate_request("DescribeKeyPairs", hash_params('KeyName', list.to_a)) request_cache_or_info :describe_key_pairs, link, QEc2DescribeKeyPairParser, @@bench, list.nil? || list.empty? rescue Exception on_exception end # Create new SSH key. Returns a hash of the key's data or an exception. # # ec2.create_key_pair('my_awesome_key') #=> # {:aws_key_name => "my_awesome_key", # :aws_fingerprint => "01:02:03:f4:25:e6:97:e8:9b:02:1a:26:32:4e:58:6b:7a:8c:9f:03", # :aws_material => "-----BEGIN RSA PRIVATE KEY-----\nMIIEpQIBAAK...Q8MDrCbuQ=\n-----END RSA PRIVATE KEY-----"} # def create_key_pair(name) link = generate_request("CreateKeyPair", 'KeyName' => name.to_s) request_info(link, QEc2CreateKeyPairParser.new(:logger => @logger)) rescue Exception on_exception end # Import existing SSH key. Returns a hash of the key's data or an exception. # # ec2.import_key_pair('my_awesome_key', # 'ssh-rsa AABB3NzaC1yc2FSAAADAQABAAABAQCntvz2Cpx8EE4lBRjQKOtwNaGeJXjgnFLaxnQH4HB+dRTinjlew+153KLCjAMbbanD9Wym/b1FfSHywP299RdTPpBZ2QD7Hh7qKp8penGszFQbaSewYQTBP9Htjn7NDg3VeVcIx0LP3lmp4ZNnYDZGLKCGJJ+ldT/cljW3FAA2/xwco98BujLlKUcU/BZlZ4zvESM3S0gF3pgOjuz2UKAEbsbuuaEQP88NZ/GXXIUgGNFoJSpxDrNCHA7pap/3gdyPq3zTkt4YK/bSxSJ24FMfYtehB36V9rqV8RsIro+yrzRBW4XcA976OKQbh5pS75rp herp@derp') # #=> # {:aws_key_name => "my_awesome_key", # :aws_fingerprint => "01:02:03:f4:25:e6:97:e8:9b:02:1a:26:32:4e:58:6b:7a:8c:9f:03"} # def import_key_pair(name, public_key) link = generate_request("ImportKeyPair", "KeyName" => name.to_s, "PublicKeyMaterial"=>Base64.encode64(public_key.to_s)) request_info(link, QEc2ImportPublicKeyParser.new(:logger => @logger)) rescue Exception on_exception end # Delete a key pair. Returns +true+ or an exception. # # ec2.delete_key_pair('my_awesome_key') #=> true # def delete_key_pair(name) link = generate_request("DeleteKeyPair", 'KeyName' => name.to_s) request_info(link, RightBoolResponseParser.new(:logger => @logger)) rescue Exception on_exception end #----------------------------------------------------------------- # Elastic IPs #----------------------------------------------------------------- # Acquire a new elastic IP address for use with your account. # Returns allocated IP address or an exception. # # ec2.allocate_address #=> '75.101.154.140' # def allocate_address link = generate_request("AllocateAddress") request_info(link, QEc2AllocateAddressParser.new(:logger => @logger)) rescue Exception on_exception end # Associate an elastic IP address with an instance. # Returns +true+ or an exception. # # ec2.associate_address('i-d630cbbf', '75.101.154.140') #=> true # def associate_address(instance_id, public_ip) link = generate_request("AssociateAddress", "InstanceId" => instance_id.to_s, "PublicIp" => public_ip.to_s) request_info(link, RightBoolResponseParser.new(:logger => @logger)) rescue Exception on_exception end # List elastic IP addresses assigned to your account. # Returns an array of 2 keys (:instance_id and :public_ip) hashes: # # ec2.describe_addresses #=> [{:instance_id=>"i-d630cbbf", :public_ip=>"75.101.154.140"}, # {:instance_id=>nil, :public_ip=>"75.101.154.141"}] # # ec2.describe_addresses('75.101.154.140') #=> [{:instance_id=>"i-d630cbbf", :public_ip=>"75.101.154.140"}] # def describe_addresses(list=[]) link = generate_request("DescribeAddresses", hash_params('PublicIp', list.to_a)) request_cache_or_info :describe_addresses, link, QEc2DescribeAddressesParser, @@bench, list.nil? || list.empty? rescue Exception on_exception end # Disassociate the specified elastic IP address from the instance to which it is assigned. # Returns +true+ or an exception. # # ec2.disassociate_address('75.101.154.140') #=> true # def disassociate_address(public_ip) link = generate_request("DisassociateAddress", "PublicIp" => public_ip.to_s) request_info(link, RightBoolResponseParser.new(:logger => @logger)) rescue Exception on_exception end # Release an elastic IP address associated with your account. # Returns +true+ or an exception. # # ec2.release_address('75.101.154.140') #=> true # def release_address(public_ip) link = generate_request("ReleaseAddress", "PublicIp" => public_ip.to_s) request_info(link, RightBoolResponseParser.new(:logger => @logger)) rescue Exception on_exception end #----------------------------------------------------------------- # Availability zones #----------------------------------------------------------------- # Describes availability zones that are currently available to the account and their states. # Returns an array of 2 keys (:zone_name and :zone_state) hashes: # # ec2.describe_availability_zones #=> [{:region_name=>"us-east-1", # :zone_name=>"us-east-1a", # :zone_state=>"available"}, ... ] # # ec2.describe_availability_zones('us-east-1c') #=> [{:region_name=>"us-east-1", # :zone_state=>"available", # :zone_name=>"us-east-1c"}] # def describe_availability_zones(list=[]) link = generate_request("DescribeAvailabilityZones", hash_params('ZoneName', list.to_a)) request_cache_or_info :describe_availability_zones, link, QEc2DescribeAvailabilityZonesParser, @@bench, list.nil? || list.empty? rescue Exception on_exception end # This is using the new way, but not sure it's backwrads compatible def describe_availability_zones2(options={}) link = generate_request("DescribeAvailabilityZones", options={}) request_info_xml_simple(self.class.connection_name, @params, link, @logger, :group_tags =>{"DBInstances" =>"DBInstance", "DBParameterGroups"=>"DBParameterGroup", "DBSecurityGroups" =>"DBSecurityGroup", "EC2SecurityGroups"=>"EC2SecurityGroup", "IPRanges" =>"IPRange"}, :force_array =>["DBInstances", "DBParameterGroups", "DBSecurityGroups", "EC2SecurityGroups", "IPRanges"], :pull_out_array =>options[:pull_out_array], :pull_out_single=>options[:pull_out_single], :wrapper =>options[:wrapper]) rescue Exception on_exception end #----------------------------------------------------------------- # Regions #----------------------------------------------------------------- # Describe regions. # # ec2.describe_regions #=> ["eu-west-1", "us-east-1"] # def describe_regions(list=[]) link = generate_request("DescribeRegions", hash_params('RegionName', list.to_a)) request_cache_or_info :describe_regions, link, QEc2DescribeRegionsParser, @@bench, list.nil? || list.empty? rescue Exception on_exception end #----------------------------------------------------------------- # EBS: Volumes #----------------------------------------------------------------- # Describe all EBS volumes. # # ec2.describe_volumes #=> # [{:aws_size => 94, # :aws_device => "/dev/sdc", # :aws_attachment_status => "attached", # :zone => "merlot", # :snapshot_id => nil, # :aws_attached_at => Wed Jun 18 08:19:28 UTC 2008, # :aws_status => "in-use", # :aws_id => "vol-60957009", # :aws_created_at => Wed Jun 18 08:19:20s UTC 2008, # :aws_instance_id => "i-c014c0a9"}, # {:aws_size => 1, # :zone => "merlot", # :snapshot_id => nil, # :aws_status => "available", # :aws_id => "vol-58957031", # :aws_created_at => Wed Jun 18 08:19:21 UTC 2008,}, ... ] # def describe_volumes(list=[]) link = generate_request("DescribeVolumes", hash_params('VolumeId', list.to_a)) request_cache_or_info :describe_volumes, link, QEc2DescribeVolumesParser, @@bench, list.nil? || list.empty? rescue Exception on_exception end # Create new EBS volume based on previously created snapshot. # +Size+ in Gigabytes. # # ec2.create_volume('snap-000000', 10, zone) #=> # {:snapshot_id => "snap-e21df98b", # :aws_status => "creating", # :aws_id => "vol-fc9f7a95", # :zone => "merlot", # :aws_created_at => Tue Jun 24 18:13:32 UTC 2008, # :aws_size => 94} # def create_volume(snapshot_id, size, zone) params = {'Size' => size.to_s, 'AvailabilityZone' => zone.to_s} params['SnapshotId'] = snapshot_id if snapshot_id && snapshot_id.length > 0 # snapshotId is conditional link = generate_request("CreateVolume", params) request_info(link, QEc2CreateVolumeParser.new(:logger => @logger)) rescue Exception on_exception end # Delete the specified EBS volume. # This does not deletes any snapshots created from this volume. # # ec2.delete_volume('vol-b48a6fdd') #=> true # def delete_volume(volume_id) link = generate_request("DeleteVolume", "VolumeId" => volume_id.to_s) request_info(link, RightBoolResponseParser.new(:logger => @logger)) rescue Exception on_exception end # Creates an Amazon EBS-backed AMI from an Amazon EBS-backed instance # Instance must be either the running or stopped state # # ec2.create_image('i-4jhdmaw', 'New image') # def create_image(instance_id, name, description="") link = generate_request("CreateImage", "InstanceId" => instance_id, "Name" => name, "Description" => description) request_info(link, QEc2CreateImageParser.new(:logger => @logger)) rescue on_exception end # Attach the specified EBS volume to a specified instance, exposing the # volume using the specified device name. # # ec2.attach_volume('vol-898a6fe0', 'i-7c905415', '/dev/sdh') #=> # { :aws_instance_id => "i-7c905415", # :aws_device => "/dev/sdh", # :aws_status => "attaching", # :aws_attached_at => "2008-03-28T14:14:39.000Z", # :aws_id => "vol-898a6fe0" } # def attach_volume(volume_id, instance_id, device) link = generate_request("AttachVolume", "VolumeId" => volume_id.to_s, "InstanceId" => instance_id.to_s, "Device" => device.to_s) request_info(link, QEc2AttachAndDetachVolumeParser.new(:logger => @logger)) rescue Exception on_exception end # Detach the specified EBS volume from the instance to which it is attached. # # ec2.detach_volume('vol-898a6fe0') #=> # { :aws_instance_id => "i-7c905415", # :aws_device => "/dev/sdh", # :aws_status => "detaching", # :aws_attached_at => "2008-03-28T14:38:34.000Z", # :aws_id => "vol-898a6fe0"} # def detach_volume(volume_id, instance_id=nil, device=nil, force=nil) hash = {"VolumeId" => volume_id.to_s} hash["InstanceId"] = instance_id.to_s unless Aws::Utils.blank?(instance_id) hash["Device"] = device.to_s unless Aws::Utils.blank?(device) hash["Force"] = 'true' if force # link = generate_request("DetachVolume", hash) request_info(link, QEc2AttachAndDetachVolumeParser.new(:logger => @logger)) rescue Exception on_exception end #----------------------------------------------------------------- # EBS: Snapshots #----------------------------------------------------------------- # Describe all EBS snapshots. # # ec2.describe_snapshots #=> # [ { :aws_progress => "100%", # :aws_status => "completed", # :aws_id => "snap-72a5401b", # :aws_volume_id => "vol-5582673c", # :aws_started_at => "2008-02-23T02:50:48.000Z"}, # { :aws_progress => "100%", # :aws_status => "completed", # :aws_id => "snap-75a5401c", # :aws_volume_id => "vol-5582673c", # :aws_started_at => "2008-02-23T16:23:19.000Z" },...] # def describe_snapshots(list=[]) link = generate_request("DescribeSnapshots", hash_params('SnapshotId', list.to_a)) request_cache_or_info :describe_snapshots, link, QEc2DescribeSnapshotsParser, @@bench, list.nil? || list.empty? rescue Exception on_exception end def describe_owned_snapshots(list=[]) params = {"Owner" => "self"} snap_ids = hash_params('SnapshotId', list.to_a) params.update(snap_ids) link = generate_request("DescribeSnapshots", params) request_cache_or_info :describe_owned_snapshots, link, QEc2DescribeSnapshotsParser, @@bench, list.nil? || list.empty? rescue Exception on_exception end # Create a snapshot of specified volume. # # ec2.create_snapshot('vol-898a6fe0') #=> # {:aws_volume_id => "vol-fd9f7a94", # :aws_started_at => Tue Jun 24 18:40:40 UTC 2008, # :aws_progress => "", # :aws_status => "pending", # :aws_id => "snap-d56783bc"} # def create_snapshot(volume_id, options={}) link = generate_request("CreateSnapshot", options.merge({"VolumeId" => volume_id.to_s})) request_info(link, QEc2CreateSnapshotParser.new(:logger => @logger)) rescue Exception on_exception end # Create a snapshot of specified volume, but with the normal retry algorithms disabled. # This method will return immediately upon error. The user can specify connect and read timeouts (in s) # for the connection to AWS. If the user does not specify timeouts, try_create_snapshot uses the default values # in Rightscale::HttpConnection. # # ec2.try_create_snapshot('vol-898a6fe0') #=> # {:aws_volume_id => "vol-fd9f7a94", # :aws_started_at => Tue Jun 24 18:40:40 UTC 2008, # :aws_progress => "", # :aws_status => "pending", # :aws_id => "snap-d56783bc"} # def try_create_snapshot(volume_id, connect_timeout = nil, read_timeout = nil) # For safety in the ensure block...we don't want to restore values # if we never read them in the first place orig_reiteration_time = nil orig_http_params = nil orig_reiteration_time = Aws::AWSErrorHandler::reiteration_time Aws::AWSErrorHandler::reiteration_time = 0 orig_http_params = Rightscale::HttpConnection::params() new_http_params = orig_http_params.dup new_http_params[:http_connection_retry_count] = 0 new_http_params[:http_connection_open_timeout] = connect_timeout if !connect_timeout.nil? new_http_params[:http_connection_read_timeout] = read_timeout if !read_timeout.nil? Rightscale::HttpConnection::params = new_http_params link = generate_request("CreateSnapshot", "VolumeId" => volume_id.to_s) request_info(link, QEc2CreateSnapshotParser.new(:logger => @logger)) rescue Exception on_exception ensure Aws::AWSErrorHandler::reiteration_time = orig_reiteration_time if orig_reiteration_time Rightscale::HttpConnection::params = orig_http_params if orig_http_params end # Delete the specified snapshot. # # ec2.delete_snapshot('snap-55a5403c') #=> true # def delete_snapshot(snapshot_id) link = generate_request("DeleteSnapshot", "SnapshotId" => snapshot_id.to_s) request_info(link, RightBoolResponseParser.new(:logger => @logger)) rescue Exception on_exception end # Add/replace one tag to a resource # http://docs.amazonwebservices.com/AWSEC2/latest/APIReference/index.html?ApiReference_query_CreateTags.html # # ec2.create_tag('ami-1a2b3c4d', 'webserver') #=> true # ec2.create_tag('i-7f4d3a2b', 'stack', 'Production') #=> true # def create_tag(resource_id, key, value = nil) link = generate_request("CreateTags", "ResourceId.1" => resource_id.to_s, "Tag.1.Key" => key.to_s, "Tag.1.Value" => value.to_s) request_info(link, RightBoolResponseParser.new(:logger => @logger)) rescue Exception on_exception end # Describe tags # http://docs.amazonwebservices.com/AWSEC2/latest/APIReference/index.html?ApiReference_query_DescribeTags.html # # ec2.describe_tags # ec2.describe_tags( # 'Filter.1.Name' => 'resource-type', 'Filter.1.Value.1' => 'instance', # 'Filter.2.Name' => 'value', 'Filter.2.Value.1' => 'Test', 'Filter.2.Value.2' => 'Production' # ) # def describe_tags(filters = {}) link = generate_request("DescribeTags", filters) request_info(link, QEc2DescribeTagsParser.new(:logger => @logger)) rescue Exception on_exception end # Delete one or all tags from a resource # http://docs.amazonwebservices.com/AWSEC2/latest/APIReference/index.html?ApiReference_query_DeleteTags.html # # ec2.delete_tag('i-7f4d3a2b', 'stack') #=> true # ec2.delete_tag('i-7f4d3a2b', 'stack', 'Production') #=> true # # "If you omit Tag.n.Value, we delete the tag regardless of its value. If # you specify this parameter with an empty string as the value, we delete the # key only if its value is an empty string." # http://docs.amazonwebservices.com/AWSEC2/latest/APIReference/ApiReference_query_DeleteTags.html # def delete_tag(resource_id, key, value = nil) request_args = {"ResourceId.1" => resource_id.to_s, "Tag.1.Key" => key.to_s} request_args["Tag.1.Value"] = value.to_s if value link = generate_request("DeleteTags", request_args) request_info(link, RightBoolResponseParser.new(:logger => @logger)) rescue Exception on_exception end #----------------------------------------------------------------- # VPC related #----------------------------------------------------------------- # Create VPC # http://docs.amazonwebservices.com/AWSEC2/latest/APIReference/ApiReference-query-CreateVpc.html # # ec2.create_vpc("10.0.0.0/16") # FIXME: EVen though the EC2 docs describe the parameter instanceTenancy, # I could not get it to recognize that def create_vpc(cidr_block = "10.0.0.0/16") params = { "CidrBlock" => cidr_block } link = generate_request("CreateVpc", params) request_info(link, QEc2VpcsParser.new("vpc", :logger => @logger)) rescue Exception on_exception end # Describe VPC's # http://docs.amazonwebservices.com/AWSEC2/latest/APIReference/ApiReference-query-DescribeVpcs.html # # ec2.describe_vpcs # ec2.describe_vpcs(vpcId1, vpcId2, 'Filter.1.Name' => 'state', 'Filter.1.Value' = > 'pending', ...) def describe_vpcs(*args) if args.last.is_a?(Hash) params = args.pop.dup else params = {} end 1.upto(args.size) { |i| params["VpcId.#{i}"] = args[i-1] } link = generate_request("DescribeVpcs", params) request_info(link, QEc2VpcsParser.new("item", :logger => @logger)) rescue Exception on_exception end # Delete VPC # http://docs.amazonwebservices.com/AWSEC2/latest/APIReference/ApiReference-query-DeleteVpc.html # # ec2.delete_vpc(vpc_id) def delete_vpc(vpc_id) params = { "VpcId" => vpc_id } link = generate_request("DeleteVpc", params) request_info(link, RightBoolResponseParser.new(:logger => @logger)) rescue Exception on_exception end # Describe network interfaces in a VPC # http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-DescribeNetworkInterfaces.html # # ec2.describe_network_interfaces # ec2.describe_network_interfaces(ifaceId1, ifaceId2, ..., # 'Filter.1.Name' => 'addresses.primary', # 'Filter.1.Value.1' => true, # 'Filter.2.Name' => ...) def describe_network_interfaces(*args) if args.last.is_a?(Hash) filters = args.pop.dup else filters = {} end ids = hash_params('NetworkInterfaceId', args) params = filters.merge(ids) params['Version'] = "2013-02-01" link = generate_request('DescribeNetworkInterfaces', params) request_info(link, QEc2NetworkInterfacesParser.new(:logger => @logger)) rescue Exception on_exception end # Create a new network interface in a VPC # http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-CreateNetworkInterface.html # # ec2.create_network_interface("subnet-a61dafcf") def create_network_interface(subnet_id, *args) if args.last.is_a?(Hash) params = args.pop.dup else params = {} end params.merge!({'SubnetId' => subnet_id, 'Version' => "2013-02-01"}) link = generate_request('CreateNetworkInterface', params) request_info(link, QEc2NetworkInterfacesParser.new(:logger => @logger)) rescue Exception on_exception end # Attach a network interface to an instance # http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-AttachNetworkInterface.html # # ec2.attach_network_interface("eni-ffda3197", "i-9cc316fe", 1) def attach_network_interface(network_interface_id, instance_id, device_index) params = { "NetworkInterfaceId" => network_interface_id, "InstanceId" => instance_id, "DeviceIndex" => device_index, "Version" => "2013-02-01", } link = generate_request('AttachNetworkInterface', params) request_info(link, QEc2AttachNetworkInterfaceParser.new(:logger => @logger)) rescue Exception on_exception end # Detach a network interface from an instance # http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-DetachNetworkInterface.html # # ec2.detach_network_interface("eni-attach-d94b09b0") # ec2.detach_network_interface("eni-attach-d94b09b0", true) def detach_network_interface(attachment_id, force=false) params = { "AttachmentId" => attachment_id, "Force" => force, "Version" => "2013-02-01", } link = generate_request('DetachNetworkInterface', params) request_info(link, RightBoolResponseParser.new(:logger => @logger)) rescue Exception on_exception end # Delete a network interface from a VPC # http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-DeleteNetworkInterface.html # # ec2.delete_network_interface("eni-ffda3197") def delete_network_interface(network_interface_id) params = { "NetworkInterfaceId" => network_interface_id, "Version" => "2013-02-01", } link = generate_request('DeleteNetworkInterface', params) request_info(link, RightBoolResponseParser.new(:logger => @logger)) rescue Exception on_exception end # Create subnet in a VPC # http://docs.amazonwebservices.com/AWSEC2/latest/APIReference/ApiReference-query-CreateSubnet.html # # ec2.create_subnet(vpc_id, cidr_block) # ec2.create_subnet(vpc_id, cidr_block, availability_zone)) def create_subnet(vpc_id, cidr_block, availability_zone = nil) params = { "VpcId" => vpc_id, "CidrBlock" => cidr_block } params["AvailabilityZone"] = availability_zone if availability_zone link = generate_request("CreateSubnet", params) request_info(link, QEc2SubnetsParser.new("subnet", :logger => @logger)) rescue Exception on_exception end # Describe subnets # http://docs.amazonwebservices.com/AWSEC2/latest/APIReference/ApiReference-query-DescribeSubnets.html # # ec2.describe_subnets # ecs.describe_subnets(subnetId1, SubnetId2, ..., # 'Filter.1.Name' => 'state', # 'Filter.1.Value.1' => 'pending', # 'Filter.2.Name' => ...) def describe_subnets(*args) if args.last.is_a?(Hash) params = args.pop.dup else params = {} end 1.upto(args.size) { |i| params["SubnetId.#{i}"] = args[i-1] } link = generate_request("DescribeSubnets", params) request_info(link, QEc2SubnetsParser.new("item", :logger => @logger)) rescue Exception on_exception end # Delete Subnet # http://docs.amazonwebservices.com/AWSEC2/latest/APIReference/ApiReference-query-DeleteSubnet.html # # ec2.delete_subnet(subnet_id) def delete_subnet(subnet_id) params = { "SubnetId" => subnet_id } link = generate_request("DeleteSubnet", params) request_info(link, RightBoolResponseParser.new(:logger => @logger)) rescue Exception on_exception end #----------------------------------------------------------------- # PARSERS: Boolean Response Parser #----------------------------------------------------------------- class RightBoolResponseParser < AwsParser #:nodoc: def tagend(name) @result = @text=='true' ? true : false if name == 'return' end end #----------------------------------------------------------------- # PARSERS: Key Pair #----------------------------------------------------------------- class QEc2DescribeKeyPairParser < AwsParser #:nodoc: def tagstart(name, attributes) @item = {} if name == 'item' end def tagend(name) case name when 'keyName' then @item[:aws_key_name] = @text when 'keyFingerprint' then @item[:aws_fingerprint] = @text when 'item' then @result << @item end end def reset @result = []; end end class QEc2CreateKeyPairParser < AwsParser #:nodoc: def tagstart(name, attributes) @result = {} if name == 'CreateKeyPairResponse' end def tagend(name) case name when 'keyName' then @result[:aws_key_name] = @text when 'keyFingerprint' then @result[:aws_fingerprint] = @text when 'keyMaterial' then @result[:aws_material] = @text end end end class QEc2ImportPublicKeyParser < QEc2CreateKeyPairParser #:nodoc: def tagstart(name, attributes) @result = {} if name == 'ImportKeyPairResponse' end end #----------------------------------------------------------------- # PARSERS: Security Groups #----------------------------------------------------------------- class QEc2UserIdGroupPairType #:nodoc: attr_accessor :userId attr_accessor :groupName end class QEc2IpRangeItemType #:nodoc: attr_accessor :cidrIp end class QEc2IpPermissionType #:nodoc: attr_accessor :ipProtocol attr_accessor :fromPort attr_accessor :toPort attr_accessor :groups attr_accessor :ipRanges end class QEc2SecurityGroupItemType #:nodoc: attr_accessor :groupName attr_accessor :groupDescription attr_accessor :ownerId attr_accessor :ipPermissions end class QEc2DescribeSecurityGroupsParser < AwsParser #:nodoc: def tagstart(name, attributes) case name when 'item' if @xmlpath=='DescribeSecurityGroupsResponse/securityGroupInfo' @group = QEc2SecurityGroupItemType.new @group.ipPermissions = [] elsif @xmlpath=='DescribeSecurityGroupsResponse/securityGroupInfo/item/ipPermissions' @perm = QEc2IpPermissionType.new @perm.ipRanges = [] @perm.groups = [] elsif @xmlpath=='DescribeSecurityGroupsResponse/securityGroupInfo/item/ipPermissions/item/groups' @sgroup = QEc2UserIdGroupPairType.new elsif @xmlpath=='DescribeSecurityGroupsResponse/securityGroupInfo/item/ipPermissions/item/ipRanges' @sIpRange = QEc2IpRangeItemType.new end end end def tagend(name) case name when 'ownerId' then @group.ownerId = @text when 'groupDescription' then @group.groupDescription = @text when 'groupName' if @xmlpath=='DescribeSecurityGroupsResponse/securityGroupInfo/item' @group.groupName = @text elsif @xmlpath=='DescribeSecurityGroupsResponse/securityGroupInfo/item/ipPermissions/item/groups/item' @sgroup.groupName = @text end when 'ipProtocol' then @perm.ipProtocol = @text when 'fromPort' then @perm.fromPort = @text when 'toPort' then @perm.toPort = @text when 'userId' then @sgroup.userId = @text when 'cidrIp' then @sIpRange.cidrIp = @text when 'item' if @xmlpath=='DescribeSecurityGroupsResponse/securityGroupInfo/item/ipPermissions/item/groups' @perm.groups << @sgroup elsif @xmlpath=='DescribeSecurityGroupsResponse/securityGroupInfo/item/ipPermissions/item/ipRanges' @perm.ipRanges << @sIpRange elsif @xmlpath=='DescribeSecurityGroupsResponse/securityGroupInfo/item/ipPermissions' @group.ipPermissions << @perm elsif @xmlpath=='DescribeSecurityGroupsResponse/securityGroupInfo' @result << @group end end end def reset @result = [] end end #----------------------------------------------------------------- # PARSERS: Images #----------------------------------------------------------------- class QEc2DescribeImagesParser < AwsParser #:nodoc: def tagstart(name, attributes) if name == 'item' && @xmlpath[%r{.*/imagesSet$}] @image = {} end end def tagend(name) case name when 'imageId' then @image[:aws_id] = @text when 'name' then @image[:aws_name] = @text when 'description' then @image[:aws_description] = @text when 'imageLocation' then @image[:aws_location] = @text when 'imageState' then @image[:aws_state] = @text when 'imageOwnerId' then @image[:aws_owner] = @text when 'isPublic' then @image[:aws_is_public]= @text == 'true' ? true : false when 'productCode' then (@image[:aws_product_codes] ||= []) << @text when 'architecture' then @image[:aws_architecture] = @text when 'imageType' then @image[:aws_image_type] = @text when 'kernelId' then @image[:aws_kernel_id] = @text when 'ramdiskId' then @image[:aws_ramdisk_id] = @text when 'rootDeviceType' then @image[:aws_root_device_type] = @text when 'rootDeviceName' then @image[:aws_root_device_name] = @text when 'hypervisor' then @image[:aws_hypervisor] = @text when 'item' then @result << @image if @xmlpath[%r{.*/imagesSet$}] end end def reset @result = [] end end class QEc2RegisterImageParser < AwsParser #:nodoc: def tagend(name) @result = @text if name == 'imageId' end end #----------------------------------------------------------------- # PARSERS: Image Attribute #----------------------------------------------------------------- class QEc2DescribeImageAttributeParser < AwsParser #:nodoc: def tagstart(name, attributes) case name when 'launchPermission' @result[:groups] = [] @result[:users] = [] when 'productCodes' @result[:aws_product_codes] = [] end end def tagend(name) # right now only 'launchPermission' is supported by Amazon. # But nobody know what will they xml later as attribute. That is why we # check for 'group' and 'userId' inside of 'launchPermission/item' case name when 'imageId' then @result[:aws_id] = @text when 'group' then @result[:groups] << @text if @xmlpath == 'DescribeImageAttributeResponse/launchPermission/item' when 'userId' then @result[:users] << @text if @xmlpath == 'DescribeImageAttributeResponse/launchPermission/item' when 'productCode' then @result[:aws_product_codes] << @text when 'kernel' then @result[:aws_kernel] = @text when 'ramdisk' then @result[:aws_ramdisk] = @text when 'blockDeviceMapping' then @result[:block_device_mapping] = @text end end def reset @result = {} end end #----------------------------------------------------------------- # PARSERS: Instances #----------------------------------------------------------------- class QEc2DescribeInstancesParser < AwsParser #:nodoc: def tagstart(name, attributes) # DescribeInstances property if (name == 'item' && @xmlpath == 'DescribeInstancesResponse/reservationSet') || # RunInstances property (name == 'RunInstancesResponse') @reservation = {:aws_groups => [], :instances_set => []} elsif (name == 'item') && # DescribeInstances property (@xmlpath=='DescribeInstancesResponse/reservationSet/item/instancesSet' || # RunInstances property @xmlpath=='RunInstancesResponse/instancesSet') # the optional params (sometimes are missing and we dont want them to be nil) @instance = {:aws_reason => '', :dns_name => '', :private_dns_name => '', :ami_launch_index => '', :ssh_key_name => '', :aws_state => '', :root_device_type => '', :root_device_name => '', :architecture => '', :subnet_id => '', :vpc_id => '', :block_device_mappings => [], :aws_product_codes => [], :tags => {}} end end def tagend(name) case name when 'rootDeviceType' then @instance[:root_device_type] = @text when 'architecture' then @instance[:architecture] = @text when 'rootDeviceName' then @instance[:root_device_name] = @text # reservation when 'reservationId' then @reservation[:aws_reservation_id] = @text when 'ownerId' then @reservation[:aws_owner] = @text when 'groupId' then @reservation[:aws_groups] << @text # instance when 'instanceId' then @instance[:aws_instance_id] = @text when 'imageId' then @instance[:aws_image_id] = @text when 'dnsName' then @instance[:dns_name] = @text when 'privateDnsName' then @instance[:private_dns_name] = @text when 'reason' then @instance[:aws_reason] = @text when 'keyName' then @instance[:ssh_key_name] = @text when 'amiLaunchIndex' then @instance[:ami_launch_index] = @text when 'code' then @instance[:aws_state_code] = @text when 'name' then @instance[:aws_state] = @text when 'productCode' then @instance[:aws_product_codes] << @text when 'instanceType' then @instance[:aws_instance_type] = @text when 'launchTime' then @instance[:aws_launch_time] = @text when 'kernelId' then @instance[:aws_kernel_id] = @text when 'ramdiskId' then @instance[:aws_ramdisk_id] = @text when 'platform' then @instance[:aws_platform] = @text when 'availabilityZone' then @instance[:aws_availability_zone] = @text when 'privateIpAddress' then @instance[:aws_private_ip_address] = @text when 'subnetId' then @instance[:subnet_id] = @text when 'vpcId' then @instance[:vpc_id] = @text when 'key' then @tag_key = @text when 'value' then @tag_value = @text when 'deviceName' then @device_name = @text when 'volumeId' then @volume_id = @text when 'state' if @xmlpath == 'DescribeInstancesResponse/reservationSet/item/instancesSet/item/monitoring' || # DescribeInstances property @xmlpath == 'RunInstancesResponse/instancesSet/item/monitoring' # RunInstances property @instance[:monitoring_state] = @text end when 'item' if @xmlpath=='DescribeInstancesResponse/reservationSet/item/instancesSet/item/tagSet' # Tags @instance[:tags][@tag_key] = @tag_value elsif @xmlpath == 'DescribeInstancesResponse/reservationSet/item/instancesSet/item/blockDeviceMapping' # Block device mappings @instance[:block_device_mappings] << { @device_name => @volume_id } elsif @xmlpath == 'DescribeInstancesResponse/reservationSet/item/instancesSet' || # DescribeInstances property @xmlpath == 'RunInstancesResponse/instancesSet' # RunInstances property @reservation[:instances_set] << @instance elsif @xmlpath=='DescribeInstancesResponse/reservationSet' # DescribeInstances property @result << @reservation end when 'RunInstancesResponse' then @result << @reservation # RunInstances property end end def reset @result = [] end end class QEc2ConfirmProductInstanceParser < AwsParser #:nodoc: def tagend(name) @result = @text if name == 'ownerId' end end class QEc2MonitorInstancesParser < AwsParser #:nodoc: def tagstart(name, attributes) @instance = {} if name == 'item' end def tagend(name) case name when 'instanceId' then @instance[:aws_instance_id] = @text when 'state' then @instance[:aws_monitoring_state] = @text when 'item' then @result << @instance end end def reset @result = [] end end class QEc2CreateImageParser < AwsParser #:nodoc: def tagend(name) @result = @text if name == 'imageId' end end class QEc2TerminateInstancesParser < AwsParser #:nodoc: def tagstart(name, attributes) @instance = {} if name == 'item' end def tagend(name) case name when 'instanceId' then @instance[:aws_instance_id] = @text when 'code' if @xmlpath == 'TerminateInstancesResponse/instancesSet/item/shutdownState' @instance[:aws_shutdown_state_code] = @text.to_i else @instance[:aws_prev_state_code] = @text.to_i end when 'name' if @xmlpath == 'TerminateInstancesResponse/instancesSet/item/shutdownState' @instance[:aws_shutdown_state] = @text else @instance[:aws_prev_state] = @text end when 'item' then @result << @instance end end def reset @result = [] end end class QEc2StopInstancesParser < AwsParser #:nodoc: def tagstart(name, attributes) @instance = {} if name == 'item' end def tagend(name) case name when 'instanceId' then @instance[:aws_instance_id] = @text when 'code' if @xmlpath == 'StopInstancesResponse/instancesSet/item/currentState' @instance[:aws_current_state_code] = @text.to_i elsif @xmlpath == 'StopInstancesResponse/instancesSet/item/previousState' @instance[:aws_prev_state_code] = @text.to_i end when 'name' if @xmlpath == 'StopInstancesResponse/instancesSet/item/currentState' @instance[:aws_current_state] = @text elsif @xmlpath == 'StopInstancesResponse/instancesSet/item/previousState' @instance[:aws_prev_state] = @text end when 'item' then @result << @instance end end def reset @result = [] end end class QEc2StartInstancesParser < AwsParser #:nodoc: def tagstart(name, attributes) @instance = {} if name == 'item' end def tagend(name) case name when 'instanceId' then @instance[:aws_instance_id] = @text when 'code' if @xmlpath == 'StartInstancesResponse/instancesSet/item/currentState' @instance[:aws_current_state_code] = @text.to_i elsif @xmlpath == 'StartInstancesResponse/instancesSet/item/previousState' @instance[:aws_prev_state_code] = @text.to_i end when 'name' if @xmlpath == 'StartInstancesResponse/instancesSet/item/currentState' @instance[:aws_current_state] = @text elsif @xmlpath == 'StartInstancesResponse/instancesSet/item/previousState' @instance[:aws_prev_state] = @text end when 'item' then @result << @instance end end def reset @result = [] end end #----------------------------------------------------------------- # PARSERS: Console #----------------------------------------------------------------- class QEc2GetConsoleOutputParser < AwsParser #:nodoc: def tagend(name) case name when 'instanceId' then @result[:aws_instance_id] = @text when 'timestamp' then @result[:aws_timestamp] = @text @result[:timestamp] = (Time.parse(@text)).utc when 'output' then @result[:aws_output] = Base64.decode64(@text) end end def reset @result = {} end end #----------------------------------------------------------------- # Instances: Wondows related part #----------------------------------------------------------------- class QEc2DescribeBundleTasksParser < AwsParser #:nodoc: def tagstart(name, attributes) @bundle = {} if name == 'item' end def tagend(name) case name # when 'requestId' then @bundle[:request_id] = @text when 'instanceId' then @bundle[:aws_instance_id] = @text when 'bundleId' then @bundle[:aws_id] = @text when 'bucket' then @bundle[:s3_bucket] = @text when 'prefix' then @bundle[:s3_prefix] = @text when 'startTime' then @bundle[:aws_start_time] = @text when 'updateTime' then @bundle[:aws_update_time] = @text when 'state' then @bundle[:aws_state] = @text when 'progress' then @bundle[:aws_progress] = @text when 'code' then @bundle[:aws_error_code] = @text when 'message' then @bundle[:aws_error_message] = @text when 'item' then @result << @bundle end end def reset @result = [] end end class QEc2BundleInstanceParser < AwsParser #:nodoc: def tagend(name) case name # when 'requestId' then @result[:request_id] = @text when 'instanceId' then @result[:aws_instance_id] = @text when 'bundleId' then @result[:aws_id] = @text when 'bucket' then @result[:s3_bucket] = @text when 'prefix' then @result[:s3_prefix] = @text when 'startTime' then @result[:aws_start_time] = @text when 'updateTime' then @result[:aws_update_time] = @text when 'state' then @result[:aws_state] = @text when 'progress' then @result[:aws_progress] = @text when 'code' then @result[:aws_error_code] = @text when 'message' then @result[:aws_error_message] = @text end end def reset @result = {} end end #----------------------------------------------------------------- # PARSERS: Elastic IPs #----------------------------------------------------------------- class QEc2AllocateAddressParser < AwsParser #:nodoc: def tagend(name) @result = @text if name == 'publicIp' end end class QEc2DescribeAddressesParser < AwsParser #:nodoc: def tagstart(name, attributes) @address = {} if name == 'item' end def tagend(name) case name when 'instanceId' then @address[:instance_id] = Aws::Utils.blank?(@text) ? nil : @text when 'publicIp' then @address[:public_ip] = @text when 'item' then @result << @address end end def reset @result = [] end end #----------------------------------------------------------------- # PARSERS: AvailabilityZones #----------------------------------------------------------------- class QEc2DescribeAvailabilityZonesParser < AwsParser #:nodoc: def tagstart(name, attributes) @zone = {} if name == 'item' end def tagend(name) case name when 'regionName' then @zone[:region_name] = @text when 'zoneName' then @zone[:zone_name] = @text when 'zoneState' then @zone[:zone_state] = @text when 'item' then @result << @zone end end def reset @result = [] end end #----------------------------------------------------------------- # PARSERS: Regions #----------------------------------------------------------------- class QEc2DescribeRegionsParser < AwsParser #:nodoc: def tagend(name) @result << @text if name == 'regionName' end def reset @result = [] end end #----------------------------------------------------------------- # PARSERS: EBS - Volumes #----------------------------------------------------------------- class QEc2CreateVolumeParser < AwsParser #:nodoc: def tagend(name) case name when 'volumeId' then @result[:aws_id] = @text when 'status' then @result[:aws_status] = @text when 'createTime' then @result[:aws_created_at] = Time.parse(@text) when 'size' then @result[:aws_size] = @text.to_i ### when 'snapshotId' then @result[:snapshot_id] = Aws::Utils.blank?(@text) ? nil : @text ### when 'availabilityZone' then @result[:zone] = @text ### end end def reset @result = {} end end class QEc2AttachAndDetachVolumeParser < AwsParser #:nodoc: def tagend(name) case name when 'volumeId' then @result[:aws_id] = @text when 'instanceId' then @result[:aws_instance_id] = @text when 'device' then @result[:aws_device] = @text when 'status' then @result[:aws_attachment_status] = @text when 'attachTime' then @result[:aws_attached_at] = Time.parse(@text) end end def reset @result = {} end end class QEc2DescribeVolumesParser < AwsParser #:nodoc: def tagstart(name, attributes) case name when 'item' case @xmlpath when 'DescribeVolumesResponse/volumeSet' then @volume = {} end end end def tagend(name) case name when 'volumeId' case @xmlpath when 'DescribeVolumesResponse/volumeSet/item' then @volume[:aws_id] = @text end when 'status' case @xmlpath when 'DescribeVolumesResponse/volumeSet/item' then @volume[:aws_status] = @text when 'DescribeVolumesResponse/volumeSet/item/attachmentSet/item' then @volume[:aws_attachment_status] = @text end when 'size' then @volume[:aws_size] = @text.to_i when 'createTime' then @volume[:aws_created_at] = Time.parse(@text) when 'instanceId' then @volume[:aws_instance_id] = @text when 'device' then @volume[:aws_device] = @text when 'attachTime' then @volume[:aws_attached_at] = Time.parse(@text) when 'snapshotId' then @volume[:snapshot_id] = Aws::Utils.blank?(@text) ? nil : @text when 'availabilityZone' then @volume[:zone] = @text when 'item' case @xmlpath when 'DescribeVolumesResponse/volumeSet' then @result << @volume end end end def reset @result = [] end end #----------------------------------------------------------------- # PARSERS: EBS - Snapshots #----------------------------------------------------------------- class QEc2DescribeSnapshotsParser < AwsParser #:nodoc: def initialize (params={}) @inside_tagset = false super(params) end def tagstart(name, attributes) case name when 'tagSet' @inside_tagset = true when 'item' @snapshot = {} unless @inside_tagset end end def tagend(name) case name when 'volumeId' then @snapshot[:aws_volume_id] = @text when 'snapshotId' then @snapshot[:aws_id] = @text when 'status' then @snapshot[:aws_status] = @text when 'startTime' then @snapshot[:aws_started_at] = Time.parse(@text) when 'progress' then @snapshot[:aws_progress] = @text when 'description' then @snapshot[:aws_description] = @text when 'ownerId' then @snapshot[:aws_owner] = @text when 'volumeSize' then @snapshot[:aws_volume_size] = @text.to_i when 'tagSet' then @inside_tagset = false when 'key' then @key = ('aws_tag_' + @text).to_sym when 'value' then @snapshot[@key] = @text when 'item' then @result << @snapshot unless @inside_tagset end end def reset @result = [] end end class QEc2CreateSnapshotParser < AwsParser #:nodoc: def tagend(name) case name when 'volumeId' then @result[:aws_volume_id] = @text when 'snapshotId' then @result[:aws_id] = @text when 'status' then @result[:aws_status] = @text when 'startTime' then @result[:aws_started_at] = Time.parse(@text) when 'progress' then @result[:aws_progress] = @text end end def reset @result = {} end end #----------------------------------------------------------------- # PARSERS: Tags #----------------------------------------------------------------- class QEc2DescribeTagsParser < AwsParser #:nodoc: def tagstart(name, attributes) @tag = {} if name == 'item' end def tagend(name) case name when 'resourceId' then @tag[:aws_resource_id] = @text when 'resourceType' then @tag[:aws_resource_type] = @text when 'key' then @tag[:aws_key] = @text when 'value' then @tag[:aws_value] = @text when 'item' then @result << @tag end end def reset @result = [] end end #----------------------------------------------------------------- # PARSERS: Vpc #----------------------------------------------------------------- class QEc2VpcsParser < AwsParser #:nodoc: def initialize(wrapper, opts = {}) super(opts) @wrapper = wrapper end def tagstart(name, attribute) @vpc = {} if name == @wrapper end def tagend(name) case name when 'vpcId' then @vpc[:vpc_id] = @text when 'state' then @vpc[:state] = @text when 'cidrBlock' then @vpc[:cidr_block] = @text when 'dhcpOptionsId' then @vpc[:dhcp_options_id] = @text when @wrapper @result << @vpc end end def reset @result = [] end end class QEc2NetworkInterfacesParser < AwsParser #:nodoc: def initialize(opts = {}) super(opts) @level = ['top'] end def tagstart(name, attribute) case name when 'groupSet' @level.push(name) @iface[:group_set] = [] @group_item = {} when 'attachment' @level.push(name) @iface[:attachment] = {} when 'tagSet' @level.push(name) @iface[:tag_set] = [] @tag_set_item = {} when 'association' @level.push(name) @association = {} when 'privateIpAddressesSet' @level.push(name) @iface[:private_ip_addresses_set] = [] @ip_address_item = {} when 'networkInterface' @iface = {} when 'item' @iface = {} if @level.last == 'top' end end def tagend(name) wrapper = @level.last case wrapper when 'top' # parse top level case name when 'networkInterfaceId' @iface[:network_interface_id] = @text when 'subnetId' @iface[:subnet_id] = @text when 'vpcId' @iface[:vpc_id] = @text when 'availabilityZone' @iface[:availability_zone] = @text when 'description' @iface[:description] = @text when 'ownerId' @iface[:owner_id] = @text when 'requesterId' @iface[:requester_id] = @text when 'requesterManaged' @iface[:requester_managed] = @text when 'status' @iface[:status] = @text when 'macAddress' @iface[:mac_address] = @text when 'privateIpAddress' @iface[:private_ip_address] = @text when 'privateDnsName' @iface[:private_dns_name] = @text when 'sourceDestCheck' @iface[:source_dest_check] = @text when 'networkInterface' @result = @iface when 'item' @result << @iface end when 'groupSet' # parse gorupset case name when 'groupId' @group_item[:group_id] = @text when 'groupName' @group_item[:group_name] = @text when 'item' @iface[:group_set] << @group_item @group_item = {} when 'groupSet' @level.pop end when 'attachment' # parse attachment case name when 'attachmentId' @iface[:attachment][:attachment_id] = @text when 'instanceId' @iface[:attachment][:instance_id] = @text when 'instanceOwnerId' @iface[:attachment][:instance_owner_id] = @text when 'deviceIndex' @iface[:attachment][:device_index] = @text when 'status' @iface[:attachment][:status] = @text when 'attachTime' @iface[:attachment][:attach_time] = @text when 'deleteOnTermination' @iface[:attachment][:delete_on_termination] = @text when 'attachment' @level.pop end when 'tagSet' # parse tagset case name when 'key' @tag_set_item[:key] = @text when 'value' @tag_set_item[:value] = @text when 'item' @iface[:tag_set] << @tag_set_item @tag_set_item = {} when 'tagSet' @level.pop end when 'association' # parse assoc case name when 'publicIp' @association[:public_ip] = @text when 'publicDnsName' @association[:public_dns_name] = @text when 'ipOwnerId' @association[:ip_owner_id] = @text when 'allocationID' @association[:allocation_id] = @text when 'associationID' @association[:association_id] = @text when 'association' @level.pop # `association` can belong both to `NetworkInterfaceType` and `NetworkInterfacePrivateIpAddressesSetItemType` if @level.last == 'top' @iface[:association] = @association else @ip_address_item[:association] = @association end end when 'privateIpAddressesSet' # parse pirvate addresses set case name when 'privateIpAddress' @ip_address_item[:private_ip_address] = @text when 'privateDnsName' @ip_address_item[:private_dns_name] = @text when 'primary' @ip_address_item[:primary] = @text when 'item' @iface[:private_ip_addresses_set] << @ip_address_item @ip_address_item = {} when 'privateIpAddressesSet' @level.pop end end end def reset @result = [] end end class QEc2AttachNetworkInterfaceParser < AwsParser #:nodoc: def initialize(opts = {}) super(opts) end def tagend(name) case name when 'attachmentId' @result['attachment_id'] = @text end end def reset @result = {} end end class QEc2SubnetsParser < AwsParser #:nodoc def initialize(wrapper, opts = {}) super(opts) @wrapper = wrapper end def tagstart(name, attribute) @subnet = {} if name == @wrapper end def tagend(name) case name when 'subnetId' then @subnet[:subnet_id] = @text when 'state' then @subnet[:state] = @text when 'vpcId' then @subnet[:vpc_id] = @text when 'cidrBlock' then @subnet[:cidr_block] = @text when 'availableIpAddressCount' then @subnet[:available_ip_address_count] = @text when 'availabilityZone' then @subnet[:availability_zone] = @text when @wrapper @result << @subnet end end def reset @result = [] end end end end aws-2.10.2/lib/ses/0000755000175000017500000000000012511515441012441 5ustar tnnntnnnaws-2.10.2/lib/ses/ses.rb0000644000175000017500000001003312511515441013555 0ustar tnnntnnnrequire_relative "../awsbase/awsbase" module Aws require 'xmlsimple' class Ses < AwsBase include AwsBaseInterface API_VERSION = "2010-12-01" DEFAULT_HOST = "email.us-east-1.amazonaws.com" DEFAULT_PATH = '/' DEFAULT_PROTOCOL = 'https' DEFAULT_PORT = 443 def self.connection_name :ses_connection end @@bench = AwsBenchmarkingBlock.new def self.bench @@bench end def self.bench_xml @@bench.xml end def self.bench_ec2 @@bench.service end # Current API version (sometimes we have to check it outside the GEM). @@api = ENV['SES_API_VERSION'] || API_VERSION def self.api @@api end def initialize(aws_access_key_id=nil, aws_secret_access_key=nil, params={}) init({:name => 'SES', :default_host => ENV['SES_URL'] ? URI.parse(ENV['SES_URL']).host : DEFAULT_HOST, :default_port => ENV['SES_URL'] ? URI.parse(ENV['SES_URL']).port : DEFAULT_PORT, :default_service => ENV['SES_URL'] ? URI.parse(ENV['SES_URL']).path : DEFAULT_PATH, :default_protocol => ENV['SES_URL'] ? URI.parse(ENV['SES_URL']).scheme : DEFAULT_PROTOCOL, :api_version => API_VERSION, :signature_version=>'3'}, aws_access_key_id || ENV['AWS_ACCESS_KEY_ID'], aws_secret_access_key|| ENV['AWS_SECRET_ACCESS_KEY'], params) end def do_request(action, params, options={}) link = generate_request(action, params) puts "request=" + link[:request].inspect resp = request_info_xml_simple3(self, link, :group_tags =>{"LoadBalancersDescriptions"=>"LoadBalancersDescription", "DBParameterGroups" =>"DBParameterGroup", "DBSecurityGroups" =>"DBSecurityGroup", "EC2SecurityGroups" =>"EC2SecurityGroup", "IPRanges" =>"IPRange"}, :force_array =>["DBInstances", "DBParameterGroups", "DBSecurityGroups", "EC2SecurityGroups", "IPRanges"], :pull_out_array =>options[:pull_out_array], :pull_out_single=>options[:pull_out_single], :wrapper =>options[:wrapper]) end #----------------------------------------------------------------- # REQUESTS #----------------------------------------------------------------- # options: # :marker => value received from previous response if IsTruncated = true # :max_items => number of items you want returned # :path_previx => for filtering results, default is / def get_send_quota(options={}) @logger.info("get_send_quota") resp = do_request("GetSendQuota", options) rescue Exception on_exception end # # name: name of certificate # public_key: public key certificate in PEM-encoded format # private_key: private key in PEM-encoded format # options: # :path => specify a path you want it stored in # :certificate_chain => contents of certificate chain def upload_server_certificate(name, public_key, private_key, options={}) params = {} params['ServerCertificateName'] = name params['PrivateKey'] = private_key params['CertificateBody'] = public_key params['CertificateChain'] = options[:certificate_chain] if options[:certificate_chain] params['Path'] = options[:path] if options[:path] p params resp = do_request("UploadServerCertificate", params, :pull_out_array=>[:list_server_certificates_result, :server_certificate_metadata_list]) rescue Exception on_exception end end endaws-2.10.2/lib/aws.rb0000644000175000017500000000122212511515441012763 0ustar tnnntnnn require 'benchmark' require 'net/https' require 'uri' require 'time' require "cgi" require "base64" require "rexml/document" require "openssl" require "digest/sha1" require 'rubygems' require 'right_http_connection' $:.unshift(File.dirname(__FILE__)) require 'awsbase/require_relative' require 'awsbase/benchmark_fix' require 'awsbase/awsbase' require 'awsbase/aws_response_array' require 'ec2/ec2' require 'ec2/mon_interface' require 's3/s3_interface' require 's3/s3' require 'sqs/sqs_interface' require 'sqs/sqs' require 'sdb/sdb_interface' require 'acf/acf_interface' require 'elb/elb_interface' require 'rds/rds' require 'iam/iam' require 'ses/ses' aws-2.10.2/lib/s3/0000755000175000017500000000000012511515441012174 5ustar tnnntnnnaws-2.10.2/lib/s3/s3.rb0000644000175000017500000003403712511515441013055 0ustar tnnntnnn# # Copyright (c) 2007-2008 RightScale Inc # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # module Aws # = Aws::S3 -- RightScale's Amazon S3 interface # The Aws::S3 class provides a complete interface to Amazon's Simple # Storage Service. # For explanations of the semantics # of each call, please refer to Amazon's documentation at # http://developer.amazonwebservices.com/connect/kbcategory.jspa?categoryID=48 # # See examples below for the bucket and buckets methods. # # Error handling: all operations raise an Aws::AwsError in case # of problems. Note that transient errors are automatically retried. # # It is a good way to use domain naming style getting a name for the buckets. # See http://docs.amazonwebservices.com/AmazonS3/2006-03-01/UsingBucket.html # about the naming convention for the buckets. This case they can be accessed using a virtual domains. # # Let assume you have 3 buckets: 'awesome-bucket', 'awesome_bucket' and 'AWEsomE-bucket'. # The first ones objects can be accessed as: http:// awesome-bucket.s3.amazonaws.com/key/object # # But the rest have to be accessed as: # http:// s3.amazonaws.com/awesome_bucket/key/object and http:// s3.amazonaws.com/AWEsomE-bucket/key/object # # See: http://docs.amazonwebservices.com/AmazonS3/2006-03-01/VirtualHosting.html for better explanation. # class S3 class Owner attr_reader :id, :name def initialize(id, name) @id = id @name = name end # Return Owner name as a +String+. def to_s @name end end require_relative 'bucket' require_relative 'key' require_relative 'grantee' attr_reader :interface # Create a new handle to an S3 account. All handles share the same per process or per thread # HTTP connection to Amazon S3. Each handle is for a specific account. # The +params+ are passed through as-is to Aws::S3Interface.new # # Params is a hash: # # {:server => 's3.amazonaws.com' # Amazon service host: 's3.amazonaws.com'(default) # :port => 443 # Amazon service port: 80 or 443(default) # :protocol => 'https' # Amazon service protocol: 'http' or 'https'(default) # :virtual_hosting => false # Force using bucket virtual hosting: https://s3.amazonaws.com/my-bucket vs. https://my-bucket.s3.amazonaws.com # :connection_mode => :default # options are # :default (will use best known safe (as in won't need explicit close) option, may change in the future) # :per_request (opens and closes a connection on every request) # :single (one thread across entire app) # :per_thread (one connection per thread) # :logger => Logger Object} # Logger instance: logs to STDOUT if omitted } def initialize(aws_access_key_id=nil, aws_secret_access_key=nil, params={}) @interface = S3Interface.new(aws_access_key_id, aws_secret_access_key, params) end def close_connection @interface.close_connection end # Retrieve a list of buckets. # Returns an array of Aws::S3::Bucket instances. # # Create handle to S3 account # s3 = Aws::S3.new(aws_access_key_id, aws_secret_access_key) # my_buckets_names = s3.buckets.map{|b| b.name} # puts "Buckets on S3: #{my_bucket_names.join(', ')}" def buckets @interface.list_all_my_buckets.map! do |entry| owner = Owner.new(entry[:owner_id], entry[:owner_display_name]) Bucket.new(self, entry[:name], entry[:creation_date], owner) end end # Retrieve an individual bucket. # If the bucket does not exist and +create+ is set, a new bucket # is created on S3. Launching this method with +create+=+true+ may # affect on the bucket's ACL if the bucket already exists. # Returns a Aws::S3::Bucket instance or +nil+ if the bucket does not exist # and +create+ is not set. # # s3 = Aws::S3.new(aws_access_key_id, aws_secret_access_key) # bucket1 = s3.bucket('my_awesome_bucket_1') # bucket1.keys #=> exception here if the bucket does not exists # ... # bucket2 = s3.bucket('my_awesome_bucket_2', true) # bucket2.keys #=> list of keys # # create a bucket at the European location with public read access # bucket3 = s3.bucket('my-awesome-bucket-3', true, 'public-read', :location => :eu) # # see http://docs.amazonwebservices.com/AmazonS3/2006-03-01/RESTAccessPolicy.html # (section: Canned Access Policies) # def bucket(name, create=false, perms=nil, headers={}) headers['x-amz-acl'] = perms if perms @interface.create_bucket(name, headers) if create return Bucket.new(self, name) # The old way below was too slow and unnecessary because it retreived all the buckets every time. # owner = Owner.new(entry[:owner_id], entry[:owner_display_name]) # buckets.each { |bucket| return bucket if bucket.name == name } # nil end end # Aws::S3Generator and Aws::S3Generator::Bucket methods: # # s3g = Aws::S3Generator.new('1...2', 'nx...Y6') #=> # # # # List all buckets(method 'GET'): # buckets_list = s3g.buckets #=> 'https://s3.amazonaws.com:443/?Signature=Y...D&Expires=1180941864&AWSAccessKeyId=1...2' # # Create bucket link (method 'PUT'): # bucket = s3g.bucket('my_awesome_bucket') #=> # # link_to_create = bucket.create_link(1.hour) #=> https://s3.amazonaws.com:443/my_awesome_bucket?Signature=4...D&Expires=1180942132&AWSAccessKeyId=1...2 # # ... or: # bucket = Aws::S3Generator::Bucket.create(s3g, 'my_awesome_bucket') #=> # # link_to_create = bucket.create_link(1.hour) #=> https://s3.amazonaws.com:443/my_awesome_bucket?Signature=4...D&Expires=1180942132&AWSAccessKeyId=1...2 # # ... or: # bucket = Aws::S3Generator::Bucket.new(s3g, 'my_awesome_bucket') #=> # # link_to_create = bucket.create_link(1.hour) #=> https://s3.amazonaws.com:443/my_awesome_bucket?Signature=4...D&Expires=1180942132&AWSAccessKeyId=1...2 # # List bucket(method 'GET'): # bucket.keys(1.day) #=> https://s3.amazonaws.com:443/my_awesome_bucket?Signature=i...D&Expires=1180942620&AWSAccessKeyId=1...2 # # Create/put key (method 'PUT'): # bucket.put('my_cool_key') #=> https://s3.amazonaws.com:443/my_awesome_bucket/my_cool_key?Signature=q...D&Expires=1180943094&AWSAccessKeyId=1...2 # # Get key data (method 'GET'): # bucket.get('logs/today/1.log', 1.hour) #=> https://s3.amazonaws.com:443/my_awesome_bucket/my_cool_key?Signature=h...M%3D&Expires=1180820032&AWSAccessKeyId=1...2 # # Delete bucket (method 'DELETE'): # bucket.delete(2.hour) #=> https://s3.amazonaws.com:443/my_awesome_bucket/logs%2Ftoday%2F1.log?Signature=4...D&Expires=1180820032&AWSAccessKeyId=1...2 # # Aws::S3Generator::Key methods: # # # Create Key instance: # key = Aws::S3Generator::Key.new(bicket, 'my_cool_key') #=> # # # Put key data (method 'PUT'): # key.put #=> https://s3.amazonaws.com:443/my_awesome_bucket/my_cool_key?Signature=2...D&Expires=1180943302&AWSAccessKeyId=1...2 # # Get key data (method 'GET'): # key.get #=> https://s3.amazonaws.com:443/my_awesome_bucket/my_cool_key?Signature=a...D&Expires=1180820032&AWSAccessKeyId=1...2 # # Head key (method 'HEAD'): # key.head #=> https://s3.amazonaws.com:443/my_awesome_bucket/my_cool_key?Signature=b...D&Expires=1180820032&AWSAccessKeyId=1...2 # # Delete key (method 'DELETE'): # key.delete #=> https://s3.amazonaws.com:443/my_awesome_bucket/my_cool_key?Signature=x...D&Expires=1180820032&AWSAccessKeyId=1...2 # class S3Generator attr_reader :interface def initialize(aws_access_key_id, aws_secret_access_key, params={}) @interface = S3Interface.new(aws_access_key_id, aws_secret_access_key, params) end # Generate link to list all buckets # # s3.buckets(1.hour) # def buckets(expires=nil, headers={}) @interface.list_all_my_buckets_link(expires, headers) end # Create new S3LinkBucket instance and generate link to create it at S3. # # bucket= s3.bucket('my_owesome_bucket') # def bucket(name, expires=nil, headers={}) Bucket.create(self, name.to_s) end class Bucket attr_reader :s3, :name def to_s @name end alias_method :full_name, :to_s # Return a public link to bucket. # # bucket.public_link #=> 'https://s3.amazonaws.com:443/my_awesome_bucket' # def public_link params = @s3.interface.params "#{params[:protocol]}://#{params[:server]}:#{params[:port]}/#{full_name}" end # Create new S3LinkBucket instance and generate creation link for it. def self.create(s3, name, expires=nil, headers={}) new(s3, name.to_s) end # Create new S3LinkBucket instance. def initialize(s3, name) @s3, @name = s3, name.to_s end # Return a link to create this bucket. # def create_link(expires=nil, headers={}) @s3.interface.create_bucket_link(@name, expires, headers) end # Generate link to list keys. # # bucket.keys # bucket.keys('prefix'=>'logs') # def keys(options=nil, expires=nil, headers={}) @s3.interface.list_bucket_link(@name, options, expires, headers) end # Return a S3Generator::Key instance. # # bucket.key('my_cool_key').get #=> https://s3.amazonaws.com:443/my_awesome_bucket/my_cool_key?Signature=B...D&Expires=1180820032&AWSAccessKeyId=1...2 # bucket.key('my_cool_key').delete #=> https://s3.amazonaws.com:443/my_awesome_bucket/my_cool_key?Signature=B...D&Expires=1180820098&AWSAccessKeyId=1...2 # def key(name) Key.new(self, name) end # Generates link to PUT key data. # # puts bucket.put('logs/today/1.log', 2.hour) # def put(key, meta_headers={}, expires=nil, headers={}) meta = Aws::S3::Key.add_meta_prefix(meta_headers) @s3.interface.put_link(@name, key.to_s, nil, expires, meta.merge(headers)) end # Generate link to GET key data. # # bucket.get('logs/today/1.log', 1.hour) # def get(key, expires=nil, headers={}) @s3.interface.get_link(@name, key.to_s, expires, headers) end # Generate link to delete bucket. # # bucket.delete(2.hour) # def delete(expires=nil, headers={}) @s3.interface.delete_bucket_link(@name, expires, headers) end end class Key attr_reader :bucket, :name def to_s @name end # Return a full S# name (bucket/key). # # key.full_name #=> 'my_awesome_bucket/cool_key' # def full_name(separator='/') "#{@bucket.to_s}#{separator}#{@name}" end # Return a public link to key. # # key.public_link #=> 'https://s3.amazonaws.com:443/my_awesome_bucket/cool_key' # def public_link params = @bucket.s3.interface.params "#{params[:protocol]}://#{params[:server]}:#{params[:port]}/#{full_name('/')}" end def initialize(bucket, name, meta_headers={}) @bucket = bucket @name = name.to_s @meta_headers = meta_headers raise 'Key name can not be empty.' if Aws::Utils.blank?(@name) end # Generate link to PUT key data. # # puts bucket.put('logs/today/1.log', '123', 2.hour) #=> https://s3.amazonaws.com:443/my_awesome_bucket/logs%2Ftoday%2F1.log?Signature=B...D&Expires=1180820032&AWSAccessKeyId=1...2 # def put(expires=nil, headers={}) @bucket.put(@name.to_s, @meta_headers, expires, headers) end # Generate link to GET key data. # # bucket.get('logs/today/1.log', 1.hour) #=> https://s3.amazonaws.com:443/my_awesome_bucket/logs%2Ftoday%2F1.log?Signature=h...M%3D&Expires=1180820032&AWSAccessKeyId=1...2 # def get(expires=nil, headers={}) @bucket.s3.interface.get_link(@bucket.to_s, @name, expires, headers) end # Generate link to delete key. # # bucket.delete(2.hour) #=> https://s3.amazonaws.com:443/my_awesome_bucket/logs%2Ftoday%2F1.log?Signature=4...D&Expires=1180820032&AWSAccessKeyId=1...2 # def delete(expires=nil, headers={}) @bucket.s3.interface.delete_link(@bucket.to_s, @name, expires, headers) end # Generate link to head key. # # bucket.head(2.hour) #=> https://s3.amazonaws.com:443/my_awesome_bucket/logs%2Ftoday%2F1.log?Signature=4...D&Expires=1180820032&AWSAccessKeyId=1...2 # def head(expires=nil, headers={}) @bucket.s3.interface.head_link(@bucket.to_s, @name, expires, headers) end end end end aws-2.10.2/lib/s3/key.rb0000644000175000017500000002344412511515441013320 0ustar tnnntnnnmodule Aws class S3::Key attr_reader :bucket, :name, :last_modified, :e_tag, :size, :storage_class, :owner attr_accessor :headers, :meta_headers attr_writer :data # Separate Amazon meta headers from other headers def self.split_meta(headers) #:nodoc: hash = headers.dup meta = {} hash.each do |key, value| if key[/^#{S3Interface::AMAZON_METADATA_PREFIX}/] meta[key.gsub(S3Interface::AMAZON_METADATA_PREFIX, '')] = value hash.delete(key) end end [hash, meta] end def self.add_meta_prefix(meta_headers, prefix=S3Interface::AMAZON_METADATA_PREFIX) meta = {} meta_headers.each do |meta_header, value| if meta_header[/#{prefix}/] meta[meta_header] = value else meta["#{S3Interface::AMAZON_METADATA_PREFIX}#{meta_header}"] = value end end meta end # Create a new Key instance, but do not create the actual key. # The +name+ is a +String+. # Returns a new Key instance. # # key = Aws::S3::Key.create(bucket, 'logs/today/1.log') #=> # # key.exists? #=> true | false # key.put('Woohoo!') #=> true # key.exists? #=> true # def self.create(bucket, name, data=nil, meta_headers={}) new(bucket, name, data, {}, meta_headers) end # Create a new Key instance, but do not create the actual key. # In normal use this method should not be called directly. # Use Aws::S3::Key.create or bucket.key() instead. # def initialize(bucket, name, data=nil, headers={}, meta_headers={}, last_modified=nil, e_tag=nil, size=nil, storage_class=nil, owner=nil) raise 'Bucket must be a Bucket instance.' unless bucket.is_a?(S3::Bucket) @bucket = bucket @name = name @data = data @e_tag = e_tag @size = size.to_i @storage_class = storage_class @owner = owner @last_modified = last_modified if @last_modified && !@last_modified.is_a?(Time) @last_modified = Time.parse(@last_modified) end @headers, @meta_headers = self.class.split_meta(headers) @meta_headers.merge!(meta_headers) end # Return key name as a String. # # key = Aws::S3::Key.create(bucket, 'logs/today/1.log') #=> # # puts key #=> 'logs/today/1.log' # def to_s @name.to_s end # Return the full S3 path to this key (bucket/key). # # key.full_name #=> 'my_awesome_bucket/cool_key' # def full_name(separator='/') "#{@bucket.to_s}#{separator}#{@name}" end # Return a public link to a key. # # key.public_link #=> 'https://s3.amazonaws.com:443/my_awesome_bucket/cool_key' # def public_link params = @bucket.s3.interface.params "#{params[:protocol]}://#{params[:server]}:#{params[:port]}/#{full_name('/')}" end # Return Key data. Retrieve this data from Amazon if it is the first time call. # TODO TRB 6/19/07 What does the above mean? Clarify. # def data get if !@data and exists? @data end # Retrieve object data and attributes from Amazon. # Returns a +String+. # def get(headers={}, &block) response = @bucket.s3.interface.get(@bucket.name, @name, headers, &block) @data = response[:object] @headers, @meta_headers = self.class.split_meta(response[:headers]) # refresh(false) Holy moly, this was doing two extra hits to s3 for making 3 hits for every get!! @data end # Store object data on S3. # Parameter +data+ is a +String+ or S3Object instance. # Returns +true+. # # key = Aws::S3::Key.create(bucket, 'logs/today/1.log') # key.data = 'Qwerty' # key.put #=> true # ... # key.put('Olala!') #=> true # def put(data=nil, perms=nil, headers={}) headers['x-amz-acl'] = perms if perms @data = data || @data meta = self.class.add_meta_prefix(@meta_headers) @bucket.s3.interface.put(@bucket.name, @name, @data, meta.merge(headers)) end # Rename an object. Returns new object name. # # key = Aws::S3::Key.create(bucket, 'logs/today/1.log') #=> # # key.rename('logs/today/2.log') #=> 'logs/today/2.log' # puts key.name #=> 'logs/today/2.log' # key.exists? #=> true # def rename(new_name) @bucket.s3.interface.rename(@bucket.name, @name, new_name) @name = new_name end # Create an object copy. Returns a destination Aws::S3::Key instance. # # # Key instance as destination # key1 = Aws::S3::Key.create(bucket, 'logs/today/1.log') #=> # # key2 = Aws::S3::Key.create(bucket, 'logs/today/2.log') #=> # # key1.put('Olala!') #=> true # key1.copy(key2) #=> # # key1.exists? #=> true # key2.exists? #=> true # puts key2.data #=> 'Olala!' # # # String as destination # key = Aws::S3::Key.create(bucket, 'logs/today/777.log') #=> # # key.put('Olala!') #=> true # new_key = key.copy('logs/today/888.log') #=> # # key.exists? #=> true # new_key.exists? #=> true # def copy(new_key_or_name) new_key_or_name = S3::Key.create(@bucket, new_key_or_name.to_s) unless new_key_or_name.is_a?(S3::Key) @bucket.s3.interface.copy(@bucket.name, @name, new_key_or_name.bucket.name, new_key_or_name.name) new_key_or_name end # Move an object to other location. Returns a destination Aws::S3::Key instance. # # # Key instance as destination # key1 = Aws::S3::Key.create(bucket, 'logs/today/1.log') #=> # # key2 = Aws::S3::Key.create(bucket, 'logs/today/2.log') #=> # # key1.put('Olala!') #=> true # key1.move(key2) #=> # # key1.exists? #=> false # key2.exists? #=> true # puts key2.data #=> 'Olala!' # # # String as destination # key = Aws::S3::Key.create(bucket, 'logs/today/777.log') #=> # # key.put('Olala!') #=> true # new_key = key.move('logs/today/888.log') #=> # # key.exists? #=> false # new_key.exists? #=> true # def move(new_key_or_name) new_key_or_name = S3::Key.create(@bucket, new_key_or_name.to_s) unless new_key_or_name.is_a?(S3::Key) @bucket.s3.interface.move(@bucket.name, @name, new_key_or_name.bucket.name, new_key_or_name.name) new_key_or_name end # Retrieve key info from bucket and update attributes. # Refresh meta-headers (by calling +head+ method) if +head+ is set. # Returns +true+ if the key exists in bucket and +false+ otherwise. # # key = Aws::S3::Key.create(bucket, 'logs/today/1.log') # key.e_tag #=> nil # key.meta_headers #=> {} # key.refresh #=> true # key.e_tag #=> '12345678901234567890bf11094484b6' # key.meta_headers #=> {"family"=>"qwerty", "name"=>"asdfg"} # def refresh(head=true) new_key = @bucket.key(self) @last_modified = new_key.last_modified @e_tag = new_key.e_tag @size = new_key.size @storage_class = new_key.storage_class @owner = new_key.owner if @last_modified self.head true else @headers = @meta_headers = {} false end end # Updates headers and meta-headers from S3. # Returns +true+. # # key.meta_headers #=> {"family"=>"qwerty"} # key.head #=> true # key.meta_headers #=> {"family"=>"qwerty", "name"=>"asdfg"} # def head @headers, @meta_headers = self.class.split_meta(@bucket.s3.interface.head(@bucket, @name)) true end # Reload meta-headers only. Returns meta-headers hash. # # key.reload_meta #=> {"family"=>"qwerty", "name"=>"asdfg"} # def reload_meta @meta_headers = self.class.split_meta(@bucket.s3.interface.head(@bucket, @name)).last end # Replace meta-headers by new hash at S3. Returns new meta-headers hash. # # key.reload_meta #=> {"family"=>"qwerty", "name"=>"asdfg"} # key.save_meta #=> {"family"=>"oops", "race" => "troll"} # key.reload_meta #=> {"family"=>"oops", "race" => "troll"} # def save_meta(meta_headers) meta = self.class.add_meta_prefix(meta_headers) @bucket.s3.interface.copy(@bucket.name, @name, @bucket.name, @name, :replace, meta) @meta_headers = self.class.split_meta(meta)[1] end # Check for existence of the key in the given bucket. # Returns +true+ or +false+. # # key = Aws::S3::Key.create(bucket,'logs/today/1.log') # key.exists? #=> false # key.put('Woohoo!') #=> true # key.exists? #=> true # def exists? @bucket.key(self).last_modified ? true : false end # Remove key from bucket. # Returns +true+. # # key.delete #=> true # def delete raise 'Key name must be specified.' if Aws::Utils.blank?(@name) @bucket.s3.interface.delete(@bucket, @name) end # Return a list of grantees. # def grantees S3::Grantee::grantees(self) end end end aws-2.10.2/lib/s3/s3_interface.rb0000644000175000017500000016453612511515441015105 0ustar tnnntnnn# # Copyright (c) 2007-2008 RightScale Inc # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # module Aws class S3Interface < AwsBase USE_100_CONTINUE_PUT_SIZE = 1_000_000 include AwsBaseInterface extend AwsBaseInterface::ClassMethods DEFAULT_HOST = 's3.amazonaws.com' DEFAULT_PORT = 443 DEFAULT_PROTOCOL = 'https' DEFAULT_SERVICE = '/' REQUEST_TTL = 30 DEFAULT_EXPIRES_AFTER = 1 * 24 * 60 * 60 # One day's worth of seconds ONE_YEAR_IN_SECONDS = 365 * 24 * 60 * 60 AMAZON_HEADER_PREFIX = 'x-amz-' AMAZON_METADATA_PREFIX = 'x-amz-meta-' def self.connection_name :s3_connection end @@bench = AwsBenchmarkingBlock.new def self.bench @@bench end def self.bench_xml @@bench.xml end def self.bench_s3 @@bench.service end # Creates new RightS3 instance. # # s3 = Aws::S3Interface.new('1E3GDYEOGFJPIT7XXXXXX','hgTHt68JY07JKUY08ftHYtERkjgtfERn57XXXXXX', {:multi_thread => true, :logger => Logger.new('/tmp/x.log')}) #=> # # # Params is a hash: # # {:server => 's3.amazonaws.com' # Amazon service host: 's3.amazonaws.com'(default) # :port => 443 # Amazon service port: 80 or 443(default) # :protocol => 'https' # Amazon service protocol: 'http' or 'https'(default) # :connection_mode => :default # options are # :default (will use best known safe (as in won't need explicit close) option, may change in the future) # :per_request (opens and closes a connection on every request) # :single (one thread across entire app) # :per_thread (one connection per thread) # :logger => Logger Object} # Logger instance: logs to STDOUT if omitted } # def initialize(aws_access_key_id=nil, aws_secret_access_key=nil, params={}) init({:name => 'S3', :default_host => ENV['S3_URL'] ? URI.parse(ENV['S3_URL']).host : DEFAULT_HOST, :default_port => ENV['S3_URL'] ? URI.parse(ENV['S3_URL']).port : DEFAULT_PORT, :default_service => ENV['S3_URL'] ? URI.parse(ENV['S3_URL']).path : DEFAULT_SERVICE, :default_protocol => ENV['S3_URL'] ? URI.parse(ENV['S3_URL']).scheme : DEFAULT_PROTOCOL}, aws_access_key_id || ENV['AWS_ACCESS_KEY_ID'], aws_secret_access_key || ENV['AWS_SECRET_ACCESS_KEY'], params) end #----------------------------------------------------------------- # Requests #----------------------------------------------------------------- # Produces canonical string for signing. def canonical_string(method, path, headers={}, expires=nil) # :nodoc: s3_headers = {} headers.each do |key, value| key = key.downcase s3_headers[key] = value.join("").strip if key[/^#{AMAZON_HEADER_PREFIX}|^content-md5$|^content-type$|^date$/o] end s3_headers['content-type'] ||= '' s3_headers['content-md5'] ||= '' s3_headers['date'] = '' if s3_headers.has_key? 'x-amz-date' s3_headers['date'] = expires if expires # prepare output string out_string = "#{method}\n" s3_headers.sort { |a, b| a[0] <=> b[0] }.each do |key, value| out_string << (key[/^#{AMAZON_HEADER_PREFIX}/o] ? "#{key}:#{value}\n" : "#{value}\n") end # ignore everything after the question mark... out_string << path.gsub(/\?.*$/, '') # ...unless there is an acl or torrent parameter out_string << '?acl' if path[/[&?]acl($|&|=)/] out_string << '?policy' if path[/[&?]policy($|&|=)/] out_string << '?torrent' if path[/[&?]torrent($|&|=)/] out_string << '?location' if path[/[&?]location($|&|=)/] out_string << '?logging' if path[/[&?]logging($|&|=)/] # this one is beta, no support for now #... also deal with params for multipart uploads API out_string << '?uploads' if path[/\?uploads$/] if path[/\?uploadId=(.*)$/] out_string << "?uploadId=#{$1}" end if path[/\?partNumber=(\d*)&uploadId=(.*)$/] out_string << "?partNumber=#{$1}&uploadId=#{$2}" end out_string end # http://docs.amazonwebservices.com/AmazonS3/2006-03-01/index.html?BucketRestrictions.html def is_dns_bucket?(bucket_name) bucket_name = bucket_name.to_s return nil unless (3..63) === bucket_name.size bucket_name.split('.').each do |component| return nil unless component[/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/] end true end def fetch_request_params(headers) #:nodoc: # default server to use server = @params[:server] service = @params[:service].to_s service.chop! if service[%r{/$}] # remove trailing '/' from service # extract bucket name and check it's dns compatibility headers[:url].to_s[%r{^([a-z0-9._-]*)(/[^?]*)?(\?.+)?}i] bucket_name, key_path, params_list = $1, $2, $3 # select request model if is_dns_bucket?(bucket_name) and !@params[:virtual_hosting] # fix a path server = "#{bucket_name}.#{server}" key_path ||= '/' path = "#{service}#{key_path}#{params_list}" else path = "#{service}/#{bucket_name}#{key_path}#{params_list}" end path_to_sign = "#{service}/#{bucket_name}#{key_path}#{params_list}" [server, path, path_to_sign] end # Generates request hash for REST API. # Assumes that headers[:url] is URL encoded (use CGI::escape) def generate_rest_request(method, headers) # :nodoc: # calculate request data server, path, path_to_sign = fetch_request_params(headers) data = headers[:data] # remove unset(==optional) and symbolyc keys headers.each { |key, value| headers.delete(key) if (value.nil? || key.is_a?(Symbol)) } # headers['content-type'] ||= '' headers['date'] = Time.now.httpdate # create request request = Net::HTTP.const_get(method.capitalize).new(path) request.body = data if data # set request headers and meta headers headers.each { |key, value| request[key.to_s] = value } #generate auth strings auth_string = canonical_string(request.method, path_to_sign, request.to_hash) signature = Utils::sign(@aws_secret_access_key, auth_string) # set other headers request['Authorization'] = "AWS #{@aws_access_key_id}:#{signature}" # prepare output hash {:request => request, :server => server, :port => @params[:port], :protocol => @params[:protocol]} end # Sends request to Amazon and parses the response. # Raises AwsError if any banana happened. def request_info(request, parser, options={}, &block) # :nodoc: # request_info2(request, parser, @params, :s3_connection, @logger, @@bench, options, &block) request_info3(self, request, parser, options, &block) end # Returns an array of customer's buckets. Each item is a +hash+. # # s3.list_all_my_buckets #=> # [{:owner_id => "00000000009314cc309ffe736daa2b264357476c7fea6efb2c3347ac3ab2792a", # :owner_display_name => "root", # :name => "bucket_name", # :creation_date => "2007-04-19T18:47:43.000Z"}, ..., {...}] # def list_all_my_buckets(headers={}) req_hash = generate_rest_request('GET', headers.merge(:url=>'')) request_info(req_hash, S3ListAllMyBucketsParser.new(:logger => @logger)) rescue on_exception end # Creates new bucket. Returns +true+ or an exception. # # # create a bucket at American server # s3.create_bucket('my-awesome-bucket-us') #=> true # # create a bucket at European server # s3.create_bucket('my-awesome-bucket-eu', :location => :eu) #=> true # def create_bucket(bucket, headers={}) data = nil unless Aws::Utils.blank?(headers[:location]) # data = "#{headers[:location].to_s.upcase}" location = headers[:location].to_s location.upcase! if location == 'eu' data = "#{location}" end req_hash = generate_rest_request('PUT', headers.merge(:url=>bucket, :data => data)) request_info(req_hash, RightHttp2xxParser.new) rescue Exception => e # if the bucket exists AWS returns an error for the location constraint interface. Drop it e.is_a?(Aws::AwsError) && e.message.include?('BucketAlreadyOwnedByYou') ? true : on_exception end # Retrieve bucket location # # s3.create_bucket('my-awesome-bucket-us') #=> true # puts s3.bucket_location('my-awesome-bucket-us') #=> '' (Amazon's default value assumed) # # s3.create_bucket('my-awesome-bucket-eu', :location => :eu) #=> true # puts s3.bucket_location('my-awesome-bucket-eu') #=> 'EU' # def bucket_location(bucket, headers={}) req_hash = generate_rest_request('GET', headers.merge(:url=>"#{bucket}?location")) request_info(req_hash, S3BucketLocationParser.new) rescue on_exception end # Retrieves the logging configuration for a bucket. # Returns a hash of {:enabled, :targetbucket, :targetprefix} # # s3.interface.get_logging_parse(:bucket => "asset_bucket") # => {:enabled=>true, :targetbucket=>"mylogbucket", :targetprefix=>"loggylogs/"} # # def get_logging_parse(params) Utils.mandatory_arguments([:bucket], params) Utils.allow_only([:bucket, :headers], params) params[:headers] = {} unless params[:headers] req_hash = generate_rest_request('GET', params[:headers].merge(:url=>"#{params[:bucket]}?logging")) request_info(req_hash, S3LoggingParser.new) rescue on_exception end # Sets logging configuration for a bucket from the XML configuration document. # params: # :bucket # :xmldoc def put_logging(params) Utils.mandatory_arguments([:bucket, :xmldoc], params) Utils.allow_only([:bucket, :xmldoc, :headers], params) params[:headers] = {} unless params[:headers] req_hash = generate_rest_request('PUT', params[:headers].merge(:url=>"#{params[:bucket]}?logging", :data => params[:xmldoc])) request_info(req_hash, S3TrueParser.new) rescue on_exception end # Deletes new bucket. Bucket must be empty! Returns +true+ or an exception. # # s3.delete_bucket('my_awesome_bucket') #=> true # # See also: force_delete_bucket method # def delete_bucket(bucket, headers={}) req_hash = generate_rest_request('DELETE', headers.merge(:url=>bucket)) request_info(req_hash, RightHttp2xxParser.new) rescue on_exception end # Returns an array of bucket's keys. Each array item (key data) is a +hash+. # # s3.list_bucket('my_awesome_bucket', { 'prefix'=>'t', 'marker'=>'', 'max-keys'=>5, delimiter=>'' }) #=> # [{:key => "test1", # :last_modified => "2007-05-18T07:00:59.000Z", # :owner_id => "00000000009314cc309ffe736daa2b264357476c7fea6efb2c3347ac3ab2792a", # :owner_display_name => "root", # :e_tag => "000000000059075b964b07152d234b70", # :storage_class => "STANDARD", # :size => 3, # :service=> {'is_truncated' => false, # 'prefix' => "t", # 'marker' => "", # 'name' => "my_awesome_bucket", # 'max-keys' => "5"}, ..., {...}] # def list_bucket(bucket, options={}, headers={}) internal_bucket = bucket.dup unless options.nil? || options.empty? internal_bucket << '?' internal_bucket << options.map { |k, v| "#{k.to_s}=#{CGI::escape v.to_s}" }.join('&') end req_hash = generate_rest_request('GET', headers.merge(:url=>internal_bucket)) request_info(req_hash, S3ListBucketParser.new(:logger => @logger)) rescue on_exception end # Incrementally list the contents of a bucket. Yields the following hash to a block: # s3.incrementally_list_bucket('my_awesome_bucket', { 'prefix'=>'t', 'marker'=>'', 'max-keys'=>5, delimiter=>'' }) yields # { # :name => 'bucketname', # :prefix => 'subfolder/', # :marker => 'fileN.jpg', # :max_keys => 234, # :delimiter => '/', # :is_truncated => true, # :next_marker => 'fileX.jpg', # :contents => [ # { :key => "file1", # :last_modified => "2007-05-18T07:00:59.000Z", # :e_tag => "000000000059075b964b07152d234b70", # :size => 3, # :storage_class => "STANDARD", # :owner_id => "00000000009314cc309ffe736daa2b264357476c7fea6efb2c3347ac3ab2792a", # :owner_display_name => "root" # }, { :key, ...}, ... {:key, ...} # ] # :common_prefixes => [ # "prefix1", # "prefix2", # ..., # "prefixN" # ] # } def incrementally_list_bucket(bucket, options={}, headers={}, &block) internal_options = (options.map {|k,v| [k.to_sym, v] }).inject({}) {|h, ar| h[ar[0]] = ar[1]; h} begin internal_bucket = bucket.dup unless internal_options.nil? || internal_options.empty? internal_bucket << '?' internal_bucket << internal_options.map { |k, v| "#{k.to_s}=#{CGI::escape v.to_s}" }.join('&') end req_hash = generate_rest_request('GET', headers.merge(:url=>internal_bucket)) response = request_info(req_hash, S3ImprovedListBucketParser.new(:logger => @logger)) there_are_more_keys = response[:is_truncated] if (there_are_more_keys) internal_options[:marker] = decide_marker(response) total_results = response[:contents].length + response[:common_prefixes].length internal_options[:'max-keys'] ? (internal_options[:'max-keys'] -= total_results) : nil end yield response end while there_are_more_keys && under_max_keys(internal_options) true rescue on_exception end private def decide_marker(response) return response[:next_marker].dup if response[:next_marker] last_key = response[:contents].last[:key] last_prefix = response[:common_prefixes].last if (!last_key) return nil if (!last_prefix) last_prefix.dup elsif (!last_prefix) last_key.dup else last_key > last_prefix ? last_key.dup : last_prefix.dup end end def under_max_keys(internal_options) internal_options[:'max-keys'] ? internal_options[:'max-keys'] > 0 : true end public # Saves object to Amazon. Returns +true+ or an exception. # Any header starting with AMAZON_METADATA_PREFIX is considered # user metadata. It will be stored with the object and returned # when you retrieve the object. The total size of the HTTP # request, not including the body, must be less than 4 KB. # # s3.put('my_awesome_bucket', 'log/current/1.log', 'Ola-la!', 'x-amz-meta-family'=>'Woho556!') #=> true # # This method is capable of 'streaming' uploads; that is, it can upload # data from a file or other IO object without first reading all the data # into memory. This is most useful for large PUTs - it is difficult to read # a 2 GB file entirely into memory before sending it to S3. # To stream an upload, pass an object that responds to 'read' (like the read # method of IO) and to either 'lstat' or 'size'. For files, this means # streaming is enabled by simply making the call: # # s3.put(bucket_name, 'S3keyname.forthisfile', File.open('localfilename.dat')) # # If the IO object you wish to stream from responds to the read method but # doesn't implement lstat or size, you can extend the object dynamically # to implement these methods, or define your own class which defines these # methods. Be sure that your class returns 'nil' from read() after having # read 'size' bytes. Otherwise S3 will drop the socket after # 'Content-Length' bytes have been uploaded, and HttpConnection will # interpret this as an error. # # This method now supports very large PUTs, where very large # is > 2 GB. # # For Win32 users: Files and IO objects should be opened in binary mode. If # a text mode IO object is passed to PUT, it will be converted to binary # mode. # def put(bucket, key, data=nil, headers={}) # On Windows, if someone opens a file in text mode, we must reset it so # to binary mode for streaming to work properly if (data.respond_to?(:binmode)) data.binmode end if data.is_a?(String) data = StringIO.new(data) # puts "encoding = #{data.external_encoding} - #{data.internal_encoding}" # data.set_encoding("UTF-8") # puts "encoding = #{data.external_encoding} - #{data.internal_encoding}" end data_size = data.respond_to?(:lstat) ? data.lstat.size : # data.respond_to?(:bytesize) ? data.bytesize : (data.respond_to?(:size) ? data.size : 0) # puts 'data_size=' + data_size.to_s if (data_size >= USE_100_CONTINUE_PUT_SIZE) headers['expect'] = '100-continue' end req_hash = generate_rest_request('PUT', headers.merge(:url =>"#{bucket}/#{CGI::escape key}", :data =>data, 'Content-Length' => data_size.to_s)) request_info(req_hash, RightHttp2xxParser.new) rescue on_exception end # New experimental API for uploading objects, introduced in Aws 1.8.1. # store_object is similar in function to the older function put, but returns the full response metadata. It also allows for optional verification # of object md5 checksums on upload. Parameters are passed as hash entries and are checked for completeness as well as for spurious arguments. # The hash of the response headers contains useful information like the Amazon request ID and the object ETag (MD5 checksum). # # If the optional :md5 argument is provided, store_object verifies that the given md5 matches the md5 returned by S3. The :verified_md5 field in the response hash is # set true or false depending on the outcome of this check. If no :md5 argument is given, :verified_md5 will be false in the response. # # The optional argument of :headers allows the caller to specify arbitrary request header values. # # s3.store_object(:bucket => "foobucket", :key => "foo", :md5 => "a507841b1bc8115094b00bbe8c1b2954", :data => "polemonium" ) # => {"x-amz-id-2"=>"SVsnS2nfDaR+ixyJUlRKM8GndRyEMS16+oZRieamuL61pPxPaTuWrWtlYaEhYrI/", # "etag"=>"\"a507841b1bc8115094b00bbe8c1b2954\"", # "date"=>"Mon, 29 Sep 2008 18:57:46 GMT", # :verified_md5=>true, # "x-amz-request-id"=>"63916465939995BA", # "server"=>"AmazonS3", # "content-length"=>"0"} # # s3.store_object(:bucket => "foobucket", :key => "foo", :data => "polemonium" ) # => {"x-amz-id-2"=>"MAt9PLjgLX9UYJ5tV2fI/5dBZdpFjlzRVpWgBDpvZpl+V+gJFcBMW2L+LBstYpbR", # "etag"=>"\"a507841b1bc8115094b00bbe8c1b2954\"", # "date"=>"Mon, 29 Sep 2008 18:58:56 GMT", # :verified_md5=>false, # "x-amz-request-id"=>"3B25A996BC2CDD3B", # "server"=>"AmazonS3", # "content-length"=>"0"} def store_object(params) Utils.allow_only([:bucket, :key, :data, :headers, :md5], params) Utils.mandatory_arguments([:bucket, :key, :data], params) params[:headers] = {} unless params[:headers] params[:data].binmode if (params[:data].respond_to?(:binmode)) # On Windows, if someone opens a file in text mode, we must reset it to binary mode for streaming to work properly if (params[:data].respond_to?(:lstat) && params[:data].lstat.size >= USE_100_CONTINUE_PUT_SIZE) || (params[:data].respond_to?(:size) && params[:data].size >= USE_100_CONTINUE_PUT_SIZE) params[:headers]['expect'] = '100-continue' end req_hash = generate_rest_request('PUT', params[:headers].merge(:url=>"#{params[:bucket]}/#{CGI::escape params[:key]}", :data=>params[:data])) resp = request_info(req_hash, S3HttpResponseHeadParser.new) if (params[:md5]) resp[:verified_md5] = (resp['etag'].gsub(/\"/, '') == params[:md5]) ? true : false else resp[:verified_md5] = false end resp rescue on_exception end # Identical in function to store_object, but requires verification that the returned ETag is identical to the checksum passed in by the user as the 'md5' argument. # If the check passes, returns the response metadata with the "verified_md5" field set true. Raises an exception if the checksums conflict. # This call is implemented as a wrapper around store_object and the user may gain different semantics by creating a custom wrapper. # # s3.store_object_and_verify(:bucket => "foobucket", :key => "foo", :md5 => "a507841b1bc8115094b00bbe8c1b2954", :data => "polemonium" ) # => {"x-amz-id-2"=>"IZN3XsH4FlBU0+XYkFTfHwaiF1tNzrm6dIW2EM/cthKvl71nldfVC0oVQyydzWpb", # "etag"=>"\"a507841b1bc8115094b00bbe8c1b2954\"", # "date"=>"Mon, 29 Sep 2008 18:38:32 GMT", # :verified_md5=>true, # "x-amz-request-id"=>"E8D7EA4FE00F5DF7", # "server"=>"AmazonS3", # "content-length"=>"0"} # # s3.store_object_and_verify(:bucket => "foobucket", :key => "foo", :md5 => "a507841b1bc8115094b00bbe8c1b2953", :data => "polemonium" ) # Aws::AwsError: Uploaded object failed MD5 checksum verification: {"x-amz-id-2"=>"HTxVtd2bf7UHHDn+WzEH43MkEjFZ26xuYvUzbstkV6nrWvECRWQWFSx91z/bl03n", # "etag"=>"\"a507841b1bc8115094b00bbe8c1b2954\"", # "date"=>"Mon, 29 Sep 2008 18:38:41 GMT", # :verified_md5=>false, # "x-amz-request-id"=>"0D7ADE09F42606F2", # "server"=>"AmazonS3", # "content-length"=>"0"} def store_object_and_verify(params) Utils.mandatory_arguments([:md5], params) r = store_object(params) r[:verified_md5] ? (return r) : (raise AwsError.new("Uploaded object failed MD5 checksum verification: #{r.inspect}")) end # Initiates a multipart upload and returns an upload ID or an exception # http://docs.amazonwebservices.com/AmazonS3/latest/API/mpUploadInitiate.html # # s3.initiate_multipart('my_awesome_bucket', 'hugeObject') => WL7dk8sqbtk3Rg641HHWaNeG6RxI4fzS8V0YvuQAfs5Hbk6WNZOU1z_AhGv # # The returned uploadId must be retained for use in uploading parts; see # http://docs.amazonwebservices.com/AmazonS3/latest/dev/mpuoverview.html # def initiate_multipart(bucket, key, headers={}) req_hash = generate_rest_request('POST', headers.merge(:url =>"#{bucket}/#{CGI::escape key}?uploads")) request_info(req_hash, S3InitiateMultipartUploadParser.new) rescue on_exception end # Uploads a part in a multipart upload - returns an Etag for the part or an exception # http://docs.amazonwebservices.com/AmazonS3/latest/API/mpUploadUploadPart.html # # Among the parameters required, clients must supply the uploadId (obtained from the initiate_multipart method call) as # well as the partNumber for this part (user-specified, determining the sequence for reassembly). # # s3.upload_part('my_awesome_bucket', 'hugeObject', "WL7dk8sqbtk3Rg641HHWaNeG6RxI", "2", File.open('localfilename.dat')) # => "b54357faf0632cce46e942fa68356b38" # # The return Etag must be retained for use in the completion of the multipart upload; see # http://docs.amazonwebservices.com/AmazonS3/latest/dev/mpuoverview.html # def upload_part(bucket, key, uploadId, partNumber, data, headers={}) if (data.respond_to?(:binmode)) data.binmode end if data.is_a?(String) data = StringIO.new(data) end data_size = data.respond_to?(:lstat) ? data.lstat.size : (data.respond_to?(:size) ? data.size : 0) if (data_size >= USE_100_CONTINUE_PUT_SIZE) headers['expect'] = '100-continue' end req_hash = generate_rest_request('PUT', headers.merge(:url =>"#{bucket}/#{CGI::escape key}?partNumber=#{partNumber}&uploadId=#{uploadId}", :data => data, 'Content-Length' => data_size.to_s)) request_info(req_hash, S3UploadPartParser.new) rescue on_exception end # Completes a multipart upload, returning true or an exception # http://docs.amazonwebservices.com/AmazonS3/latest/API/mpUploadComplete.html # # Clients must specify the uploadId (obtained from the initiate_multipart call) and the reassembly manifest hash # which specifies the each partNumber corresponding etag (obtained from the upload_part call): # # s3.complete_multipart('my_awesome_bucket', 'hugeObject', "WL7dk8sqbtk3Rg641HHWaNeG6RxI", # {"1"=>"a54357aff0632cce46d942af68356b38", "2"=>"0c78aef83f66abc1fa1e8477f296d394"}) => true # # See http://docs.amazonwebservices.com/AmazonS3/latest/dev/mpuoverview.html # def complete_multipart(bucket, key, uploadId, manifest_hash, headers={}) parts_string = manifest_hash.inject("") do |res, (part,etag)| res<< < #{part} "#{etag}" END_PARTS res end data = < #{parts_string} EOS req_hash = generate_rest_request('POST', headers.merge(:url => "#{bucket}/#{CGI::escape key}?uploadId=#{uploadId}", :data => data)) request_info(req_hash, RightHttp2xxParser.new) rescue on_exception end # List parts of a multipart upload, returning a hash or an exception # http://docs.amazonwebservices.com/AmazonS3/latest/API/mpUploadListParts.html # # response looks like: # # { :bucket=>"mariosFoo_awesome_test_bucket_000A1", # :key=>"mariosFoosegmented", # :upload_id=>"jQKX7JdJBTrbvLn9apUPIXkt14FHdp6nMZVg--" # :parts=> [ {:part_number=>"1", :last_modified=>"2012-10-30T15:06:28.000Z", # :etag=>"\"78f871f6f01673a4aca05b1f8e26df08\"", :size=>"6276589"}, # {:part_number=>"2", :last_modified=>"2012-10-30T15:08:22.000Z", # :etag=>"\"e7b94a1e959ca066026da3ec63aad321\"", :size=>"7454095"}] } # # Clients must specify the uploadId (obtained from the initiate_multipart call) # # s3.list_parts('my_awesome_bucket', 'hugeObject', "WL7dk8sqbtk3Rg641HHWaNeG6RxI", # # See http://docs.amazonwebservices.com/AmazonS3/latest/dev/mpuoverview.html # def list_parts(bucket, key, uploadId, headers={}) req_hash = generate_rest_request('GET', headers.merge(:url=>"#{bucket}/#{CGI::escape key}?uploadId=#{uploadId}")) request_info(req_hash, S3ListMultipartPartsParser.new) end # Retrieves object data from Amazon. Returns a +hash+ or an exception. # # s3.get('my_awesome_bucket', 'log/curent/1.log') #=> # # {:object => "Ola-la!", # :headers => {"last-modified" => "Wed, 23 May 2007 09:08:04 GMT", # "content-type" => "", # "etag" => "\"000000000096f4ee74bc4596443ef2a4\"", # "date" => "Wed, 23 May 2007 09:08:03 GMT", # "x-amz-id-2" => "ZZZZZZZZZZZZZZZZZZZZ1HJXZoehfrS4QxcxTdNGldR7w/FVqblP50fU8cuIMLiu", # "x-amz-meta-family" => "Woho556!", # "x-amz-request-id" => "0000000C246D770C", # "server" => "AmazonS3", # "content-length" => "7"}} # # If a block is provided, yields incrementally to the block as # the response is read. For large responses, this function is ideal as # the response can be 'streamed'. The hash containing header fields is # still returned. # Example: # foo = File.new('./chunder.txt', File::CREAT|File::RDWR) # rhdr = s3.get('aws-test', 'Cent5V1_7_1.img.part.00') do |chunk| # foo.write(chunk) # end # foo.close # def get(bucket, key, headers={}, &block) req_hash = generate_rest_request('GET', headers.merge(:url=>"#{bucket}/#{CGI::escape key}")) request_info(req_hash, S3HttpResponseBodyParser.new, &block) rescue on_exception end # New experimental API for retrieving objects, introduced in Aws 1.8.1. # retrieve_object is similar in function to the older function get. It allows for optional verification # of object md5 checksums on retrieval. Parameters are passed as hash entries and are checked for completeness as well as for spurious arguments. # # If the optional :md5 argument is provided, retrieve_object verifies that the given md5 matches the md5 returned by S3. The :verified_md5 field in the response hash is # set true or false depending on the outcome of this check. If no :md5 argument is given, :verified_md5 will be false in the response. # # The optional argument of :headers allows the caller to specify arbitrary request header values. # Mandatory arguments: # :bucket - the bucket in which the object is stored # :key - the object address (or path) within the bucket # Optional arguments: # :headers - hash of additional HTTP headers to include with the request # :md5 - MD5 checksum against which to verify the retrieved object # # s3.retrieve_object(:bucket => "foobucket", :key => "foo") # => {:verified_md5=>false, # :headers=>{"last-modified"=>"Mon, 29 Sep 2008 18:58:56 GMT", # "x-amz-id-2"=>"2Aj3TDz6HP5109qly//18uHZ2a1TNHGLns9hyAtq2ved7wmzEXDOPGRHOYEa3Qnp", # "content-type"=>"", # "etag"=>"\"a507841b1bc8115094b00bbe8c1b2954\"", # "date"=>"Tue, 30 Sep 2008 00:52:44 GMT", # "x-amz-request-id"=>"EE4855DE27A2688C", # "server"=>"AmazonS3", # "content-length"=>"10"}, # :object=>"polemonium"} # # s3.retrieve_object(:bucket => "foobucket", :key => "foo", :md5=>'a507841b1bc8115094b00bbe8c1b2954') # => {:verified_md5=>true, # :headers=>{"last-modified"=>"Mon, 29 Sep 2008 18:58:56 GMT", # "x-amz-id-2"=>"mLWQcI+VuKVIdpTaPXEo84g0cz+vzmRLbj79TS8eFPfw19cGFOPxuLy4uGYVCvdH", # "content-type"=>"", "etag"=>"\"a507841b1bc8115094b00bbe8c1b2954\"", # "date"=>"Tue, 30 Sep 2008 00:53:08 GMT", # "x-amz-request-id"=>"6E7F317356580599", # "server"=>"AmazonS3", # "content-length"=>"10"}, # :object=>"polemonium"} # If a block is provided, yields incrementally to the block as # the response is read. For large responses, this function is ideal as # the response can be 'streamed'. The hash containing header fields is # still returned. def retrieve_object(params, &block) Utils.mandatory_arguments([:bucket, :key], params) Utils.allow_only([:bucket, :key, :headers, :md5], params) params[:headers] = {} unless params[:headers] req_hash = generate_rest_request('GET', params[:headers].merge(:url=>"#{params[:bucket]}/#{CGI::escape params[:key]}")) resp = request_info(req_hash, S3HttpResponseBodyParser.new, &block) resp[:verified_md5] = false if (params[:md5] && (resp[:headers]['etag'].gsub(/\"/, '') == params[:md5])) resp[:verified_md5] = true end resp rescue on_exception end # Identical in function to retrieve_object, but requires verification that the returned ETag is identical to the checksum passed in by the user as the 'md5' argument. # If the check passes, returns the response metadata with the "verified_md5" field set true. Raises an exception if the checksums conflict. # This call is implemented as a wrapper around retrieve_object and the user may gain different semantics by creating a custom wrapper. def retrieve_object_and_verify(params, &block) Utils.mandatory_arguments([:md5], params) resp = retrieve_object(params, &block) return resp if resp[:verified_md5] raise AwsError.new("Retrieved object failed MD5 checksum verification: #{resp.inspect}") end # Retrieves object metadata. Returns a +hash+ of http_response_headers. # # s3.head('my_awesome_bucket', 'log/curent/1.log') #=> # {"last-modified" => "Wed, 23 May 2007 09:08:04 GMT", # "content-type" => "", # "etag" => "\"000000000096f4ee74bc4596443ef2a4\"", # "date" => "Wed, 23 May 2007 09:08:03 GMT", # "x-amz-id-2" => "ZZZZZZZZZZZZZZZZZZZZ1HJXZoehfrS4QxcxTdNGldR7w/FVqblP50fU8cuIMLiu", # "x-amz-meta-family" => "Woho556!", # "x-amz-request-id" => "0000000C246D770C", # "server" => "AmazonS3", # "content-length" => "7"} # def head(bucket, key, headers={}) req_hash = generate_rest_request('HEAD', headers.merge(:url=>"#{bucket}/#{CGI::escape key}")) request_info(req_hash, S3HttpResponseHeadParser.new) rescue on_exception end # Deletes key. Returns +true+ or an exception. # # s3.delete('my_awesome_bucket', 'log/curent/1.log') #=> true # def delete(bucket, key='', headers={}) req_hash = generate_rest_request('DELETE', headers.merge(:url=>"#{bucket}/#{CGI::escape key}")) request_info(req_hash, RightHttp2xxParser.new) rescue on_exception end # Copy an object. # directive: :copy - copy meta-headers from source (default value) # :replace - replace meta-headers by passed ones # # # copy a key with meta-headers # s3.copy('b1', 'key1', 'b1', 'key1_copy') #=> {:e_tag=>"\"e8b...8d\"", :last_modified=>"2008-05-11T10:25:22.000Z"} # # # copy a key, overwrite meta-headers # s3.copy('b1', 'key2', 'b1', 'key2_copy', :replace, 'x-amz-meta-family'=>'Woho555!') #=> {:e_tag=>"\"e8b...8d\"", :last_modified=>"2008-05-11T10:26:22.000Z"} # # see: http://docs.amazonwebservices.com/AmazonS3/2006-03-01/UsingCopyingObjects.html # http://docs.amazonwebservices.com/AmazonS3/2006-03-01/RESTObjectCOPY.html # def copy(src_bucket, src_key, dest_bucket, dest_key=nil, directive=:copy, headers={}) dest_key ||= src_key headers['x-amz-metadata-directive'] = directive.to_s.upcase headers['x-amz-copy-source'] = "#{src_bucket}/#{CGI::escape src_key}" req_hash = generate_rest_request('PUT', headers.merge(:url=>"#{dest_bucket}/#{CGI::escape dest_key}")) request_info(req_hash, S3CopyParser.new) rescue on_exception end # Move an object. # directive: :copy - copy meta-headers from source (default value) # :replace - replace meta-headers by passed ones # # # move bucket1/key1 to bucket1/key2 # s3.move('bucket1', 'key1', 'bucket1', 'key2') #=> {:e_tag=>"\"e8b...8d\"", :last_modified=>"2008-05-11T10:27:22.000Z"} # # # move bucket1/key1 to bucket2/key2 with new meta-headers assignment # s3.copy('bucket1', 'key1', 'bucket2', 'key2', :replace, 'x-amz-meta-family'=>'Woho555!') #=> {:e_tag=>"\"e8b...8d\"", :last_modified=>"2008-05-11T10:28:22.000Z"} # def move(src_bucket, src_key, dest_bucket, dest_key=nil, directive=:copy, headers={}) copy_result = copy(src_bucket, src_key, dest_bucket, dest_key, directive, headers) # delete an original key if it differs from a destination one delete(src_bucket, src_key) unless src_bucket == dest_bucket && src_key == dest_key copy_result end # Rename an object. # # # rename bucket1/key1 to bucket1/key2 # s3.rename('bucket1', 'key1', 'key2') #=> {:e_tag=>"\"e8b...8d\"", :last_modified=>"2008-05-11T10:29:22.000Z"} # def rename(src_bucket, src_key, dest_key, headers={}) move(src_bucket, src_key, src_bucket, dest_key, :copy, headers) end # Retieves the ACL (access control policy) for a bucket or object. Returns a hash of headers and xml doc with ACL data. See: http://docs.amazonwebservices.com/AmazonS3/2006-03-01/RESTAccessPolicy.html. # # s3.get_acl('my_awesome_bucket', 'log/curent/1.log') #=> # {:headers => {"x-amz-id-2"=>"B3BdDMDUz+phFF2mGBH04E46ZD4Qb9HF5PoPHqDRWBv+NVGeA3TOQ3BkVvPBjgxX", # "content-type"=>"application/xml;charset=ISO-8859-1", # "date"=>"Wed, 23 May 2007 09:40:16 GMT", # "x-amz-request-id"=>"B183FA7AB5FBB4DD", # "server"=>"AmazonS3", # "transfer-encoding"=>"chunked"}, # :object => "\n # 16144ab2929314cc309ffe736daa2b264357476c7fea6efb2c3347ac3ab2792aroot # # 16144ab2929314cc309ffe736daa2b264357476c7fea6efb2c3347ac3ab2792aroot # FULL_CONTROL" } # def get_acl(bucket, key='', headers={}) key = Aws::Utils.blank?(key) ? '' : "/#{CGI::escape key}" req_hash = generate_rest_request('GET', headers.merge(:url=>"#{bucket}#{key}?acl")) request_info(req_hash, S3HttpResponseBodyParser.new) rescue on_exception end # Retieves the ACL (access control policy) for a bucket or object. # Returns a hash of {:owner, :grantees} # # s3.get_acl_parse('my_awesome_bucket', 'log/curent/1.log') #=> # # { :grantees=> # { "16...2a"=> # { :display_name=>"root", # :permissions=>["FULL_CONTROL"], # :attributes=> # { "xsi:type"=>"CanonicalUser", # "xmlns:xsi"=>"http://www.w3.org/2001/XMLSchema-instance"}}, # "http://acs.amazonaws.com/groups/global/AllUsers"=> # { :display_name=>"AllUsers", # :permissions=>["READ"], # :attributes=> # { "xsi:type"=>"Group", # "xmlns:xsi"=>"http://www.w3.org/2001/XMLSchema-instance"}}}, # :owner=> # { :id=>"16..2a", # :display_name=>"root"}} # def get_acl_parse(bucket, key='', headers={}) key = Aws::Utils.blank?(key) ? '' : "/#{CGI::escape key}" req_hash = generate_rest_request('GET', headers.merge(:url=>"#{bucket}#{key}?acl")) acl = request_info(req_hash, S3AclParser.new(:logger => @logger)) result = {} result[:owner] = acl[:owner] result[:grantees] = {} acl[:grantees].each do |grantee| key = grantee[:id] || grantee[:uri] if result[:grantees].key?(key) result[:grantees][key][:permissions] << grantee[:permissions] else result[:grantees][key] = {:display_name => grantee[:display_name] || grantee[:uri].to_s[/[^\/]*$/], :permissions => grantee[:permissions].lines.to_a, :attributes => grantee[:attributes]} end end result rescue on_exception end # Sets the ACL on a bucket or object. def put_acl(bucket, key, acl_xml_doc, headers={}) key = Aws::Utils.blank?(key) ? '' : "/#{CGI::escape key}" req_hash = generate_rest_request('PUT', headers.merge(:url=>"#{bucket}#{key}?acl", :data=>acl_xml_doc)) request_info(req_hash, S3HttpResponseBodyParser.new) rescue on_exception end # Retieves the ACL (access control policy) for a bucket. Returns a hash of headers and xml doc with ACL data. def get_bucket_acl(bucket, headers={}) return get_acl(bucket, '', headers) rescue on_exception end # Sets the ACL on a bucket only. def put_bucket_acl(bucket, acl_xml_doc, headers={}) return put_acl(bucket, '', acl_xml_doc, headers) rescue on_exception end def get_bucket_policy(bucket) req_hash = generate_rest_request('GET', {:url=>"#{bucket}?policy"}) request_info(req_hash, S3HttpResponseBodyParser.new) rescue on_exception end def put_bucket_policy(bucket, policy) key = Aws::Utils.blank?(key) ? '' : "/#{CGI::escape key}" req_hash = generate_rest_request('PUT', {:url=>"#{bucket}?policy", :data=>policy}) request_info(req_hash, S3HttpResponseBodyParser.new) rescue on_exception end # Removes all keys from bucket. Returns +true+ or an exception. # # s3.clear_bucket('my_awesome_bucket') #=> true # def clear_bucket(bucket) incrementally_list_bucket(bucket) do |results| p results results[:contents].each { |key| p key; delete(bucket, key[:key]) } end true rescue on_exception end # Deletes all keys in bucket then deletes bucket. Returns +true+ or an exception. # # s3.force_delete_bucket('my_awesome_bucket') # def force_delete_bucket(bucket) clear_bucket(bucket) delete_bucket(bucket) rescue on_exception end # Deletes all keys where the 'folder_key' may be assumed as 'folder' name. Returns an array of string keys that have been deleted. # # s3.list_bucket('my_awesome_bucket').map{|key_data| key_data[:key]} #=> ['test','test/2/34','test/3','test1','test1/logs'] # s3.delete_folder('my_awesome_bucket','test') #=> ['test','test/2/34','test/3'] # def delete_folder(bucket, folder_key, separator='/') folder_key.chomp!(separator) allkeys = [] incrementally_list_bucket(bucket, {'prefix' => folder_key}) do |results| keys = results[:contents].map { |s3_key| s3_key[:key][/^#{folder_key}($|#{separator}.*)/] ? s3_key[:key] : nil }.compact keys.each { |key| delete(bucket, key) } allkeys << keys end allkeys rescue on_exception end # Retrieves object data only (headers are omitted). Returns +string+ or an exception. # # s3.get('my_awesome_bucket', 'log/curent/1.log') #=> 'Ola-la!' # def get_object(bucket, key, headers={}) get(bucket, key, headers)[:object] rescue on_exception end #----------------------------------------------------------------- # Query API: Links #----------------------------------------------------------------- # Generates link for QUERY API def generate_link(method, headers={}, expires=nil) #:nodoc: # calculate request data server, path, path_to_sign = fetch_request_params(headers) # expiration time expires ||= DEFAULT_EXPIRES_AFTER expires = Time.now.utc + expires if expires.is_a?(Fixnum) && (expires < ONE_YEAR_IN_SECONDS) expires = expires.to_i # remove unset(==optional) and symbolyc keys headers.each { |key, value| headers.delete(key) if (value.nil? || key.is_a?(Symbol)) } #generate auth strings auth_string = canonical_string(method, path_to_sign, headers, expires) signature = CGI::escape(Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest.new("sha1"), @aws_secret_access_key, auth_string)).strip) # path building addon = "Signature=#{signature}&Expires=#{expires}&AWSAccessKeyId=#{@aws_access_key_id}" path += path[/\?/] ? "&#{addon}" : "?#{addon}" "#{@params[:protocol]}://#{server}:#{@params[:port]}#{path}" rescue on_exception end # Generates link for 'ListAllMyBuckets'. # # s3.list_all_my_buckets_link #=> url string # def list_all_my_buckets_link(expires=nil, headers={}) generate_link('GET', headers.merge(:url=>''), expires) rescue on_exception end # Generates link for 'CreateBucket'. # # s3.create_bucket_link('my_awesome_bucket') #=> url string # def create_bucket_link(bucket, expires=nil, headers={}) generate_link('PUT', headers.merge(:url=>bucket), expires) rescue on_exception end # Generates link for 'DeleteBucket'. # # s3.delete_bucket_link('my_awesome_bucket') #=> url string # def delete_bucket_link(bucket, expires=nil, headers={}) generate_link('DELETE', headers.merge(:url=>bucket), expires) rescue on_exception end # Generates link for 'ListBucket'. # # s3.list_bucket_link('my_awesome_bucket') #=> url string # def list_bucket_link(bucket, options=nil, expires=nil, headers={}) unless options.nil? || options.empty? bucket << '?' bucket << options.map { |k, v| "#{k.to_s}=#{CGI::escape v.to_s}" }.join('&') end generate_link('GET', headers.merge(:url=>bucket), expires) rescue on_exception end # Generates link for 'PutObject'. # # s3.put_link('my_awesome_bucket',key, object) #=> url string # def put_link(bucket, key, data=nil, expires=nil, headers={}) generate_link('PUT', headers.merge(:url=>"#{bucket}/#{Utils::URLencode key}", :data=>data), expires) rescue on_exception end # Generates link for 'GetObject'. # # if a bucket comply with virtual hosting naming then retuns a link with the # bucket as a part of host name: # # s3.get_link('my-awesome-bucket',key) #=> https://my-awesome-bucket.s3.amazonaws.com:443/asia%2Fcustomers?Signature=nh7... # # otherwise returns an old style link (the bucket is a part of path): # # s3.get_link('my_awesome_bucket',key) #=> https://s3.amazonaws.com:443/my_awesome_bucket/asia%2Fcustomers?Signature=QAO... # # see http://docs.amazonwebservices.com/AmazonS3/2006-03-01/VirtualHosting.html def get_link(bucket, key, expires=nil, headers={}) generate_link('GET', headers.merge(:url=>"#{bucket}/#{Utils::URLencode key.to_s}"), expires) rescue on_exception end # Generates link for 'HeadObject'. # # s3.head_link('my_awesome_bucket',key) #=> url string # def head_link(bucket, key, expires=nil, headers={}) generate_link('HEAD', headers.merge(:url=>"#{bucket}/#{Utils::URLencode key}"), expires) rescue on_exception end # Generates link for 'DeleteObject'. # # s3.delete_link('my_awesome_bucket',key) #=> url string # def delete_link(bucket, key, expires=nil, headers={}) generate_link('DELETE', headers.merge(:url=>"#{bucket}/#{Utils::URLencode key}"), expires) rescue on_exception end # Generates link for 'GetACL'. # # s3.get_acl_link('my_awesome_bucket',key) #=> url string # def get_acl_link(bucket, key='', headers={}) return generate_link('GET', headers.merge(:url=>"#{bucket}/#{Utils::URLencode key}?acl")) rescue on_exception end # Generates link for 'PutACL'. # # s3.put_acl_link('my_awesome_bucket',key) #=> url string # def put_acl_link(bucket, key='', headers={}) return generate_link('PUT', headers.merge(:url=>"#{bucket}/#{Utils::URLencode key}?acl")) rescue on_exception end # Generates link for 'GetBucketACL'. # # s3.get_acl_link('my_awesome_bucket',key) #=> url string # def get_bucket_acl_link(bucket, headers={}) return get_acl_link(bucket, '', headers) rescue on_exception end # Generates link for 'PutBucketACL'. # # s3.put_acl_link('my_awesome_bucket',key) #=> url string # def put_bucket_acl_link(bucket, acl_xml_doc, headers={}) return put_acl_link(bucket, '', acl_xml_doc, headers) rescue on_exception end #----------------------------------------------------------------- # PARSERS: #----------------------------------------------------------------- class S3ListAllMyBucketsParser < AwsParser # :nodoc: def reset @result = [] @owner = {} end def tagstart(name, attributes) @current_bucket = {} if name == 'Bucket' end def tagend(name) case name when 'ID'; @owner[:owner_id] = @text when 'DisplayName'; @owner[:owner_display_name] = @text when 'Name'; @current_bucket[:name] = @text when 'CreationDate'; @current_bucket[:creation_date] = @text when 'Bucket'; @result << @current_bucket.merge(@owner) end end end class S3ListBucketParser < AwsParser # :nodoc: def reset @result = [] @service = {} @current_key = {} end def tagstart(name, attributes) @current_key = {} if name == 'Contents' end def tagend(name) case name # service info when 'Name'; @service['name'] = @text when 'Prefix'; @service['prefix'] = @text when 'Marker'; @service['marker'] = @text when 'MaxKeys'; @service['max-keys'] = @text when 'Delimiter'; @service['delimiter'] = @text when 'IsTruncated'; @service['is_truncated'] = (@text =~ /false/ ? false : true) # key data when 'Key'; @current_key[:key] = @text when 'LastModified'; @current_key[:last_modified] = @text when 'ETag'; @current_key[:e_tag] = @text when 'Size'; @current_key[:size] = @text.to_i when 'StorageClass'; @current_key[:storage_class] = @text when 'ID'; @current_key[:owner_id] = @text when 'DisplayName'; @current_key[:owner_display_name] = @text when 'Contents'; @current_key[:service] = @service; @result << @current_key end end end class S3ImprovedListBucketParser < AwsParser # :nodoc: def reset @result = {} @result[:contents] = [] @result[:common_prefixes] = [] @contents = [] @current_key = {} @common_prefixes = [] @in_common_prefixes = false end def tagstart(name, attributes) @current_key = {} if name == 'Contents' @in_common_prefixes = true if name == 'CommonPrefixes' end def tagend(name) case name # service info when 'Name'; @result[:name] = @text # Amazon uses the same tag for the search prefix and for the entries # in common prefix...so use our simple flag to see which element # we are parsing when 'Prefix'; @in_common_prefixes ? @common_prefixes << @text : @result[:prefix] = @text when 'Marker'; @result[:marker] = @text when 'MaxKeys'; @result[:max_keys] = @text when 'Delimiter'; @result[:delimiter] = @text when 'IsTruncated'; @result[:is_truncated] = (@text =~ /false/ ? false : true) when 'NextMarker'; @result[:next_marker] = @text # key data when 'Key'; @current_key[:key] = @text when 'LastModified'; @current_key[:last_modified] = @text when 'ETag'; @current_key[:e_tag] = @text when 'Size'; @current_key[:size] = @text.to_i when 'StorageClass'; @current_key[:storage_class] = @text when 'ID'; @current_key[:owner_id] = @text when 'DisplayName'; @current_key[:owner_display_name] = @text when 'Contents'; @result[:contents] << @current_key # Common Prefix stuff when 'CommonPrefixes'; @result[:common_prefixes] = @common_prefixes; @in_common_prefixes = false end end end class S3BucketLocationParser < AwsParser # :nodoc: def reset @result = '' end def tagend(name) @result = @text if name == 'LocationConstraint' end end class S3AclParser < AwsParser # :nodoc: def reset @result = {:grantees=>[], :owner=>{}} @current_grantee = {} end def tagstart(name, attributes) @current_grantee = {:attributes => attributes} if name=='Grantee' end def tagend(name) case name # service info when 'ID' if @xmlpath == 'AccessControlPolicy/Owner' @result[:owner][:id] = @text else @current_grantee[:id] = @text end when 'DisplayName' if @xmlpath == 'AccessControlPolicy/Owner' @result[:owner][:display_name] = @text else @current_grantee[:display_name] = @text end when 'URI' @current_grantee[:uri] = @text when 'Permission' @current_grantee[:permissions] = @text when 'Grant' @result[:grantees] << @current_grantee end end end class S3LoggingParser < AwsParser # :nodoc: def reset @result = {:enabled => false, :targetbucket => '', :targetprefix => ''} @current_grantee = {} end def tagend(name) case name # service info when 'TargetBucket' if @xmlpath == 'BucketLoggingStatus/LoggingEnabled' @result[:targetbucket] = @text @result[:enabled] = true end when 'TargetPrefix' if @xmlpath == 'BucketLoggingStatus/LoggingEnabled' @result[:targetprefix] = @text @result[:enabled] = true end end end end class S3CopyParser < AwsParser # :nodoc: def reset @result = {} end def tagend(name) case name when 'LastModified' then @result[:last_modified] = @text when 'ETag' then @result[:e_tag] = @text end end end class S3InitiateMultipartUploadParser < AwsParser # :nodoc: def reset @result = "" end def tagend(name) @result = @text if name == 'UploadId' end end class S3ListMultipartPartsParser < AwsParser # :nodoc: def reset @result = {} @result[:parts] = [] @current_part={} end def tagstart(name, attributes) @current_part={} if name=='Part' end def tagend(name) case name when 'PartNumber' @current_part[:part_number] = @text when 'ETag' @current_part[:etag] = @text when 'Size' @current_part[:size] = @text when 'LastModified' @current_part[:last_modified] = @text when 'Bucket' @result[:bucket] = @text when 'Key' @result[:key] = @text when 'UploadId' @result[:upload_id] = @text when 'Part' @result[:parts] << @current_part end end end #----------------------------------------------------------------- # PARSERS: Non XML #----------------------------------------------------------------- class S3HttpResponseParser # :nodoc: attr_reader :result def parse(response) @result = response end def headers_to_string(headers) result = {} headers.each do |key, value| value = value[0] if value.is_a?(Array) && value.size<2 result[key] = value end result end end class S3HttpResponseBodyParser < S3HttpResponseParser # :nodoc: def parse(response) x = response.body x= x.respond_to?(:force_encoding) ? x.force_encoding("UTF-8") : x # puts 'x.encoding = ' + response.body.encoding.to_s @result = { :object => x, :headers => headers_to_string(response.to_hash) } end end class S3HttpResponseHeadParser < S3HttpResponseParser # :nodoc: def parse(response) @result = headers_to_string(response.to_hash) end end class S3UploadPartParser < S3HttpResponseParser # :nodoc: def parse(response) @result = headers_to_string(response.to_hash)["etag"].gsub!("\"", "") end end end end aws-2.10.2/lib/s3/bucket.rb0000644000175000017500000002357112511515441014006 0ustar tnnntnnnmodule Aws class S3::Bucket attr_reader :s3, :name, :owner, :creation_date # Create a Bucket instance. # If the bucket does not exist and +create+ is set, a new bucket # is created on S3. Launching this method with +create+=+true+ may # affect on the bucket's ACL if the bucket already exists. # Returns Bucket instance or +nil+ if the bucket does not exist # and +create+ is not set. # # s3 = Aws::S3.new(aws_access_key_id, aws_secret_access_key) # ... # bucket1 = Aws::S3::Bucket.create(s3, 'my_awesome_bucket_1') # bucket1.keys #=> exception here if the bucket does not exists # ... # bucket2 = Aws::S3::Bucket.create(s3, 'my_awesome_bucket_2', true) # bucket2.keys #=> list of keys # # create a bucket at the European location with public read access # bucket3 = Aws::S3::Bucket.create(s3,'my-awesome-bucket-3', true, 'public-read', :location => :eu) # # see http://docs.amazonwebservices.com/AmazonS3/2006-03-01/RESTAccessPolicy.html # (section: Canned Access Policies) # def self.create(s3, name, create=false, perms=nil, headers={}) s3.bucket(name, create, perms, headers) end # Create a bucket instance. In normal use this method should # not be called directly. # Use Aws::S3::Bucket.create or Aws::S3.bucket instead. def initialize(s3, name, creation_date=nil, owner=nil) @s3 = s3 @name = name @owner = owner @creation_date = creation_date if @creation_date && !@creation_date.is_a?(Time) @creation_date = Time.parse(@creation_date) end end # Return bucket name as a String. # # bucket = Aws::S3.bucket('my_awesome_bucket') # puts bucket #=> 'my_awesome_bucket' # def to_s @name.to_s end alias_method :full_name, :to_s # Return a public link to bucket. # # bucket.public_link #=> 'https://s3.amazonaws.com:443/my_awesome_bucket' # def public_link params = @s3.interface.params "#{params[:protocol]}://#{params[:server]}:#{params[:port]}/#{full_name}" end # Returns the bucket location def location @location ||= @s3.interface.bucket_location(@name) end # Retrieves the logging configuration for a bucket. # Returns a hash of {:enabled, :targetbucket, :targetprefix} # # bucket.logging_info() # => {:enabled=>true, :targetbucket=>"mylogbucket", :targetprefix=>"loggylogs/"} def logging_info @s3.interface.get_logging_parse(:bucket => @name) end # Enables S3 server access logging on a bucket. The target bucket must have been properly configured to receive server # access logs. # Params: # :targetbucket - either the target bucket object or the name of the target bucket # :targetprefix - the prefix under which all logs should be stored # # bucket.enable_logging(:targetbucket=>"mylogbucket", :targetprefix=>"loggylogs/") # => true def enable_logging(params) Utils.mandatory_arguments([:targetbucket, :targetprefix], params) Utils.allow_only([:targetbucket, :targetprefix], params) xmldoc = "#{params[:targetbucket]}#{params[:targetprefix]}" @s3.interface.put_logging(:bucket => @name, :xmldoc => xmldoc) end # Disables S3 server access logging on a bucket. Takes no arguments. def disable_logging xmldoc = "" @s3.interface.put_logging(:bucket => @name, :xmldoc => xmldoc) end # Retrieve a group of keys from Amazon. # +options+ is a hash: { 'prefix'=>'', 'marker'=>'', 'max-keys'=>5, 'delimiter'=>'' }). # Retrieves meta-headers information if +head+ it +true+. # Returns an array of Key instances. # # bucket.keys #=> # returns all keys from bucket # bucket.keys('prefix' => 'logs') #=> # returns all keys that starts with 'logs' # def keys(options={}, head=false) keys_and_service(options, head)[0] end # Same as +keys+ method but return an array of [keys, service_data]. # where +service_data+ is a hash with additional output information. # # keys, service = bucket.keys_and_service({'max-keys'=> 2, 'prefix' => 'logs'}) # p keys #=> # 2 keys array # p service #=> {"max-keys"=>"2", "prefix"=>"logs", "name"=>"my_awesome_bucket", "marker"=>"", "is_truncated"=>true} # def keys_and_service(options={}, head=false) opt = {}; options.each { |key, value| opt[key.to_s] = value } service_data = {} service_list = {} list = [] @s3.interface.incrementally_list_bucket(@name, opt) do |thislist| service_list = thislist thislist[:contents].each do |entry| owner = S3::Owner.new(entry[:owner_id], entry[:owner_display_name]) key = S3::Key.new(self, entry[:key], nil, {}, {}, entry[:last_modified], entry[:e_tag], entry[:size], entry[:storage_class], owner) key.head if head list << key end end service_list.each_key do |key| service_data[key] = service_list[key] unless (key == :contents || key == :common_prefixes) end [list, service_data] end # Retrieve key information from Amazon. # The +key_name+ is a +String+ or Key instance. # Retrieves meta-header information if +head+ is +true+. # Returns new Key instance. # # key = bucket.key('logs/today/1.log', true) #=> # # # is the same as: # key = Aws::S3::Key.create(bucket, 'logs/today/1.log') # key.head # def key(key_name, head=false) raise 'Key name can not be empty.' if Aws::Utils.blank?(key_name) key_instance = nil # if this key exists - find it .... keys({'prefix'=>key_name}, head).each do |key| if key.name == key_name.to_s key_instance = key break end end # .... else this key is unknown unless key_instance key_instance = S3::Key.create(self, key_name.to_s) end key_instance end # Store object data. # The +key+ is a +String+ or Key instance. # Returns +true+. # # bucket.put('logs/today/1.log', 'Olala!') #=> true # def put(key, data=nil, meta_headers={}, perms=nil, headers={}) key = S3::Key.create(self, key.to_s, data, meta_headers) unless key.is_a?(S3::Key) key.put(data, perms, headers) end # Retrieve object data from Amazon. # The +key+ is a +String+ or Key. # Returns data. # # data = bucket.get('logs/today/1.log') #=> # puts data #=> 'sasfasfasdf' # def get(key, headers={}, &block) key = S3::Key.create(self, key.to_s) unless key.is_a?(S3::Key) key.get(headers, &block) end # Retrieve object data from Amazon. # The +key+ is a +String+ or Key. # Returns Key instance. # # key = bucket.get('logs/today/1.log') #=> # puts key.data #=> 'sasfasfasdf' # def get_key(key, headers={}) key = S3::Key.create(self, key.to_s, headers) unless key.is_a?(S3::Key) return key end # Rename object. Returns Aws::S3::Key instance. # # new_key = bucket.rename_key('logs/today/1.log','logs/today/2.log') #=> # # puts key.name #=> 'logs/today/2.log' # key.exists? #=> true # def rename_key(old_key_or_name, new_name) old_key_or_name = S3::Key.create(self, old_key_or_name.to_s) unless old_key_or_name.is_a?(S3::Key) old_key_or_name.rename(new_name) old_key_or_name end # Create an object copy. Returns a destination Aws::S3::Key instance. # # new_key = bucket.copy_key('logs/today/1.log','logs/today/2.log') #=> # # puts key.name #=> 'logs/today/2.log' # key.exists? #=> true # def copy_key(old_key_or_name, new_key_or_name) old_key_or_name = S3::Key.create(self, old_key_or_name.to_s) unless old_key_or_name.is_a?(S3::Key) old_key_or_name.copy(new_key_or_name) end # Move an object to other location. Returns a destination Aws::S3::Key instance. # # new_key = bucket.copy_key('logs/today/1.log','logs/today/2.log') #=> # # puts key.name #=> 'logs/today/2.log' # key.exists? #=> true # def move_key(old_key_or_name, new_key_or_name) old_key_or_name = S3::Key.create(self, old_key_or_name.to_s) unless old_key_or_name.is_a?(S3::Key) old_key_or_name.move(new_key_or_name) end # Remove all keys from a bucket. # Returns +true+. # # bucket.clear #=> true # def clear @s3.interface.clear_bucket(@name) end # Delete all keys where the 'folder_key' can be interpreted # as a 'folder' name. # Returns an array of string keys that have been deleted. # # bucket.keys.map{|key| key.name}.join(', ') #=> 'test, test/2/34, test/3, test1, test1/logs' # bucket.delete_folder('test') #=> ['test','test/2/34','test/3'] # def delete_folder(folder, separator='/') @s3.interface.delete_folder(@name, folder, separator) end # Delete a bucket. Bucket must be empty. # If +force+ is set, clears and deletes the bucket. # Returns +true+. # # bucket.delete(true) #=> true # def delete(force=false) force ? @s3.interface.force_delete_bucket(@name) : @s3.interface.delete_bucket(@name) end # Deletes an object from s3 in this bucket. def delete_key(key) @s3.interface.delete(name, key) end # Return a list of grantees. # def grantees S3::Grantee::grantees(self) end end end aws-2.10.2/lib/s3/grantee.rb0000644000175000017500000001761212511515441014155 0ustar tnnntnnnmodule Aws # There are 2 ways to set permissions for a bucket or key (called a +thing+ below): # # 1 . Use +perms+ param to set 'Canned Access Policies' when calling the bucket.create, # bucket.put and key.put methods. # The +perms+ param can take these values: 'private', 'public-read', 'public-read-write' and # 'authenticated-read'. # (see http://docs.amazonwebservices.com/AmazonS3/2006-03-01/RESTAccessPolicy.html). # # bucket = s3.bucket('bucket_for_kd_test_13', true, 'public-read') # key.put('Woohoo!','public-read-write' ) # # 2 . Use Grantee instances (the permission is a +String+ or an +Array+ of: 'READ', 'WRITE', # 'READ_ACP', 'WRITE_ACP', 'FULL_CONTROL'): # # bucket = s3.bucket('my_awesome_bucket', true) # grantee1 = Aws::S3::Grantee.new(bucket, 'a123b...223c', FULL_CONTROL, :apply) # grantee2 = Aws::S3::Grantee.new(bucket, 'xy3v3...5fhp', [READ, WRITE], :apply) # # There is only one way to get and to remove permission (via Grantee instances): # # grantees = bucket.grantees # a list of Grantees that have any access for this bucket # grantee1 = Aws::S3::Grantee.new(bucket, 'a123b...223c') # grantee1.perms #=> returns a list of perms for this grantee to that bucket # ... # grantee1.drop # remove all perms for this grantee # grantee2.revoke('WRITE') # revoke write access only # class S3::Grantee # A bucket or a key the grantee has an access to. attr_reader :thing # Grantee Amazon id. attr_reader :id # Grantee display name. attr_reader :name # Array of permissions. attr_accessor :perms # Retrieve Owner information and a list of Grantee instances that have # a access to this thing (bucket or key). # # bucket = s3.bucket('my_awesome_bucket', true, 'public-read') # ... # Aws::S3::Grantee.owner_and_grantees(bucket) #=> [owner, grantees] # def self.owner_and_grantees(thing) if thing.is_a?(S3::Bucket) bucket, key = thing, '' else bucket, key = thing.bucket, thing end hash = bucket.s3.interface.get_acl_parse(bucket.to_s, key.to_s) owner = S3::Owner.new(hash[:owner][:id], hash[:owner][:display_name]) grantees = [] hash[:grantees].each do |id, params| grantees << new(thing, id, params[:permissions], nil, params[:display_name]) end [owner, grantees] end # Retrieves a list of Grantees instances that have an access to this thing(bucket or key). # # bucket = s3.bucket('my_awesome_bucket', true, 'public-read') # ... # Aws::S3::Grantee.grantees(bucket) #=> grantees # def self.grantees(thing) owner_and_grantees(thing)[1] end def self.put_acl(thing, owner, grantees) #:nodoc: if thing.is_a?(S3::Bucket) bucket, key = thing, '' else bucket, key = thing.bucket, thing end body = "" + "" + "#{owner.id}" + "#{owner.name}" + "" + "" + grantees.map { |grantee| grantee.to_xml }.join + "" + "" bucket.s3.interface.put_acl(bucket.to_s, key.to_s, body) end # Create a new Grantee instance. # Grantee +id+ must exist on S3. If +action+ == :refresh, then retrieve # permissions from S3 and update @perms. If +action+ == :apply, then apply # perms to +thing+ at S3. If +action+ == :apply_and_refresh then it performs. # both the actions. This is used for the new grantees that had no perms to # this thing before. The default action is :refresh. # # bucket = s3.bucket('my_awesome_bucket', true, 'public-read') # grantee1 = Aws::S3::Grantee.new(bucket, 'a123b...223c', FULL_CONTROL) # ... # grantee2 = Aws::S3::Grantee.new(bucket, 'abcde...asdf', [FULL_CONTROL, READ], :apply) # grantee3 = Aws::S3::Grantee.new(bucket, 'aaaaa...aaaa', 'READ', :apply_and_refresh) # def initialize(thing, id, perms=[], action=:refresh, name=nil) @thing = thing @id = id @name = name @perms = perms.to_a case action when :apply then apply when :refresh then refresh when :apply_and_refresh then apply; refresh end end # Return +true+ if the grantee has any permissions to the thing. def exists? self.class.grantees(@thing).each do |grantee| return true if @id == grantee.id end false end # Return Grantee type (+String+): "Group" or "CanonicalUser". def type @id[/^http:/] ? "Group" : "CanonicalUser" end # Return a name or an id. def to_s @name || @id end # Add permissions for grantee. # Permissions: 'READ', 'WRITE', 'READ_ACP', 'WRITE_ACP', 'FULL_CONTROL'. # See http://docs.amazonwebservices.com/AmazonS3/2006-03-01/UsingPermissions.html . # Returns +true+. # # grantee.grant('FULL_CONTROL') #=> true # grantee.grant('FULL_CONTROL','WRITE','READ') #=> true # grantee.grant(['WRITE_ACP','READ','READ_ACP']) #=> true # def grant(*permissions) permissions.flatten! old_perms = @perms.dup @perms += permissions @perms.uniq! return true if @perms == old_perms apply end # Revoke permissions for grantee. # Permissions: 'READ', 'WRITE', 'READ_ACP', 'WRITE_ACP', 'FULL_CONTROL' # See http://docs.amazonwebservices.com/AmazonS3/2006-03-01/UsingPermissions.html . # Default value is 'FULL_CONTROL'. # Returns +true+. # # grantee.revoke('READ') #=> true # grantee.revoke('FULL_CONTROL','WRITE') #=> true # grantee.revoke(['READ_ACP','WRITE_ACP']) #=> true # def revoke(*permissions) permissions.flatten! old_perms = @perms.dup @perms -= permissions @perms.uniq! return true if @perms == old_perms apply end # Revoke all permissions for this grantee. # Returns +true+. # # grantee.drop #=> true # def drop @perms = [] apply end # Refresh grantee perms for its +thing+. # Returns +true+ if the grantee has perms for this +thing+ or # +false+ otherwise, and updates @perms value as a side-effect. # # grantee.grant('FULL_CONTROL') #=> true # grantee.refresh #=> true # grantee.drop #=> true # grantee.refresh #=> false # def refresh @perms = [] self.class.grantees(@thing).each do |grantee| if @id == grantee.id @name = grantee.name @perms = grantee.perms return true end end false end # Apply current grantee @perms to +thing+. This method is called internally by the +grant+ # and +revoke+ methods. In normal use this method should not # be called directly. # # grantee.perms = ['FULL_CONTROL'] # grantee.apply #=> true # def apply @perms.uniq! owner, grantees = self.class.owner_and_grantees(@thing) # walk through all the grantees and replace the data for the current one and ... grantees.map! { |grantee| grantee.id == @id ? self : grantee } # ... if this grantee is not known - add this bad boy to a list grantees << self unless grantees.include?(self) # set permissions self.class.put_acl(@thing, owner, grantees) end def to_xml # :nodoc: id_str = @id[/^http/] ? "#{@id}" : "#{@id}" grants = '' @perms.each do |perm| grants << "" + "#{id_str}" + "#{perm}" + "" end grants end end end aws-2.10.2/lib/right_aws.rb0000644000175000017500000000333012511515441014162 0ustar tnnntnnn# # Copyright (c) 2007-2008 RightScale Inc # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # require 'benchmark' require 'net/https' require 'uri' require 'time' require "cgi" require "base64" require "rexml/document" require "openssl" require "digest/sha1" require 'rubygems' require 'right_http_connection' $:.unshift(File.dirname(__FILE__)) require 'awsbase/benchmark_fix' #require 'awsbase/support' require 'awsbase/awsbase' require 'ec2/ec2' require 'ec2/mon_interface' require 's3/s3_interface' require 's3/s3' require 'sqs/sqs_interface' require 'sqs/sqs' require 'sdb/sdb_interface' require 'acf/acf_interface' require 'elb/elb_interface' # backwards compatible. # @deprecated module RightAws include Aws extend Aws end aws-2.10.2/lib/sqs/0000755000175000017500000000000012511515441012455 5ustar tnnntnnnaws-2.10.2/lib/sqs/sqs_interface.rb0000644000175000017500000004631612511515441015642 0ustar tnnntnnn# # Copyright (c) 2007-2008 RightScale Inc # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # module Aws # # Right::Aws::SqsInterface - RightScale's low-level Amazon SQS interface # for API version 2008-01-01 and later. # For explanations of the semantics # of each call, please refer to Amazon's documentation at # http://developer.amazonwebservices.com/connect/kbcategory.jspa?categoryID=31 # # This class provides a procedural interface to SQS. Conceptually it is # mostly a pass-through interface to SQS and its API is very similar to the # bare SQS API. For a somewhat higher-level and object-oriented interface, see # Aws::Sqs. class SqsInterface < AwsBase include AwsBaseInterface API_VERSION = "2009-02-01" DEFAULT_HOST = "queue.amazonaws.com" DEFAULT_PORT = 443 DEFAULT_PROTOCOL = 'https' REQUEST_TTL = 30 DEFAULT_VISIBILITY_TIMEOUT = 30 def self.connection_name :sqs_connection end @@bench = AwsBenchmarkingBlock.new def self.bench @@bench end def self.bench_xml @@bench.xml end def self.bench_sqs @@bench.service end @@api = API_VERSION def self.api @@api end # Creates a new SqsInterface instance. This instance is limited to # operations on SQS objects created with Amazon's 2008-01-01 API version. This # interface will not work on objects created with prior API versions. See # Amazon's article "Migrating to Amazon SQS API version 2008-01-01" at: # http://developer.amazonwebservices.com/connect/entry.jspa?externalID=1148 # # sqs = Aws::SqsInterface.new('1E3GDYEOGFJPIT75KDT40','hgTHt68JY07JKUY08ftHYtERkjgtfERn57DFE379', {:multi_thread => true, :logger => Logger.new('/tmp/x.log')}) # # Params is a hash: # # {:server => 'queue.amazonaws.com' # Amazon service host: 'queue.amazonaws.com' (default) # :port => 443 # Amazon service port: 80 or 443 (default) # :multi_thread => true|false # Multi-threaded (connection per each thread): true or false (default) # :signature_version => '0' # The signature version : '0' or '1'(default) # :logger => Logger Object} # Logger instance: logs to STDOUT if omitted } # def initialize(aws_access_key_id=nil, aws_secret_access_key=nil, params={}) init({:name => 'SQS', :default_host => ENV['SQS_URL'] ? URI.parse(ENV['SQS_URL']).host : DEFAULT_HOST, :default_port => ENV['SQS_URL'] ? URI.parse(ENV['SQS_URL']).port : DEFAULT_PORT, :default_protocol => ENV['SQS_URL'] ? URI.parse(ENV['SQS_URL']).scheme : DEFAULT_PROTOCOL, :api_version => API_VERSION}, aws_access_key_id || ENV['AWS_ACCESS_KEY_ID'], aws_secret_access_key || ENV['AWS_SECRET_ACCESS_KEY'], params) end #----------------------------------------------------------------- # Requests #----------------------------------------------------------------- # Generates a request hash for the query API def generate_request(action, param={}) # :nodoc: # For operation requests on a queue, the queue URI will be a parameter, # so we first extract it from the call parameters. Next we remove any # parameters with no value or with symbolic keys. We add the header # fields required in all requests, and then the headers passed in as # params. We sort the header fields alphabetically and then generate the # signature before URL escaping the resulting query and sending it. service = param[:queue_url] ? URI(param[:queue_url]).path : '/' param.each { |key, value| param.delete(key) if (value.nil? || key.is_a?(Symbol)) } service_hash = {"Action" => action, "Expires" => (Time.now + REQUEST_TTL).utc.strftime("%Y-%m-%dT%H:%M:%SZ"), "AWSAccessKeyId" => @aws_access_key_id, "Version" => API_VERSION} service_hash.update(param) service_params = signed_service_params(@aws_secret_access_key, service_hash, :get, @params[:server], service) request = Net::HTTP::Get.new("#{Utils.URLencode(service)}?#{service_params}") # prepare output hash {:request => request, :server => @params[:server], :port => @params[:port], :protocol => @params[:protocol]} end def generate_post_request(action, param={}) # :nodoc: service = param[:queue_url] ? URI(param[:queue_url]).path : '/' message = param[:message] # extract message body if nesessary param.each { |key, value| param.delete(key) if (value.nil? || key.is_a?(Symbol)) } service_hash = {"Action" => action, "Expires" => (Time.now + REQUEST_TTL).utc.strftime("%Y-%m-%dT%H:%M:%SZ"), "AWSAccessKeyId" => @aws_access_key_id, "MessageBody" => message, "Version" => API_VERSION} service_hash.update(param) # service_params = signed_service_params(@aws_secret_access_key, service_hash, :post, @params[:server], service) request = Net::HTTP::Post.new(Utils::URLencode(service)) request['Content-Type'] = 'application/x-www-form-urlencoded; charset=utf-8' request.body = service_params # prepare output hash {:request => request, :server => @params[:server], :port => @params[:port], :protocol => @params[:protocol]} end # Sends request to Amazon and parses the response # Raises AwsError if any banana happened # todo: remove this and switch to using request_info2 def request_info(request, parser, options={}) # :nodoc: conn = get_conn(self.class.connection_name, @params, @logger) request_info_impl(conn, @@bench, request, parser, options) end # Creates a new queue, returning its URI. # # sqs.create_queue('my_awesome_queue') #=> 'http://queue.amazonaws.com/ZZ7XXXYYYBINS/my_awesome_queue' # def create_queue(queue_name, default_visibility_timeout=nil) req_hash = generate_request('CreateQueue', 'QueueName' => queue_name, 'DefaultVisibilityTimeout' => default_visibility_timeout || DEFAULT_VISIBILITY_TIMEOUT) request_info(req_hash, SqsCreateQueueParser.new(:logger => @logger)) rescue on_exception end # Lists all queues owned by this user that have names beginning with +queue_name_prefix+. # If +queue_name_prefix+ is omitted then retrieves a list of all queues. # Queue creation is an eventual operation and created queues may not show up in immediately subsequent list_queues calls. # # sqs.create_queue('my_awesome_queue') # sqs.create_queue('my_awesome_queue_2') # sqs.list_queues('my_awesome') #=> ['http://queue.amazonaws.com/ZZ7XXXYYYBINS/my_awesome_queue','http://queue.amazonaws.com/ZZ7XXXYYYBINS/my_awesome_queue_2'] # def list_queues(queue_name_prefix=nil) req_hash = generate_request('ListQueues', 'QueueNamePrefix' => queue_name_prefix) request_info(req_hash, SqsListQueuesParser.new(:logger => @logger)) rescue on_exception end # Deletes queue. Any messages in the queue are permanently lost. # Returns +true+ or an exception. # Queue deletion can take up to 60 s to propagate through SQS. Thus, after a deletion, subsequent list_queues calls # may still show the deleted queue. It is not unusual within the 60 s window to see the deleted queue absent from # one list_queues call but present in the subsequent one. Deletion is eventual. # # sqs.delete_queue('http://queue.amazonaws.com/ZZ7XXXYYYBINS/my_awesome_queue_2') #=> true # # def delete_queue(queue_url) req_hash = generate_request('DeleteQueue', :queue_url => queue_url) request_info(req_hash, SqsStatusParser.new(:logger => @logger)) rescue on_exception end # Retrieves the queue attribute(s). Returns a hash of attribute(s) or an exception. # # sqs.get_queue_attributes('http://queue.amazonaws.com/ZZ7XXXYYYBINS/my_awesome_queue') # #=> {"ApproximateNumberOfMessages"=>"0", "VisibilityTimeout"=>"30"} # def get_queue_attributes(queue_url, attribute='All') req_hash = generate_request('GetQueueAttributes', 'AttributeName' => attribute, :queue_url => queue_url) request_info(req_hash, SqsGetQueueAttributesParser.new(:logger => @logger)) rescue on_exception end # Sets queue attribute. Returns +true+ or an exception. # # sqs.set_queue_attributes('http://queue.amazonaws.com/ZZ7XXXYYYBINS/my_awesome_queue', "VisibilityTimeout", 10) #=> true # # From the SQS Dev Guide: # "Currently, you can set only the # VisibilityTimeout attribute for a queue... # When you change a queue's attributes, the change can take up to 60 seconds to propagate # throughout the SQS system." # # NB: Attribute values may not be immediately available to other queries # for some time after an update. See the SQS documentation for # semantics, but in general propagation can take up to 60 s. def set_queue_attributes(queue_url, attribute, value) req_hash = generate_request('SetQueueAttributes', 'Attribute.Name' => attribute, 'Attribute.Value' => value, :queue_url => queue_url) request_info(req_hash, SqsStatusParser.new(:logger => @logger)) rescue on_exception end # Retrieves a list of messages from queue. Returns an array of hashes in format: {:id=>'message_id', body=>'message_body'} # # sqs.receive_message('http://queue.amazonaws.com/ZZ7XXXYYYBINS/my_awesome_queue',10, 5) #=> # [{"ReceiptHandle"=>"Euvo62...kw==", "MD5OfBody"=>"16af2171b5b83cfa35ce254966ba81e3", # "Body"=>"Goodbyte World!", "MessageId"=>"MUM4WlAyR...pYOTA="}, ..., {}] # # Normally this call returns fewer messages than the maximum specified, # even if they are available. # def receive_message(queue_url, max_number_of_messages=1, visibility_timeout=nil) return [] if max_number_of_messages == 0 req_hash = generate_post_request('ReceiveMessage', 'MaxNumberOfMessages' => max_number_of_messages, 'VisibilityTimeout' => visibility_timeout, :queue_url => queue_url) request_info(req_hash, SqsReceiveMessageParser.new(:logger => @logger)) rescue on_exception end # Sends a new message to a queue. Message size is limited to 8 KB. # If successful, this call returns a hash containing key/value pairs for # "MessageId" and "MD5OfMessageBody": # # sqs.send_message('http://queue.amazonaws.com/ZZ7XXXYYYBINS/my_awesome_queue', 'message_1') #=> "1234567890...0987654321" # => {"MessageId"=>"MEs4M0JKNlRCRTBBSENaMjROTk58QVFRNzNEREhDVFlFOVJDQ1JKNjF8UTdBRllCUlJUMjhKMUI1WDJSWDE=", "MD5OfMessageBody"=>"16af2171b5b83cfa35ce254966ba81e3"} # # On failure, send_message raises an exception. # # def send_message(queue_url, message) req_hash = generate_post_request('SendMessage', :message => message, :queue_url => queue_url) request_info(req_hash, SqsSendMessagesParser.new(:logger => @logger)) rescue on_exception end # Same as send_message alias_method :push_message, :send_message # Deletes message from queue. Returns +true+ or an exception. Amazon # returns +true+ on deletion of non-existent messages. You must use the # receipt handle for a message to delete it, not the message ID. # # From the SQS Developer Guide: # "It is possible you will receive a message even after you have deleted it. This might happen # on rare occasions if one of the servers storing a copy of the message is unavailable when # you request to delete the message. The copy remains on the server and might be returned to # you again on a subsequent receive request. You should create your system to be # idempotent so that receiving a particular message more than once is not a problem. " # # sqs.delete_message('http://queue.amazonaws.com/ZZ7XXXYYYBINS/my_awesome_queue', 'Euvo62/1nlIet...ao03hd9Sa0w==') #=> true # def delete_message(queue_url, receipt_handle) req_hash = generate_request('DeleteMessage', 'ReceiptHandle' => receipt_handle, :queue_url => queue_url) request_info(req_hash, SqsStatusParser.new(:logger => @logger)) rescue on_exception end # Sets new visibility timeout value for a message identified by "receipt_handle" # Check out the Amazon SQS API documentation for further details. def change_message_visibility(queue_url, receipt_handle, visibility_timeout) req_hash = generate_request( "ChangeMessageVisibility", "ReceiptHandle" => receipt_handle, "VisibilityTimeout" => visibility_timeout, :queue_url => queue_url ) request_info(req_hash, SqsStatusParser.new(:logger => @logger)) rescue on_exception end # Given the queue's short name, this call returns the queue URL or +nil+ if queue is not found # sqs.queue_url_by_name('my_awesome_queue') #=> 'http://queue.amazonaws.com/ZZ7XXXYYYBINS/my_awesome_queue' # def queue_url_by_name(queue_name) return queue_name if queue_name.include?('/') queue_urls = list_queues(queue_name) queue_urls.each do |queue_url| return queue_url if queue_name_by_url(queue_url) == queue_name end nil rescue on_exception end # Returns short queue name by url. # # RightSqs.queue_name_by_url('http://queue.amazonaws.com/ZZ7XXXYYYBINS/my_awesome_queue') #=> 'my_awesome_queue' # def self.queue_name_by_url(queue_url) queue_url[/[^\/]*$/] # rescue # on_exception end # Returns short queue name by url. # # sqs.queue_name_by_url('http://queue.amazonaws.com/ZZ7XXXYYYBINS/my_awesome_queue') #=> 'my_awesome_queue' # def queue_name_by_url(queue_url) self.class.queue_name_by_url(queue_url) rescue on_exception end # Returns approximate number of messages in queue. # # sqs.get_queue_length('http://queue.amazonaws.com/ZZ7XXXYYYBINS/my_awesome_queue') #=> 3 # def get_queue_length(queue_url) get_queue_attributes(queue_url)['ApproximateNumberOfMessages'].to_i rescue on_exception end # Removes all visible messages from queue. Return +true+ or an exception. # # sqs.clear_queue('http://queue.amazonaws.com/ZZ7XXXYYYBINS/my_awesome_queue') #=> true # def clear_queue(queue_url) while (pop_messages(queue_url, 10).length > 0); end # delete all messages in queue true rescue on_exception end # Pops (retrieves and deletes) up to 'number_of_messages' from queue. Returns an array of retrieved messages in format: [{:id=>'message_id', :body=>'message_body'}]. # # sqs.pop_messages('http://queue.amazonaws.com/ZZ7XXXYYYBINS/my_awesome_queue', 3) #=> # [{"ReceiptHandle"=>"Euvo62/...+Zw==", "MD5OfBody"=>"16af2...81e3", "Body"=>"Goodbyte World!", # "MessageId"=>"MEZI...JSWDE="}, {...}, ... , {...} ] # def pop_messages(queue_url, number_of_messages=1) messages = receive_message(queue_url, number_of_messages) messages.each do |message| delete_message(queue_url, message['ReceiptHandle']) end messages rescue on_exception end # Pops (retrieves and deletes) first accessible message from queue. Returns the message in format {:id=>'message_id', :body=>'message_body'} or +nil+. # # sqs.pop_message('http://queue.amazonaws.com/ZZ7XXXYYYBINS/my_awesome_queue') #=> # {:id=>"12345678904GEZX9746N|0N9ED344VK5Z3SV1DTM0|1RVYH4X3TJ0987654321", :body=>"message_1"} # def pop_message(queue_url) messages = pop_messages(queue_url) messages.nil? ? nil : messages[0] rescue on_exception end #----------------------------------------------------------------- # PARSERS: Status Response Parser #----------------------------------------------------------------- class SqsStatusParser < AwsParser # :nodoc: def tagend(name) if name == 'ResponseMetadata' @result = true end end end #----------------------------------------------------------------- # PARSERS: Queue #----------------------------------------------------------------- class SqsCreateQueueParser < AwsParser # :nodoc: def tagend(name) @result = @text if name == 'QueueUrl' end end class SqsListQueuesParser < AwsParser # :nodoc: def reset @result = [] end def tagend(name) @result << @text if name == 'QueueUrl' end end class SqsGetQueueAttributesParser < AwsParser # :nodoc: def reset @result = {} end def tagend(name) case name when 'Name'; @current_attribute = @text when 'Value'; @result[@current_attribute] = @text end end end #----------------------------------------------------------------- # PARSERS: Messages #----------------------------------------------------------------- class SqsReceiveMessageParser < AwsParser # :nodoc: def reset @result = [] end def tagstart(name, attributes) @current_message = {} if name == 'Message' end def tagend(name) case name when 'MessageId'; @current_message['MessageId'] = @text when 'ReceiptHandle'; @current_message['ReceiptHandle'] = @text when 'MD5OfBody'; @current_message['MD5OfBody'] = @text when 'Body'; @current_message['Body'] = @text; @result << @current_message end end end class SqsSendMessagesParser < AwsParser # :nodoc: def reset @result = {} end def tagend(name) case name when 'MessageId'; @result['MessageId'] = @text when 'MD5OfMessageBody'; @result['MD5OfMessageBody'] = @text end end end end end aws-2.10.2/lib/sqs/sqs.rb0000644000175000017500000002466312511515441013623 0ustar tnnntnnn# # Copyright (c) 2008 RightScale Inc # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # module Aws # # Aws::Sqs -- RightScale's Amazon SQS interface, API version # 2008-01-01 and later. # The Aws::Sqs class provides a complete interface to the second generation of Amazon's Simple # Queue Service. # For explanations of the semantics # of each call, please refer to Amazon's documentation at # http://developer.amazonwebservices.com/connect/kbcategory.jspa?categoryID=31 # # # Aws::Sqs is built atop Aws::SqsInterface, a lower-level # procedural API that may be appropriate for certain programs. # # Error handling: all operations raise an Aws::AwsError in case # of problems. Note that transient errors are automatically retried. # # sqs = Aws::Sqs.new(aws_access_key_id, aws_secret_access_key) # queue1 = sqs.queue('my_awesome_queue') # ... # queue2 = Aws::Sqs::Queue.create(sqs, 'my_cool_queue', true) # puts queue2.size # ... # message1 = queue2.receive # message1.visibility = 0 # puts message1 # ... # queue2.clear(true) # queue2.send_message('Ola-la!') # message2 = queue2.pop # ... # # NB: Second-generation SQS has eliminated the entire access grant mechanism present in Gen 1. # # Params is a hash: # # {:server => 'queue.amazonaws.com' # Amazon service host: 'queue.amazonaws.com' (default) # :port => 443 # Amazon service port: 80 or 443 (default) # :multi_thread => true|false # Multi-threaded (connection per each thread): true or false (default) # :signature_version => '0' # The signature version : '0' or '1'(default) # :logger => Logger Object} # Logger instance: logs to STDOUT if omitted } class Sqs attr_reader :interface def initialize(aws_access_key_id=nil, aws_secret_access_key=nil, params={}) @interface = SqsInterface.new(aws_access_key_id, aws_secret_access_key, params) end def close_connection @interface.close_connection end # Retrieves a list of queues. # Returns an +array+ of +Queue+ instances. # # Aws::Sqs.queues #=> array of queues # def queues(prefix=nil) @interface.list_queues(prefix).map do |url| Queue.new(self, url) end end # Returns Queue instance by queue name. # If the queue does not exist at Amazon SQS and +create+ is true, the method creates it. # # Aws::Sqs.queue('my_awesome_queue') #=> # # def queue(queue_name, create=true, visibility=nil) if create url = @interface.create_queue(queue_name, visibility) # this returns the url even if it exists else url = @interface.queue_url_by_name(queue_name) end url ? Queue.new(self, url) : nil end class Queue attr_reader :name, :url, :sqs # Returns Queue instance by queue name. # If the queue does not exist at Amazon SQS and +create+ is true, the method creates it. # # Aws::Sqs::Queue.create(sqs, 'my_awesome_queue') #=> # # def self.create(sqs, url_or_name, create=true, visibility=nil) sqs.queue(url_or_name, create, visibility) end # Creates new Queue instance. # Does not create a queue at Amazon. # # queue = Aws::Sqs::Queue.new(sqs, 'my_awesome_queue') # def initialize(sqs, url_or_name) @sqs = sqs @url = @sqs.interface.queue_url_by_name(url_or_name) raise ResourceNotFoundError, "Queue '#{url_or_name}' not found." unless @url @name = @sqs.interface.queue_name_by_url(@url) end # Retrieves queue size. # # queue.size #=> 1 # def size @sqs.interface.get_queue_length(@url) end # Clears queue, deleting only the visible messages. Any message within its visibility # timeout will not be deleted, and will re-appear in the queue in the # future when the timeout expires. # # To delete all messages in a queue and eliminate the chance of any # messages re-appearing in the future, it's best to delete the queue and # re-create it as a new queue. Note that doing this will take at least 60 # s since SQS does not allow re-creation of a queue within this interval. # # queue.clear() #=> true # def clear() @sqs.interface.clear_queue(@url) end # Deletes queue. Any messages in the queue will be permanently lost. # Returns +true+. # # NB: Use with caution; severe data loss is possible! # # queue.delete(true) #=> true # def delete(force=false) @sqs.interface.delete_queue(@url) end # Sends new message to queue. # Returns new Message instance that has been sent to queue. def send_message(message) message = message.to_s res = @sqs.interface.send_message(@url, message) msg = Message.new(self, res['MessageId'], nil, message) msg.send_checksum = res['MD5OfMessageBody'] msg.sent_at = Time.now msg end alias_method :push, :send_message # Retrieves several messages from queue. # Returns an array of Message instances. # # queue.receive_messages(2,10) #=> array of messages # def receive_messages(number_of_messages=1, visibility=nil) list = @sqs.interface.receive_message(@url, number_of_messages, visibility) list.map! do |entry| msg = Message.new(self, entry['MessageId'], entry['ReceiptHandle'], entry['Body'], visibility) msg.received_at = Time.now msg.receive_checksum = entry['MD5OfBody'] msg end end # Retrieves first accessible message from queue. # Returns Message instance or +nil+ it the queue is empty. # # queue.receive #=> # # def receive(visibility=nil) list = receive_messages(1, visibility) list.empty? ? nil : list[0] end # Pops (and deletes) first accessible message from queue. # Returns Message instance or +nil+ if the queue is empty. # # queue.pop #=> # # def pop list = @sqs.interface.pop_messages(@url, 1) return nil if list.empty? entry = list[0] msg = Message.new(self, entry['MessageId'], entry['ReceiptHandle'], entry['Body'], visibility) msg.received_at = Time.now msg.receive_checksum = entry['MD5OfBody'] msg end # Retrieves +VisibilityTimeout+ value for the queue. # Returns new timeout value. # # queue.visibility #=> 30 # def visibility @sqs.interface.get_queue_attributes(@url, 'VisibilityTimeout')['VisibilityTimeout'] end # Sets new +VisibilityTimeout+ for the queue. # Returns new timeout value. # # queue.visibility #=> 30 # queue.visibility = 33 # queue.visibility #=> 33 # def visibility=(visibility_timeout) @sqs.interface.set_queue_attributes(@url, 'VisibilityTimeout', visibility_timeout) visibility_timeout end # Sets new queue attribute value. # Not all attributes may be changed: +ApproximateNumberOfMessages+ (for example) is a read only attribute. # Returns a value to be assigned to attribute. # Currently, 'VisibilityTimeout' is the only settable queue attribute. # Attempting to set non-existent attributes generates an indignant # exception. # # queue.set_attribute('VisibilityTimeout', '100') #=> '100' # queue.get_attribute('VisibilityTimeout') #=> '100' # def set_attribute(attribute, value) @sqs.interface.set_queue_attributes(@url, attribute, value) value end # Retrieves queue attributes. # At this moment Amazon supports +VisibilityTimeout+ and +ApproximateNumberOfMessages+ only. # If the name of attribute is set, returns its value. Otherwise, returns a hash of attributes. # # queue.get_attribute('VisibilityTimeout') #=> {"VisibilityTimeout"=>"45"} # def get_attribute(attribute='All') attributes = @sqs.interface.get_queue_attributes(@url, attribute) attribute=='All' ? attributes : attributes[attribute] end end class Message attr_reader :queue, :id, :body, :visibility, :receipt_handle attr_accessor :sent_at, :received_at, :send_checksum, :receive_checksum def initialize(queue, id=nil, rh = nil, body=nil, visibility=nil) @queue = queue @id = id @receipt_handle = rh @body = body @visibility = visibility @sent_at = nil @received_at = nil @send_checksum = nil @receive_checksum = nil end # Returns +Message+ instance body. def to_s @body end # Removes message from queue. # Returns +true+. def delete @queue.sqs.interface.delete_message(@queue.url, @receipt_handle) if @receipt_handle end # Updates visibility timeout. def visibility=(visibility_timeout) if @receipt_handle @queue.sqs.interface.change_message_visibility( @queue.url, @receipt_handle, visibility_timeout ) @visibility = visibility_timeout end end end end end aws-2.10.2/lib/iam/0000755000175000017500000000000012511515441012415 5ustar tnnntnnnaws-2.10.2/lib/iam/iam.rb0000644000175000017500000001043112511515441013507 0ustar tnnntnnnmodule Aws require 'xmlsimple' class Iam < AwsBase include AwsBaseInterface API_VERSION = "2010-05-08" DEFAULT_HOST = "iam.amazonaws.com" DEFAULT_PATH = '/' DEFAULT_PROTOCOL = 'https' DEFAULT_PORT = 443 def self.connection_name :iam_connection end @@bench = AwsBenchmarkingBlock.new def self.bench @@bench end def self.bench_xml @@bench.xml end def self.bench_ec2 @@bench.service end # Current API version (sometimes we have to check it outside the GEM). @@api = ENV['IAM_API_VERSION'] || API_VERSION def self.api @@api end def initialize(aws_access_key_id=nil, aws_secret_access_key=nil, params={}) init({:name => 'IAM', :default_host => ENV['IAM_URL'] ? URI.parse(ENV['IAM_URL']).host : DEFAULT_HOST, :default_port => ENV['IAM_URL'] ? URI.parse(ENV['IAM_URL']).port : DEFAULT_PORT, :default_service => ENV['IAM_URL'] ? URI.parse(ENV['IAM_URL']).path : DEFAULT_PATH, :default_protocol => ENV['IAM_URL'] ? URI.parse(ENV['IAM_URL']).scheme : DEFAULT_PROTOCOL, :api_version => API_VERSION}, aws_access_key_id || ENV['AWS_ACCESS_KEY_ID'], aws_secret_access_key|| ENV['AWS_SECRET_ACCESS_KEY'], params) end def do_request(action, params, options={}) link = generate_request(action, params) p link[:request] resp = request_info_xml_simple(:iam_connection, @params, link, @logger, :group_tags =>{"LoadBalancersDescriptions"=>"LoadBalancersDescription", "DBParameterGroups" =>"DBParameterGroup", "DBSecurityGroups" =>"DBSecurityGroup", "EC2SecurityGroups" =>"EC2SecurityGroup", "IPRanges" =>"IPRange"}, :force_array =>["DBInstances", "DBParameterGroups", "DBSecurityGroups", "EC2SecurityGroups", "IPRanges"], :pull_out_array =>options[:pull_out_array], :pull_out_single=>options[:pull_out_single], :wrapper =>options[:wrapper]) end #----------------------------------------------------------------- # REQUESTS #----------------------------------------------------------------- # options: # :marker => value received from previous response if IsTruncated = true # :max_items => number of items you want returned # :path_previx => for filtering results, default is / def list_server_certificates(options={}) @logger.info("Listing server certificates...") params = {} params['Marker'] = options[:marker] if options[:marker] params['MaxItems'] = options[:max_items] if options[:max_items] params['PathPrefix'] = options[:path_prefix] if options[:path_prefix] resp = do_request("ListServerCertificates", params, :pull_out_array=>[:list_server_certificates_result, :server_certificate_metadata_list]) rescue Exception on_exception end # # name: name of certificate # public_key: public key certificate in PEM-encoded format # private_key: private key in PEM-encoded format # options: # :path => specify a path you want it stored in # :certificate_chain => contents of certificate chain def upload_server_certificate(name, public_key, private_key, options={}) params = {} params['ServerCertificateName'] = name params['PrivateKey'] = private_key params['CertificateBody'] = public_key params['CertificateChain'] = options[:certificate_chain] if options[:certificate_chain] params['Path'] = options[:path] if options[:path] p params resp = do_request("UploadServerCertificate", params, :pull_out_array=>[:list_server_certificates_result, :server_certificate_metadata_list]) rescue Exception on_exception end end endaws-2.10.2/Rakefile0000644000175000017500000000305312511515441012547 0ustar tnnntnnnrequire 'rubygems' require 'rake' require 'rake/testtask' Rake::TestTask.new(:test) do |test| test.libs << 'lib' << 'test' test.pattern = 'test/**/test_*.rb' test.verbose = true end task :default => :test require 'rdoc/task' Rake::RDocTask.new do |rdoc| version = File.exist?('VERSION') ? File.read('VERSION') : "" rdoc.rdoc_dir = 'doc' rdoc.title = "iron_mq #{version}" rdoc.rdoc_files.include('README*') rdoc.rdoc_files.include('lib/**/*.rb') end desc "Test just the SQS interface" task :testsqs do require 'test/test_credentials' require 'test/http_connection' TestCredentials.get_credentials require 'test/sqs/test_sqs.rb' end desc "Test just the S3 interface" task :tests3 do require 'test/test_credentials' require 'test/http_connection' TestCredentials.get_credentials require 'test/s3/test_s3.rb' end desc "Test just the S3 interface using local stubs" task :tests3local do require 'test/test_credentials' require 'test/http_connection' TestCredentials.get_credentials require 'test/s3/test_s3_stubbed.rb' end desc "Test just the EC2 interface" task :testec2 do require 'test/test_credentials' TestCredentials.get_credentials require 'test/ec2/test_ec2.rb' end desc "Test just the SDB interface" task :testsdb do require 'test/test_credentials' TestCredentials.get_credentials require 'test/sdb/test_sdb.rb' end desc "Test CloudFront interface" task :testacf do require 'test/test_credentials' TestCredentials.get_credentials require 'test/acf/test_acf.rb' end aws-2.10.2/README.markdown0000644000175000017500000001317712511515441013613 0ustar tnnntnnn# Appoxy AWS Library A Ruby gem for all Amazon Web Services. Brought to you by: [![Appoxy](https://lh5.googleusercontent.com/_-J9DSaseOX8/TX2Bq564w-I/AAAAAAAAxYU/xjeReyoxa8o/s800/appoxy-small%20%282%29.png)](http://www.appoxy.com) ## Discussion Group [http://groups.google.com/group/ruby-aws](http://groups.google.com/group/ruby-aws) ## Documentation [Ruby Docs](http://rubydoc.info/gems/aws/2.4.5/frames) ## Appoxy Amazon Web Services Ruby Gems Published by [Appoxy LLC](http://www.appoxy.com), under the MIT License. Special thanks to RightScale from which this project is forked. ## INSTALL: gem install aws Then `require 'aws'` in your application. ## DESCRIPTION: The AWS gems have been designed to provide a robust, fast, and secure interface to Amazon EC2, EBS, S3, SQS, SDB, and CloudFront. The AWS gems comprise: - Aws::Ec2 -- interface to Amazon EC2 (Elastic Compute Cloud) and the associated EBS (Elastic Block Store) - Aws::S3 and Aws::S3Interface -- interface to Amazon S3 (Simple Storage Service) - Aws::Sqs and Aws::SqsInterface -- interface to Amazon SQS (Simple Queue Service) - Aws::SdbInterface -- interface to Amazon SDB (SimpleDB). See [SimpleRecord for an ActiveRecord like gem](https://github.com/appoxy/simple_record). - Aws::AcfInterface -- interface to Amazon CloudFront, a content distribution service - Aws::ElbInterface -- interface to Amazon Load Balancing service - Aws::MonInterface -- interface to Amazon CloudWatch monitoring service - Aws::Iam -- for AWS Identity and Access Management To use a single piece intead of loading all of then, you can require it explicitly for example: `require 'aws/sqs'`. ## FEATURES: - Full programmmatic access to EC2, EBS, S3, SQS, SDB, ELB, and CloudFront. - Complete error handling: all operations check for errors and report complete error information by raising an AwsError. - Persistent HTTP connections with robust network-level retry layer using RightHttpConnection). This includes socket timeouts and retries. - Robust HTTP-level retry layer. Certain (user-adjustable) HTTP errors returned by Amazon's services are classified as temporary errors. These errors are automaticallly retried using exponentially increasing intervals. The number of retries is user-configurable. - Fast REXML-based parsing of responses (as fast as a pure Ruby solution allows). - Uses libxml (if available) for faster response parsing. - Support for large S3 list operations. Buckets and key subfolders containing many (> 1000) keys are listed in entirety. Operations based on list (like bucket clear) work on arbitrary numbers of keys. - Support for streaming GETs from S3, and streaming PUTs to S3 if the data source is a file. - Support for single-threaded usage, multithreaded usage, as well as usage with multiple AWS accounts. - Support for both first- and second-generation SQS (API versions 2007-05-01 and 2008-01-01). These versions of SQS are not compatible. - Support for signature versions 0, 1 and 2 on all services. - Interoperability with any cloud running Eucalyptus (http://eucalyptus.cs.ucsb.edu) - Test suite (requires AWS account to do "live" testing). ## THREADING: All AWS interfaces offer three threading options: 1. Use a single persistent HTTP connection per process. :single 2. Use a persistent HTTP connection per Ruby thread. :per_thread 3. Open a new connection for each request. :per_request Either way, it doesn't matter how many (for example) Aws::S3 objects you create, they all use the same per-program or per-thread connection. The purpose of sharing the connection is to keep a single persistent HTTP connection open to avoid paying connection overhead on every request. However, if you have multiple concurrent threads, you may want or need an HTTP connection per thread to enable concurrent requests to AWS. The way this plays out in practice is: 1. If you have a non-multithreaded Ruby program, use the non-multithreaded setting. 2. If you have a multi-threaded Ruby program, use the multithreaded setting to enable concurrent requests to S3 (or SQS, or SDB, or EC2). 3. For running under Mongrel/Rails, use the non-multithreaded setting even though mongrel is multithreaded. This is because only one Rails handler is invoked at time (i.e. it acts like a single-threaded program) Note that due to limitations in the I/O of the Ruby interpreter you may not get the degree of parallelism you may expect with the multi-threaded setting. By default, EC2/S3/SQS/SDB/ACF interface instances are created in per_request mode. Set params[:connection_mode] to :per_thread in the initialization arguments to use multithreaded mode. == LICENSE: Copyright (c) 2007-2009 RightScale, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.