borg-rb 0.0.1 → 0.0.2
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 +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
|
[](https://gemnasium.com/B0RG/borg)
|
7
7
|
[](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:
|