egads 0.0.8 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +6 -14
- data/.gitignore +1 -0
- data/Gemfile +3 -0
- data/Guardfile +24 -0
- data/bin/egads +1 -1
- data/egads.gemspec +1 -0
- data/example/egads_remote.yml +5 -2
- data/lib/egads.rb +6 -2
- data/lib/egads/command.rb +18 -0
- data/lib/egads/command/build.rb +114 -0
- data/lib/egads/command/extract.rb +68 -0
- data/lib/egads/command/release.rb +64 -0
- data/lib/egads/command/stage.rb +78 -0
- data/lib/egads/command/trim.rb +18 -0
- data/lib/egads/command/upload.rb +32 -0
- data/lib/egads/config.rb +8 -4
- data/lib/egads/group.rb +45 -0
- data/lib/egads/version.rb +1 -1
- data/spec/egads_build_spec.rb +29 -0
- data/spec/egads_config_spec.rb +49 -4
- data/spec/egads_extract_spec.rb +10 -0
- data/spec/egads_release_spec.rb +10 -0
- data/spec/egads_s3_tarball_spec.rb +2 -2
- data/spec/egads_stage_spec.rb +10 -0
- data/spec/egads_trim_spec.rb +10 -0
- data/spec/egads_upload_spec.rb +10 -0
- data/spec/spec_helper.rb +18 -0
- metadata +38 -19
- data/Gemfile.lock +0 -49
- data/lib/egads/cli.rb +0 -221
- data/lib/egads/ext/thor_actions.rb +0 -25
checksums.yaml
CHANGED
@@ -1,15 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
metadata.gz: !binary |-
|
9
|
-
M2VhOWNlMmEwMjI0MTIzNjRkODFjMWYzNTU0YzY3OWIxYWZhNGEzMmI5MTgw
|
10
|
-
MmU3NjdhMTk1Y2FjMDdmODExODMyNjkwOWFjNjVmZjE2YjU3ZWQxZDdjZTA2
|
11
|
-
MjRmN2I3NDJkZGFmZmZiOTI4YzU1MDMzMjg3ODQ0Y2M0Nzg4ZDg=
|
12
|
-
data.tar.gz: !binary |-
|
13
|
-
NWM4MWQ5ZjQ2YmVkMzg2OTJhYjBhNDZmMTdkZDgxZTkwZjI5NTYwNWU5ZTcw
|
14
|
-
MGM3OGY0ZTFjMzA3MjYzMDQ5OTE1N2U1MzNhMWNiODNiMjJjNjQxZjViMDdi
|
15
|
-
MGI0OWI5ZTlhNWE5YTNiODY3M2NmYTI5OWIzM2I0MjhjZmU0M2Y=
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 42ec1036274bdc5a2f82a3e9b7cc104a13b026ed
|
4
|
+
data.tar.gz: 805811a6fcba6b004c181c7c0f0afbd281633904
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f6c59fa7fae40a6c6ef32422367b5f3deacddafdaace0320f23fad1b6495e5c5f13bb76ee0224b845b1cb574b4e90a600594eb30db0a015f9e5ecf090e662203
|
7
|
+
data.tar.gz: f5bdec599424d240ecae7aefce605614a7e1dde7fc0b4de35d76a1c3ba8e254d4d63bde4b41e24a2ab2474a1baf4049f477f63268b7dd716f9b3b5672713e1f7
|
data/.gitignore
CHANGED
data/Gemfile
CHANGED
data/Guardfile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
guard 'minitest' do
|
5
|
+
# with Minitest::Unit
|
6
|
+
#watch(%r|^test/(.*)\/?test_(.*)\.rb|)
|
7
|
+
#watch(%r|^lib/(.*)([^/]+)\.rb|) { |m| "test/#{m[1]}test_#{m[2]}.rb" }
|
8
|
+
#watch(%r|^test/test_helper\.rb|) { "test" }
|
9
|
+
|
10
|
+
# with Minitest::Spec
|
11
|
+
watch(%r|^spec/(.*)_spec\.rb|)
|
12
|
+
watch(%r|^lib/(.*)([^/]+)\.rb|) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
|
13
|
+
watch(%r|^spec/spec_helper\.rb|) { "spec" }
|
14
|
+
|
15
|
+
# Rails 3.2
|
16
|
+
# watch(%r|^app/controllers/(.*)\.rb|) { |m| "test/controllers/#{m[1]}_test.rb" }
|
17
|
+
# watch(%r|^app/helpers/(.*)\.rb|) { |m| "test/helpers/#{m[1]}_test.rb" }
|
18
|
+
# watch(%r|^app/models/(.*)\.rb|) { |m| "test/unit/#{m[1]}_test.rb" }
|
19
|
+
|
20
|
+
# Rails
|
21
|
+
# watch(%r|^app/controllers/(.*)\.rb|) { |m| "test/functional/#{m[1]}_test.rb" }
|
22
|
+
# watch(%r|^app/helpers/(.*)\.rb|) { |m| "test/helpers/#{m[1]}_test.rb" }
|
23
|
+
# watch(%r|^app/models/(.*)\.rb|) { |m| "test/unit/#{m[1]}_test.rb" }
|
24
|
+
end
|
data/bin/egads
CHANGED
data/egads.gemspec
CHANGED
@@ -21,6 +21,7 @@ Gem::Specification.new do |s|
|
|
21
21
|
s.add_dependency "thor"
|
22
22
|
s.add_development_dependency "rake"
|
23
23
|
s.add_development_dependency "minitest"
|
24
|
+
#s.add_development_dependency "simple_mock" # Via http://tatey.com/2012/02/07/mocking-with-minitest-mock-and-simple-delegator/
|
24
25
|
|
25
26
|
s.description = %s{
|
26
27
|
A collection of scripts for making a deployable tarball of a git commit,
|
data/example/egads_remote.yml
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# AWS S3 config for generating signed URL for tarball
|
1
2
|
s3:
|
2
3
|
bucket: my-bucket
|
3
4
|
access_key: mykey
|
@@ -10,9 +11,11 @@ release_to: /var/apps/my_project/current
|
|
10
11
|
|
11
12
|
restart_command: /etc/init.d/rails_services restart
|
12
13
|
|
14
|
+
# Options to pass to bundler
|
15
|
+
bundler:
|
16
|
+
options: --deployment --quiet --without development:test --path /var/apps/my_project/shared/bundle
|
17
|
+
|
13
18
|
# environment variables to set before executing commands
|
14
19
|
env:
|
15
20
|
RAILS_ENV: production
|
16
21
|
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
CHANGED
@@ -1,7 +1,11 @@
|
|
1
1
|
require 'yaml'
|
2
2
|
require 'fog'
|
3
3
|
require 'thor'
|
4
|
+
require 'benchmark'
|
5
|
+
|
6
|
+
module Egads; end
|
7
|
+
|
4
8
|
require 'egads/config'
|
5
9
|
require 'egads/s3_tarball'
|
6
|
-
require 'egads/
|
7
|
-
require 'egads/
|
10
|
+
require 'egads/group'
|
11
|
+
require 'egads/command'
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Egads
|
2
|
+
class Command < Thor
|
3
|
+
require 'egads/command/build'
|
4
|
+
require 'egads/command/upload'
|
5
|
+
require 'egads/command/extract'
|
6
|
+
require 'egads/command/stage'
|
7
|
+
require 'egads/command/release'
|
8
|
+
require 'egads/command/trim'
|
9
|
+
|
10
|
+
register(Build, 'build', 'build [REV]', '[local] Compiles a deployable tarball of the current commit and uploads it to S3')
|
11
|
+
register(Upload, 'upload', 'upload SHA', '[local, plumbing] Uploads a tarball for SHA to S3')
|
12
|
+
register(Extract, 'extract', 'extract SHA', '[remote, plumbing] Downloads tarball for SHA from S3 and extracts it to the filesystem')
|
13
|
+
register(Stage, 'stage', 'stage SHA', '[remote, plumbing] Downloads tarball for SHA from S3 and extracts it to the filesystem')
|
14
|
+
register(Release, 'release', 'release SHA', '[remote, plumbing] Downloads tarball for SHA from S3 and extracts it to the filesystem')
|
15
|
+
register(Trim, 'trime', 'trim [N]', "[remote, plumbing] Deletes old releases, keeping the N most recent (by mtime)")
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
module Egads
|
2
|
+
class Build < Group
|
3
|
+
include Thor::Actions
|
4
|
+
|
5
|
+
desc "[local] Compiles a deployable tarball of the current commit and uploads it to S3"
|
6
|
+
class_option :force, type: :boolean, aliases: '-f', default: false, banner: "Build and overwrite existing tarball on S3"
|
7
|
+
class_option :wait, type: :boolean, aliases: '-w', default: false, banner: "Wait for the build to exist. Poll S3 every 2 seconds."
|
8
|
+
class_option 'no-upload', type: :boolean, default: false, banner: "Don't upload the tarball to S3"
|
9
|
+
argument :rev, type: :string, default: 'HEAD', desc: 'git revision to build'
|
10
|
+
|
11
|
+
def check_build
|
12
|
+
say_status :rev, "#{rev} parsed to #{sha}"
|
13
|
+
|
14
|
+
wait_for_build if options[:wait]
|
15
|
+
unless should_build?
|
16
|
+
say_status :done, "Tarball for #{sha} already exists. Pass --force to rebuild."
|
17
|
+
exit 0
|
18
|
+
end
|
19
|
+
|
20
|
+
say_status :rev, "#{rev} parsed to #{sha}"
|
21
|
+
exit 1 unless can_build?
|
22
|
+
end
|
23
|
+
|
24
|
+
def make_git_archive
|
25
|
+
say_status :build, "Making tarball for #{sha}", :yellow
|
26
|
+
FileUtils.mkdir_p(File.dirname(tarball.local_tar_path))
|
27
|
+
run_with_code "git archive #{sha} --format=tar > #{tarball.local_tar_path}"
|
28
|
+
end
|
29
|
+
|
30
|
+
def append_revision_file
|
31
|
+
File.open('REVISION', 'w') {|f| f << sha + "\n" }
|
32
|
+
run_with_code "tar -uf #{tarball.local_tar_path} REVISION"
|
33
|
+
end
|
34
|
+
|
35
|
+
def run_after_build_hooks
|
36
|
+
run_hooks_for(:build,:after)
|
37
|
+
end
|
38
|
+
|
39
|
+
def append_extra_paths
|
40
|
+
extra_paths = Config.build_extra_paths
|
41
|
+
if extra_paths.any?
|
42
|
+
run_with_code "tar -uf #{tarball.local_tar_path} #{extra_paths * " "}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def gzip_archive
|
47
|
+
run_with_code "gzip -9f #{tarball.local_tar_path}"
|
48
|
+
end
|
49
|
+
|
50
|
+
def upload
|
51
|
+
invoke(Egads::Upload, [sha]) unless options['no-upload']
|
52
|
+
end
|
53
|
+
|
54
|
+
module BuildHelpers
|
55
|
+
def sha
|
56
|
+
@sha ||= run_with_code("git rev-parse --verify #{rev}").strip
|
57
|
+
end
|
58
|
+
|
59
|
+
def short_sha
|
60
|
+
sha[0,7]
|
61
|
+
end
|
62
|
+
|
63
|
+
def tarball
|
64
|
+
@tarball ||= S3Tarball.new(sha)
|
65
|
+
end
|
66
|
+
|
67
|
+
def should_build?
|
68
|
+
options[:force] || !tarball.exists?
|
69
|
+
end
|
70
|
+
|
71
|
+
def can_build?
|
72
|
+
sha_is_checked_out? && working_directory_is_clean?
|
73
|
+
end
|
74
|
+
|
75
|
+
def sha_is_checked_out?
|
76
|
+
head = run_with_code("git rev-parse --verify HEAD", capture: true).strip
|
77
|
+
short_head = head[0,7]
|
78
|
+
head == sha or error [
|
79
|
+
"Cannot build #{short_sha} because #{short_head} is checked out.",
|
80
|
+
"Run `git checkout #{short_sha}` and try again"
|
81
|
+
]
|
82
|
+
end
|
83
|
+
|
84
|
+
def working_directory_is_clean?
|
85
|
+
run("git status -s", capture: true).empty? or
|
86
|
+
error [
|
87
|
+
"Cannot build #{short_sha} because the working directory is not clean.",
|
88
|
+
"Stash your changes with `git add . && git stash` and try again."
|
89
|
+
]
|
90
|
+
end
|
91
|
+
|
92
|
+
def error(message)
|
93
|
+
lines = Array(message)
|
94
|
+
say_status :error, lines.shift, :red
|
95
|
+
lines.each {|line| say_status '', line }
|
96
|
+
|
97
|
+
false
|
98
|
+
end
|
99
|
+
|
100
|
+
def wait_for_build
|
101
|
+
say_status :wait, "Waiting for tarball to exist...", :yellow
|
102
|
+
loop do
|
103
|
+
start = Time.now
|
104
|
+
break if tarball.exists?
|
105
|
+
printf '.'
|
106
|
+
sleep [2 - (Time.now - start), 0].max
|
107
|
+
end
|
108
|
+
printf "\n"
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
include BuildHelpers
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Egads
|
2
|
+
class Extract < Group
|
3
|
+
include Thor::Actions
|
4
|
+
|
5
|
+
desc "[remote, plumbing] Downloads tarball for SHA from S3 and extracts it to the filesystem"
|
6
|
+
class_option :force, type: :boolean, default: false, banner: "Overwrite existing files"
|
7
|
+
argument :sha, type: :string, required: true, desc: 'git SHA to download and extract'
|
8
|
+
|
9
|
+
def setup_environment
|
10
|
+
RemoteConfig.setup_environment
|
11
|
+
end
|
12
|
+
|
13
|
+
def download
|
14
|
+
if should_download?
|
15
|
+
say_status :download, "Downloading tarball for #{sha}", :yellow
|
16
|
+
FileUtils.mkdir_p(release_dir)
|
17
|
+
duration = Benchmark.realtime do
|
18
|
+
File.open(path, 'w') {|f| f << tarball.contents }
|
19
|
+
end
|
20
|
+
size = File.size(path)
|
21
|
+
say_status :done, "Downloaded in %.1f seconds (%.1f KB/s)" % [duration, (size.to_f / 2**10) / duration]
|
22
|
+
else
|
23
|
+
say_status :done, "Tarball already downloaded. Use --force to overwrite"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def extract
|
28
|
+
# Check revision file to see if tarball is already extracted
|
29
|
+
if should_extract?
|
30
|
+
# Silence stderr warnings "Ignoring unknown extended header keyword"
|
31
|
+
# due to BSD/GNU tar.
|
32
|
+
inside(release_dir) { run_with_code "tar -zxf #{path} 2>/dev/null" }
|
33
|
+
else
|
34
|
+
say_status :done, "Tarball already extracted. Use --force to overwrite"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def mark_as_extracted
|
39
|
+
FileUtils.touch(extract_flag_path)
|
40
|
+
end
|
41
|
+
|
42
|
+
protected
|
43
|
+
def release_dir
|
44
|
+
RemoteConfig.release_dir(sha)
|
45
|
+
end
|
46
|
+
|
47
|
+
def path
|
48
|
+
File.join(release_dir, "#{sha}.tar.gz")
|
49
|
+
end
|
50
|
+
|
51
|
+
def tarball
|
52
|
+
@tarball ||= S3Tarball.new(sha, true)
|
53
|
+
end
|
54
|
+
|
55
|
+
def should_download?
|
56
|
+
options[:force] || File.zero?(path) || !File.exists?(path)
|
57
|
+
end
|
58
|
+
|
59
|
+
def extract_flag_path
|
60
|
+
File.join(release_dir, '.egads-extract-success')
|
61
|
+
end
|
62
|
+
|
63
|
+
def should_extract?
|
64
|
+
options[:force] || !File.exists?(extract_flag_path)
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Egads
|
2
|
+
class Release < Group
|
3
|
+
include Thor::Actions
|
4
|
+
|
5
|
+
desc "[remote] Symlinks SHA to current and restarts services. If needed, stages SHA"
|
6
|
+
class_option :force, type: :boolean, default: false, banner: "Overwrite existing release"
|
7
|
+
argument :sha, type: :string, required: true, desc: 'git SHA to stage'
|
8
|
+
def setup_environment
|
9
|
+
RemoteConfig.setup_environment
|
10
|
+
end
|
11
|
+
|
12
|
+
def stage
|
13
|
+
invoke(Egads::Stage, [sha], options)
|
14
|
+
end
|
15
|
+
|
16
|
+
def run_before_release_hooks
|
17
|
+
return unless should_release?
|
18
|
+
inside(dir) { run_hooks_for(:release, :before) }
|
19
|
+
end
|
20
|
+
|
21
|
+
def symlink_release
|
22
|
+
return unless should_release?
|
23
|
+
symlink_directory(dir, release_to)
|
24
|
+
end
|
25
|
+
|
26
|
+
def restart
|
27
|
+
return unless should_release?
|
28
|
+
|
29
|
+
inside release_to do
|
30
|
+
# Restart services
|
31
|
+
run_with_code(RemoteConfig.restart_command)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def run_after_release_hooks
|
36
|
+
inside release_to do
|
37
|
+
run_hooks_for(:release, :after)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def trim
|
42
|
+
FileUtils.touch(dir) # Ensure this release isn't trimmed
|
43
|
+
invoke(:trim, [4], {})
|
44
|
+
end
|
45
|
+
|
46
|
+
protected
|
47
|
+
def dir
|
48
|
+
RemoteConfig.release_dir(sha)
|
49
|
+
end
|
50
|
+
|
51
|
+
def release_to
|
52
|
+
RemoteConfig.release_to
|
53
|
+
end
|
54
|
+
|
55
|
+
def current_symlink_destination
|
56
|
+
File.readlink(RemoteConfig.release_to) rescue nil
|
57
|
+
end
|
58
|
+
|
59
|
+
def should_release?
|
60
|
+
@should_release = options[:force] || dir != current_symlink_destination unless defined?(@should_release)
|
61
|
+
@should_release
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Egads
|
2
|
+
class Stage < Group
|
3
|
+
include Thor::Actions
|
4
|
+
|
5
|
+
|
6
|
+
desc "[remote] Readies SHA for release. If needed, generates URL for SHA and extracts"
|
7
|
+
class_option :force, type: :boolean, default: false, banner: "Overwrite existing files"
|
8
|
+
argument :sha, type: :string, required: true, desc: 'git SHA to stage'
|
9
|
+
|
10
|
+
def setup_environment
|
11
|
+
RemoteConfig.setup_environment
|
12
|
+
end
|
13
|
+
|
14
|
+
def extract
|
15
|
+
invoke(Egads::Extract, [sha], options)
|
16
|
+
end
|
17
|
+
|
18
|
+
def run_before_hooks
|
19
|
+
return unless should_stage?
|
20
|
+
inside(dir){ run_hooks_for(:stage, :before) }
|
21
|
+
end
|
22
|
+
|
23
|
+
def bundle
|
24
|
+
return unless should_stage?
|
25
|
+
|
26
|
+
inside(dir) do
|
27
|
+
run_with_code("bundle install #{RemoteConfig.bundler_options}") if File.readable?("Gemfile")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def symlink_system_paths
|
32
|
+
return unless should_stage? && shared_path
|
33
|
+
symlink_directory File.join(shared_path, 'system'), File.join(dir, 'public', 'system')
|
34
|
+
symlink_directory File.join(shared_path, 'log'), File.join(dir, 'log')
|
35
|
+
end
|
36
|
+
|
37
|
+
def symlink_config_files
|
38
|
+
return unless should_stage? && shared_path
|
39
|
+
|
40
|
+
shared_config = File.join(shared_path, 'config')
|
41
|
+
if File.directory?(shared_config)
|
42
|
+
Dir.glob("#{shared_config}/*").each do |source|
|
43
|
+
basename = File.basename(source)
|
44
|
+
destination = File.join(dir, 'config', basename)
|
45
|
+
symlink(source, destination)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def run_after_stage_hooks
|
51
|
+
return unless should_stage?
|
52
|
+
inside(dir) { run_hooks_for(:stage, :after) }
|
53
|
+
end
|
54
|
+
|
55
|
+
def mark_as_staged
|
56
|
+
FileUtils.touch(stage_flag_path)
|
57
|
+
end
|
58
|
+
|
59
|
+
protected
|
60
|
+
def dir
|
61
|
+
RemoteConfig.release_dir(sha)
|
62
|
+
end
|
63
|
+
|
64
|
+
def stage_flag_path
|
65
|
+
File.join(dir, '.egads-stage-success')
|
66
|
+
end
|
67
|
+
|
68
|
+
def should_stage?
|
69
|
+
options[:force] || !File.exists?(stage_flag_path)
|
70
|
+
end
|
71
|
+
|
72
|
+
def shared_path
|
73
|
+
ENV['SHARED_PATH']
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Egads
|
2
|
+
class Trim < Group
|
3
|
+
include Thor::Actions
|
4
|
+
|
5
|
+
|
6
|
+
desc "[remote, plumbing] Deletes old releases, keeping the N most recent (by mtime)"
|
7
|
+
def trim(n=4)
|
8
|
+
inside RemoteConfig.extract_to do
|
9
|
+
dirs = Dir.glob('*').sort_by{|path| File.mtime(path) }.reverse[n..-1].to_a
|
10
|
+
dirs.each do |dir|
|
11
|
+
say_status :trim, "Deleting #{dir}"
|
12
|
+
FileUtils.rm_rf(dir)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Egads
|
2
|
+
class Upload < Group
|
3
|
+
include Thor::Actions
|
4
|
+
|
5
|
+
desc "[local, plumbing] Uploads a tarball for SHA to S3"
|
6
|
+
argument :sha, type: :string, required: true, desc: 'git SHA to upload'
|
7
|
+
|
8
|
+
attr_reader :sha
|
9
|
+
def upload
|
10
|
+
@sha = sha
|
11
|
+
size = File.size(path)
|
12
|
+
|
13
|
+
say_status :upload, "Uploading tarball (%.1f MB)" % (size.to_f / 2**20), :yellow
|
14
|
+
duration = Benchmark.realtime do
|
15
|
+
tarball.upload(path)
|
16
|
+
end
|
17
|
+
say_status :done, "Uploaded in %.1f seconds (%.1f KB/s)" % [duration, (size.to_f / 2**10) / duration]
|
18
|
+
|
19
|
+
File.delete(path)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
def tarball
|
24
|
+
@tarball ||= S3Tarball.new(sha)
|
25
|
+
end
|
26
|
+
|
27
|
+
def path
|
28
|
+
tarball.local_gzipped_path
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
data/lib/egads/config.rb
CHANGED
@@ -56,10 +56,6 @@ module Egads
|
|
56
56
|
path
|
57
57
|
end
|
58
58
|
|
59
|
-
def self.release_dir(sha)
|
60
|
-
File.join(config['extract_to'], sha)
|
61
|
-
end
|
62
|
-
|
63
59
|
def self.release_to
|
64
60
|
config['release_to']
|
65
61
|
end
|
@@ -68,6 +64,10 @@ module Egads
|
|
68
64
|
config['extract_to']
|
69
65
|
end
|
70
66
|
|
67
|
+
def self.release_dir(sha)
|
68
|
+
File.join(config['extract_to'], sha)
|
69
|
+
end
|
70
|
+
|
71
71
|
# Set environment variables from the config
|
72
72
|
def self.setup_environment
|
73
73
|
config['env'].each{|k,v| ENV[k] = v.to_s } if config['env']
|
@@ -76,5 +76,9 @@ module Egads
|
|
76
76
|
def self.restart_command
|
77
77
|
config['restart_command']
|
78
78
|
end
|
79
|
+
|
80
|
+
def self.bundler_options
|
81
|
+
config['bundler']['options'] if config['bundler']
|
82
|
+
end
|
79
83
|
end
|
80
84
|
end
|
data/lib/egads/group.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
module Egads
|
2
|
+
class CommandError < Thor::Error; end
|
3
|
+
|
4
|
+
class Group < Thor::Group
|
5
|
+
|
6
|
+
protected
|
7
|
+
def run_with_code(command, config={})
|
8
|
+
result = nil
|
9
|
+
duration = Benchmark.realtime do
|
10
|
+
result = run(command, config.merge(capture: true))
|
11
|
+
end
|
12
|
+
say_status :done, "Finished in %.1f seconds" % duration
|
13
|
+
|
14
|
+
if $? != 0
|
15
|
+
raise CommandError.new("`#{command}` failed with exit status #{$?.exitstatus.inspect}")
|
16
|
+
end
|
17
|
+
result
|
18
|
+
end
|
19
|
+
|
20
|
+
# Run command hooks from config file
|
21
|
+
# E.g. run_hooks_for(:build, :after)
|
22
|
+
def run_hooks_for(cmd, hook)
|
23
|
+
say_status :hooks, "Running #{cmd} #{hook} hooks"
|
24
|
+
Config.hooks_for(cmd, hook).each do |command|
|
25
|
+
run_with_code command
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Symlinks a directory
|
30
|
+
# NB that `ln -f` doesn't work with directories.
|
31
|
+
# This is not atomic.
|
32
|
+
def symlink_directory(src, dest)
|
33
|
+
raise ArgumentError.new("#{src} is not a directory") unless File.directory?(src)
|
34
|
+
say_status :symlink, "from #{src} to #{dest}"
|
35
|
+
FileUtils.rm_rf(dest)
|
36
|
+
FileUtils.ln_s(src, dest)
|
37
|
+
end
|
38
|
+
|
39
|
+
def symlink(src, dest)
|
40
|
+
raise ArgumentError.new("#{src} is not a file") unless File.file?(src)
|
41
|
+
say_status :symlink, "from #{src} to #{dest}"
|
42
|
+
FileUtils.ln_sf(src, dest)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/egads/version.rb
CHANGED
@@ -0,0 +1,29 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
|
3
|
+
describe "Egads::Build" do
|
4
|
+
setup_configs!
|
5
|
+
subject { Egads::Build }
|
6
|
+
|
7
|
+
it 'should run the correct tasks' do
|
8
|
+
subject.commands.keys.must_equal %w(check_build make_git_archive append_revision_file run_after_build_hooks append_extra_paths gzip_archive upload)
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'takes one argument' do
|
12
|
+
subject.arguments.size.must_equal 1
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'has a rev argument' do
|
16
|
+
rev = subject.arguments.detect{|arg| arg.name == 'rev'}
|
17
|
+
rev.default.must_equal 'HEAD'
|
18
|
+
rev.required.must_equal false
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "Egags::Build instance" do
|
24
|
+
subject { Egads::Build.new }
|
25
|
+
|
26
|
+
it "has rev HEAD" do
|
27
|
+
subject.rev.must_equal 'HEAD'
|
28
|
+
end
|
29
|
+
end
|
data/spec/egads_config_spec.rb
CHANGED
@@ -8,16 +8,61 @@ describe Egads::Config do
|
|
8
8
|
end
|
9
9
|
|
10
10
|
describe "with an config file" do
|
11
|
-
|
12
|
-
|
11
|
+
setup_configs!
|
12
|
+
|
13
|
+
let(:yml) { YAML.load_file("example/egads.yml") }
|
14
|
+
|
15
|
+
describe '#config' do
|
16
|
+
it 'is a hash' do
|
17
|
+
subject.config.must_equal yml
|
18
|
+
end
|
19
|
+
end
|
13
20
|
|
14
21
|
it "has an S3 bucket" do
|
15
|
-
subject.s3_bucket.key.must_equal '
|
22
|
+
subject.s3_bucket.key.must_equal yml['s3']['bucket']
|
16
23
|
end
|
17
24
|
|
18
25
|
it "has an S3 prefix" do
|
19
|
-
subject.s3_prefix.must_equal '
|
26
|
+
subject.s3_prefix.must_equal yml['s3']['prefix']
|
20
27
|
end
|
21
28
|
end
|
22
29
|
|
23
30
|
end
|
31
|
+
|
32
|
+
describe Egads::RemoteConfig do
|
33
|
+
subject { Egads::RemoteConfig }
|
34
|
+
|
35
|
+
it "raises ArgumentError for missing config" do
|
36
|
+
-> { subject.config_path }.must_raise(ArgumentError)
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "with an config file" do
|
40
|
+
setup_configs!
|
41
|
+
let(:yml) { YAML.load_file("example/egads_remote.yml") }
|
42
|
+
|
43
|
+
describe '#config' do
|
44
|
+
it('is a hash') { subject.config.must_equal yml }
|
45
|
+
end
|
46
|
+
|
47
|
+
it('#release_to') { subject.release_to.must_equal yml['release_to'] }
|
48
|
+
it('#extract_to') { subject.extract_to.must_equal yml['extract_to'] }
|
49
|
+
it('#release_dir') { subject.release_dir('abc').must_equal File.join(yml['extract_to'], 'abc') }
|
50
|
+
it('#restart_command') { subject.restart_command.must_equal yml['restart_command'] }
|
51
|
+
it('#bundler_options') { subject.bundler_options.must_equal yml['bundler']['options'] }
|
52
|
+
|
53
|
+
describe '#setup_environment' do
|
54
|
+
before { subject.setup_environment }
|
55
|
+
after do
|
56
|
+
# Delete ENV from yaml data
|
57
|
+
yml['env'].keys.each{|key| ENV.delete(key) }
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'should set ENV values' do
|
61
|
+
yml['env'].each do |key, value|
|
62
|
+
ENV[key].must_equal value
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
|
3
|
+
describe "Egads::Release" do
|
4
|
+
setup_configs!
|
5
|
+
subject { Egads::Release }
|
6
|
+
|
7
|
+
it 'should run the correct tasks' do
|
8
|
+
subject.commands.keys.must_equal %w(setup_environment stage run_before_release_hooks symlink_release restart run_after_release_hooks trim)
|
9
|
+
end
|
10
|
+
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
require_relative 'spec_helper'
|
2
2
|
|
3
3
|
describe Egads::S3Tarball do
|
4
|
+
setup_configs!
|
4
5
|
|
5
6
|
before { ENV['EGADS_CONFIG'] = "example/egads.yml" }
|
6
7
|
after { ENV.delete('EGAGS_CONFIG') }
|
@@ -18,11 +19,10 @@ describe Egads::S3Tarball do
|
|
18
19
|
|
19
20
|
describe 'when uploaded' do
|
20
21
|
before do
|
21
|
-
Egads::Config.s3_bucket.save # Ensure bucket exists
|
22
22
|
subject.upload(ENV['EGADS_CONFIG'])
|
23
23
|
end
|
24
24
|
|
25
|
-
it('should exist') { subject.exists
|
25
|
+
it('should exist') { (!! subject.exists?).must_equal true }
|
26
26
|
end
|
27
27
|
|
28
28
|
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
|
3
|
+
describe "Egads::Stage" do
|
4
|
+
setup_configs!
|
5
|
+
subject { Egads::Stage }
|
6
|
+
|
7
|
+
it 'should run the correct tasks' do
|
8
|
+
subject.commands.keys.must_equal %w(setup_environment extract run_before_hooks bundle symlink_system_paths symlink_config_files run_after_stage_hooks mark_as_staged)
|
9
|
+
end
|
10
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -7,11 +7,29 @@ require "egads"
|
|
7
7
|
|
8
8
|
Fog.mock!
|
9
9
|
|
10
|
+
SHA = 'deadbeef' * 5 # Test git sha
|
11
|
+
|
10
12
|
begin
|
11
13
|
require 'debugger'
|
12
14
|
rescue LoadError
|
13
15
|
puts "Skipping debugger"
|
14
16
|
end
|
15
17
|
|
18
|
+
# Extensions
|
16
19
|
class Minitest::Spec
|
20
|
+
|
21
|
+
def self.setup_configs!
|
22
|
+
before do
|
23
|
+
ENV['EGADS_CONFIG'] = "example/egads.yml"
|
24
|
+
ENV['EGADS_REMOTE_CONFIG'] = "example/egads_remote.yml"
|
25
|
+
Egads::Config.s3_bucket.save # Ensure bucket exists
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
after do
|
30
|
+
ENV.delete('EGADS_CONFIG')
|
31
|
+
ENV.delete('EGADS_REMOTE_CONFIG')
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
17
35
|
end
|
metadata
CHANGED
@@ -1,73 +1,75 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: egads
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Aaron Suggs
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-06-
|
11
|
+
date: 2013-06-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: fog
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- -
|
17
|
+
- - '>='
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '0'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- -
|
24
|
+
- - '>='
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: thor
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- -
|
31
|
+
- - '>='
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: '0'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- -
|
38
|
+
- - '>='
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rake
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- -
|
45
|
+
- - '>='
|
46
46
|
- !ruby/object:Gem::Version
|
47
47
|
version: '0'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- -
|
52
|
+
- - '>='
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: minitest
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- -
|
59
|
+
- - '>='
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: '0'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- -
|
66
|
+
- - '>='
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
|
-
description:
|
70
|
-
|
69
|
+
description: |2-
|
70
|
+
|
71
|
+
A collection of scripts for making a deployable tarball of a git commit,
|
72
|
+
uploading it to Amazon S3, and downloading it to your servers.
|
71
73
|
email:
|
72
74
|
- aaron@ktheory.com
|
73
75
|
executables:
|
@@ -78,7 +80,7 @@ extra_rdoc_files:
|
|
78
80
|
files:
|
79
81
|
- .gitignore
|
80
82
|
- Gemfile
|
81
|
-
-
|
83
|
+
- Guardfile
|
82
84
|
- README.md
|
83
85
|
- Rakefile
|
84
86
|
- bin/egads
|
@@ -87,13 +89,25 @@ files:
|
|
87
89
|
- example/egads_remote.yml
|
88
90
|
- lib/egads.rb
|
89
91
|
- lib/egads/capistrano.rb
|
90
|
-
- lib/egads/
|
92
|
+
- lib/egads/command.rb
|
93
|
+
- lib/egads/command/build.rb
|
94
|
+
- lib/egads/command/extract.rb
|
95
|
+
- lib/egads/command/release.rb
|
96
|
+
- lib/egads/command/stage.rb
|
97
|
+
- lib/egads/command/trim.rb
|
98
|
+
- lib/egads/command/upload.rb
|
91
99
|
- lib/egads/config.rb
|
92
|
-
- lib/egads/
|
100
|
+
- lib/egads/group.rb
|
93
101
|
- lib/egads/s3_tarball.rb
|
94
102
|
- lib/egads/version.rb
|
103
|
+
- spec/egads_build_spec.rb
|
95
104
|
- spec/egads_config_spec.rb
|
105
|
+
- spec/egads_extract_spec.rb
|
106
|
+
- spec/egads_release_spec.rb
|
96
107
|
- spec/egads_s3_tarball_spec.rb
|
108
|
+
- spec/egads_stage_spec.rb
|
109
|
+
- spec/egads_trim_spec.rb
|
110
|
+
- spec/egads_upload_spec.rb
|
97
111
|
- spec/spec_helper.rb
|
98
112
|
homepage: https://github.com/kickstarter/egads
|
99
113
|
licenses: []
|
@@ -105,22 +119,27 @@ require_paths:
|
|
105
119
|
- lib
|
106
120
|
required_ruby_version: !ruby/object:Gem::Requirement
|
107
121
|
requirements:
|
108
|
-
- -
|
122
|
+
- - '>='
|
109
123
|
- !ruby/object:Gem::Version
|
110
124
|
version: '0'
|
111
125
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
112
126
|
requirements:
|
113
|
-
- -
|
127
|
+
- - '>='
|
114
128
|
- !ruby/object:Gem::Version
|
115
129
|
version: '0'
|
116
130
|
requirements: []
|
117
131
|
rubyforge_project:
|
118
|
-
rubygems_version: 2.0.
|
132
|
+
rubygems_version: 2.0.2
|
119
133
|
signing_key:
|
120
134
|
specification_version: 4
|
121
135
|
summary: Extensible Git Archive Deploy Strategy
|
122
136
|
test_files:
|
137
|
+
- spec/egads_build_spec.rb
|
123
138
|
- spec/egads_config_spec.rb
|
139
|
+
- spec/egads_extract_spec.rb
|
140
|
+
- spec/egads_release_spec.rb
|
124
141
|
- spec/egads_s3_tarball_spec.rb
|
142
|
+
- spec/egads_stage_spec.rb
|
143
|
+
- spec/egads_trim_spec.rb
|
144
|
+
- spec/egads_upload_spec.rb
|
125
145
|
- spec/spec_helper.rb
|
126
|
-
has_rdoc:
|
data/Gemfile.lock
DELETED
@@ -1,49 +0,0 @@
|
|
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/lib/egads/cli.rb
DELETED
@@ -1,221 +0,0 @@
|
|
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, :after)
|
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
|
-
return
|
63
|
-
end
|
64
|
-
|
65
|
-
path = tarball.local_gzipped_path
|
66
|
-
size = File.size(path)
|
67
|
-
|
68
|
-
say "Uploading tarball (%.1f MB)" % (size.to_f / 2**20)
|
69
|
-
duration = Benchmark.realtime do
|
70
|
-
tarball.upload(path)
|
71
|
-
end
|
72
|
-
say "Uploaded in %.1f seconds (%.1f KB/s)" % [duration, (size.to_f / 2**10) / duration]
|
73
|
-
|
74
|
-
File.delete(path)
|
75
|
-
end
|
76
|
-
|
77
|
-
##
|
78
|
-
# Remote commands
|
79
|
-
|
80
|
-
desc "extract SHA", "[remote, plumbing] Downloads tarball for SHA from S3 and extracts it to the filesystem"
|
81
|
-
method_option :force, type: :boolean, default: false, banner: "Overwrite existing files"
|
82
|
-
def extract(sha)
|
83
|
-
RemoteConfig.setup_environment
|
84
|
-
dir = RemoteConfig.release_dir(sha)
|
85
|
-
path = File.join(dir, "#{sha}.tar.gz")
|
86
|
-
tarball = S3Tarball.new(sha, true)
|
87
|
-
|
88
|
-
inside dir do
|
89
|
-
if options[:force] || File.zero?(path) || !File.exists?(path)
|
90
|
-
say "Downloading tarball"
|
91
|
-
duration = Benchmark.realtime do
|
92
|
-
File.open(path, 'w') {|f| f << tarball.contents }
|
93
|
-
end
|
94
|
-
size = File.size(path)
|
95
|
-
say "Downloaded in %.1f seconds (%.1f KB/s)" % [duration, (size.to_f / 2**10) / duration]
|
96
|
-
else
|
97
|
-
say "Tarball already downloaded. Use --force to overwrite"
|
98
|
-
end
|
99
|
-
|
100
|
-
# Check revision file to see if tarball is already extracted
|
101
|
-
extract_flag_path = File.join(dir, '.egads-extract-success')
|
102
|
-
if options[:force] || !File.exists?(extract_flag_path)
|
103
|
-
say "Extracting tarball"
|
104
|
-
run_or_die "tar -zxf #{path}"
|
105
|
-
else
|
106
|
-
say "Tarball already extracted. Use --force to overwrite"
|
107
|
-
end
|
108
|
-
FileUtils.touch(extract_flag_path)
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
desc "stage SHA", "[remote] Readies SHA for release. If needed, generates URL for SHA and extracts"
|
113
|
-
method_option :force, type: :boolean, default: false, banner: "Overwrite existing files"
|
114
|
-
def stage(sha)
|
115
|
-
RemoteConfig.setup_environment
|
116
|
-
invoke(:extract, [sha], options)
|
117
|
-
dir = RemoteConfig.release_dir(sha)
|
118
|
-
stage_flag_path = File.join(dir, '.egads-stage-success')
|
119
|
-
if options[:force] || !File.exists?(stage_flag_path)
|
120
|
-
inside dir do
|
121
|
-
run_hooks_for(:stage, :before)
|
122
|
-
|
123
|
-
# Bundler
|
124
|
-
if File.readable?("Gemfile")
|
125
|
-
bundler_args = %w(--deployment --quiet)
|
126
|
-
# Hack to force bundle options overridden by --deployment
|
127
|
-
bundler_args << "--without #{ENV['BUNDLE_WITHOUT']}" if ENV['BUNDLE_WITHOUT']
|
128
|
-
bundler_args << "--path #{ENV['BUNDLE_PATH']}" if ENV['BUNDLE_PATH']
|
129
|
-
|
130
|
-
run_or_die("bundle install #{bundler_args * ' '}")
|
131
|
-
end
|
132
|
-
|
133
|
-
if shared_path = ENV['SHARED_PATH']
|
134
|
-
symlink_directory File.join(shared_path, 'system'), File.join(dir, 'public', 'system')
|
135
|
-
symlink_directory File.join(shared_path, 'log'), File.join(dir, 'log')
|
136
|
-
|
137
|
-
# Symlink config files
|
138
|
-
shared_config = File.join(shared_path, 'config')
|
139
|
-
if File.directory?(shared_config)
|
140
|
-
Dir.glob("#{shared_config}/*").each do |source|
|
141
|
-
basename = File.basename(source)
|
142
|
-
destination = File.join(dir, 'config', basename)
|
143
|
-
symlink(source, destination)
|
144
|
-
end
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
|
-
run_hooks_for(:stage, :after)
|
149
|
-
end
|
150
|
-
else
|
151
|
-
say "Already staged. Use --force to overwrite"
|
152
|
-
end
|
153
|
-
FileUtils.touch(stage_flag_path)
|
154
|
-
end
|
155
|
-
|
156
|
-
desc "release SHA", "[remote] Symlinks SHA to current and restarts services. If needed, stages SHA"
|
157
|
-
method_option :force, type: :boolean, default: false, banner: "Overwrite existing files while staging"
|
158
|
-
def release(sha)
|
159
|
-
RemoteConfig.setup_environment
|
160
|
-
invoke(:stage, [sha], options)
|
161
|
-
dir = RemoteConfig.release_dir(sha)
|
162
|
-
inside dir do
|
163
|
-
run_hooks_for(:release, :before)
|
164
|
-
end
|
165
|
-
|
166
|
-
# destination of the current symlink
|
167
|
-
current_release = File.readlink(RemoteConfig.release_to) rescue nil
|
168
|
-
unless dir == current_release
|
169
|
-
# Symlink this release to the release_to
|
170
|
-
symlink_directory(dir, RemoteConfig.release_to) unless dir == current_release
|
171
|
-
end
|
172
|
-
|
173
|
-
inside RemoteConfig.release_to do
|
174
|
-
# Restart services
|
175
|
-
run_or_die(RemoteConfig.restart_command)
|
176
|
-
run_hooks_for(:release, :after)
|
177
|
-
end
|
178
|
-
|
179
|
-
FileUtils.touch(dir) # Ensure this release isn't trimmed
|
180
|
-
invoke(:trim, [4])
|
181
|
-
end
|
182
|
-
|
183
|
-
desc "trim N", "[remote, plumbing] Deletes old releases, keeping the N most recent (by mtime)"
|
184
|
-
method_option :force, type: :boolean, default: false, banner: "No op, compatible with release"
|
185
|
-
def trim(n=4)
|
186
|
-
dirs = Dir.glob('*').sort_by{|path| File.mtime(path) }.reverse[n..-1].to_a
|
187
|
-
dirs.each do |dir|
|
188
|
-
say_status :trim, "Deleting #{dir}"
|
189
|
-
FileUtils.rm_rf(dir)
|
190
|
-
end
|
191
|
-
end
|
192
|
-
|
193
|
-
private
|
194
|
-
# Run command hooks from config file
|
195
|
-
# E.g. run_hooks_for(:build, :after)
|
196
|
-
def run_hooks_for(cmd, hook)
|
197
|
-
say_status :hooks, "Running #{cmd} #{hook} hooks"
|
198
|
-
Config.hooks_for(cmd, hook).each do |command|
|
199
|
-
say "Running `#{command}`"
|
200
|
-
run_or_die command
|
201
|
-
end
|
202
|
-
end
|
203
|
-
|
204
|
-
# Symlinks a directory
|
205
|
-
# NB that `ln -f` doesn't work with directories.
|
206
|
-
# This is not atomic.
|
207
|
-
def symlink_directory(src, dest)
|
208
|
-
raise ArgumentError.new("#{src} is not a directory") unless File.directory?(src)
|
209
|
-
say_status :symlink, "from #{src} to #{dest}"
|
210
|
-
FileUtils.rm_rf(dest)
|
211
|
-
FileUtils.ln_s(src, dest)
|
212
|
-
end
|
213
|
-
|
214
|
-
def symlink(src, dest)
|
215
|
-
raise ArgumentError.new("#{src} is not a file") unless File.file?(src)
|
216
|
-
say_status :symlink, "from #{src} to #{dest}"
|
217
|
-
FileUtils.ln_sf(src, dest)
|
218
|
-
end
|
219
|
-
|
220
|
-
end
|
221
|
-
end
|
@@ -1,25 +0,0 @@
|
|
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
|