egads 0.0.8 → 0.2.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 +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
|