s3_deployer 0.6.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 844de0917a0347758d662fe6dc799217dc7d16c5
4
+ data.tar.gz: 3ea7e007d1c486ed7186fc76703de849a138b984
5
+ SHA512:
6
+ metadata.gz: 3b7f997173a27444996997c954c2d75c3c6dc9ee8766841d9ffb146da5d7fb588d15bcabf9420118a1a6f9548327fb7d536db96832227019b9fe38591a870453
7
+ data.tar.gz: 6d8cc55576a7c732503dfa014b6a69f3c3e3c880d6499a7208cc5578694c98afbbf6cc6007710915d0e15c5569ac1708ad38a67b4d37a51cc6f7aa265b92381a
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ vendor/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in s3_deployer.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,142 @@
1
+ # S3Deployer
2
+
3
+ Tool for versioned deploying of client-side apps (or literally anything) to S3
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 's3_deployer'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install s3_deployer
18
+
19
+ ## Usage
20
+
21
+ You need to specify s3_deployer_config.rb file in your home directory, which may look like this:
22
+
23
+ ```ruby
24
+ S3Deployer.configure do
25
+ bucket "some-bucket"
26
+ region 'us-east-1'
27
+ app_name "devastator"
28
+ app_path "path/to/#{app_name}#{"-#{version}" if version && version != ""}"
29
+ dist_dir "dist"
30
+ gzip [/\.js$/, /\.css$/] # or just use 'true' to gzip everything
31
+ colorize true
32
+ time_zone "America/Los_Angeles" # Useful when you develop from different timezones (e.g. for distributed team,
33
+ # or when deploy from some build server), to be consistent with revision numbers
34
+
35
+ before_stage ->(version) do
36
+ # Some custom code to execute before deploy or stage
37
+ end
38
+
39
+ after_stage ->(version) do
40
+ # Some custom code to execute after deploy or stage
41
+ end
42
+
43
+ before_switch ->(version) do
44
+ # Some custom code to execute before deploy or switch
45
+ end
46
+
47
+ after_switch ->(version) do
48
+ # Some custom code to execute after deploy or switch
49
+ end
50
+
51
+ before_deploy ->(version) do
52
+ # Some custom code to execute before deploy
53
+ end
54
+
55
+ after_deploy ->(version) do
56
+ # Some custom code to execute after deploy
57
+ end
58
+
59
+ # You also can specify environment-specific settings, the default environment is 'production'
60
+ environment(:development) do
61
+ bucket "some-bucket-dev"
62
+ end
63
+
64
+ access_key_id 'your S3 access key id'
65
+ secret_access_key 'your S3 secret access key'
66
+ end
67
+ ```
68
+
69
+ Note the 'dist_dir' setting, you should put all the necessary files to there, which should be sent to S3, before deploy.
70
+
71
+ Then, you need to include Deployer's tasks to your Rakefile, like:
72
+
73
+ ```ruby
74
+ require 'rubygems'
75
+ require 'bundler'
76
+ Bundler.setup
77
+
78
+ require 's3_deployer/tasks'
79
+ require './s3_deployer_config'
80
+ ```
81
+
82
+ There are 3 main tasks - for deploy, switch and stage. When you stage, it gets all the files from the dist_dir,
83
+ and copies them to S3. It creates a directories structure on S3, like:
84
+
85
+ ```
86
+ /path
87
+ /to
88
+ /app
89
+ /20130809134509
90
+ /20130809140328
91
+ ...
92
+ SHAS
93
+ ```
94
+
95
+ These '20130809134509'-like directories are actually 'staged' versions of the app. Directory name is just
96
+ a revision name, in the format "%Y%m%d%H%M%S".
97
+
98
+ Then, you have to do 'switch', which just copies the selected revision directory into 'current'.
99
+ So, after 'switch' e.g. to 20130809134509, the directory structure will be like
100
+
101
+ ```
102
+ /path
103
+ /to
104
+ /app
105
+ /20130809134509
106
+ /20130809140328
107
+ ...
108
+ /current
109
+ CURRENT_REVISION
110
+ SHAS
111
+ ```
112
+
113
+ 'current' contains the currently used copy of the app. Your app should use files from this directory.
114
+
115
+ You also could do "deploy", it is basically "stage", and then "switch" to just staged revision.
116
+
117
+ So, use it like this:
118
+
119
+ ```bash
120
+ $ rake s3_deployer:stage # only creates timestamp dir, like 20130809134509, but doesn't override the 'current' dir
121
+ $ rake s3_deployer:switch REVISION=20130809140330
122
+ $ rake s3_deployer:deploy
123
+ $ rake s3_deployer:deploy VERSION=new-stuff # check the example of deployer.rb above to see how it is used
124
+ $ rake s3_deployer:current # get the currently deployed revision
125
+ $ rake s3_deployer:list # get the list of all deployed revisions and their SHAs and commit subjects
126
+ ```
127
+
128
+ If you want to run s3_deployer in some specific environment, use ENV variable:
129
+
130
+ ```bash
131
+ $ ENV=development rake s3_deployer:deploy
132
+ ```
133
+
134
+ Default environment is 'production'
135
+
136
+ ## Contributing
137
+
138
+ 1. Clone it
139
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
140
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
141
+ 4. Push to the branch (`git push origin my-new-feature`)
142
+ 5. Create new Pull Request pointing to master
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,22 @@
1
+ class S3Deployer
2
+ class Color
3
+ class << self
4
+ def green(text)
5
+ self.new(32).wrap(text)
6
+ end
7
+
8
+ def yellow(text)
9
+ self.new(33).wrap(text)
10
+ end
11
+ end
12
+
13
+ def initialize(color)
14
+ @color = color
15
+ end
16
+
17
+ def wrap(text)
18
+ "\e[#{@color}m#{text}\e[0m"
19
+ end
20
+ end
21
+ end
22
+
@@ -0,0 +1,36 @@
1
+ class S3Deployer
2
+ class Config
3
+ attr_reader :version, :revision, :env
4
+
5
+ def initialize
6
+ @version = ENV["VERSION"] || ""
7
+ @revision = ENV["REVISION"] || ""
8
+ @env = ENV["ENV"] || "production"
9
+ @env_settings = {}
10
+ colorize true
11
+ time_zone "GMT"
12
+ current_path "current"
13
+ end
14
+
15
+ %w{
16
+ region bucket app_name app_path mixbook_host dist_dir access_key_id secret_access_key
17
+ gzip colorize time_zone current_path cache_control
18
+ before_deploy after_deploy before_stage after_stage before_switch after_switch
19
+ }.each do |method|
20
+ define_method method do |value = :omitted|
21
+ instance_variable_set("@#{method}", value) unless value == :omitted
22
+ instance_variable_get("@#{method}")
23
+ end
24
+ end
25
+
26
+ def environment(name, &block)
27
+ @env_settings[name.to_s] = block
28
+ end
29
+
30
+ def apply_environment_settings!
31
+ if @env_settings[@env.to_s]
32
+ instance_eval(&@env_settings[@env.to_s])
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,28 @@
1
+ require 's3_deployer'
2
+
3
+ namespace :s3_deployer do
4
+ desc "Deploy"
5
+ task :deploy do
6
+ S3Deployer.deploy!
7
+ end
8
+
9
+ desc "Deploy the revision, but don't change it to the 'current' revision"
10
+ task :stage do
11
+ S3Deployer.stage!
12
+ end
13
+
14
+ desc "Switch"
15
+ task :switch do
16
+ S3Deployer.switch!
17
+ end
18
+
19
+ desc "Get current revision number"
20
+ task :current do
21
+ S3Deployer.current
22
+ end
23
+
24
+ desc "Get the list of deployed revisions"
25
+ task :list do
26
+ S3Deployer.list
27
+ end
28
+ end
@@ -0,0 +1,3 @@
1
+ class S3Deployer
2
+ VERSION = "0.6.0"
3
+ end
@@ -0,0 +1,251 @@
1
+ require 'json'
2
+ require 'zlib'
3
+ require 'stringio'
4
+ require 'tzinfo'
5
+ require 'rexml/document'
6
+ require 'parallel'
7
+ require 'aws-sdk'
8
+
9
+ require "s3_deployer/config"
10
+ require "s3_deployer/color"
11
+ require "s3_deployer/version"
12
+
13
+ class S3Deployer
14
+ DATE_FORMAT = "%Y%m%d%H%M%S"
15
+ CURRENT_REVISION = "CURRENT_REVISION"
16
+ class << self
17
+ attr_reader :config
18
+
19
+ def configure(&block)
20
+ @config = Config.new
21
+ @config.instance_eval(&block)
22
+ @config.apply_environment_settings!
23
+
24
+ Aws.config.update({
25
+ region: config.region,
26
+ credentials: Aws::Credentials.new(config.access_key_id, config.secret_access_key),
27
+ })
28
+ end
29
+
30
+ def execute(cmd)
31
+ puts "Running '#{cmd}'"
32
+ system(cmd, out: $stdout, err: :out)
33
+ end
34
+
35
+ def deploy!
36
+ revision = time_zone.now.strftime(DATE_FORMAT)
37
+ config.before_deploy[revision] if config.before_deploy
38
+ stage!(revision)
39
+ switch!(revision)
40
+ config.after_deploy[revision] if config.after_deploy
41
+ end
42
+
43
+ def stage!(revision = time_zone.now.strftime(DATE_FORMAT))
44
+ puts "Staging #{colorize(:green, revision)}"
45
+ config.before_stage[revision] if config.before_stage
46
+ copy_files_to_s3(revision)
47
+ store_git_hash(revision)
48
+ config.after_stage[revision] if config.after_stage
49
+ end
50
+
51
+ def switch!(revision = config.revision)
52
+ current_revision = get_current_revision
53
+ current_sha = sha_of_revision(current_revision)
54
+ sha = sha_of_revision(revision)
55
+ puts "Switching from #{colorize(:green, current_revision)} (#{colorize(:yellow, current_sha && current_sha[0..7])}) " +
56
+ "to #{colorize(:green, revision)} (#{colorize(:yellow, sha && sha[0..7])})"
57
+ if !revision || revision.strip.empty?
58
+ warn "You must specify the revision by REVISION env variable"
59
+ exit(1)
60
+ end
61
+ revision = normalize_revision(revision)
62
+ config.before_switch[current_revision, revision] if config.before_switch
63
+ prefix = config.app_path.empty? ? revision : File.join(config.app_path, revision)
64
+ list_of_objects = []
65
+ Aws::S3::Resource.new.bucket(config.bucket).objects(prefix: prefix).each do |object_summary|
66
+ list_of_objects << object_summary
67
+ end
68
+ Parallel.each(list_of_objects, in_threads: 20) do |object_summary|
69
+ object = object_summary.object
70
+ target_path = config.app_path.empty? ? @config.current_path : File.join(config.app_path, @config.current_path)
71
+ path = object.key.gsub(prefix, target_path)
72
+ value = object.get.body.read
73
+ value = object.content_encoding == "gzip" ? decompress(value) : value
74
+ store_value(path, value)
75
+ end
76
+ store_current_revision(revision)
77
+ config.after_switch[current_revision, revision] if config.after_switch
78
+ end
79
+
80
+ def current
81
+ current_revision = get_current_revision
82
+ if current_revision
83
+ puts "Current revision: #{current_revision} - #{get_datetime_from_revision(current_revision)}"
84
+ else
85
+ puts "There is no information about the current revision"
86
+ end
87
+ end
88
+
89
+ def normalize_revision(revision)
90
+ if revision && !revision.empty?
91
+ datetime = get_datetime_from_revision(revision)
92
+ if datetime
93
+ revision
94
+ else
95
+ shas_by_revisions.detect { |k, v| v.start_with?(revision) }.first
96
+ end
97
+ end
98
+ end
99
+
100
+ def list
101
+ puts "Getting the list of deployed revisions..."
102
+ current_revision = get_current_revision
103
+ get_list_of_revisions.each do |rev|
104
+ datetime = get_datetime_from_revision(rev)
105
+ sha = shas_by_revisions[rev]
106
+ title = sha ? `git show -s --format=%s #{sha}`.strip : nil
107
+ string = "#{rev} - #{datetime} #{sha ? " - #{sha[0..7]}" : ""} #{title ? "(#{title})" : ""} #{" <= current" if rev == current_revision}"
108
+ puts string
109
+ end
110
+ end
111
+
112
+ def changes(from, to)
113
+ from_sha = sha_of_revision(from)
114
+ to_sha = sha_of_revision(to)
115
+ if from_sha && to_sha
116
+ `git log --oneline --reverse #{from_sha}...#{to_sha}`.split("\n").map(&:strip)
117
+ else
118
+ []
119
+ end
120
+ end
121
+
122
+ def sha_of_revision(revision)
123
+ shas_by_revisions[normalize_revision(revision)]
124
+ end
125
+
126
+ private
127
+
128
+ def copy_files_to_s3(rev)
129
+ dir = File.join(config.app_path, rev)
130
+ Parallel.each(source_files_list, in_threads: 20) do |file|
131
+ s3_file = Pathname.new(file).relative_path_from(Pathname.new(config.dist_dir)).to_s
132
+ store_value(File.join(dir, s3_file), File.read(file))
133
+ end
134
+ end
135
+
136
+ def get_list_of_revisions
137
+ prefix = File.join(config.app_path)
138
+ body = Aws::S3::Client.new.list_objects({bucket: config.bucket, delimiter: '/', prefix: prefix + "/"})
139
+ body.common_prefixes.map(&:prefix).map { |e| e.gsub(prefix, "").gsub("/", "") }.select do |dir|
140
+ !!(Time.strptime(dir, DATE_FORMAT) rescue nil)
141
+ end.sort
142
+ end
143
+
144
+ def app_path_with_bucket
145
+ File.join(config.bucket, config.app_path)
146
+ end
147
+
148
+ def get_datetime_from_revision(revision)
149
+ date = Time.strptime(revision, DATE_FORMAT) rescue nil
150
+ date.strftime("%m/%d/%Y %H:%M") if date
151
+ end
152
+
153
+ def shas_by_revisions
154
+ @shas_by_revisions ||= get_value(File.join(config.app_path, "SHAS")).split("\n").inject({}) do |memo, line|
155
+ revision, sha = line.split(" - ").map(&:strip)
156
+ memo[revision] = sha
157
+ memo
158
+ end
159
+ rescue Aws::S3::Errors::NoSuchKey
160
+ {}
161
+ end
162
+
163
+ def current_revision_path
164
+ File.join(config.app_path, CURRENT_REVISION)
165
+ end
166
+
167
+ def get_value(key)
168
+ puts "Retrieving value #{key} on S3"
169
+ Aws::S3::Resource.new.bucket(config.bucket).object(key).get.body.read
170
+ end
171
+
172
+ def store_current_revision(revision)
173
+ store_value(current_revision_path, revision)
174
+ end
175
+
176
+ def store_git_hash(time)
177
+ value = shas_by_revisions.
178
+ merge(time => `git rev-parse HEAD`.strip).
179
+ map { |sha, rev| "#{sha} - #{rev}" }.join("\n")
180
+ store_value(File.join(config.app_path, "SHAS"), value)
181
+ @shas_by_revisions = nil
182
+ end
183
+
184
+ def get_current_revision
185
+ get_value(current_revision_path)
186
+ rescue Aws::S3::Errors::NoSuchKey
187
+ nil
188
+ end
189
+
190
+ def store_value(key, value)
191
+ puts "Storing value #{colorize(:yellow, key)} on S3#{", #{colorize(:green, 'gzipped')}" if should_compress?(key)}"
192
+ options = {acl: "public-read"}
193
+ if config.cache_control && !config.cache_control.empty?
194
+ options[:cache_control] = config.cache_control
195
+ end
196
+ if should_compress?(key)
197
+ options[:content_encoding] = "gzip"
198
+ value = compress(value)
199
+ end
200
+ Aws::S3::Resource.new.bucket(config.bucket).object(key).put(options.merge(body: value))
201
+ end
202
+
203
+ def should_compress?(key)
204
+ if [true, false, nil].include?(config.gzip)
205
+ !!config.gzip
206
+ else
207
+ key != CURRENT_REVISION && Array(config.gzip).any? { |regexp| key.match(regexp) }
208
+ end
209
+ end
210
+
211
+ def compress(source)
212
+ output = Stream.new
213
+ gz = Zlib::GzipWriter.new(output, Zlib::DEFAULT_COMPRESSION, Zlib::DEFAULT_STRATEGY)
214
+ gz.write(source)
215
+ gz.close
216
+ output.string
217
+ end
218
+
219
+ def decompress(source)
220
+ begin
221
+ Zlib::GzipReader.new(StringIO.new(source)).read
222
+ rescue Zlib::GzipFile::Error
223
+ source
224
+ end
225
+ end
226
+
227
+ def source_files_list
228
+ Dir.glob(File.join(config.dist_dir, "**/*")).select { |f| File.file?(f) }
229
+ end
230
+
231
+ def colorize(color, text)
232
+ config.colorize ? Color.send(color, text) : text
233
+ end
234
+
235
+ def time_zone
236
+ TZInfo::Timezone.get(config.time_zone)
237
+ end
238
+
239
+ class Stream < StringIO
240
+ def initialize(*)
241
+ super
242
+ set_encoding "BINARY"
243
+ end
244
+
245
+ def close
246
+ rewind
247
+ end
248
+ end
249
+
250
+ end
251
+ end
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 's3_deployer/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "s3_deployer"
8
+ spec.version = S3Deployer::VERSION
9
+ spec.authors = ["Anton Astashov"]
10
+ spec.email = ["anton.astashov@gmail.com"]
11
+ spec.description = "Simple gem for deploying client apps to S3"
12
+ spec.summary = "Simple gem for deploying client apps to S3"
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency 'tzinfo'
22
+ spec.add_dependency 'json'
23
+ spec.add_dependency 'parallel'
24
+ spec.add_dependency 'aws-sdk'
25
+
26
+ spec.add_development_dependency "bundler", "~> 1.3"
27
+ spec.add_development_dependency "rake"
28
+ end
metadata ADDED
@@ -0,0 +1,138 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: s3_deployer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.6.0
5
+ platform: ruby
6
+ authors:
7
+ - Anton Astashov
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-10-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: tzinfo
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: json
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: parallel
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: aws-sdk
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: bundler
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.3'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.3'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rake
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: Simple gem for deploying client apps to S3
98
+ email:
99
+ - anton.astashov@gmail.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - ".gitignore"
105
+ - Gemfile
106
+ - README.md
107
+ - Rakefile
108
+ - lib/s3_deployer.rb
109
+ - lib/s3_deployer/color.rb
110
+ - lib/s3_deployer/config.rb
111
+ - lib/s3_deployer/tasks.rb
112
+ - lib/s3_deployer/version.rb
113
+ - s3_deployer.gemspec
114
+ homepage: ''
115
+ licenses:
116
+ - MIT
117
+ metadata: {}
118
+ post_install_message:
119
+ rdoc_options: []
120
+ require_paths:
121
+ - lib
122
+ required_ruby_version: !ruby/object:Gem::Requirement
123
+ requirements:
124
+ - - ">="
125
+ - !ruby/object:Gem::Version
126
+ version: '0'
127
+ required_rubygems_version: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ requirements: []
133
+ rubyforge_project:
134
+ rubygems_version: 2.2.2
135
+ signing_key:
136
+ specification_version: 4
137
+ summary: Simple gem for deploying client apps to S3
138
+ test_files: []