right_aws_api 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/HISTORY +2 -0
- data/LICENSE +19 -0
- data/README.md +164 -0
- data/Rakefile +38 -0
- data/lib/cloud/aws/as/manager.rb +118 -0
- data/lib/cloud/aws/base/helpers/utils.rb +328 -0
- data/lib/cloud/aws/base/manager.rb +186 -0
- data/lib/cloud/aws/base/parsers/response_error.rb +117 -0
- data/lib/cloud/aws/base/routines/request_signer.rb +80 -0
- data/lib/cloud/aws/cf/manager.rb +171 -0
- data/lib/cloud/aws/cf/routines/request_signer.rb +70 -0
- data/lib/cloud/aws/cf/wrappers/default.rb +213 -0
- data/lib/cloud/aws/cfm/manager.rb +90 -0
- data/lib/cloud/aws/cw/manager.rb +113 -0
- data/lib/cloud/aws/eb/manager.rb +87 -0
- data/lib/cloud/aws/ec/manager.rb +91 -0
- data/lib/cloud/aws/ec2/manager.rb +222 -0
- data/lib/cloud/aws/elb/manager.rb +120 -0
- data/lib/cloud/aws/emr/manager.rb +86 -0
- data/lib/cloud/aws/iam/manager.rb +100 -0
- data/lib/cloud/aws/rds/manager.rb +110 -0
- data/lib/cloud/aws/route53/manager.rb +177 -0
- data/lib/cloud/aws/route53/routines/request_signer.rb +70 -0
- data/lib/cloud/aws/route53/wrappers/default.rb +132 -0
- data/lib/cloud/aws/s3/manager.rb +373 -0
- data/lib/cloud/aws/s3/parsers/response_error.rb +76 -0
- data/lib/cloud/aws/s3/routines/request_signer.rb +243 -0
- data/lib/cloud/aws/s3/wrappers/default.rb +315 -0
- data/lib/cloud/aws/sdb/manager.rb +150 -0
- data/lib/cloud/aws/sns/manager.rb +96 -0
- data/lib/cloud/aws/sqs/manager.rb +335 -0
- data/lib/right_aws_api.rb +45 -0
- data/lib/right_aws_api_version.rb +40 -0
- data/right_aws_api.gemspec +55 -0
- data/spec/describe_calls.rb +92 -0
- 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
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
|