clientside_aws 0.0.17

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 (46) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/Dockerfile +46 -0
  4. data/Gemfile +23 -0
  5. data/Gemfile.lock +99 -0
  6. data/README.md +105 -0
  7. data/bin/clientside_aws_build +6 -0
  8. data/bin/clientside_aws_run +5 -0
  9. data/bin/clientside_aws_test +4 -0
  10. data/clientside_aws.gemspec +31 -0
  11. data/clientside_aws/dynamodb.rb +722 -0
  12. data/clientside_aws/ec2.rb +103 -0
  13. data/clientside_aws/elastic_transcoder.rb +179 -0
  14. data/clientside_aws/firehose.rb +13 -0
  15. data/clientside_aws/kinesis.rb +13 -0
  16. data/clientside_aws/mock/core.rb +7 -0
  17. data/clientside_aws/mock/firehose.rb +14 -0
  18. data/clientside_aws/mock/kinesis.rb +18 -0
  19. data/clientside_aws/mock/s3.rb +59 -0
  20. data/clientside_aws/mock/ses.rb +74 -0
  21. data/clientside_aws/mock/sns.rb +17 -0
  22. data/clientside_aws/s3.rb +223 -0
  23. data/clientside_aws/ses.rb +9 -0
  24. data/clientside_aws/sns.rb +41 -0
  25. data/clientside_aws/sqs.rb +233 -0
  26. data/docker/clientside-aws-run +3 -0
  27. data/docker/redis-server-run +2 -0
  28. data/index.rb +57 -0
  29. data/lib/clientside_aws.rb +27 -0
  30. data/lib/clientside_aws/configuration.rb +14 -0
  31. data/lib/clientside_aws/mock.rb +224 -0
  32. data/lib/clientside_aws/version.rb +3 -0
  33. data/public/images/jscruff.jpg +0 -0
  34. data/public/images/spacer.gif +0 -0
  35. data/public/images/stock_video.mp4 +0 -0
  36. data/spec/dynamodb_spec.rb +1069 -0
  37. data/spec/ec2_spec.rb +138 -0
  38. data/spec/firehose_spec.rb +16 -0
  39. data/spec/kinesis_spec.rb +22 -0
  40. data/spec/s3_spec.rb +219 -0
  41. data/spec/sns_spec.rb +72 -0
  42. data/spec/spec_helper.rb +71 -0
  43. data/spec/sqs_spec.rb +87 -0
  44. data/spec/test_client/test.rb +45 -0
  45. data/spec/transcoder_spec.rb +138 -0
  46. metadata +241 -0
