borg-rb 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/.travis.yml +8 -2
- data/CHANGELOG.md +11 -0
- data/README.md +19 -11
- data/borg.gemspec +3 -1
- data/lib/borg/version.rb +1 -1
- data/skeleton/Capfile +2 -1
- data/spec/acceptance/borgify_spec.rb +49 -0
- data/spec/acceptance_spec_helper.rb +16 -0
- data/spec/spec_helper.rb +0 -1
- data/spec/support/isolated_environment.rb +58 -0
- data/spec/support/matchers/succeed.rb +14 -0
- data/spec/support/platform.rb +81 -0
- data/spec/support/shared_contexts/acceptance.rb +28 -0
- data/spec/support/subprocess.rb +259 -0
- data/spec/support/temp_dir.rb +33 -0
- metadata +37 -6
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
MWM1NWU1YWYzZGJmYjE1Y2NiNWU3NjllOTU3MDgyMTdiOTA0NzFlMQ==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
ZWVkNGIxM2VlZjE1NGU2YTI1MDNmYjM0NWJlMGExY2U3YTlmZTMxMA==
|
7
7
|
!binary "U0hBNTEy":
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
ZTFmNjM3Mjc0MmU2ZDFmOTNhNDY2NzQ3NWY3ZTQ0ODNiMDRjMzdjYjFiYWUx
|
10
|
+
MzBmM2I2MDkyMzI0YTQ0OTZlMGYwOWQ5ODQ3ZTdlM2Y4NjA5YzRjZmE4M2Rk
|
11
|
+
MDYyMmEwZjMxMDQ2YTgzZTJmZTIyNTRjZjdlZTQ1MTlkNjk3OTY=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
M2E4OTY5ZmFhMzFlMTYxMjFiZWY2ZmRhNDJlMWFmNmY4NDVmOGRjYjM2ZWFh
|
14
|
+
Mzg2NDBmMDAyZGFlODNmOWU2YmIxOTMyYjMwNjkxYzM2ZTc0MmYwZTdjMzU4
|
15
|
+
ZjBjZGFhMDA4ZjQwMzVmMzA2ODJhMDE2ODc0ODQxNzNkMTRhMWM=
|
data/.travis.yml
CHANGED
@@ -1,6 +1,12 @@
|
|
1
1
|
language: ruby
|
2
2
|
rvm:
|
3
3
|
- 1.9.3
|
4
|
-
-
|
5
|
-
-
|
4
|
+
- 2.0.0
|
5
|
+
- ruby-head
|
6
|
+
- jruby-19mode # JRuby in 1.9 mode
|
7
|
+
- rbx-19mode # Rubinius in 1.9 mode
|
8
|
+
matrix:
|
9
|
+
allow_failures:
|
10
|
+
- rvm: ruby-head
|
11
|
+
|
6
12
|
script: bundle exec rspec spec
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
## Current (Unreleased)
|
2
|
+
|
3
|
+
## 0.0.2 / 3-22-2013
|
4
|
+
|
5
|
+
* Fix load problem in Capfile in generated skeleton
|
6
|
+
|
7
|
+
|
8
|
+
## 0.0.1 / 3-21-2013
|
9
|
+
|
10
|
+
* Created initial project skeleton: application config, multi-stage app config
|
11
|
+
* Created initial plugin skeleton for adding recipes
|
data/README.md
CHANGED
@@ -6,8 +6,8 @@ deployment.
|
|
6
6
|
[![Dependency Status](https://gemnasium.com/B0RG/borg.png)](https://gemnasium.com/B0RG/borg)
|
7
7
|
[![Code Climate](https://codeclimate.com/github/B0RG/borg.png)](https://codeclimate.com/github/B0RG/borg)
|
8
8
|
|
9
|
-
|
10
|
-
|
9
|
+
## Setup
|
10
|
+
### Deployer Package
|
11
11
|
|
12
12
|
`borgify` Sets up the following structure
|
13
13
|
|
@@ -18,11 +18,11 @@ my-deployer-package
|
|
18
18
|
| | ├── application1.rb
|
19
19
|
│ | └── application2.rb
|
20
20
|
| ├── initilizers
|
21
|
-
| | ├──
|
22
|
-
│ | └──
|
21
|
+
| | ├── initializer1.rb
|
22
|
+
│ | └── initializer2.rb
|
23
23
|
| └── recipies
|
24
|
-
| ├──
|
25
|
-
│ └──
|
24
|
+
| ├── recipe1.rb
|
25
|
+
│ └── recipe2.rb
|
26
26
|
├── Capfile
|
27
27
|
├── Gemfile
|
28
28
|
└── Gemfile.lock
|
@@ -42,18 +42,18 @@ load 'borg'
|
|
42
42
|
|
43
43
|
```
|
44
44
|
|
45
|
-
|
45
|
+
### Services Package
|
46
46
|
`borgify plugin` Sets up the following structure
|
47
47
|
|
48
48
|
```
|
49
49
|
my-service-package
|
50
50
|
├── cap
|
51
51
|
| ├── initilizers
|
52
|
-
| | ├──
|
53
|
-
│ | └──
|
52
|
+
| | ├── initializer1.rb
|
53
|
+
│ | └── initializer2.rb
|
54
54
|
| └── recipies
|
55
|
-
| ├──
|
56
|
-
│ └──
|
55
|
+
| ├── recipe1.rb
|
56
|
+
│ └── recipe2.rb
|
57
57
|
├── my-service-package.gemspec
|
58
58
|
├── Gemfile
|
59
59
|
└── Gemfile.lock
|
@@ -83,3 +83,11 @@ then all the blocks will be run for that application/stage.
|
|
83
83
|
The CLI enforces that all configs be specified at the start. Consider the command `borg app1:stage1 app2 deploy`
|
84
84
|
will result in config app1:stage1, all configs for app2 (1 config for each stage, if there is no stage it assumes the app is the only stage)
|
85
85
|
to be load and the deploy task be run against all of them.
|
86
|
+
|
87
|
+
## Contributing
|
88
|
+
|
89
|
+
1. Fork it
|
90
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
91
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
92
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
93
|
+
5. Create new Pull Request
|
data/borg.gemspec
CHANGED
@@ -18,10 +18,12 @@ Gem::Specification.new do |s|
|
|
18
18
|
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
19
19
|
s.require_paths = ["lib"]
|
20
20
|
|
21
|
-
s.add_dependency "capistrano"
|
21
|
+
s.add_dependency "capistrano", "~> 2.14.2"
|
22
22
|
s.add_dependency "capistrano_colors"
|
23
23
|
s.add_dependency "colored"
|
24
24
|
s.add_dependency "term-ansicolor"
|
25
25
|
|
26
26
|
s.add_development_dependency "rspec"
|
27
|
+
s.add_development_dependency "childprocess"
|
28
|
+
|
27
29
|
end
|
data/lib/borg/version.rb
CHANGED
data/skeleton/Capfile
CHANGED
@@ -4,6 +4,7 @@
|
|
4
4
|
# runs on :exit events when ctrl-c is hit
|
5
5
|
# set :borg_sigint_triggers_exit, true
|
6
6
|
|
7
|
-
load 'borg.rb
|
7
|
+
load "#{Gem::Specification.find_by_name('borg-rb').gem_dir}/lib/borg.rb"
|
8
|
+
|
8
9
|
# load any other borg gems here.
|
9
10
|
# NOTE: require ends up causing the initializers to be called every time a config is loaded.
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'acceptance_spec_helper'
|
2
|
+
|
3
|
+
describe "borgify" do
|
4
|
+
include_context "acceptance"
|
5
|
+
|
6
|
+
before do
|
7
|
+
assert_execute("borgify")
|
8
|
+
@workdir = environment.workdir_path
|
9
|
+
end
|
10
|
+
|
11
|
+
it "creates the right files and directories" do
|
12
|
+
# Gemfile
|
13
|
+
gemfile = @workdir.join("Gemfile")
|
14
|
+
expect(gemfile.exist?).to be_true
|
15
|
+
expect(gemfile.read).to match(/^source "https:\/\/rubygems.org"$/)
|
16
|
+
expect(gemfile.read).to match(/borg-rb/)
|
17
|
+
|
18
|
+
# Capfile
|
19
|
+
capfile = @workdir.join("Capfile")
|
20
|
+
expect(capfile.exist?).to be_true
|
21
|
+
|
22
|
+
# lib directory
|
23
|
+
capfile = @workdir.join("lib")
|
24
|
+
expect(capfile.exist?).to be_true
|
25
|
+
|
26
|
+
# cap directory with the subdirectories: applications, initializers, recipes
|
27
|
+
cap_dir = @workdir.join("cap")
|
28
|
+
expect(cap_dir.exist?).to be_true
|
29
|
+
expect(cap_dir.join("applications")).to be_true
|
30
|
+
expect(cap_dir.join("initializers")).to be_true
|
31
|
+
expect(cap_dir.join("recipes")).to be_true
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "borgify plugin" do
|
36
|
+
include_context "acceptance"
|
37
|
+
|
38
|
+
before do
|
39
|
+
assert_execute("borgify", "plugin")
|
40
|
+
@workdir = environment.workdir_path
|
41
|
+
end
|
42
|
+
|
43
|
+
it "creates the right files and directories" do
|
44
|
+
# the gemspec
|
45
|
+
directory_name = File.basename(@workdir = environment.workdir_path)
|
46
|
+
gemspec = @workdir = environment.workdir_path.join("#{directory_name}.gemspec")
|
47
|
+
expect(gemspec.exist?).to be_true
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
Dir["./spec/support/**/*.rb"].sort.each { |f| require f }
|
4
|
+
|
5
|
+
RSpec.configure do |config|
|
6
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
7
|
+
config.run_all_when_everything_filtered = true
|
8
|
+
config.filter_run :focus
|
9
|
+
|
10
|
+
# Run specs in random order to surface order dependencies. If you find an
|
11
|
+
# order dependency and want to debug it, you can fix the order by providing
|
12
|
+
# the seed, which is printed after each run.
|
13
|
+
# --seed 1234
|
14
|
+
config.order = 'random'
|
15
|
+
|
16
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -0,0 +1,58 @@
|
|
1
|
+
require "fileutils"
|
2
|
+
require "pathname"
|
3
|
+
require "childprocess"
|
4
|
+
|
5
|
+
module Support
|
6
|
+
# This class creates a temporary directory to act as the working directory
|
7
|
+
#
|
8
|
+
# Modified from: https://github.com/mitchellh/vagrant/blob/master/spec/support/isolated_environment.rb
|
9
|
+
class IsolatedEnvironment
|
10
|
+
ROOT_DIR = Pathname.new(File.expand_path("../../", __FILE__))
|
11
|
+
|
12
|
+
attr_reader :workdir, :workdir_path
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
# Create a temporary directory for our work
|
16
|
+
@workdir = TempDir.new("borg")
|
17
|
+
@workdir_path = Pathname.new(TempDir.new("borg").path)
|
18
|
+
# puts "Initialize isolated environment: #{@workdir_path.to_s}"
|
19
|
+
end
|
20
|
+
|
21
|
+
# Copies a skeleton into this isolated environment. This is useful
|
22
|
+
# for testing environments that require a complex setup.
|
23
|
+
#
|
24
|
+
# @param [String] name Name of the skeleton in the root directory.
|
25
|
+
def skeleton!(name)
|
26
|
+
# Copy all the files into the home directory
|
27
|
+
source = Dir.glob(ROOT_DIR.join(name).join("*").to_s)
|
28
|
+
FileUtils.cp_r(source, @workdir.to_s)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Executes a command in the context of this isolated environment.
|
32
|
+
# Any command executed will therefore see our temporary directory
|
33
|
+
# as the home directory.
|
34
|
+
def execute(command, *argN)
|
35
|
+
# Determine the options
|
36
|
+
options = argN.last.is_a?(Hash) ? argN.pop : {}
|
37
|
+
options = {
|
38
|
+
workdir: @workdir_path,
|
39
|
+
notify: [:stdin, :stderr, :stdout]
|
40
|
+
}.merge(options)
|
41
|
+
|
42
|
+
# Add the options to be passed on
|
43
|
+
argN << options
|
44
|
+
|
45
|
+
# Execute, logging out the stdout/stderr as we get it
|
46
|
+
# puts("Executing: #{[command].concat(argN).inspect}")
|
47
|
+
Support::Subprocess.execute(command, *argN) do |type, data|
|
48
|
+
yield type, data if block_given?
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Closes the environment and cleans it up
|
53
|
+
def close
|
54
|
+
# puts "Removing isolated environment: #{@workdir.path}"
|
55
|
+
FileUtils.rm_rf(@workdir.path)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# Matcher that verifies that a process succeeds.
|
2
|
+
RSpec::Matchers.define :succeed do
|
3
|
+
match do |thing|
|
4
|
+
expect(thing.exit_code).to eq 0
|
5
|
+
end
|
6
|
+
|
7
|
+
failure_message_for_should do |actual|
|
8
|
+
"expected process to succeed. exit code: #{actual.exit_code}"
|
9
|
+
end
|
10
|
+
|
11
|
+
failure_message_for_should_not do |actual|
|
12
|
+
"expected process to fail. exit code: #{actual.exit_code}"
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module Support
|
2
|
+
# This class just contains some platform checking code.
|
3
|
+
# source: https://github.com/mitchellh/vagrant/blob/master/lib/vagrant/util/platform.rb)
|
4
|
+
class Platform
|
5
|
+
class << self
|
6
|
+
def tiger?
|
7
|
+
platform.include?("darwin8")
|
8
|
+
end
|
9
|
+
|
10
|
+
def leopard?
|
11
|
+
platform.include?("darwin9")
|
12
|
+
end
|
13
|
+
|
14
|
+
def cygwin?
|
15
|
+
platform.include?("cygwin")
|
16
|
+
end
|
17
|
+
|
18
|
+
[:darwin, :bsd, :freebsd, :linux, :solaris].each do |type|
|
19
|
+
define_method("#{type}?") do
|
20
|
+
platform.include?(type.to_s)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def windows?
|
25
|
+
%W[mingw mswin].each do |text|
|
26
|
+
return true if platform.include?(text)
|
27
|
+
end
|
28
|
+
|
29
|
+
false
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns boolean noting whether this is a 64-bit CPU. This
|
33
|
+
# is not 100% accurate and there could easily be false negatives.
|
34
|
+
#
|
35
|
+
# @return [Boolean]
|
36
|
+
def bit64?
|
37
|
+
["x86_64", "amd64"].include?(RbConfig::CONFIG["host_cpu"])
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns boolean noting whether this is a 32-bit CPU. This
|
41
|
+
# can easily throw false positives since it relies on {#bit64?}.
|
42
|
+
#
|
43
|
+
# @return [Boolean]
|
44
|
+
def bit32?
|
45
|
+
!bit64?
|
46
|
+
end
|
47
|
+
|
48
|
+
# This takes as input a path as a string and converts it into
|
49
|
+
# a platform-friendly version of the path. This is most important
|
50
|
+
# when using the path in shell environments with Cygwin.
|
51
|
+
#
|
52
|
+
# @param [String] path
|
53
|
+
# @return [String]
|
54
|
+
def platform_path(path)
|
55
|
+
return path if !cygwin?
|
56
|
+
|
57
|
+
process = Subprocess.execute("cygpath", "-u", path.to_s)
|
58
|
+
process.stdout.chomp
|
59
|
+
end
|
60
|
+
|
61
|
+
# Returns a boolean noting whether the terminal supports color.
|
62
|
+
# output.
|
63
|
+
def terminal_supports_colors?
|
64
|
+
if windows?
|
65
|
+
return ENV.has_key?("ANSICON")
|
66
|
+
end
|
67
|
+
|
68
|
+
true
|
69
|
+
end
|
70
|
+
|
71
|
+
def tar_file_options
|
72
|
+
# create, write only, fail if the file exists, binary if windows
|
73
|
+
File::WRONLY | File::EXCL | File::CREAT | (windows? ? File::BINARY : 0)
|
74
|
+
end
|
75
|
+
|
76
|
+
def platform
|
77
|
+
RbConfig::CONFIG["host_os"].downcase
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require "support/isolated_environment"
|
2
|
+
|
3
|
+
shared_context "acceptance" do
|
4
|
+
# Setup the environment so that we have an isolated area to run our acceptance tests
|
5
|
+
let!(:environment) { Support::IsolatedEnvironment.new }
|
6
|
+
|
7
|
+
after do
|
8
|
+
environment.close
|
9
|
+
end
|
10
|
+
|
11
|
+
# Executes the given command in the context of the isolated environment.
|
12
|
+
#
|
13
|
+
# @return [Object]
|
14
|
+
def execute(*args, &block)
|
15
|
+
environment.execute(*args, &block)
|
16
|
+
end
|
17
|
+
|
18
|
+
# This method is an assertion helper for asserting that a process
|
19
|
+
# succeeds. It is a wrapper around `execute` that asserts that the
|
20
|
+
# exit status was successful.
|
21
|
+
def assert_execute(*args, &block)
|
22
|
+
result = execute(*args, &block)
|
23
|
+
expect(result).to succeed
|
24
|
+
#assert(result.exit_code == 0, "expected '#{args.join(" ")}' to succeed")
|
25
|
+
result
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,259 @@
|
|
1
|
+
require 'childprocess'
|
2
|
+
|
3
|
+
module Support
|
4
|
+
# Execute a command in a subprocess, gathering the results and
|
5
|
+
# exit status.
|
6
|
+
#
|
7
|
+
# This class also allows you to read the data as it is outputted
|
8
|
+
# from the subprocess in real time, by simply passing a block to
|
9
|
+
# the execute method.
|
10
|
+
#
|
11
|
+
# Modified from: https://github.com/mitchellh/vagrant/blob/master/lib/vagrant/util/subprocess.rb
|
12
|
+
class Subprocess
|
13
|
+
# The chunk size for reading from subprocess IO.
|
14
|
+
READ_CHUNK_SIZE = 4096
|
15
|
+
|
16
|
+
# Convenience method for executing a method.
|
17
|
+
def self.execute(*command, &block)
|
18
|
+
new(*command).execute(&block)
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(*command)
|
22
|
+
@options = command.last.is_a?(Hash) ? command.pop : {}
|
23
|
+
@command = command
|
24
|
+
end
|
25
|
+
|
26
|
+
def execute
|
27
|
+
# Get the timeout, if we have one
|
28
|
+
timeout = @options[:timeout]
|
29
|
+
|
30
|
+
# Get the working directory
|
31
|
+
workdir = @options[:workdir] || Dir.pwd
|
32
|
+
|
33
|
+
# Get what we're interested in being notified about
|
34
|
+
notify = @options[:notify] || []
|
35
|
+
notify = [notify] if !notify.is_a?(Array)
|
36
|
+
if notify.empty? && block_given?
|
37
|
+
# If a block is given, subscribers must be given, otherwise the
|
38
|
+
# block is never called. This is usually NOT what you want, so this
|
39
|
+
# is an error.
|
40
|
+
message = "A list of notify subscriptions must be given if a block is given"
|
41
|
+
raise ArgumentError, message
|
42
|
+
end
|
43
|
+
|
44
|
+
# Let's get some more useful booleans that we access a lot so
|
45
|
+
# we're not constantly calling an `include` check
|
46
|
+
notify_table = {}
|
47
|
+
notify_table[:stderr] = notify.include?(:stderr)
|
48
|
+
notify_table[:stdout] = notify.include?(:stdout)
|
49
|
+
notify_stdin = notify.include?(:stdin)
|
50
|
+
|
51
|
+
# Build the ChildProcess
|
52
|
+
process = ChildProcess.build(*@command)
|
53
|
+
|
54
|
+
# Create the pipes so we can read the output in real time as
|
55
|
+
# we execute the command.
|
56
|
+
stdout, stdout_writer = IO.pipe
|
57
|
+
stderr, stderr_writer = IO.pipe
|
58
|
+
process.io.stdout = stdout_writer
|
59
|
+
process.io.stderr = stderr_writer
|
60
|
+
process.duplex = true
|
61
|
+
|
62
|
+
# Set the environment on the process if we must
|
63
|
+
if @options[:env]
|
64
|
+
@options[:env].each do |k, v|
|
65
|
+
process.environment[k] = v
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Start the process
|
70
|
+
begin
|
71
|
+
Dir.chdir(workdir) do
|
72
|
+
process.start
|
73
|
+
end
|
74
|
+
rescue ChildProcess::LaunchError => ex
|
75
|
+
# Raise our own version of the error so that users of the class
|
76
|
+
# don't need to be aware of ChildProcess
|
77
|
+
raise LaunchError.new(ex.message)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Make sure the stdin does not buffer
|
81
|
+
process.io.stdin.sync = true
|
82
|
+
|
83
|
+
if RUBY_PLATFORM != "java"
|
84
|
+
# On Java, we have to close after. See down the method...
|
85
|
+
# Otherwise, we close the writers right here, since we're
|
86
|
+
# not on the writing side.
|
87
|
+
stdout_writer.close
|
88
|
+
stderr_writer.close
|
89
|
+
end
|
90
|
+
|
91
|
+
# Create a dictionary to store all the output we see.
|
92
|
+
io_data = { :stdout => "", :stderr => "" }
|
93
|
+
|
94
|
+
# Record the start time for timeout purposes
|
95
|
+
start_time = Time.now.to_i
|
96
|
+
|
97
|
+
while true
|
98
|
+
writers = notify_stdin ? [process.io.stdin] : []
|
99
|
+
results = IO.select([stdout, stderr], writers, nil, timeout || 0.1)
|
100
|
+
results ||= []
|
101
|
+
readers = results[0]
|
102
|
+
writers = results[1]
|
103
|
+
|
104
|
+
# Check if we have exceeded our timeout
|
105
|
+
raise TimeoutExceeded, process.pid if timeout && (Time.now.to_i - start_time) > timeout
|
106
|
+
|
107
|
+
# Check the readers to see if they're ready
|
108
|
+
if readers && !readers.empty?
|
109
|
+
readers.each do |r|
|
110
|
+
# Read from the IO object
|
111
|
+
data = read_io(r)
|
112
|
+
|
113
|
+
# We don't need to do anything if the data is empty
|
114
|
+
next if data.empty?
|
115
|
+
|
116
|
+
io_name = r == stdout ? :stdout : :stderr
|
117
|
+
|
118
|
+
io_data[io_name] += data
|
119
|
+
yield io_name, data if block_given? && notify_table[io_name]
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# Break out if the process exited. We have to do this before
|
124
|
+
# attempting to write to stdin otherwise we'll get a broken pipe
|
125
|
+
# error.
|
126
|
+
break if process.exited?
|
127
|
+
|
128
|
+
# Check the writers to see if they're ready, and notify any listeners
|
129
|
+
if writers && !writers.empty?
|
130
|
+
yield :stdin, process.io.stdin if block_given?
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# Wait for the process to end.
|
135
|
+
begin
|
136
|
+
remaining = (timeout || 32000) - (Time.now.to_i - start_time)
|
137
|
+
remaining = 0 if remaining < 0
|
138
|
+
|
139
|
+
process.poll_for_exit(remaining)
|
140
|
+
rescue ChildProcess::TimeoutError
|
141
|
+
raise TimeoutExceeded, process.pid
|
142
|
+
end
|
143
|
+
|
144
|
+
# Read the final output data, since it is possible we missed a small
|
145
|
+
# amount of text between the time we last read data and when the
|
146
|
+
# process exited.
|
147
|
+
[stdout, stderr].each do |io|
|
148
|
+
# Read the extra data, ignoring if there isn't any
|
149
|
+
extra_data = read_io(io)
|
150
|
+
next if extra_data == ""
|
151
|
+
|
152
|
+
# Log it out and accumulate
|
153
|
+
io_name = io == stdout ? :stdout : :stderr
|
154
|
+
io_data[io_name] += extra_data
|
155
|
+
|
156
|
+
# Yield to any listeners any remaining data
|
157
|
+
yield io_name, extra_data if block_given?
|
158
|
+
end
|
159
|
+
|
160
|
+
if RUBY_PLATFORM == "java"
|
161
|
+
# On JRuby, we need to close the writers after the process,
|
162
|
+
# for some reason. See GH-711.
|
163
|
+
stdout_writer.close
|
164
|
+
stderr_writer.close
|
165
|
+
end
|
166
|
+
|
167
|
+
# Return an exit status container
|
168
|
+
Result.new(process.exit_code, io_data[:stdout], io_data[:stderr])
|
169
|
+
end
|
170
|
+
|
171
|
+
protected
|
172
|
+
|
173
|
+
# Reads data from an IO object while it can, returning the data it reads.
|
174
|
+
# When it encounters a case when it can't read anymore, it returns the
|
175
|
+
# data.
|
176
|
+
#
|
177
|
+
# @return [String]
|
178
|
+
def read_io(io)
|
179
|
+
data = ""
|
180
|
+
|
181
|
+
while true
|
182
|
+
begin
|
183
|
+
if Platform.windows?
|
184
|
+
# Windows doesn't support non-blocking reads on
|
185
|
+
# file descriptors or pipes so we have to get
|
186
|
+
# a bit more creative.
|
187
|
+
|
188
|
+
# Check if data is actually ready on this IO device.
|
189
|
+
# We have to do this since `readpartial` will actually block
|
190
|
+
# until data is available, which can cause blocking forever
|
191
|
+
# in some cases.
|
192
|
+
results = IO.select([io], nil, nil, 0.1)
|
193
|
+
break if !results || results[0].empty?
|
194
|
+
|
195
|
+
# Read!
|
196
|
+
data << io.readpartial(READ_CHUNK_SIZE)
|
197
|
+
else
|
198
|
+
# Do a simple non-blocking read on the IO object
|
199
|
+
data << io.read_nonblock(READ_CHUNK_SIZE)
|
200
|
+
end
|
201
|
+
rescue Exception => e
|
202
|
+
# The catch-all rescue here is to support multiple Ruby versions,
|
203
|
+
# since we use some Ruby 1.9 specific exceptions.
|
204
|
+
|
205
|
+
breakable = false
|
206
|
+
if e.is_a?(EOFError)
|
207
|
+
# An `EOFError` means this IO object is done!
|
208
|
+
breakable = true
|
209
|
+
elsif defined?(IO::WaitReadable) && e.is_a?(IO::WaitReadable)
|
210
|
+
# IO::WaitReadable is only available on Ruby 1.9+
|
211
|
+
|
212
|
+
# An IO::WaitReadable means there may be more IO but this
|
213
|
+
# IO object is not ready to be read from yet. No problem,
|
214
|
+
# we read as much as we can, so we break.
|
215
|
+
breakable = true
|
216
|
+
elsif e.is_a?(Errno::EAGAIN)
|
217
|
+
# Otherwise, we just look for the EAGAIN error which should be
|
218
|
+
# all that IO::WaitReadable does in Ruby 1.9.
|
219
|
+
breakable = true
|
220
|
+
end
|
221
|
+
|
222
|
+
# Break out if we're supposed to. Otherwise re-raise the error
|
223
|
+
# because it is a real problem.
|
224
|
+
break if breakable
|
225
|
+
raise
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
data
|
230
|
+
end
|
231
|
+
|
232
|
+
# An error which raises when a process fails to start
|
233
|
+
class LaunchError < StandardError; end
|
234
|
+
|
235
|
+
# An error which occurs when the process doesn't end within
|
236
|
+
# the given timeout.
|
237
|
+
class TimeoutExceeded < StandardError
|
238
|
+
attr_reader :pid
|
239
|
+
|
240
|
+
def initialize(pid)
|
241
|
+
super()
|
242
|
+
@pid = pid
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
# Container class to store the results of executing a subprocess.
|
247
|
+
class Result
|
248
|
+
attr_reader :exit_code
|
249
|
+
attr_reader :stdout
|
250
|
+
attr_reader :stderr
|
251
|
+
|
252
|
+
def initialize(exit_code, stdout, stderr)
|
253
|
+
@exit_code = exit_code
|
254
|
+
@stdout = stdout
|
255
|
+
@stderr = stderr
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'tempfile'
|
3
|
+
|
4
|
+
module Support
|
5
|
+
# This class provides an easy way of creating a temporary
|
6
|
+
# directory and having it removed when the application exits.
|
7
|
+
#
|
8
|
+
# Modified from: https://github.com/mitchellh/vagrant/blob/master/spec/support/tempdir.rb
|
9
|
+
class TempDir
|
10
|
+
attr_reader :path
|
11
|
+
|
12
|
+
def initialize(basename = "borg")
|
13
|
+
@path = Dir.mktmpdir(basename)
|
14
|
+
|
15
|
+
# Setup a finalizer to delete the directory. This is the same way
|
16
|
+
# that Tempfile and friends do this...
|
17
|
+
@cleanup_proc = lambda do
|
18
|
+
FileUtils.rm_rf(@path) if File.directory?(@path)
|
19
|
+
end
|
20
|
+
|
21
|
+
ObjectSpace.define_finalizer(self, @cleanup_proc)
|
22
|
+
end
|
23
|
+
|
24
|
+
# This deletes the temporary directory.
|
25
|
+
def unlink
|
26
|
+
# Delete the directory
|
27
|
+
@cleanup_proc.call
|
28
|
+
|
29
|
+
# Undefine the finalizer since we're all cleaned up
|
30
|
+
ObjectSpace.undefine_finalizer(self)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
metadata
CHANGED
@@ -1,29 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: borg-rb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Identified
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-03-
|
11
|
+
date: 2013-03-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: capistrano
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- -
|
17
|
+
- - ~>
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 2.14.2
|
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
|
-
version:
|
26
|
+
version: 2.14.2
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: capistrano_colors
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -80,6 +80,20 @@ dependencies:
|
|
80
80
|
- - ! '>='
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: childprocess
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ! '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ! '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
83
97
|
description: Ruby-based software provisioning and deployment framework
|
84
98
|
email:
|
85
99
|
- phil@identified.com
|
@@ -92,6 +106,7 @@ extra_rdoc_files: []
|
|
92
106
|
files:
|
93
107
|
- .gitignore
|
94
108
|
- .travis.yml
|
109
|
+
- CHANGELOG.md
|
95
110
|
- Capfile
|
96
111
|
- Gemfile
|
97
112
|
- README.md
|
@@ -119,11 +134,19 @@ files:
|
|
119
134
|
- skeleton/cap/recipies/.gitkeep
|
120
135
|
- skeleton/lib/.gitkeep
|
121
136
|
- skeleton/mygem.gemspec.skeleton
|
137
|
+
- spec/acceptance/borgify_spec.rb
|
138
|
+
- spec/acceptance_spec_helper.rb
|
122
139
|
- spec/lib/borg/cli/applications_spec.rb
|
123
140
|
- spec/lib/borg/configuration/applications_spec.rb
|
124
141
|
- spec/lib/borg/configuration/assimilator_spec.rb
|
125
142
|
- spec/lib/borg/configuration/stages_spec.rb
|
126
143
|
- spec/spec_helper.rb
|
144
|
+
- spec/support/isolated_environment.rb
|
145
|
+
- spec/support/matchers/succeed.rb
|
146
|
+
- spec/support/platform.rb
|
147
|
+
- spec/support/shared_contexts/acceptance.rb
|
148
|
+
- spec/support/subprocess.rb
|
149
|
+
- spec/support/temp_dir.rb
|
127
150
|
homepage: https://github.com/B0RG/borg
|
128
151
|
licenses:
|
129
152
|
- MIT
|
@@ -149,9 +172,17 @@ signing_key:
|
|
149
172
|
specification_version: 4
|
150
173
|
summary: Ruby-based software provisioning and deployment framework
|
151
174
|
test_files:
|
175
|
+
- spec/acceptance/borgify_spec.rb
|
176
|
+
- spec/acceptance_spec_helper.rb
|
152
177
|
- spec/lib/borg/cli/applications_spec.rb
|
153
178
|
- spec/lib/borg/configuration/applications_spec.rb
|
154
179
|
- spec/lib/borg/configuration/assimilator_spec.rb
|
155
180
|
- spec/lib/borg/configuration/stages_spec.rb
|
156
181
|
- spec/spec_helper.rb
|
182
|
+
- spec/support/isolated_environment.rb
|
183
|
+
- spec/support/matchers/succeed.rb
|
184
|
+
- spec/support/platform.rb
|
185
|
+
- spec/support/shared_contexts/acceptance.rb
|
186
|
+
- spec/support/subprocess.rb
|
187
|
+
- spec/support/temp_dir.rb
|
157
188
|
has_rdoc:
|