clientside_aws 0.0.17

Sign up to get free protection for your applications and to get access to all the features.
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,103 @@
1
+ require 'ipaddr'
2
+
3
+ helpers do
4
+ def hkey_from_params(params)
5
+ "#{params['IpPermissions.1.IpProtocol']}:" \
6
+ "#{params['IpPermissions.1.FromPort']}:" \
7
+ "#{params['IpPermissions.1.ToPort']}"
8
+ end
9
+
10
+ def authorize_security_group_ingress(params)
11
+ hkey = hkey_from_params(params)
12
+ existing = AWS_REDIS.hget("ingress:#{params['GroupId']}",
13
+ hkey)
14
+ value = existing ? JSON.parse(existing).to_set : Set.new
15
+ ip_addr = IPAddr.new(params['IpPermissions.1.IpRanges.1.CidrIp'])
16
+ mask = params['IpPermissions.1.IpRanges.1.CidrIp'].split('/').last
17
+ # Interpret the mask, so 10.0.0.1/24 converts to 10.0.0.0/24
18
+ value << "#{ip_addr}/#{mask}"
19
+
20
+ AWS_REDIS.hset("ingress:#{params['GroupId']}",
21
+ hkey,
22
+ value.to_a.to_json)
23
+ end
24
+
25
+ def revoke_security_group_ingress(params)
26
+ hkey = hkey_from_params(params)
27
+ value = AWS_REDIS.hget("ingress:#{params['GroupId']}", hkey)
28
+
29
+ return unless value
30
+
31
+ new_value = JSON.parse(value).reject do |r|
32
+ r == params['IpPermissions.1.IpRanges.1.CidrIp']
33
+ end
34
+
35
+ if new_value.length.positive?
36
+ AWS_REDIS.hset("ingress:#{params['GroupId']}", hkey, new_value.to_json)
37
+ else
38
+ AWS_REDIS.hdel("ingress:#{params['GroupId']}", hkey)
39
+ end
40
+ end
41
+
42
+ def describe_security_groups
43
+ group_id = params['GroupId.1']
44
+
45
+ xml = Builder::XmlMarkup.new
46
+ xml.instruct!
47
+ xmlns = 'http://ec2.amazonaws.com/doc/2016-11-15/'
48
+ xml.DescribeSecurityGroupsResponse(xmlns: xmlns) do
49
+ xml.tag!(:requestId, SecureRandom.hex(10))
50
+ xml.securityGroupInfo do
51
+ xml.item do
52
+ xml.tag!(:ownerId, SecureRandom.hex(10))
53
+ xml.tag!(:groupId, group_id)
54
+ xml.tag!(:groupName, 'group-name')
55
+ xml.tag!(:groupDescription, 'group-description')
56
+ xml.tag!(:vpcId, 'vpc-00000000')
57
+ xml.ipPermissions do
58
+ AWS_REDIS.hkeys("ingress:#{group_id}").each do |protocol_port_tuple|
59
+ xml.item do
60
+ (protocol, from_port, to_port) = protocol_port_tuple.split(':')
61
+
62
+ xml.tag!(:ipProtocol, protocol)
63
+ xml.tag!(:fromPort, from_port.to_i)
64
+ xml.tag!(:toPort, to_port.to_i)
65
+ xml.tag!(:groups, nil)
66
+
67
+ xml.ipRanges do
68
+ ingress_permissions = \
69
+ AWS_REDIS.hget("ingress:#{group_id}", protocol_port_tuple)
70
+ JSON.parse(ingress_permissions).each do |cidr_ip|
71
+ xml.item do
72
+ xml.tag!(:cidrIp, cidr_ip)
73
+ end
74
+ end
75
+ end
76
+ xml.tag!(:ipv6Ranges, nil)
77
+ xml.tag!(:prefixListIds, nil)
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+
85
+ content_type :xml
86
+ xml.target!
87
+ end
88
+ end
89
+
90
+ post %r{/ec2(\.(\w+?)\.amazonaws\.com)?/?(.*)} do
91
+ case params[:Action]
92
+ when 'AuthorizeSecurityGroupIngress'
93
+ authorize_security_group_ingress(params)
94
+
95
+ 200
96
+ when 'RevokeSecurityGroupIngress'
97
+ revoke_security_group_ingress(params)
98
+
99
+ 200
100
+ when 'DescribeSecurityGroups'
101
+ describe_security_groups
102
+ end
103
+ end
@@ -0,0 +1,179 @@
1
+ helpers do
2
+ def encode_video(source_key, dest_key, pipeline_id)
3
+ pipeline = JSON.parse(AWS_REDIS.get("pipeline:#{pipeline_id}"))
4
+ bucket = pipeline['InputBucket']
5
+ input_obj_name = source_key
6
+ input_obj_key = "s3:bucket:#{bucket}:#{input_obj_name}"
7
+
8
+ bucket = pipeline['OutputBucket']
9
+ output_obj_name = dest_key
10
+ output_obj_key = "s3:bucket:#{bucket}:#{output_obj_name}"
11
+ input_obj_body = AWS_REDIS.hget input_obj_key, 'body'
12
+
13
+ tmp = Tempfile.new(source_key)
14
+ tmp.write(input_obj_body)
15
+ tmp.close
16
+
17
+ # Setup paths
18
+ encoded_path = tmp.path + '.enc'
19
+ faststart_path = tmp.path + '.fast'
20
+ final_path = nil
21
+
22
+ # Android is already encoded; ios needs re-encoding
23
+ # Everyone gets faststart treatment
24
+ ffmpeg_video(tmp.path, encoded_path)
25
+
26
+ if File.exist?(encoded_path)
27
+ final_path = if faststart_video(encoded_path, faststart_path)
28
+ faststart_path
29
+ else
30
+ encoded_path
31
+ end
32
+ end
33
+
34
+ if final_path && File.exist?(final_path)
35
+ # Write
36
+ file = File.open(final_path, 'rb')
37
+ video = file.read
38
+ file.close
39
+
40
+ AWS_REDIS.hset output_obj_key, 'body', video
41
+ AWS_REDIS.hset output_obj_key, 'content-type', 'video/mp4'
42
+
43
+ begin
44
+ File.delete(final_path) if File.exist?(final_path)
45
+ rescue Exception => e
46
+ end
47
+ begin
48
+ File.delete(encoded_path) if File.exist?(encoded_path)
49
+ rescue Exception => e
50
+ end
51
+ begin
52
+ File.delete(faststart_path) if File.exist?(faststart_path)
53
+ rescue Exception => e
54
+ end
55
+ begin
56
+ tmp.delete
57
+ rescue Exception => e
58
+ end
59
+ end
60
+ end
61
+
62
+ def ffmpeg_video(path, output)
63
+ flip = nil
64
+ transpose = nil
65
+ info = `avprobe #{path} 2>&1`
66
+ match = info.match(/rotate\s+:\s(\d+)\s/)
67
+ if match
68
+ rotation = match[1]
69
+ if rotation == '90'
70
+ transpose = 1
71
+ elsif rotation == '270'
72
+ transpose = 2
73
+ elsif rotation == '180'
74
+ flip = true
75
+ end
76
+ end
77
+
78
+ args = []
79
+
80
+ args << 'avconv'
81
+ args << '-y'
82
+
83
+ args << '-i'
84
+ args << path
85
+
86
+ args << '-f'
87
+ args << 'mp4'
88
+
89
+ if transpose
90
+ args << '-vf'
91
+ args << "transpose=#{transpose}"
92
+ elsif flip
93
+ args << '-vf'
94
+ args << 'vflip,hflip'
95
+ end
96
+
97
+ args << '-b:v'
98
+ args << '900k'
99
+
100
+ args << '-vcodec'
101
+ args << 'libx264'
102
+
103
+ args << '-ac'
104
+ args << '1'
105
+
106
+ args << '-ar'
107
+ args << '44100'
108
+
109
+ args << '-profile:v'
110
+ args << 'baseline'
111
+
112
+ # Just for avconv, because it complains about aac
113
+ args << '-strict'
114
+ args << 'experimental'
115
+
116
+ args << output + '-tmp'
117
+
118
+ encode_command = args.join(' ')
119
+ _results = `#{encode_command} 2>&1`
120
+
121
+ `mv #{output}-tmp #{output} 2>&1`
122
+ end
123
+
124
+ def faststart_video(path, output)
125
+ unless `which qt-faststart`.empty?
126
+ `qt-faststart #{path} #{output}`
127
+ return true
128
+ end
129
+
130
+ false
131
+ end
132
+
133
+ def create_job(args)
134
+ input_obj_name = args['Input']['Key']
135
+ output_obj_name = args['Output']['Key']
136
+ pipeline_id = args['PipelineId']
137
+
138
+ encode_video(input_obj_name, output_obj_name, pipeline_id)
139
+
140
+ content_type 'application/x-amz-json-1.0'
141
+ { Job: {
142
+ Id: 1,
143
+ Input: args['Input'],
144
+ Output: args['Output'],
145
+ PipelineId: args['PipelineId']
146
+ } }.to_json
147
+ end
148
+
149
+ def create_pipeline(args)
150
+ if AWS_REDIS.get "pipeline:#{args['Name']}"
151
+ pipeline_id = AWS_REDIS.get "pipeline:#{args['Name']}"
152
+ else
153
+ pipeline_id = SecureRandom.hex(10) + args['OutputBucket']
154
+ AWS_REDIS.set "pipeline:#{pipeline_id}", args.to_json
155
+ AWS_REDIS.set "pipeline:#{args['Name']}", pipeline_id
156
+ end
157
+
158
+ content_type 'application/x-amz-json-1.0'
159
+ { Pipeline: {
160
+ Id: pipeline_id,
161
+ Name: args['Name'],
162
+ Status: 'Completed',
163
+ InputBucket: args['InputBucket'],
164
+ OutputBucket: args['OutputBucket'],
165
+ Role: args['Role'],
166
+ Notifications: args['Notifications']
167
+ } }.to_json
168
+ end
169
+ end
170
+
171
+ post %r{/elastictranscoder\.(.+?)\.amazonaws\.com/?(.*)?} do
172
+ args = JSON.parse(env['rack.input'].read)
173
+
174
+ if args['Input'] && args['Output']
175
+ create_job(args)
176
+ else
177
+ create_pipeline(args)
178
+ end
179
+ end
@@ -0,0 +1,13 @@
1
+ post %r{/firehose(\.(\w+?)\.amazonaws\.com)?/?(.*)} do
2
+ args = if env['REQUEST_METHOD'] == 'POST'
3
+ JSON.parse(env['rack.input'].read)
4
+ else
5
+ env['rack.request.form_hash']
6
+ end
7
+
8
+ AWS_REDIS.zadd "firehose:#{args['DeliveryStreamName']}",
9
+ Time.now.to_i,
10
+ Base64.decode64(args['Records'].to_json)
11
+
12
+ 200
13
+ end
@@ -0,0 +1,13 @@
1
+ post %r{/kinesis(\.(\w+?)\.amazonaws\.com)?/?(.*)} do
2
+ args = if env['REQUEST_METHOD'] == 'POST'
3
+ JSON.parse(env['rack.input'].read)
4
+ else
5
+ env['rack.request.form_hash']
6
+ end
7
+
8
+ AWS_REDIS.zadd "kinesis:#{args['StreamName']}",
9
+ Time.now.to_i,
10
+ Base64.decode64(args['Data'])
11
+
12
+ 200
13
+ end
@@ -0,0 +1,7 @@
1
+ module AWS
2
+ module Core
3
+ class << self
4
+ attr_accessor :testing
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,14 @@
1
+ module Aws
2
+ module Firehose
3
+ class Client
4
+ # Monkeypatch to save the last sent message
5
+ attr_reader :last_stream_name
6
+ attr_reader :last_records
7
+
8
+ def put_record_batch(delivery_stream_name:, records:)
9
+ @last_stream_name = delivery_stream_name
10
+ @last_records = records
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,18 @@
1
+ module AWS
2
+ class Kinesis
3
+ class Client < Core::JSONClient
4
+ # Monkeypatch to save the last sent message
5
+ class V20131202
6
+ attr_reader :last_stream_name
7
+ attr_reader :last_data
8
+ attr_reader :last_partition_key
9
+
10
+ def put_record(stream_name:, data:, partition_key:)
11
+ @last_stream_name = stream_name
12
+ @last_data = data
13
+ @last_partition_key = partition_key
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,59 @@
1
+ module AWS
2
+ class S3
3
+ class Bucket
4
+ begin
5
+ old_exists = instance_method(:exists?)
6
+ define_method(:exists?) do
7
+ begin
8
+ old_exists.bind(self).call
9
+ rescue Errors::NoSuchKey
10
+ false # bucket does not exist
11
+ end
12
+ end
13
+ rescue NameError
14
+ # aws-sdk-v1 is not being used
15
+ end
16
+ end
17
+
18
+ class PresignedPost
19
+ @@host = nil
20
+ @@port = nil
21
+ def self.mock_host=(host)
22
+ @@host = host
23
+ end
24
+
25
+ def self.mock_port=(port)
26
+ @@port = port
27
+ end
28
+
29
+ def mock_host
30
+ @@host || config.s3_endpoint.split(':').first
31
+ end
32
+
33
+ def mock_port
34
+ @@port || config.s3_endpoint.split(':')[1].split('/').first.to_i
35
+ end
36
+
37
+ def url
38
+ URI::HTTP.build(host: mock_host, path: "/s3/#{bucket.name}", port: mock_port)
39
+ end
40
+ end
41
+
42
+ # class Client < Core::Client
43
+ # module Validators
44
+ # # this keeps it from fucking up our hostname
45
+ # def path_style_bucket_name?(_bucket_name)
46
+ # true
47
+ # end
48
+ # end
49
+ # end
50
+
51
+ # class S3Object
52
+ # def presign_v4(method, _options)
53
+ # if method == :read || method == :get
54
+ # "http://#{client.endpoint}/#{bucket.name}/#{key}"
55
+ # end
56
+ # end
57
+ # end
58
+ end
59
+ end
@@ -0,0 +1,74 @@
1
+ require 'uuid'
2
+
3
+ module AWS
4
+ # Mock SES and enable retrieval of last message sent
5
+ # We also save messages to message_directory, if set
6
+ class SimpleEmailService
7
+ class SESMessage
8
+ def initialize
9
+ @id = SecureRandom.hex(10)
10
+ end
11
+
12
+ def successful?
13
+ true
14
+ end
15
+
16
+ def data
17
+ { message_id: @id }
18
+ end
19
+ end
20
+
21
+ @@message_directory = nil
22
+ @@sent_message = nil
23
+ @@sent_email = nil
24
+ def self.mock_clear_sent
25
+ @@sent_email = nil
26
+ @@sent_message = nil
27
+ end
28
+
29
+ def self.message_directory=(path)
30
+ @@message_directory = path
31
+ end
32
+
33
+ def self.mock_sent_email(clear = nil)
34
+ msg = @@sent_email
35
+ mock_clear_sent if clear
36
+ msg
37
+ end
38
+
39
+ def self.mock_sent_message(clear = nil)
40
+ msg = @@sent_message
41
+ mock_clear_sent if clear
42
+ msg
43
+ end
44
+
45
+ def quotas
46
+ { max_24_hour_send: 200, max_send_rate: 100.0, sent_last_24_hours: 22 }
47
+ end
48
+
49
+ def send_email(msg)
50
+ ses_message = SESMessage.new
51
+ to_adr = msg[:to]
52
+ from_adr = msg[:from]
53
+ _to_adr = to_adr[/(?<=<).*(?=>)/]
54
+ _from_adr = from_adr[/(?<=<).*(?=>)/]
55
+ fname = ses_message.data[:message_id]
56
+ log_msg("#{fname}.txt", "#{msg[:subject]}\n\n#{msg[:body_text]}") if msg[:body_text]
57
+ log_msg("#{fname}.html", msg[:body_html]) if msg[:body_html]
58
+ @@sent_email = msg
59
+ @@sent_message = ses_message
60
+ ses_message
61
+ end
62
+
63
+ private
64
+
65
+ def log_msg(file_name, content)
66
+ email_dir = @@message_directory
67
+ if email_dir
68
+ email_dir += '/' unless email_dir.end_with? '/'
69
+ FileUtils.mkdir_p(email_dir) unless File.directory?(email_dir)
70
+ File.open("#{email_dir}#{file_name}", 'w') { |file| file.write(content) }
71
+ end
72
+ end
73
+ end
74
+ end