@@ -0,0 +1,17 @@
1
+ module AWS
2
+ class SNS
3
+ class Client < Core::QueryClient
4
+ # Monkeypatch to save the last sent message
5
+ class V20100331
6
+ attr_reader :last_msg
7
+
8
+ def publish(target_arn:,
9
+ message_structure:,
10
+ message:,
11
+ message_attributes: nil)
12
+ @last_msg = message
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,223 @@
1
+ require 'builder'
2
+
3
+ helpers do
4
+ def get_file(bucket:, file_name:)
5
+ halt 404, objectNotFound if AWS_REDIS.hget("s3:bucket:#{bucket}:#{file_name}", 'body').nil?
6
+
7
+ body = download_file(bucket, file_name)
8
+ content_type = AWS_REDIS.hget("s3:bucket:#{bucket}:#{file_name}", 'content-type')
9
+ response.headers['content-type'] = content_type.nil? ? 'html' : content_type
10
+ # response.headers["Content-Length"] = body.length.to_s
11
+ response.headers['etag'] = Digest::MD5.hexdigest(body)
12
+ response.body = body
13
+
14
+ status 200
15
+ end
16
+
17
+ def list_buckets
18
+ buckets = AWS_REDIS.keys 's3:bucket:*'
19
+
20
+ xml = Builder::XmlMarkup.new
21
+ xml.instruct!
22
+ xml.ListAllMyBucketsResult(xmlns: 'http://s3.amazonaws.com/doc/2006-03-01') do
23
+ xml.Owner do
24
+ xml.tag!(:ID, SecureRandom.hex(10))
25
+ xml.tag!(:DisplayName, 'Fake Owner')
26
+ end
27
+ xml.Buckets do
28
+ buckets.each do |bucket|
29
+ xml.Bucket do
30
+ xml.tag!(:Name, bucket.split(':').last)
31
+ xml.tag!(:CreationDate, Time.at(AWS_REDIS.hget(bucket, 'created_at').to_i).xmlschema)
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ content_type :xml
38
+ xml.target!
39
+ end
40
+
41
+ def list_objects(bucket)
42
+ xml = Builder::XmlMarkup.new
43
+ xml.instruct!
44
+ xml.ListBucketResult(xmlns: 'http://doc.s3.amazonaws.com/2006-03-01') do
45
+ objects = AWS_REDIS.keys "s3:bucket:#{bucket}:*"
46
+ xml.tag!(:Name, bucket)
47
+ xml.tag!(:KeyCount, objects.length)
48
+ xml.tag!(:Prefix, nil)
49
+ xml.tag!(:Marker, nil)
50
+ xml.tag!(:MaxKeys, 1000)
51
+ xml.tag!(:IsTruncated, false)
52
+
53
+ objects.each do |object|
54
+ prefix = params.key?('prefix') ? params['prefix'] : nil
55
+ next unless prefix.nil? || \
56
+ object.start_with?("s3:bucket:#{bucket}:#{prefix}")
57
+
58
+ xml.Contents do
59
+ key = AWS_REDIS.hget object, 'key'
60
+ last_modified = AWS_REDIS.hget object, 'last_modified'
61
+ etag = AWS_REDIS.hget object, 'etag'
62
+ size = AWS_REDIS.hget object, 'size'
63
+
64
+ xml.tag!(:Key, key)
65
+ xml.tag!(:LastModified, Time.at(last_modified.to_i).xmlschema)
66
+ xml.tag!(:ETag, etag)
67
+ xml.tag!(:Size, size)
68
+ xml.tag!(:Storage, 'STANDARD')
69
+ xml.Owner do
70
+ xml.tag!(:ID, SecureRandom.hex(10))
71
+ xml.tag!(:DisplayName, 'fake@example.com')
72
+ end
73
+ end
74
+ end
75
+ end
76
+
77
+ content_type :xml
78
+ xml.target!
79
+ end
80
+
81
+ def objectNotFound
82
+ xml = Builder::XmlMarkup.new
83
+ xml.instruct!
84
+ xml.Error do
85
+ xml.tag!(:Code, 'NoSuchKey')
86
+ xml.tag!(:Message, 'The specified key does not exist.')
87
+ end
88
+ content_type :xml
89
+ xml.target!
90
+ end
91
+
92
+ def download_file(bucket, obj_name)
93
+ obj_key = "s3:bucket:#{bucket}:#{obj_name}"
94
+ (AWS_REDIS.hget obj_key, 'body').force_encoding('UTF-8')
95
+ end
96
+ end
97
+
98
+ # Get all of the buckets
99
+ # Host: s3[.-][us-mockregion-1].amazonaws.com
100
+ get %r{/s3.*?\.amazonaws\.com/?} do
101
+ list_buckets
102
+
103
+ status 200
104
+ end
105
+
106
+ # Get bucket
107
+ # Host: bucket-name.s3[.-][us-mockregion-1].amazonaws.com
108
+ get %r{/(.*?)\.(s3.*?\.amazonaws\.com)/?$} do
109
+ bucket = params[:captures][0]
110
+ halt 404 unless AWS_REDIS.exists "s3:bucket:#{bucket}"
111
+ list_objects(bucket)
112
+ end
113
+
114
+ # Get a file a bucket
115
+ # Host: bucket-name.s3[.-][us-mockregion-1].amazonaws.com/file-name
116
+ get %r{/(.*?)\.(s3.*?\.amazonaws\.com)?/(.+)} do
117
+ bucket = params[:captures][0]
118
+ file_name = params[:captures][2]
119
+
120
+ get_file(bucket: bucket, file_name: file_name)
121
+ end
122
+
123
+ # Old-style way of referencing S3
124
+ get %r{/s3/([^/]+)?/(.+)} do
125
+ bucket = params[:captures][0]
126
+ file_name = params[:captures][1]
127
+
128
+ get_file(bucket: bucket, file_name: file_name)
129
+ end
130
+
131
+ # Bucket creation
132
+ # Host: bucket-name.s3[.-][us-mockregion-1].amazonaws.com
133
+ put %r{/(.*?)\.(s3\.?.*?\.amazonaws\.com)/?$} do
134
+ bucket = params[:captures][0]
135
+ AWS_REDIS.hset "s3:bucket:#{bucket}", 'created_at', Time.now.to_i
136
+
137
+ status 200
138
+ end
139
+
140
+ # Upload file into a bucket
141
+ # Host: bucket-name.s3[.-][us-mockregion-1].amazonaws.com/file-name
142
+ put %r{/(.*?)\.s3\.(.*?\.amazonaws\.com)?/(.+)} do
143
+ bucket = params[:captures][0]
144
+ file_name = params[:captures][2]
145
+
146
+ # upload the file (chunking not implemented) to fake S3
147
+ if file_name && bucket
148
+ file_name = file_name[1..-1] if '/' == file_name[0]
149
+ clientside_aws_testing = \
150
+ defined?(Sinatra::Base.settings.clientside_aws_testing) && \
151
+ Sinatra::Base.settings.clientside_aws_testing
152
+ body_send = clientside_aws_testing ? params[:body] : request.body.read
153
+
154
+ # Handle the copy_XXX case
155
+ if body_send.nil? || body_send.empty?
156
+ %w(HTTP_X_AMZ_COPY_SOURCE x-amz-copy-source X-Amz-Copy-Source).each do |copy_source_key|
157
+ next unless env.key?(copy_source_key)
158
+ copy_source = env[copy_source_key]
159
+ if copy_source.start_with?('/')
160
+ (_extra, srcbucket, srcfile) = copy_source.split('/')
161
+ else
162
+ (srcbucket, srcfile) = copy_source.split('/')
163
+ end
164
+ body_send = download_file(srcbucket, srcfile)
165
+ break
166
+ end
167
+ end
168
+
169
+ if AWS_REDIS.hget("s3:bucket:#{bucket}:#{file_name}", 'size').to_i > 0 &&
170
+ (body_send.nil? || body_send.empty?) &&
171
+ env.key?('HTTP_X_AMZ_ACL')
172
+ # you are setting the ACL only on a file that already seems to exist
173
+ # Do not update the body
174
+ else
175
+ AWS_REDIS.hset "s3:bucket:#{bucket}:#{file_name}", 'body', body_send
176
+ AWS_REDIS.hset "s3:bucket:#{bucket}:#{file_name}", 'size', body_send.length
177
+ end
178
+
179
+ AWS_REDIS.hset "s3:bucket:#{bucket}:#{file_name}", 'key', file_name
180
+ AWS_REDIS.hset "s3:bucket:#{bucket}:#{file_name}", 'last_modified', Time.now.to_i
181
+ AWS_REDIS.hset "s3:bucket:#{bucket}:#{file_name}", 'etag', Digest::MD5.hexdigest(body_send)
182
+ AWS_REDIS.hset "s3:bucket:#{bucket}:#{file_name}", 'acl', env['HTTP_X_AMZ_ACL']
183
+
184
+ %w(content-type Content-Type CONTENT_TYPE).each do |content_type_key|
185
+ next unless env.key?(content_type_key)
186
+ AWS_REDIS.hset "s3:bucket:#{bucket}:#{file_name}",
187
+ 'content-type',
188
+ env[content_type_key]
189
+ break
190
+ end
191
+ end
192
+
193
+ status 200
194
+ end
195
+
196
+ # Delete file from a bucket
197
+ # Host: bucket-name.s3[.-][us-mockregion-1].amazonaws.com/file-name
198
+ delete %r{/(.*?)\.s3\.(.*?\.amazonaws\.com)?/(.+)} do
199
+ bucket = params[:captures][0]
200
+ file_name = params[:captures][2]
201
+ AWS_REDIS.del "s3:bucket:#{bucket}:#{file_name}"
202
+
203
+ status 200
204
+ end
205
+
206
+ # post %r{/s3(.*?\.amazonaws\.com)?/([^/]+)/?} do
207
+ # # upload the file (chunking not implemented) to fake S3
208
+ # bucket = params[:captures][1]
209
+ # file_name = params[:key]
210
+ # if file_name
211
+ # file_name = file_name[1..-1] if file_name.start_with? '/'
212
+ # body_send = params[:file]
213
+ # body_send = body_send[:tempfile].read unless AWS::Core.testing
214
+ #
215
+ # AWS_REDIS.hset "s3:bucket:#{bucket}:#{file_name}", 'body', body_send
216
+ # if env.key?('content-type')
217
+ # AWS_REDIS.hset "s3:bucket:#{bucket}:#{file_name}", 'content-type', env['content-type']
218
+ # elsif env.key?('CONTENT_TYPE')
219
+ # AWS_REDIS.hset "s3:bucket:#{bucket}:#{file_name}", 'content-type', env['CONTENT_TYPE']
220
+ # end
221
+ # end
222
+ # status 200
223
+ # end
@@ -0,0 +1,9 @@
1
+ post "/ses/?" do
2
+ # No-op for now
3
+ 200
4
+ end
5
+
6
+ post %r{/ses(\.(\w+?)\.amazonaws\.com)?/(.*)} do
7
+ # No-op for now
8
+ 200
9
+ end
@@ -0,0 +1,41 @@
1
+ helpers do
2
+ def create_platform_endpoint(platform_application_arn:)
3
+ xml = Builder::XmlMarkup.new
4
+ xml.instruct!
5
+
6
+ xml.CreatePlatformEndpointResponse do
7
+ xml.CreatePlatformEndpointResult do
8
+ platform = if platform_application_arn =~ %r{app/APNS}
9
+ 'endpoint/APNS/'
10
+ elsif platform_application_arn =~ %r{app/WNS}
11
+ 'endpoint/WNS/'
12
+ elsif platform_application_arn =~ %r{app/GCM}
13
+ 'endpoint/GCM/'
14
+ else
15
+ 'endpoint/UNKNOWN/'
16
+ end
17
+
18
+ xml.tag!(:EndpointArn,
19
+ 'arn:aws:sns:us-east-1:999999999999:' \
20
+ "#{platform}MYAPP/#{SecureRandom.hex(10)}")
21
+ end
22
+ end
23
+
24
+ content_type :xml
25
+ xml.target!
26
+ end
27
+ end
28
+
29
+ get %r{/sns\.(\w+?)\.amazonaws\.com/?(.*)} do
30
+ 200
31
+ end
32
+
33
+ post %r{/sns(\.(\w+?)\.amazonaws\.com)?/?(.*)} do
34
+ case params[:Action]
35
+ when 'CreatePlatformEndpoint'
36
+ create_platform_endpoint(platform_application_arn:
37
+ params['PlatformApplicationArn'])
38
+ else
39
+ 200
40
+ end
41
+ end
@@ -0,0 +1,233 @@
1
+ SQS_DEFAULT_VISIBILITY_TIMEOUT = \
2
+ (ENV['SQS_VISIBILITY_TIMEOUT'] || 60 * 60 * 6).to_i
3
+
4
+ helpers do
5
+ def get_queue_url
6
+ queue_name = params[:QueueName]
7
+
8
+ xml = Builder::XmlMarkup.new
9
+ xml.instruct!
10
+
11
+ xml.GetQueueUrlResponse do
12
+ xml.GetQueueUrlResult do
13
+ xml.tag!(:QueueUrl, queue_name)
14
+ end
15
+ xml.ResponseMetadata do
16
+ xml.tag!(:RequestId, SecureRandom.hex(10))
17
+ end
18
+ end
19
+
20
+ content_type :xml
21
+ xml.target!
22
+ end
23
+
24
+ def get_queue_attributes
25
+ queue = params[:QueueUrl]
26
+
27
+ xml = Builder::XmlMarkup.new
28
+ xml.instruct!
29
+ xml.GetQueueAttributesResponse do
30
+ xml.GetQueueAttributesResult do
31
+ xml.Attribute do
32
+ xml.tag!(:Name, 'ApproximateNumberOfMessages')
33
+ xml.tag!(:Value, (AWS_REDIS.llen queue))
34
+ end
35
+ xml.Attribute do
36
+ xml.tag!(:Name, 'ApproximateNumberOfMessagesNotVisible')
37
+ xml.tag!(:Value, (AWS_REDIS.keys 'sqs:pending:*').length)
38
+ end
39
+ end
40
+ xml.ResponseMetadata do
41
+ xml.tag!(:RequestId, SecureRandom.hex(10))
42
+ end
43
+ end
44
+
45
+ content_type :xml
46
+ xml.target!
47
+ end
48
+
49
+ def delete_message
50
+ AWS_REDIS.del("sqs:pending:#{params['ReceiptHandle']}")
51
+ 200
52
+ end
53
+
54
+ def delete_message_batch
55
+ deleted_messages = []
56
+ keys = params.keys.select { |k| k =~ /DeleteMessageBatchRequestEntry/ }
57
+ keys.each do |k|
58
+ AWS_REDIS.del("sqs:pending:#{params[k]}") if k =~ /ReceiptHandle$/
59
+ deleted_messages << params[k] if k =~ /Id$/
60
+ end
61
+
62
+ xml = Builder::XmlMarkup.new
63
+ xml.instruct!
64
+ xml.DeleteMessageBatchResponse do
65
+ xml.DeleteMessageBatchResult do
66
+ deleted_messages.each do |msg_id|
67
+ xml.DeleteMessageBatchResultEntry do
68
+ xml.tag!(:Id, msg_id)
69
+ end
70
+ end
71
+ end
72
+ xml.ResponseMetadata do
73
+ xml.tag!(:RequestId, SecureRandom.hex(10))
74
+ end
75
+ end
76
+
77
+ content_type :xml
78
+ xml.target!
79
+ end
80
+
81
+ def receive_message
82
+ queue = params[:QueueUrl]
83
+ max_messages = params[:MaxNumberOfMessages].to_i
84
+ max_messages = 1 if max_messages.zero?
85
+
86
+ xml = Builder::XmlMarkup.new
87
+ xml.instruct!
88
+
89
+ if AWS_REDIS.llen(queue).zero?
90
+ xml.ReceiveMessageResponse do
91
+ xml.ReceiveMessageResult do
92
+ end
93
+ xml.ResponseMetadata do
94
+ xml.tag!(:RequestId, SecureRandom.hex(10))
95
+ end
96
+ end
97
+
98
+ return xml.target!
99
+ end
100
+
101
+ results_json = []
102
+ max_messages.times do
103
+ raw_message = AWS_REDIS.rpop queue
104
+ results_json << JSON.parse(raw_message.force_encoding('UTF-8'))
105
+ break if (AWS_REDIS.llen queue).zero?
106
+ end
107
+
108
+ xml.ReceiveMessageResponse do
109
+ xml.ReceiveMessageResult do
110
+ results_json.each do |result|
111
+ xml.Message do
112
+ xml.tag!(:MessageId, result['MessageId'])
113
+ xml.tag!(:ReceiptHandle, result['ReceiptHandle'])
114
+ xml.tag!(:MD5OfBody, Digest::MD5.hexdigest(result['MessageBody']))
115
+ xml.tag!(:Body, result['MessageBody'])
116
+ xml.Attribute do
117
+ xml.tag!(:Name, 'SenderId')
118
+ xml.tag!(:Value, '1')
119
+ end
120
+ xml.Attribute do
121
+ xml.tag!(:Name, 'SentTimestamp')
122
+ xml.tag!(:Value, result['Timestamp'])
123
+ end
124
+ xml.Attribute do
125
+ xml.tag!(:Name, 'ReceiptHandle')
126
+ xml.tag!(:Value, result['Timestamp'])
127
+ end
128
+ xml.Attribute do
129
+ xml.tag!(:Name, 'ApproximateReceiveCount')
130
+ xml.tag!(:Value, '1')
131
+ end
132
+ xml.Attribute do
133
+ xml.tag!(:Name, 'ApproximateFirstReceiveTimestamp')
134
+ xml.tag!(:Value, result['Timestamp'] * 1000)
135
+ end
136
+ end
137
+ redis_key = "sqs:pending:#{result['ReceiptHandle']}"
138
+ payload = { queue: queue, message: result, received: Time.now }
139
+ AWS_REDIS.set(redis_key, payload.to_json)
140
+ end
141
+ end
142
+ xml.ResponseMetadata do
143
+ xml.tag!(:RequestId, results_json.first['RequestId'])
144
+ end
145
+ end
146
+
147
+ content_type :xml
148
+ xml.target!
149
+ end
150
+
151
+ def send_message_batch
152
+ queue = params[:QueueUrl]
153
+ idx = 1
154
+ loop do
155
+ break unless params["SendMessageBatchRequestEntry.#{idx}.MessageBody"]
156
+ send_message(queue, params["SendMessageBatchRequestEntry.#{idx}.MessageBody"])
157
+ idx += 1
158
+ end
159
+
160
+ halt 200
161
+ end
162
+
163
+ def send_message(queue, message_body)
164
+ message_id = SecureRandom.hex(10)
165
+ request_id = SecureRandom.hex(10)
166
+ msg = {
167
+ MessageBody: message_body,
168
+ MessageId: message_id,
169
+ RequestId: request_id,
170
+ Timestamp: Time.now.to_i,
171
+ ReceiptHandle: Base64.encode64(message_id).strip
172
+ }
173
+ AWS_REDIS.lpush(queue, msg.to_json)
174
+
175
+ xml = Builder::XmlMarkup.new
176
+ xml.instruct!
177
+ xml.SendMessageResponse do
178
+ xml.SendMessageResult do
179
+ xml.tag!(:MD5OfMessageBody, Digest::MD5.hexdigest(message_body))
180
+ xml.tag!(:MessageId, message_id)
181
+ end
182
+ xml.ResponseMetadata do
183
+ xml.tag!(:RequestId, request_id)
184
+ end
185
+ end
186
+
187
+ content_type :xml
188
+ xml.target!
189
+ end
190
+
191
+ def __check_pending
192
+ AWS_REDIS.keys('sqs:pending:*').each do |key|
193
+ json = AWS_REDIS.get(key).to_s.force_encoding("UTF-8")
194
+ begin
195
+ payload = JSON.parse(json)
196
+ time_received = Time.parse(payload['received'])
197
+ since_received = Time.now - time_received
198
+
199
+ if since_received > SQS_DEFAULT_VISIBILITY_TIMEOUT
200
+ AWS_REDIS.lpush(payload['queue'], payload['message'].to_json)
201
+ AWS_REDIS.del(key)
202
+ end
203
+ rescue => e
204
+ puts "INVALID PENDING PAYLOAD: #{e.message} #{json} #{e.backtrace}"
205
+ STDOUT.flush
206
+ AWS_REDIS.del(key)
207
+ end
208
+ end
209
+ end
210
+ end
211
+
212
+ post %r{/sqs(\.(\w+?)\.amazonaws\.com)?/?(.*)} do
213
+ case params[:Action]
214
+ when 'SendMessage'
215
+ send_message(params[:QueueUrl], params[:MessageBody])
216
+ when 'SendMessageBatch'
217
+ send_message_batch
218
+ when 'ReceiveMessage'
219
+ __check_pending
220
+ receive_message
221
+ when 'DeleteMessage'
222
+ delete_message
223
+ when 'DeleteMessageBatch'
224
+ delete_message_batch
225
+ when 'GetQueueAttributes'
226
+ __check_pending
227
+ get_queue_attributes
228
+ when 'GetQueueUrl'
229
+ get_queue_url
230
+ else
231
+ halt 500, "Unknown action #{params.inspect}"
232
+ end
233
+ end