right_aws_api 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +7 -0
  2. data/HISTORY +2 -0
  3. data/LICENSE +19 -0
  4. data/README.md +164 -0
  5. data/Rakefile +38 -0
  6. data/lib/cloud/aws/as/manager.rb +118 -0
  7. data/lib/cloud/aws/base/helpers/utils.rb +328 -0
  8. data/lib/cloud/aws/base/manager.rb +186 -0
  9. data/lib/cloud/aws/base/parsers/response_error.rb +117 -0
  10. data/lib/cloud/aws/base/routines/request_signer.rb +80 -0
  11. data/lib/cloud/aws/cf/manager.rb +171 -0
  12. data/lib/cloud/aws/cf/routines/request_signer.rb +70 -0
  13. data/lib/cloud/aws/cf/wrappers/default.rb +213 -0
  14. data/lib/cloud/aws/cfm/manager.rb +90 -0
  15. data/lib/cloud/aws/cw/manager.rb +113 -0
  16. data/lib/cloud/aws/eb/manager.rb +87 -0
  17. data/lib/cloud/aws/ec/manager.rb +91 -0
  18. data/lib/cloud/aws/ec2/manager.rb +222 -0
  19. data/lib/cloud/aws/elb/manager.rb +120 -0
  20. data/lib/cloud/aws/emr/manager.rb +86 -0
  21. data/lib/cloud/aws/iam/manager.rb +100 -0
  22. data/lib/cloud/aws/rds/manager.rb +110 -0
  23. data/lib/cloud/aws/route53/manager.rb +177 -0
  24. data/lib/cloud/aws/route53/routines/request_signer.rb +70 -0
  25. data/lib/cloud/aws/route53/wrappers/default.rb +132 -0
  26. data/lib/cloud/aws/s3/manager.rb +373 -0
  27. data/lib/cloud/aws/s3/parsers/response_error.rb +76 -0
  28. data/lib/cloud/aws/s3/routines/request_signer.rb +243 -0
  29. data/lib/cloud/aws/s3/wrappers/default.rb +315 -0
  30. data/lib/cloud/aws/sdb/manager.rb +150 -0
  31. data/lib/cloud/aws/sns/manager.rb +96 -0
  32. data/lib/cloud/aws/sqs/manager.rb +335 -0
  33. data/lib/right_aws_api.rb +45 -0
  34. data/lib/right_aws_api_version.rb +40 -0
  35. data/right_aws_api.gemspec +55 -0
  36. data/spec/describe_calls.rb +92 -0
  37. metadata +118 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 113d86af588e3e120100ac9a99ca962f716e8ee9
