egads 0.0.1
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 +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:
|