right_aws_api 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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