4
+ data.tar.gz: 50cece7801a4993dff77168a339fed0bfff64a3d
5
+ SHA512:
6
+ metadata.gz: 2abec6e84d93498ea70d7299814becc689deab4a63b9b3f0f8b5324a1ed71a26c4bbf25b5b71e14177e541c74aebaa2752e4ae6afe08c93fc7737a096b073b20
7
+ data.tar.gz: 1b661a9ed16de1e9ee55e5e39d95165856b5fef65450703accf71822e1c18759b58c65137cdad6aa5dd366d00d6b6f260244ded69959e2ef17727d24e5cd0c27
data/HISTORY ADDED
@@ -0,0 +1,2 @@
1
+ == 2013-06-28
2
+ - pre-release candidate created
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 RightScale, Inc, All Rights Reserved Worldwide.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+ The above copyright notice and this permission notice shall be included in
12
+ all copies or substantial portions of the Software.
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,164 @@
1
+ # AWS (Amazon Web Services) library for Ruby
2
+
3
+ This gem provides access to many AWS cloud services.
4
+ Unlike many other AWS libaries this gem is a very thin adaptation layer over the AWS APIs.
5
+ It is highly meta-programmed and exposes the exact AWS API calls with the
6
+ exact AWS parameters. There are two big benefits to this approach: you don't have to translate betwen the
7
+ AWS docs and the library in order to figure out what to do, and there's nothing in the AWS APIs you can't
8
+ call through the library. The library doesn't even need to be updated when AWS introduces new API features.
9
+ The downside to this approach is that when things don't work it's sometimes more difficult to figure out
10
+ what's happening... Another downside is that method names and parameter names follow the AWS API spec and
11
+ thereby run against Ruby conventions.
12
+
13
+ If you encounter problems please open a github issue.
14
+
15
+ ## Docs
16
+
17
+ For some getting-started info see further down but for detailed docs see
18
+ http://rightscale.github.io/right_aws_api/frames.html#!file.README.html
19
+
20
+ ## Features
21
+
22
+ The gem supports the following AWS services out of the box:
23
+
24
+ - {RightScale::CloudApi::AWS::AS::Manager Auto Scaling (AS)}
25
+ - {RightScale::CloudApi::AWS::CF::Manager Cloud Front (CF)}
26
+ - {RightScale::CloudApi::AWS::CFM::Manager Cloud Formation (CFM)}
27
+ - {RightScale::CloudApi::AWS::CW::Manager Cloud Watch (CW)}
28
+ - {RightScale::CloudApi::AWS::EB::Manager Elastic Beanstalk (EB)}
29
+ - {RightScale::CloudApi::AWS::EC::Manager Elasti Cache (EC)}
30
+ - {RightScale::CloudApi::AWS::EC2::Manager Elastic Compute Cloud (EC2)}
31
+ - {RightScale::CloudApi::AWS::ELB::Manager Elastic Load Balancing (ELB)}
32
+ - {RightScale::CloudApi::AWS::EMR::Manager Elastic Map Reduce (EMR)}
33
+ - {RightScale::CloudApi::AWS::IAM::Manager Identity and Access Management (IAM)}
34
+ - {RightScale::CloudApi::AWS::RDS::Manager Relational Database Service (RDS)}
35
+ - {RightScale::CloudApi::AWS::Route53::Manager Route 53 (Route53)}
36
+ - {RightScale::CloudApi::AWS::S3::Manager Simple Storage Service (S3)}
37
+ - {RightScale::CloudApi::AWS::SDB::Manager Simple DB (SDB)}
38
+ - {RightScale::CloudApi::AWS::SNS::Manager Simple Notification Service (SNS)}
39
+ - {RightScale::CloudApi::AWS::SQS::Manager Simple Queue Service (SQS)}
40
+
41
+ And it is easy to add support for other. You will need to refer to
42
+ the AWS docs (http://aws.amazon.com/documentation/) for all the API params and usage explanations.
43
+
44
+ ## Basic usage
45
+
46
+ ### Amazon Elastic Compute Cloud (EC2)
47
+
48
+ This library supports all existing and future EC2 API calls. If you know that EC2 supports a
49
+ call and you know what params it accepts - just call the method with those params.
50
+
51
+ #### Basics
52
+
53
+ ```ruby
54
+ require "right_aws_api"
55
+
56
+ key = ENV['AWS_ACCESS_KEY_ID']
57
+ secret = ENV['AWS_SECRET_ACCESS_KEY']
58
+ endpoint = 'https://us-east-1.ec2.amazonaws.com'
59
+ ec2 = RightScale::CloudApi::AWS::EC2::Manager.new(key, secret, endpoint)
60
+
61
+ ec2.ThisCallMustBeSupportedByEc2('Param.1' => 'A', 'Param.2' => 'B')
62
+ ```
63
+
64
+ #### EC2 Instances
65
+
66
+ ```ruby
67
+ require "right_aws_api"
68
+
69
+ # Get a list of your instances
70
+ ec2.DescribeInstances
71
+
72
+ # Describe custom Instance(s)
73
+ ec2.DescribeInstances('InstanceId' => "i-2ba7c640")
74
+ ec2.DescribeInstances('InstanceId' => ["i-2ba7c640", "i-7db9101e"])
75
+
76
+ # Describe Instances with filteringSame (another way, the result is the same):
77
+ ec2.DescribeInstances(
78
+ 'Filter' => [
79
+ {'Name' => 'architecture',
80
+ 'Value' => 'i386'},
81
+ {'Name' => 'availability-zone',
82
+ 'Value' => [ 'us-east-1a', 'us-east-1d' ]},
83
+ {'Name' => 'instance-type',
84
+ 'Value' => 'm1.small'} ]
85
+ )
86
+
87
+ # Run an new instance:
88
+ ec2.RunInstances(
89
+ 'ImageId' => 'ami-8ef607e7',
90
+ 'MinCount' => 1,
91
+ 'MaxCount' => 1,
92
+ 'KeyName' => 'kd: alex',
93
+ 'UserData' => RightScale::CloudApi::Utils::base64en('Hehehehe!!!!'),
94
+ 'InstanceType' => 'c1.medium',
95
+ 'ClientToken' => RightScale::CloudApi::Utils::generate_token,
96
+ 'SecurityGroupId' => ['sg-f71a089e', 'sg-c71a08ae' ],
97
+ 'Placement+' => {
98
+ 'AvailabilityZone' => 'us-east-1d',
99
+ 'Tenancy' => 'default' },
100
+ 'BlockDeviceMapping' => [
101
+ {'DeviceName' => '/dev/sdc',
102
+ 'Ebs' => {
103
+ 'SnapshotId' => 'snap-e40fd188',
104
+ 'VolumeSize' => 3,
105
+ 'DeleteOnTermination' => true}} ]
106
+ )
107
+
108
+ # Terminate your instance:
109
+ ec2.TerminateInstances("InstanceId" => ["i-67c87504", "i-67c87504"])
110
+ ```
111
+
112
+ ### Amazon Simple Storage Service (S3)
113
+
114
+ ```ruby
115
+ require "right_aws_api"
116
+
117
+ key = ENV['AWS_ACCESS_KEY_ID']
118
+ secret = ENV['AWS_SECRET_ACCESS_KEY']
119
+ endpoint = 'https://s3.amazonaws.com'
120
+ s3 = RightScale::CloudApi::AWS::S3::Manager::new(key, secret, endpoint)
121
+
122
+ s3.ListBuckets
123
+ s3.GetBucketAcl('Bucket' => 'my-lovely-bucket')
124
+ s3.GetObject('Bucket' => 'my-lovely-bucket', 'Object' => 'fairies.txt')
125
+ ```
126
+
127
+ ### Amazon Simple Queue Service (SQS)
128
+
129
+ ```ruby
130
+ require "right_aws_api"
131
+
132
+ key = ENV['AWS_ACCESS_KEY_ID']
133
+ secret = ENV['AWS_SECRET_ACCESS_KEY']
134
+ account_number = ENV['AWS_ACCOUNT_NUMBER']
135
+ endpoint = 'https://sqs.us-east-1.amazonaws.com'
136
+ sqs = RightScale::CloudApi::AWS::SQS::Manager.new(key, secret, account_number, endpoint)
137
+
138
+ # List all queues
139
+ sqs.ListQueues
140
+
141
+ # Create a new one
142
+ sqs.CreateQueue(
143
+ 'QueueName' => 'myCoolQueue',
144
+ 'Attribute' => [
145
+ { 'Name' => 'VisibilityTimeout', 'Value' => 40 },
146
+ { 'Name' => 'MaximumMessageSize', 'Value' => 2048 } ])
147
+
148
+ # Send a message
149
+ message = URI::escape('Woohoo!!!')
150
+ sqs.SendMessage('myCoolQueue', 'MessageBody' => message)
151
+
152
+ # Receive a message
153
+ sqs.ReceiveMessage('myCoolQueue')
154
+
155
+ # Kill the queue
156
+ sqs.DeleteQueue('myCoolQueue')
157
+ ```
158
+
159
+ ## Dependencies
160
+
161
+ This gem depends on a base gem which is shared across all RightScale cloud libraries:
162
+ https://github.com/rightscale/right_cloud_api_base
163
+
164
+ ### (c) 2014 by RightScale, Inc., see the LICENSE file for the open-source license.
data/Rakefile ADDED
@@ -0,0 +1,38 @@
1
+ #--
2
+ # Copyright (c) 2013 RightScale, Inc.
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # 'Software'), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ require 'rubygems'
25
+
26
+ begin
27
+ require 'bundler'
28
+ Bundler::GemHelper.install_tasks
29
+ rescue LoadError => e
30
+ STDERR.puts("Bundler is not available, some rake tasks will not be defined: #{e.message}")
31
+ end
32
+
33
+ require 'rspec/core/rake_task'
34
+
35
+ RSpec::Core::RakeTask.new(:spec)
36
+
37
+ task :default => :spec
38
+
@@ -0,0 +1,118 @@
1
+ #--
2
+ # Copyright (c) 2013 RightScale, Inc.
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # 'Software'), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ require "cloud/aws/base/manager"
25
+
26
+ module RightScale
27
+ module CloudApi
28
+ module AWS
29
+
30
+ # AutoScaling namespace
31
+ module AS
32
+
33
+ # Amazon AutoScaling (AS) compatible manager (thread safe).
34
+ #
35
+ # @example
36
+ # require "right_aws_api"
37
+ #
38
+ # # Create a manager to access Auto Scaling.
39
+ # as = RightScale::CloudApi::AWS::AS::Manager::new(key, secret, 'https://autoscaling.us-east-1.amazonaws.com')
40
+ #
41
+ # # Get a description of each Auto Scaling instance in the InstanceIds list.
42
+ # as.DescribeAutoScalingInstances #=>
43
+ # {"DescribeAutoScalingGroupsResponse"=>
44
+ # {"@xmlns"=>"http://autoscaling.amazonaws.com/doc/2011-01-01/",
45
+ # "DescribeAutoScalingGroupsResult"=>
46
+ # {"AutoScalingGroups"=>
47
+ # {"member"=>
48
+ # {"SuspendedProcesses"=>nil,
49
+ # "Tags"=>nil,
50
+ # "AutoScalingGroupName"=>"CentOS.5.1-c-array",
51
+ # "HealthCheckType"=>"EC2",
52
+ # "CreatedTime"=>"2009-05-28T09:31:21.133Z",
53
+ # "EnabledMetrics"=>nil,
54
+ # "LaunchConfigurationName"=>"CentOS.5.1-c",
55
+ # "Instances"=>nil,
56
+ # "DesiredCapacity"=>"0",
57
+ # "AvailabilityZones"=>{"member"=>"us-east-1a"},
58
+ # "LoadBalancerNames"=>nil,
59
+ # "MinSize"=>"0",
60
+ # "VPCZoneIdentifier"=>nil,
61
+ # "HealthCheckGracePeriod"=>"0",
62
+ # "DefaultCooldown"=>"0",
63
+ # "AutoScalingGroupARN"=>
64
+ # "arn:aws:autoscaling:us-east-1:82...25:autoScalingGroup:47..5f-0d65-46cb-8a0c-0..000:autoScalingGroupName/CentOS.5.1-c-array",
65
+ # "TerminationPolicies"=>{"member"=>"Default"},
66
+ # "MaxSize"=>"3"}}},
67
+ # "ResponseMetadata"=>{"RequestId"=>"04022bd4-4f5d-11e2-b437-318e12cd4660"}}}
68
+ #
69
+ # @example
70
+ # # Get only records you need:
71
+ # as.DescribeAutoScalingInstances( 'AutoScalingGroupNames.member' => ["CentOS.5.1-c-array", "CentOS.5.2-d-array"])
72
+ #
73
+ # @example
74
+ # # Set the max number of records to bre returned:
75
+ # as.DescribeAutoScalingInstances('MaxRecords' => 3)
76
+ #
77
+ # @example
78
+ # # Create a new Auto Scaling group
79
+ # as.CreateAutoScalingGroup('AutoScalingGroupName' => 'my-test-asgroup',
80
+ # 'DesiredCapacity' => 5,
81
+ # 'PlacementGroup' => 'my-cool-group')
82
+ #
83
+ # @see ApiManager
84
+ # @see http://docs.amazonwebservices.com/AutoScaling/latest/APIReference/API_Operations.html
85
+ #
86
+ class Manager < AWS::Manager
87
+ end
88
+
89
+
90
+ # Amazon AutoScaling (AS) compatible manager (thread unsafe).
91
+ #
92
+ # @see Manager
93
+ #
94
+ class ApiManager < AWS::ApiManager
95
+
96
+ # Default API version for AutoScaling service.
97
+ # To override the API version use :api_version key when instantiating a manager.
98
+ #
99
+ DEFAULT_API_VERSION = '2011-01-01'
100
+
101
+ error_pattern :abort_on_timeout, :path => /Action=(Create)/
102
+ error_pattern :retry, :response => /InternalError|Unavailable/i
103
+ error_pattern :disconnect_and_abort, :code => /5..|403|408/
104
+ error_pattern :disconnect_and_abort, :code => /4../, :if => Proc.new{ |opts| rand(100) < 10 }
105
+
106
+ set :response_error_parser => Parser::AWS::ResponseErrorV2
107
+
108
+ cache_pattern :verb => /get|post/,
109
+ :path => /Action=List/,
110
+ :if => Proc::new{ |o| (o[:params].keys - COMMON_QUERY_PARAMS)._blank? },
111
+ :key => Proc::new{ |o| o[:params]['Action'] },
112
+ :sign => Proc::new{ |o| o[:response].body.to_s.sub(%r{<RequestId>.+?</RequestId>}i,'') }
113
+ end
114
+ end
115
+
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,328 @@
1
+ #--
2
+ # Copyright (c) 2013 RightScale, Inc.
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # 'Software'), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ module RightScale
25
+ module CloudApi
26
+ # Helper methods namespace
27
+ module Utils
28
+ # AWS helpers namespace
29
+ module AWS
30
+
31
+ @@digest1 = OpenSSL::Digest.new("sha1")
32
+ @@digest256 = nil
33
+ if OpenSSL::OPENSSL_VERSION_NUMBER > 0x00908000
34
+ @@digest256 = OpenSSL::Digest.new("sha256") rescue nil # Some installations may not support sha256
35
+ end
36
+
37
+
38
+ # Generates a signature for the given string, secret access key and digest
39
+ #
40
+ # @param [String] aws_secret_access_key
41
+ # @param [String] auth_string
42
+ # @param [String] digest
43
+ #
44
+ # @return [String] The signature.
45
+ #
46
+ # @example
47
+ # RightScale::CloudApi::Utils::AWS.sign('my-secret-key', 'something-that-needs-to-be-signed') #=>
48
+ # 'kdHo0Ks4KkypU1CkYZzAxFIIX+0='
49
+ #
50
+ def self.sign(aws_secret_access_key, auth_string, digest=nil)
51
+ Utils::base64en(OpenSSL::HMAC.digest(digest || @@digest1, aws_secret_access_key, auth_string))
52
+ end
53
+
54
+
55
+ # Returns ISO-8601 representation for the given time
56
+ #
57
+ # @param [Time,Fixnum] time
58
+ # @return [String]
59
+ #
60
+ # @example
61
+ # RightScale::CloudApi::Utils::AWS.utc_iso8601(Time.now) #=> '2013-03-22T21:00:21.000Z'
62
+ #
63
+ # @example
64
+ # RightScale::CloudApi::Utils::AWS.utc_iso8601(0) #=> '1970-01-01T00:00:00.000Z'
65
+ #
66
+ def self.utc_iso8601(time)
67
+ case
68
+ when time.is_a?(Fixnum) then Time::at(time)
69
+ when time.is_a?(String) then Time::parse(time)
70
+ else time
71
+ end.utc.strftime("%Y-%m-%dT%H:%M:%S.000Z")
72
+ end
73
+
74
+
75
+ # Escapes a string accordingly to Amazon rules
76
+ # @see http://docs.aws.amazon.com/general/latest/gr/signature-version-2.html
77
+ #
78
+ # @param [String] string
79
+ # @return [String]
80
+ #
81
+ # @example
82
+ # RightScale::CloudApi::Utils::AWS.amz_escape('something >= 13') #=>
83
+ # 'something%20%3E%3D%2013'
84
+ #
85
+ def self.amz_escape(string)
86
+ string = string.to_s
87
+ # Use UTF-8 if current ruby supports it (1.9+)
88
+ string = string.encode("UTF-8") if string.respond_to?(:encode)
89
+ # CGI::escape is too clever:
90
+ # - it escapes '~' when Amazon wants it to be un-escaped
91
+ # - it escapes ' ' as '+' but Amazon loves it as '%20'
92
+ CGI.escape(string).gsub('%7E','~').gsub('+','%20')
93
+ end
94
+
95
+
96
+ # Signature Version 2
97
+ #
98
+ # EC2, SQS and SDB requests must be signed by this guy
99
+ #
100
+ # @param [String] aws_secret_access_key
101
+ # @param [Hash] params
102
+ # @param [String,Symbol] verb 'get' | 'post'
103
+ # @param [String] host
104
+ # @param [String] urn
105
+ #
106
+ # @return [String]
107
+ #
108
+ # @example
109
+ # params = {'InstanceId' => 'i-00000000'}
110
+ # sign_v2_signature('secret', params, :get, 'ec2.amazonaws.com', '/') #=>
111
+ # "InstanceId=i-00000000&SignatureMethod=HmacSHA256&SignatureVersion=2&
112
+ # Timestamp=2014-03-12T21%3A52%3A21.000Z&Signature=gR2x3oWmNbh4bdZksPS
113
+ # sg3t7U0zbTcnFOfizWF3Zujw%3D"
114
+ #
115
+ # @see http://docs.aws.amazon.com/general/latest/gr/signature-version-2.html
116
+ # @see http://aws.amazon.com/articles/1928?_encoding=UTF8&jiveRedirect=1
117
+ #
118
+ def self.sign_v2_signature(aws_secret_access_key, params, verb, host, urn)
119
+ params["Timestamp"] ||= utc_iso8601(Time.now) unless params["Expires"]
120
+ params["SignatureVersion"] = '2'
121
+ # select a signing method (make an old openssl working with sha1)
122
+ # make 'HmacSHA256' to be a default one
123
+ params['SignatureMethod'] = 'HmacSHA256' unless ['HmacSHA256', 'HmacSHA1'].include?(params['SignatureMethod'])
124
+ params['SignatureMethod'] = 'HmacSHA1' unless @@digest256
125
+ # select a digest
126
+ digest = (params['SignatureMethod'] == 'HmacSHA256' ? @@digest256 : @@digest1)
127
+ # form string to sign
128
+ canonical_string = Utils::params_to_urn(params){ |value| amz_escape(value) }
129
+ string_to_sign = "#{verb.to_s.upcase}\n" +
130
+ "#{host.downcase}\n" +
131
+ "#{urn}\n" +
132
+ "#{canonical_string}"
133
+ "#{canonical_string}&Signature=#{amz_escape(sign(aws_secret_access_key, string_to_sign, digest))}"
134
+ end
135
+
136
+
137
+ # Returns +true+ if the provided bucket name is a DNS compliant bucket name
138
+ #
139
+ # @see http://docs.aws.amazon.com/AmazonS3/2006-03-01/dev/BucketRestrictions.html
140
+ #
141
+ # @param [String] bucket_name
142
+ # @return [Boolean]
143
+ #
144
+ # @example
145
+ # RightScale::CloudApi::Utils::AWS.is_dns_bucket?('my') #=> false
146
+ #
147
+ # @example
148
+ # RightScale::CloudApi::Utils::AWS.is_dns_bucket?('my_bycket') #=> false
149
+ #
150
+ # @example
151
+ # RightScale::CloudApi::Utils::AWS.is_dns_bucket?('my-bucket') #=> true
152
+ #
153
+ def self.is_dns_bucket?(bucket_name)
154
+ bucket_name = bucket_name.to_s
155
+ return false unless (3..63) === bucket_name.size
156
+ bucket_name.split('.').each do |component|
157
+ return false unless component[/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/]
158
+ end
159
+ true
160
+ end
161
+
162
+
163
+ # Signs and Authenticates REST Requests
164
+ #
165
+ # @param [String] aws_secret_access_key
166
+ # @param [String,Symbol] verb 'get' | 'post'
167
+ # @param [String] canonicalized_resource
168
+ # @param [Hash] _headers
169
+ #
170
+ # @return [String]
171
+ #
172
+ # @example
173
+ # sign_s3_signature('secret', :get, 'xxx/yyy/zzz/object', {'header'=>'value'}) #=>
174
+ # "i85igH0sftHD/cGZcLiBKcYEuks="
175
+ #
176
+ # @see http://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html
177
+ #
178
+ def self.sign_s3_signature(aws_secret_access_key, verb, canonicalized_resource, _headers={})
179
+ headers = {}
180
+ # Make sure all our headers ara downcased
181
+ _headers.each do |key, value|
182
+ headers[key.to_s.downcase] = value.is_a?(Array) ? value.join(',') : value
183
+ end
184
+ content_md5 = headers['content-md5']
185
+ content_type = headers['content-type']
186
+ date = headers['x-amz-date'] || headers['date'] || headers['expires']
187
+ canonicalized_x_amz_headers = headers.select{|key, value| key[/^x-amz-/]}.sort.map{|key, value| "#{key}:#{value}"}.join("\n")
188
+ canonicalized_x_amz_headers << "\n" unless canonicalized_x_amz_headers._blank?
189
+ # StringToSign
190
+ string_to_sign = "#{verb.to_s.upcase}\n" +
191
+ "#{content_md5}\n" +
192
+ "#{content_type}\n" +
193
+ "#{date}\n" +
194
+ "#{canonicalized_x_amz_headers}"+
195
+ "#{canonicalized_resource}"
196
+ sign(aws_secret_access_key, string_to_sign)
197
+ end
198
+
199
+
200
+ # Parametrizes data to the format that Amazon EC2 (and compatible APIs) loves
201
+ #
202
+ # @param [Hash] data
203
+ #
204
+ # @return [Hash]
205
+ #
206
+ # @example
207
+ # # Where hash is:
208
+ # { Name.?.Mask => Value | [ Values ],
209
+ # NamePrefix.? => [{ SubNameA.1 => ValueA.1, SubNameB.1 => ValueB.1 }, # any simple parameter
210
+ # ...,
211
+ # { SubNameN.X => ValueN.X, SubNameM.X => ValueN.X }] # see BlockDeviceMapping case
212
+ #
213
+ # @example
214
+ # parametrize( 'ParamA' => 'a',
215
+ # 'ParamB' => ['b', 'c'],
216
+ # 'ParamC.?.Something' => ['d', 'e'],
217
+ # 'Filter' => [ { 'Key' => 'A', 'Value' => ['aa','ab']},
218
+ # { 'Key' => 'B', 'Value' => ['ba','bb']}] ) #=>
219
+ # {
220
+ # "Filter.1.Key" => "A",
221
+ # "Filter.1.Value.1" => "aa",
222
+ # "Filter.1.Value.2" => "ab",
223
+ # "Filter.2.Key" => "B",
224
+ # "Filter.2.Value.1" => "ba",
225
+ # "Filter.2.Value.2" => "bb",
226
+ # "ParamA" => "a",
227
+ # "ParamB.1" => "b",
228
+ # "ParamB.2" => "c",
229
+ # "ParamC.1.Something" => "d",
230
+ # "ParamC.2.Something" => "e"
231
+ # }
232
+ #
233
+ # @example
234
+ # # BlockDeviceMapping example
235
+ # parametrize( 'ImageId' => 'i-01234567',
236
+ # 'MinCount' => 1,
237
+ # 'MaxCount' => 2,
238
+ # 'KeyName' => 'my-key',
239
+ # 'SecurityGroupId' => ['sg-01234567', 'sg-12345670', 'sg-23456701'],
240
+ # 'BlockDeviceMapping' => [
241
+ # { 'DeviceName' => '/dev/sda1',
242
+ # 'Ebs.SnapshotId' => 'snap-01234567',
243
+ # 'Ebs.VolumeSize' => 20,
244
+ # 'Ebs.DeleteOnTermination' => true },
245
+ # { 'DeviceName' => '/dev/sdb1',
246
+ # 'Ebs.SnapshotId' => 'snap-12345670',
247
+ # 'Ebs.VolumeSize' => 10,
248
+ # 'Ebs.DeleteOnTermination' => false } ] ) #=>
249
+ # {
250
+ # "BlockDeviceMapping.1.DeviceName" => "/dev/sda1",
251
+ # "BlockDeviceMapping.1.Ebs.DeleteOnTermination" => true,
252
+ # "BlockDeviceMapping.1.Ebs.SnapshotId" => "snap-01234567",
253
+ # "BlockDeviceMapping.1.Ebs.VolumeSize" => 20,
254
+ # "BlockDeviceMapping.2.DeviceName" => "/dev/sdb1",
255
+ # "BlockDeviceMapping.2.Ebs.DeleteOnTermination" => false,
256
+ # "BlockDeviceMapping.2.Ebs.SnapshotId" => "snap-12345670",
257
+ # "BlockDeviceMapping.2.Ebs.VolumeSize" => 10,
258
+ # "ImageId" => "i-01234567",
259
+ # "KeyName" => "my-key",
260
+ # "MaxCount" => 2,
261
+ # "MinCount" => 1,
262
+ # "SecurityGroupId.1" => "sg-01234567",
263
+ # "SecurityGroupId.2" => "sg-12345670",
264
+ # "SecurityGroupId.3" => "sg-23456701"
265
+ # }
266
+ #
267
+ # @example
268
+ # parametrize( 'DomainName' => 'kdclient',
269
+ # 'Item' => [ { 'ItemName' => 'konstantin',
270
+ # 'Attribute' => [ { 'Name' => 'sex', 'Value' => 'male' },
271
+ # { 'Name' => 'age', 'Value' => '38'} ] },
272
+ # { 'ItemName' => 'alex',
273
+ # 'Attribute' => [ { 'Name' => 'sex', 'Value' => 'male' },
274
+ # { 'Name' => 'weight', 'Value' => '188'},
275
+ # { 'Name' => 'age', 'Value' => '42'} ] },
276
+ # { 'ItemName' => 'diana',
277
+ # 'Attribute' => [ { 'Name' => 'sex', 'Value' => 'female' },
278
+ # { 'Name' => 'weight', 'Value' => '120'},
279
+ # { 'Name' => 'age', 'Value' => '25'} ] } ] ) #=>
280
+ # { "DomainName" => "kdclient",
281
+ # "Item.1.ItemName" => "konstantin",
282
+ # "Item.1.Attribute.1.Name" => "sex",
283
+ # "Item.1.Attribute.1.Value" => "male",
284
+ # "Item.1.Attribute.2.Name" => "weight",
285
+ # "Item.1.Attribute.2.Value" => "170",
286
+ # "Item.1.Attribute.3.Name" => "age",
287
+ # "Item.1.Attribute.3.Value" => "38",
288
+ # "Item.2.ItemName" => "alex",
289
+ # "Item.2.Attribute.1.Name" => "sex",
290
+ # "Item.2.Attribute.1.Value" => "male",
291
+ # "Item.2.Attribute.2.Name" => "weight",
292
+ # "Item.2.Attribute.2.Value" => "188",
293
+ # "Item.2.Attribute.3.Name" => "age",
294
+ # "Item.2.Attribute.3.Value" => "42",
295
+ # "Item.3.ItemName" => "diana",
296
+ # "Item.3.Attribute.1.Name" => "sex",
297
+ # "Item.3.Attribute.1.Value" => "female",
298
+ # "Item.3.Attribute.2.Name" => "weight",
299
+ # "Item.3.Attribute.2.Value" => "120",
300
+ # "Item.3.Attribute.3.Name" => "age",
301
+ # "Item.3.Attribute.3.Value" => "25"}
302
+ #
303
+ def self.parametrize(data)
304
+ return data unless data.is_a?(Hash)
305
+ result = {}
306
+ #
307
+ data.each do |mask, values|
308
+ current_values = Utils::arrayify(values)
309
+ current_mask = mask.dup.to_s
310
+ current_mask << ".?" unless current_mask[/\?/] if current_values.size > 1
311
+ #
312
+ current_values.dup.each_with_index do |value, idx|
313
+ key = current_mask.sub('?', (idx+1).to_s)
314
+ item = parametrize(value)
315
+ if item.is_a?(Hash)
316
+ item.each{ |k, v| result["#{key}.#{k}"] = v }
317
+ else
318
+ result[key] = item
319
+ end
320
+ end
321
+ end
322
+ result
323
+ end
324
+
325
+ end
326
+ end
327
+ end
328
+ end