egads 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +49 -0
- data/README.md +13 -0
- data/Rakefile +9 -0
- data/bin/egads +5 -0
- data/egads.gemspec +28 -0
- data/example/egads.yml +32 -0
- data/example/egads_remote.yml +18 -0
- data/lib/egads.rb +7 -0
- data/lib/egads/capistrano.rb +28 -0
- data/lib/egads/cli.rb +190 -0
- data/lib/egads/config.rb +79 -0
- data/lib/egads/ext/thor_actions.rb +25 -0
- data/lib/egads/s3_tarball.rb +41 -0
- data/lib/egads/version.rb +3 -0
- data/spec/egads_config_spec.rb +23 -0
- data/spec/egads_s3_tarball_spec.rb +30 -0
- data/spec/spec_helper.rb +17 -0
- metadata +126 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
YTY5Zjg1MjIxZjgxYmMxN2QwZTBmYjdkYjUwMjBlMjAwNzRlN2EzMg==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
MDYyMmIzODJjMzNjY2FiMWRjNzA2OTk0MjA2ZWVmYmJlY2Q5ZjQxMw==
|
7
|
+
!binary "U0hBNTEy":
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
NjZjYWJlN2I2M2QyYzIyYzc5MjI4OTY2MWNjZjBkOTJlMDJmODAxYmQ5NTAw
|
10
|
+
YmYxYmE0OTFjZDcwZDgxYTY2YmM3ZjRkZDk4ZWJjZjBhNTM2ODY1MDhkOTcx
|
11
|
+
MTNmNTdhYmE3ZDExNGQ3MDdhYTk1MGYxODZkMWE3OTcwYTliZjk=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
Mzg5ZDM3MjUxMzk5ZGZhYzc5ZGVjOWM1OWIxNWFkZGUzZTdkZmYxZDZjYmQz
|
14
|
+
NDU2YjhlMDkwYmEyODdhN2EzMTNmYmVhZTRmMjI5YWYxOGU3OWFiM2VkZjEz
|
15
|
+
Mjg5ZTlmZjVhZDdkZjU1MjlhODFjMzI3ODE0YzI5Mjc0YzE1ZWI=
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
egads (0.0.1)
|
5
|
+
fog
|
6
|
+
thor
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: https://rubygems.org/
|
10
|
+
specs:
|
11
|
+
builder (3.2.2)
|
12
|
+
columnize (0.3.6)
|
13
|
+
debugger (1.5.0)
|
14
|
+
columnize (>= 0.3.1)
|
15
|
+
debugger-linecache (~> 1.2.0)
|
16
|
+
debugger-ruby_core_source (~> 1.2.0)
|
17
|
+
debugger-linecache (1.2.0)
|
18
|
+
debugger-ruby_core_source (1.2.0)
|
19
|
+
excon (0.22.1)
|
20
|
+
fog (1.11.1)
|
21
|
+
builder
|
22
|
+
excon (~> 0.20)
|
23
|
+
formatador (~> 0.2.0)
|
24
|
+
json (~> 1.7)
|
25
|
+
mime-types
|
26
|
+
net-scp (~> 1.1)
|
27
|
+
net-ssh (>= 2.1.3)
|
28
|
+
nokogiri (~> 1.5.0)
|
29
|
+
ruby-hmac
|
30
|
+
formatador (0.2.4)
|
31
|
+
json (1.8.0)
|
32
|
+
mime-types (1.23)
|
33
|
+
minitest (5.0.3)
|
34
|
+
net-scp (1.1.1)
|
35
|
+
net-ssh (>= 2.6.5)
|
36
|
+
net-ssh (2.6.7)
|
37
|
+
nokogiri (1.5.9)
|
38
|
+
rake (10.0.4)
|
39
|
+
ruby-hmac (0.4.0)
|
40
|
+
thor (0.18.1)
|
41
|
+
|
42
|
+
PLATFORMS
|
43
|
+
ruby
|
44
|
+
|
45
|
+
DEPENDENCIES
|
46
|
+
debugger
|
47
|
+
egads!
|
48
|
+
minitest
|
49
|
+
rake
|
data/README.md
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# egads: Extensible Git-Archive Deploy Strategy
|
2
|
+
|
3
|
+
## Installation
|
4
|
+
|
5
|
+
For local work (building and uploading tarballs), put `egads` in your Gemfile:
|
6
|
+
|
7
|
+
# In Gemfile
|
8
|
+
gem 'egads'
|
9
|
+
|
10
|
+
On remote machines (to which you deploy), `egads` must be in your PATH.
|
11
|
+
So install `egads` as a system gem:
|
12
|
+
|
13
|
+
sudo gem install egads
|
data/Rakefile
ADDED
data/bin/egads
ADDED
data/egads.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
$:.unshift 'lib'
|
2
|
+
require 'egads/version'
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "egads"
|
6
|
+
s.version = Egads::VERSION
|
7
|
+
s.summary = "Extensible Git Archive Deploy Strategy"
|
8
|
+
s.homepage = "https://github.com/kickstarter/egads"
|
9
|
+
s.email = ["aaron@ktheory.com"]
|
10
|
+
s.authors = ["Aaron Suggs"]
|
11
|
+
|
12
|
+
s.files = `git ls-files`.split($/)
|
13
|
+
s.executables = s.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
14
|
+
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
15
|
+
s.require_paths = ["lib"]
|
16
|
+
|
17
|
+
s.extra_rdoc_files = [ "README.md" ]
|
18
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
19
|
+
|
20
|
+
s.add_dependency "fog"
|
21
|
+
s.add_dependency "thor"
|
22
|
+
s.add_development_dependency "rake"
|
23
|
+
s.add_development_dependency "minitest"
|
24
|
+
|
25
|
+
s.description = %s{
|
26
|
+
A collection of scripts for making a deployable tarball of a git commit,
|
27
|
+
uploading it to Amazon S3, and downloading it to your servers.}
|
28
|
+
end
|
data/example/egads.yml
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
s3:
|
2
|
+
bucket: my-bucket
|
3
|
+
access_key: mykey
|
4
|
+
secret_key: mysecret
|
5
|
+
prefix: my_project # Optional prefix for S3 paths
|
6
|
+
|
7
|
+
build:
|
8
|
+
# Additional paths to include in the built tarball
|
9
|
+
extra_paths:
|
10
|
+
- public/assets
|
11
|
+
- public/stylesheets
|
12
|
+
|
13
|
+
before:
|
14
|
+
- script/egads/before-build
|
15
|
+
after:
|
16
|
+
- script/egads/after-build
|
17
|
+
|
18
|
+
upload:
|
19
|
+
before:
|
20
|
+
- script/egads/before-upload
|
21
|
+
after:
|
22
|
+
- script/egads/after-upload
|
23
|
+
|
24
|
+
stage:
|
25
|
+
# No before-stage hook
|
26
|
+
after:
|
27
|
+
- script/egads/after-stage
|
28
|
+
release:
|
29
|
+
before:
|
30
|
+
- script/egads/before-release
|
31
|
+
after:
|
32
|
+
- script/egads/after-release
|
@@ -0,0 +1,18 @@
|
|
1
|
+
s3:
|
2
|
+
bucket: my-bucket
|
3
|
+
access_key: mykey
|
4
|
+
secret_key: mysecret
|
5
|
+
prefix: my_project # Optional prefix for S3 paths
|
6
|
+
|
7
|
+
# Path where tarballs are extracted
|
8
|
+
extract_to: /var/apps/my_project/releases
|
9
|
+
release_to: /var/apps/my_project/current
|
10
|
+
|
11
|
+
restart_command: /etc/init.d/rails_services restart
|
12
|
+
|
13
|
+
# environment variables to set before executing commands
|
14
|
+
env:
|
15
|
+
RAILS_ENV: production
|
16
|
+
SHARED_PATH: /var/apps/my_project/shared
|
17
|
+
BUNDLE_PATH: /var/apps/my_project/shared/bundle
|
18
|
+
BUNDLE_WITHOUT: development:test
|
data/lib/egads.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# Capistrano configuration.
|
2
|
+
# Use `load 'egads/capistrano'` instead of `load 'deploy'` in your Capfile
|
3
|
+
Capistrano::Configuration.instance.load do
|
4
|
+
namespace :deploy do
|
5
|
+
desc "Deploy"
|
6
|
+
task :default do
|
7
|
+
deploy.upload
|
8
|
+
deploy.stage
|
9
|
+
deploy.release
|
10
|
+
end
|
11
|
+
|
12
|
+
desc "Prepares for release by bundling gems, symlinking shared files, etc"
|
13
|
+
task :stage do
|
14
|
+
run "egads stage #{sha}"
|
15
|
+
end
|
16
|
+
|
17
|
+
desc "Runs the release script to symlink a staged deploy and restarts services"
|
18
|
+
task :release do
|
19
|
+
run "egads release #{sha}"
|
20
|
+
end
|
21
|
+
|
22
|
+
desc "Checks that a deployable tarball is on S3; creates it if missing"
|
23
|
+
task :upload do
|
24
|
+
`bundle exec egads build`
|
25
|
+
abort "Failed to upload build" if $?.exitstatus != 0
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/egads/cli.rb
ADDED
@@ -0,0 +1,190 @@
|
|
1
|
+
module Egads
|
2
|
+
class CLI < Thor
|
3
|
+
include Thor::Actions
|
4
|
+
##
|
5
|
+
# Local commands
|
6
|
+
|
7
|
+
desc "build", "[local] Compiles a deployable tarball of the current commit and uploads it to S3"
|
8
|
+
method_option :force, type: :boolean, aliases: '-f', default: false, banner: "Build and overwrite existing tarball on S3"
|
9
|
+
method_option 'no-upload', type: :boolean, default: false, banner: "Don't upload the tarball to S3"
|
10
|
+
def build(rev='HEAD')
|
11
|
+
sha = run_or_die("git rev-parse --verify #{rev}", capture: true).strip
|
12
|
+
tarball = S3Tarball.new(sha)
|
13
|
+
if !options[:force] && tarball.exists?
|
14
|
+
say "Tarball for #{sha} already exists. Pass --force to rebuild."
|
15
|
+
return
|
16
|
+
end
|
17
|
+
|
18
|
+
say "Building tarball for #{sha}..."
|
19
|
+
# Check if we're on sha, if not, ask to check it out
|
20
|
+
head = run_or_die("git rev-parse --verify HEAD", capture: true).strip
|
21
|
+
unless head == sha
|
22
|
+
say "** Error **"
|
23
|
+
say "Trying to build #{sha[0,7]}, but #{head[0,7]} is checked out."
|
24
|
+
say "Run `git checkout #{head[0,7]}` and try again."
|
25
|
+
exit 1
|
26
|
+
end
|
27
|
+
|
28
|
+
# Ensure clean working directory
|
29
|
+
unless run("git status -s", capture: true).empty?
|
30
|
+
say "** Error **"
|
31
|
+
say "Working directory is not clean."
|
32
|
+
say "Stash your changes with `git add . && git stash` and try again."
|
33
|
+
exit 1
|
34
|
+
end
|
35
|
+
|
36
|
+
# Make git archive
|
37
|
+
FileUtils.mkdir_p(File.dirname(tarball.local_tar_path))
|
38
|
+
run_or_die "git archive #{sha} --format=tar > #{tarball.local_tar_path}"
|
39
|
+
|
40
|
+
# Write REVISION and add to tarball
|
41
|
+
File.open('REVISION', 'w') {|f| f << sha + "\n" }
|
42
|
+
run_or_die "tar -uf #{tarball.local_tar_path} REVISION"
|
43
|
+
|
44
|
+
run_hooks_for(:build, :post)
|
45
|
+
|
46
|
+
extra_paths = Config.build_extra_paths
|
47
|
+
if extra_paths.any?
|
48
|
+
run_or_die "tar -uf #{tarball.local_tar_path} #{extra_paths * " "}"
|
49
|
+
end
|
50
|
+
|
51
|
+
run_or_die "gzip -9f #{tarball.local_tar_path}"
|
52
|
+
|
53
|
+
invoke(:upload, [sha], force: options[:force]) unless options['no-upload']
|
54
|
+
end
|
55
|
+
|
56
|
+
method_option :force, type: :boolean, aliases: '-f', default: false, banner: "Overwrite existing tarball on S3"
|
57
|
+
desc "upload SHA", "[local, plumbing] Uploads a tarball for SHA to S3"
|
58
|
+
def upload(sha)
|
59
|
+
tarball = S3Tarball.new(sha)
|
60
|
+
if !options[:force] && tarball.exists?
|
61
|
+
say "Tarball for #{sha} already exists. Pass --force to upload again."
|
62
|
+
end
|
63
|
+
|
64
|
+
path = tarball.local_gzipped_path
|
65
|
+
size = File.size(path)
|
66
|
+
|
67
|
+
say "Uploading tarball (%.1f MB)" % (size.to_f / 2**20)
|
68
|
+
duration = Benchmark.realtime do
|
69
|
+
tarball.upload(path)
|
70
|
+
end
|
71
|
+
say "Uploaded in %.1f seconds (%.1f KB/s)" % [duration, (size.to_f / 2**10) / duration]
|
72
|
+
|
73
|
+
File.delete(path)
|
74
|
+
end
|
75
|
+
|
76
|
+
##
|
77
|
+
# Remote commands
|
78
|
+
|
79
|
+
desc "extract SHA", "[remote, plumbing] Downloads tarball for SHA from S3 and extracts it to the filesystem"
|
80
|
+
method_option :force, type: :boolean, default: false, banner: "Overwrite existing files"
|
81
|
+
def extract(sha)
|
82
|
+
dir = RemoteConfig.release_dir
|
83
|
+
path = File.join(dir, "#{sha}.tar.gz")
|
84
|
+
tarball = S3Tarball.new(sha, true)
|
85
|
+
|
86
|
+
inside dir do
|
87
|
+
if options[:force] || !File.exists?(path)
|
88
|
+
say "Downloading tarball"
|
89
|
+
duration = Benchmark.realtime do
|
90
|
+
File.open(path, 'w') {|f| f << tarball.body }
|
91
|
+
end
|
92
|
+
size = File.size(path)
|
93
|
+
say "Downloaded in %.1f seconds (%.1f KB/s)" % [duration, (size.to_f / 2**10) / duration]
|
94
|
+
else
|
95
|
+
say "Tarball already downloads. Use --force to overwrite"
|
96
|
+
end
|
97
|
+
|
98
|
+
# Check revision file to see if tarball is already extracted
|
99
|
+
extract_flag_path File.join(dir, '.egads-extract-success')
|
100
|
+
if options[:force] || !File.exists?(extract_flag_path)
|
101
|
+
say "Extracting tarball"
|
102
|
+
run_or_die "tar -zxf #{path}"
|
103
|
+
else
|
104
|
+
say "Tarball already extracted. Use --force to overwrite"
|
105
|
+
end
|
106
|
+
FileUtils.touch(extract_flag_path)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
desc "stage SHA", "[remote] Readies SHA for release. If needed, generates URL for SHA and extracts"
|
111
|
+
method_option :force, type: :boolean, default: false, banner: "Overwrite existing files"
|
112
|
+
def stage(sha)
|
113
|
+
invoke(:extract, [sha], options)
|
114
|
+
dir = RemoteConfig.release_dir
|
115
|
+
stage_flag_path = File.join(dir, '.egads-stage-success')
|
116
|
+
if options[:force] || !File.exists?(stage_flag_path)
|
117
|
+
inside dir do
|
118
|
+
run_hooks_for(:stage, :before)
|
119
|
+
|
120
|
+
run_or_die("bundle install --deployment --quiet") if File.readable?("GEMFILE")
|
121
|
+
if ENV['SHARED_PATH']
|
122
|
+
symlinked_dirs = %w(public/system tmp/pids log)
|
123
|
+
symlinked_dirs.each do |d|
|
124
|
+
source = File.join(ENV['SHARED_PATH'], d)
|
125
|
+
destination = File.join(dir, d)
|
126
|
+
symlink_directory(source, destination)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
run_hooks_for(:stage, :after)
|
131
|
+
end
|
132
|
+
else
|
133
|
+
say "Already staged. Use --force to overwrite"
|
134
|
+
end
|
135
|
+
FileUtils.touch(stage_flag_path)
|
136
|
+
end
|
137
|
+
|
138
|
+
desc "release SHA", "[remote] Symlinks SHA to current and restarts services. If needed, stages SHA"
|
139
|
+
method_option :force, type: :boolean, default: false, banner: "Overwrite existing files while staging"
|
140
|
+
def release(sha)
|
141
|
+
invoke(:stage, [sha], options)
|
142
|
+
dir = RemoteConfig.release_dir
|
143
|
+
inside dir do
|
144
|
+
run_hooks_for(:release, :before)
|
145
|
+
end
|
146
|
+
|
147
|
+
symlink_directory(dir, RemoteConfig.release_to) unless RemoteConfig.release_to == File.readlink(dir)
|
148
|
+
inside RemoteConfig.release_to do
|
149
|
+
# Restart services
|
150
|
+
run_or_die(RemoteConfig.restart_command)
|
151
|
+
run_hooks_for(:release, :after)
|
152
|
+
end
|
153
|
+
invoke(:clean)
|
154
|
+
|
155
|
+
end
|
156
|
+
|
157
|
+
desc "clean N", "[remote, plumbing] Deletes old releases, keeping the N most recent (by mtime)"
|
158
|
+
def clean(n=4)
|
159
|
+
inside RemoteConfig.extract_to do
|
160
|
+
dirs = Dir.glob('*').sort_by{|path| File.mtime(path) }
|
161
|
+
dirs.slice!(n..-1)
|
162
|
+
dirs.each do |dir|
|
163
|
+
say_status :clean, "Deleting #{dir}"
|
164
|
+
FileUtils.rm_rf(dir)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
private
|
170
|
+
# Run command hooks from config file
|
171
|
+
# E.g. run_hooks_for(:build, :post)
|
172
|
+
def run_hooks_for(cmd, hook)
|
173
|
+
say_status :hooks, "Running #{cmd} #{hook} hooks"
|
174
|
+
Config.hooks_for(cmd, hook).each do |command|
|
175
|
+
say "Running `#{command}`"
|
176
|
+
run_or_die command
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
# Symlinks a directory
|
181
|
+
# NB that `ln -f` doesn't work with directories.
|
182
|
+
# This is not atomic.
|
183
|
+
def symlink_directory(src, dest)
|
184
|
+
raise ArgumentError.new("#{src} is not a directory") unless File.directory?(src)
|
185
|
+
say_status :symlink, "from #{src} to #{dest}"
|
186
|
+
FileUtils.rm_rf(dest)
|
187
|
+
FileUtils.ln_s(src, dest)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
data/lib/egads/config.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
module Egads
|
2
|
+
|
3
|
+
module CommonConfig
|
4
|
+
def config
|
5
|
+
@config ||= YAML.load_file(config_path)
|
6
|
+
end
|
7
|
+
|
8
|
+
def s3_bucket
|
9
|
+
return @bucket if @bucket
|
10
|
+
fog = Fog::Storage::AWS.new(aws_access_key_id: config['s3']['access_key'], aws_secret_access_key: config['s3']['secret_key'])
|
11
|
+
@bucket ||= fog.directories.new(key: config['s3']['bucket'])
|
12
|
+
end
|
13
|
+
|
14
|
+
def s3_prefix
|
15
|
+
config['s3']['prefix']
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
# Local config in the tarball or current working directory
|
21
|
+
module Config
|
22
|
+
extend CommonConfig
|
23
|
+
|
24
|
+
def self.config_path
|
25
|
+
path = ENV['EGADS_CONFIG'] || File.join(ENV['PWD'], 'egads.yml')
|
26
|
+
unless path && File.readable?(path)
|
27
|
+
raise ArgumentError.new("Could not read config file. Set either EGADS_CONFIG, or create egads.yml in the current directory")
|
28
|
+
end
|
29
|
+
path
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns the hooks in the config for cmd and hook.
|
33
|
+
# E.g. hooks_for(:build, :post)
|
34
|
+
def self.hooks_for(cmd, hook)
|
35
|
+
if Hash === config[cmd.to_s]
|
36
|
+
Array(config[cmd.to_s][hook.to_s])
|
37
|
+
else
|
38
|
+
[]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.build_extra_paths
|
43
|
+
config['build'] && Array(config['build']['extra_paths'])
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Remote config for the extract command (before data in tarball is available)
|
48
|
+
module RemoteConfig
|
49
|
+
extend CommonConfig
|
50
|
+
|
51
|
+
def self.config_path
|
52
|
+
path = ENV['EGADS_REMOTE_CONFIG'] || "/etc/egads.yml"
|
53
|
+
unless path && File.readable?(path)
|
54
|
+
raise ArgumentError.new("Could not read remote config file. Set either EGADS_REMOTE_CONFIG, or create /etc/egads.yml")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.release_dir(sha)
|
59
|
+
File.join(config['extract_to'], sha)
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.release_to
|
63
|
+
config['release_to']
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.extract_to
|
67
|
+
config['extract_to']
|
68
|
+
end
|
69
|
+
|
70
|
+
# Set environment variables from the config
|
71
|
+
def self.setup_environment
|
72
|
+
config['env'].each{|k,v| ENV[k] = v } if config['env']
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.restart_command
|
76
|
+
config['restart_command']
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'benchmark'
|
3
|
+
class Thor
|
4
|
+
class CommandFailedError < Error; end
|
5
|
+
|
6
|
+
module Actions
|
7
|
+
# runs command, raises CommandFailedError unless exit status is 0.
|
8
|
+
# Also logs duration
|
9
|
+
def run_or_die(command, config={})
|
10
|
+
result = nil
|
11
|
+
duration = Benchmark.realtime do
|
12
|
+
result = run(command, config)
|
13
|
+
end
|
14
|
+
if behavior == :invoke && $?.exitstatus != 0
|
15
|
+
message = "#{command} failed with %s" % ($?.exitstatus ? "exit status #{$?.exitstatus}" : "no exit status (likely force killed)")
|
16
|
+
raise Thor::CommandFailedError.new(message)
|
17
|
+
end
|
18
|
+
|
19
|
+
say_status :done, "in %.1f seconds" % duration, config.fetch(:verbose, true)
|
20
|
+
|
21
|
+
result
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Egads
|
2
|
+
class S3Tarball
|
3
|
+
attr_reader :sha, :remote
|
4
|
+
def initialize(sha, remote = false)
|
5
|
+
@sha = sha
|
6
|
+
@remote = remote
|
7
|
+
end
|
8
|
+
|
9
|
+
def key
|
10
|
+
[Config.s3_prefix, "#{sha}.tar.gz"].compact * '/'
|
11
|
+
end
|
12
|
+
|
13
|
+
def exists?
|
14
|
+
bucket.files.head(key)
|
15
|
+
end
|
16
|
+
|
17
|
+
def local_tar_path
|
18
|
+
"tmp/#{sha}.tar"
|
19
|
+
end
|
20
|
+
|
21
|
+
def local_gzipped_path
|
22
|
+
"#{local_tar_path}.gz"
|
23
|
+
end
|
24
|
+
|
25
|
+
def upload(path=local_gzipped_path)
|
26
|
+
File.open(path) {|f|
|
27
|
+
bucket.files.create(key: key, body: f)
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
# Load the file contents from S3
|
32
|
+
def contents
|
33
|
+
bucket.files.get(key: key).body
|
34
|
+
end
|
35
|
+
|
36
|
+
def bucket
|
37
|
+
remote ? RemoteConfig.s3_bucket : Config.s3_bucket
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
|
3
|
+
describe Egads::Config do
|
4
|
+
|
5
|
+
subject { Egads::Config }
|
6
|
+
it "raises ArgumentError for missing config" do
|
7
|
+
-> { subject.config_path }.must_raise(ArgumentError)
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "with an config file" do
|
11
|
+
before { ENV['EGADS_CONFIG'] = "example/egads.yml" }
|
12
|
+
after { ENV.delete('EGAGS_CONFIG') }
|
13
|
+
|
14
|
+
it "has an S3 bucket" do
|
15
|
+
subject.s3_bucket.key.must_equal 'my-bucket'
|
16
|
+
end
|
17
|
+
|
18
|
+
it "has an S3 prefix" do
|
19
|
+
subject.s3_prefix.must_equal 'my_project'
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
|
3
|
+
describe Egads::S3Tarball do
|
4
|
+
|
5
|
+
before { ENV['EGADS_CONFIG'] = "example/egads.yml" }
|
6
|
+
after { ENV.delete('EGAGS_CONFIG') }
|
7
|
+
|
8
|
+
subject { Egads::S3Tarball.new('sha') }
|
9
|
+
|
10
|
+
it('has a sha') { subject.sha.must_equal 'sha' }
|
11
|
+
it('has a key') { subject.key.must_equal 'my_project/sha.tar.gz' }
|
12
|
+
|
13
|
+
it "has an S3 bucket" do
|
14
|
+
subject.bucket.must_equal Egads::Config.s3_bucket
|
15
|
+
end
|
16
|
+
|
17
|
+
it('should not exist') { subject.exists?.must_be_nil }
|
18
|
+
|
19
|
+
describe 'when uploaded' do
|
20
|
+
before do
|
21
|
+
Egads::Config.s3_bucket.save # Ensure bucket exists
|
22
|
+
subject.upload(ENV['EGADS_CONFIG'])
|
23
|
+
end
|
24
|
+
|
25
|
+
it('should exist') { subject.exists?.wont_be_nil }
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
end
|
30
|
+
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "bundler/setup"
|
3
|
+
|
4
|
+
require "minitest/autorun"
|
5
|
+
require "minitest/pride"
|
6
|
+
require "egads"
|
7
|
+
|
8
|
+
Fog.mock!
|
9
|
+
|
10
|
+
begin
|
11
|
+
require 'debugger'
|
12
|
+
rescue LoadError
|
13
|
+
puts "Skipping debugger"
|
14
|
+
end
|
15
|
+
|
16
|
+
class Minitest::Spec
|
17
|
+
end
|
metadata
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: egads
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Aaron Suggs
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-06-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: fog
|
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: thor
|
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: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ! '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
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: minitest
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ! '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: ! "\n A collection of scripts for making a deployable tarball of a
|
70
|
+
git commit,\n uploading it to Amazon S3, and downloading it to your servers."
|
71
|
+
email:
|
72
|
+
- aaron@ktheory.com
|
73
|
+
executables:
|
74
|
+
- egads
|
75
|
+
extensions: []
|
76
|
+
extra_rdoc_files:
|
77
|
+
- README.md
|
78
|
+
files:
|
79
|
+
- .gitignore
|
80
|
+
- Gemfile
|
81
|
+
- Gemfile.lock
|
82
|
+
- README.md
|
83
|
+
- Rakefile
|
84
|
+
- bin/egads
|
85
|
+
- egads.gemspec
|
86
|
+
- example/egads.yml
|
87
|
+
- example/egads_remote.yml
|
88
|
+
- lib/egads.rb
|
89
|
+
- lib/egads/capistrano.rb
|
90
|
+
- lib/egads/cli.rb
|
91
|
+
- lib/egads/config.rb
|
92
|
+
- lib/egads/ext/thor_actions.rb
|
93
|
+
- lib/egads/s3_tarball.rb
|
94
|
+
- lib/egads/version.rb
|
95
|
+
- spec/egads_config_spec.rb
|
96
|
+
- spec/egads_s3_tarball_spec.rb
|
97
|
+
- spec/spec_helper.rb
|
98
|
+
homepage: https://github.com/kickstarter/egads
|
99
|
+
licenses: []
|
100
|
+
metadata: {}
|
101
|
+
post_install_message:
|
102
|
+
rdoc_options:
|
103
|
+
- --charset=UTF-8
|
104
|
+
require_paths:
|
105
|
+
- lib
|
106
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ! '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
112
|
+
requirements:
|
113
|
+
- - ! '>='
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: '0'
|
116
|
+
requirements: []
|
117
|
+
rubyforge_project:
|
118
|
+
rubygems_version: 2.0.3
|
119
|
+
signing_key:
|
120
|
+
specification_version: 4
|
121
|
+
summary: Extensible Git Archive Deploy Strategy
|
122
|
+
test_files:
|
123
|
+
- spec/egads_config_spec.rb
|
124
|
+
- spec/egads_s3_tarball_spec.rb
|
125
|
+
- spec/spec_helper.rb
|
126
|
+
has_rdoc:
|