vgrnt 0.0.2 → 0.0.3
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 +4 -4
- data/.gitignore +5 -0
- data/.travis.yml +4 -0
- data/CHANGELOG.md +11 -0
- data/Gemfile +0 -0
- data/README.md +87 -39
- data/Rakefile +44 -0
- data/Vagrantfile +0 -0
- data/docs/DEVNOTES.md +23 -0
- data/docs/vboxmanage-irregular-commands.txt +0 -0
- data/lib/vgrnt.rb +0 -0
- data/lib/vgrnt/base.rb +43 -46
- data/lib/vgrnt/util/vagrantfile.rb +38 -0
- data/lib/vgrnt/util/virtualbox.rb +49 -0
- data/lib/vgrnt/version.rb +1 -1
- data/spec/acceptance/app_spec.rb +114 -0
- data/spec/acceptance/fixtures/multivm/Vagrantfile +16 -0
- data/spec/acceptance/fixtures/neveron/Vagrantfile +9 -0
- data/spec/acceptance/fixtures/simple/Vagrantfile +20 -0
- data/spec/acceptance/support/acceptance_helper.rb +72 -0
- data/spec/acceptance/util_spec.rb +63 -0
- data/spec/spec_helper.rb +8 -0
- data/spec/unit/util_spec.rb +13 -0
- data/vgrnt.gemspec +1 -0
- metadata +36 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 03a6d4695c381a141cfe79bc0e9e1f59a935b86f
|
4
|
+
data.tar.gz: 715132621d0eca3a4e2d56b9193fcabbd14c9c13
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ff27c6d4a016745f9b270a37c3b4ead82389e309384065ed121b98bcf3b3c96251e2a2c300b7fc80f1cf746a4599b29387288eb4bac5d78b6661a0d62c6e725f
|
7
|
+
data.tar.gz: 1fbb99c60ba3c748d8921c8349e2a93cda9396b49f8fe1f1582a199acb1a8b2ba24bbf213694b50dfca267eb0faa25101090c918aebcdc6d74739f0647072f9e
|
data/.gitignore
CHANGED
data/.travis.yml
ADDED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
+
## [0.0.3](https://github.com/dergachev/vagrant/compare/v0.0.2...v0.0.3) (Nov 12, 2013)
|
2
|
+
|
3
|
+
FEATURES:
|
4
|
+
|
5
|
+
- Added support for `vgrnt status`
|
6
|
+
|
7
|
+
IMPROVEMENTS:
|
8
|
+
|
9
|
+
- Refactored codebase a bit
|
10
|
+
- Implemented acceptance tests (unit tests still non-existant)
|
11
|
+
|
1
12
|
## [0.0.2](https://github.com/dergachev/vagrant/compare/v0.0.1...v0.0.2) (Oct 30, 2013)
|
2
13
|
|
3
14
|
BUGFIX:
|
data/Gemfile
CHANGED
File without changes
|
data/README.md
CHANGED
@@ -1,29 +1,38 @@
|
|
1
|
-
vgrnt
|
1
|
+
vgrnt [](http://badge.fury.io/rb/vgrnt) [](https://travis-ci.org/dergachev/vgrnt)
|
2
2
|
=====
|
3
3
|
|
4
|
-
[](http://badge.fury.io/rb/vgrnt)
|
5
|
-
|
6
4
|
`vgrnt` is a partial reimplementation of a few `vagrant` operations that I
|
7
|
-
found to be unbearably slow.
|
5
|
+
found to be unbearably slow in my typical usage. Here's a benchmark on my
|
6
|
+
quad-core 2013 MacBook Pro:
|
8
7
|
|
9
8
|
```bash
|
10
|
-
time vagrant
|
11
|
-
# real
|
9
|
+
time vagrant status
|
10
|
+
# real 0m2.895s
|
12
11
|
|
13
|
-
time vgrnt
|
14
|
-
# real
|
12
|
+
time vgrnt status
|
13
|
+
# real 0m0.316s
|
15
14
|
```
|
16
15
|
|
17
|
-
|
18
|
-
superfluous gems
|
19
|
-
|
20
|
-
|
16
|
+
This is achieved by naively reimplementing these commands, without requiring
|
17
|
+
superfluous gems. Given how ashamed I am of the ugly hacks involved, the name
|
18
|
+
`vgrnt` was chosen to be deliberately unpronouncable. If you must use it,
|
19
|
+
probably best to keep it to yourself.
|
21
20
|
|
21
|
+
## Usage
|
22
22
|
|
23
|
-
|
23
|
+
### vgrnt status
|
24
24
|
|
25
|
-
Same as `vagrant
|
26
|
-
|
25
|
+
Same as `vagrant status` but faster.
|
26
|
+
|
27
|
+
```
|
28
|
+
vgrnt status
|
29
|
+
```
|
30
|
+
|
31
|
+
Does its dirty work by sneaking around in your Vagrantfile and the `./.vagrant` directory.
|
32
|
+
|
33
|
+
### vgrnt ssh
|
34
|
+
|
35
|
+
Same as `vagrant ssh` but faster.
|
27
36
|
|
28
37
|
```
|
29
38
|
vgrnt ssh
|
@@ -31,7 +40,11 @@ vgrnt ssh vm-name # supports multi-vm environments
|
|
31
40
|
vgrnt ssh -- ls /tmp # extra ssh args after --
|
32
41
|
```
|
33
42
|
|
34
|
-
|
43
|
+
Will read `.vgrnt-sshconfig` file for SSH options, or try to
|
44
|
+
guess the options by shelling out to VBoxManage.
|
45
|
+
|
46
|
+
|
47
|
+
### vgrnt ssh-config
|
35
48
|
|
36
49
|
Runs `vgrnt ssh-config > .vgrnt-sshconfig`. Use this if `vgrnt ssh` doesn't
|
37
50
|
seem to work, because you have a non-standard setup.
|
@@ -43,7 +56,7 @@ vgrnt ssh-config
|
|
43
56
|
vgrnt ssh-config vm-name # supports multi-vm environments
|
44
57
|
```
|
45
58
|
|
46
|
-
|
59
|
+
### vgrnt vboxmanage
|
47
60
|
|
48
61
|
Simplifies calling `VBoxManage` on the already created VM. Injects
|
49
62
|
the VM ID (eg 0398e43a-d35f-4c84-bc81-6807f5d11262) in the right spot.
|
@@ -64,49 +77,84 @@ vgrnt vboxmanage metrics query VM_ID
|
|
64
77
|
# => VBoxManage metrics query 0398e43a-d35f-4c84-bc81-6807f5d11262
|
65
78
|
```
|
66
79
|
|
67
|
-
##
|
80
|
+
## Installation
|
68
81
|
|
69
|
-
*
|
70
|
-
* vgrnt reprovision (equivalent to running `vagrant ssh -- chef-solo /tmp...`
|
71
|
-
* vgrnt destroy (how hard is it to kill a VM??)
|
72
|
-
* vgrnt snapshot (it's currently implemented as a vagrant plugin, but who needs decoupling?)
|
82
|
+
vgrnt *should not* be installed as a vagrant plugin. Instead, just install the gem:
|
73
83
|
|
74
|
-
|
84
|
+
gem install vgrnt --no-rdoc --no-ri
|
75
85
|
|
86
|
+
vgrnt only works with Vagrant 1.1+ running on Linux/OSX, with the VirtualBox provider.
|
76
87
|
|
77
|
-
|
78
|
-
* Only works from vagrant project root, not in a subfolder
|
79
|
-
* Tests, what tests?
|
88
|
+
## Why is Vagrant slow?
|
80
89
|
|
81
|
-
|
90
|
+
Vagrant itself isn't actually slow. The lag is mostly as a result of popular
|
91
|
+
but poorly constructed plugins:
|
82
92
|
|
83
|
-
|
84
|
-
|
93
|
+
```bash
|
94
|
+
time vgrnt ssh -- '/bin/true'
|
95
|
+
# real 0m0.387s
|
85
96
|
|
86
|
-
|
97
|
+
time vagrant ssh -- '/bin/true'
|
98
|
+
# real 0m2.102s
|
99
|
+
|
100
|
+
for P in $(vagrant plugin list | awk '{print $1}' ) ; do
|
101
|
+
vagrant plugin uninstall $P
|
102
|
+
done
|
103
|
+
# Uninstalling the 'vagrant-berkshelf' plugin...
|
104
|
+
# Uninstalling the 'vagrant-cachier' plugin...
|
105
|
+
# Uninstalling the 'vagrant-omnibus' plugin...
|
106
|
+
|
107
|
+
time vagrant ssh -- '/bin/true'
|
108
|
+
# real 0m0.716s
|
109
|
+
```
|
87
110
|
|
111
|
+
In my case, the culprit is [vagrant-berkshelf eager-loading
|
112
|
+
dependencies](https://github.com/RiotGames/vagrant-berkshelf/issues/101).
|
113
|
+
However, any vagrant plugins introduce lag, so perhaps Vagrant's plugin
|
114
|
+
architecture could be modified to make this less of a problem.
|
115
|
+
|
116
|
+
## Caveats
|
117
|
+
|
118
|
+
* At the moment, it requires OSX/Linux and Virtualbox.
|
119
|
+
* Only works from vagrant project root, not in a subfolder
|
120
|
+
|
121
|
+
## Todo
|
122
|
+
|
123
|
+
* Ipmlement `vgrnt reprovision`
|
124
|
+
- roughly equivalent to running `vagrant ssh -- chef-solo /tmp...`
|
125
|
+
* Integrate `vagrant-vbox-snapshot` plugin.
|
126
|
+
* Simplify everything by using VAGRANT_NO_PLUGINS env variable
|
127
|
+
- See https://github.com/mitchellh/vagrant/blob/master/lib/vagrant.rb#L179
|
88
128
|
|
89
129
|
## Development
|
90
130
|
|
91
|
-
To
|
131
|
+
To hack on this plugin, do the following:
|
92
132
|
|
93
133
|
```
|
94
|
-
#
|
95
|
-
|
96
|
-
|
134
|
+
# fork the repo to your account
|
135
|
+
|
136
|
+
# clone your fork, and create a feature branch to work on
|
137
|
+
git clone https://github.com/USERNAME/vgrnt.git
|
138
|
+
cd vgrnt
|
97
139
|
git checkout -b MY-NEW-FEATURE
|
98
140
|
|
99
141
|
# installs the vagrant gem, which is a dev dependency
|
100
|
-
bundle install
|
142
|
+
bundle install
|
101
143
|
|
102
144
|
# hack on the plugin
|
103
|
-
vim lib/
|
145
|
+
vim lib/vgrnt/base.rb # or any other file
|
146
|
+
|
147
|
+
# test out your changes in the context of this repo
|
148
|
+
cd spec/acceptance/fixtures/simple
|
149
|
+
bundle exec vgrnt ssh -- 'ls /tmp'
|
104
150
|
|
105
|
-
#
|
106
|
-
bundle exec
|
151
|
+
# run ALL the tests (not just unit)
|
152
|
+
bundle exec rake acceptance:prepare # spin up VMs to test against
|
153
|
+
bundle exec rake spec
|
154
|
+
bundle exec rake acceptance:clean # destroy testing VMs
|
107
155
|
|
108
156
|
# commit, push, and do a pull-request
|
109
157
|
```
|
110
158
|
|
111
|
-
See [DEVNOTES.md](https://github.com/dergachev/
|
159
|
+
See [DEVNOTES.md](https://github.com/dergachev/vgrnt/blob/master/docs/DEVNOTES.md)
|
112
160
|
for the notes I compiled while developing this plugin.
|
data/Rakefile
CHANGED
@@ -1,2 +1,46 @@
|
|
1
1
|
require 'bundler'
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
|
2
4
|
Bundler::GemHelper.install_tasks
|
5
|
+
|
6
|
+
RSpec::Core::RakeTask.new(:spec)
|
7
|
+
|
8
|
+
desc "Run unit tests"
|
9
|
+
RSpec::Core::RakeTask.new('spec:unit') { |t| t.pattern = "./spec/unit/**/*_spec.rb" }
|
10
|
+
|
11
|
+
desc "Run acceptance tests (requires VBoxManage)"
|
12
|
+
RSpec::Core::RakeTask.new('spec:acceptance') { |t| t.pattern = "./spec/acceptance/**/*_spec.rb" }
|
13
|
+
|
14
|
+
desc "Run acceptance tests (requires VBoxManage)"
|
15
|
+
RSpec::Core::RakeTask.new('spec:acceptance:fast') do |t|
|
16
|
+
t.rspec_opts = "--tag ~slow"
|
17
|
+
t.pattern = "./spec/acceptance/**/*_spec.rb"
|
18
|
+
end
|
19
|
+
|
20
|
+
desc 'Default task which runs all specs'
|
21
|
+
task :default => 'spec:unit'
|
22
|
+
|
23
|
+
desc "Start all VMs required by tests"
|
24
|
+
task "acceptance:prepare" do
|
25
|
+
Dir["spec/acceptance/fixtures/{simple,multivm}"].each do |d|
|
26
|
+
# not doing this in parallel / background due to Vagrant bug
|
27
|
+
# see https://github.com/mitchellh/vagrant/pull/2484
|
28
|
+
Dir.chdir(d) do |d|
|
29
|
+
puts "In " + `pwd`
|
30
|
+
sh "VAGRANT_NO_PLUGINS=1 vagrant up"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
desc "Stop all VMs started by tests"
|
36
|
+
task "acceptance:clean" do
|
37
|
+
Dir["spec/acceptance/fixtures/*"].each do |d|
|
38
|
+
Dir.chdir(d) do |d|
|
39
|
+
Dir[".vagrant/machines/*"].each do |name|
|
40
|
+
name = File.basename(name)
|
41
|
+
puts "In " + `pwd`
|
42
|
+
sh "VAGRANT_NO_PLUGINS=1 vagrant destroy #{name} -f > /dev/null &"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/Vagrantfile
CHANGED
File without changes
|
data/docs/DEVNOTES.md
CHANGED
@@ -33,3 +33,26 @@ git push --tags
|
|
33
33
|
* http://timelessrepo.com/making-ruby-gems
|
34
34
|
* http://asciicasts.com/episodes/245-new-gem-with-bundler
|
35
35
|
|
36
|
+
## Nov 6 2013 - Notes on rspec
|
37
|
+
|
38
|
+
Ruby:
|
39
|
+
|
40
|
+
* http://stackoverflow.com/questions/2232/calling-bash-commands-from-ruby
|
41
|
+
|
42
|
+
How to write a test:
|
43
|
+
|
44
|
+
* https://github.com/dergachev/rspec-tutorial/blob/master/spec/acceptance/rspec_tutorial_spec.rb
|
45
|
+
* http://blog.davidchelimsky.net/blog/2012/05/13/spec-smell-explicit-use-of-subject/
|
46
|
+
* http://betterspecs.org/
|
47
|
+
|
48
|
+
Rspec features:
|
49
|
+
|
50
|
+
* https://www.relishapp.com/rspec/rspec-expectations/docs/built-in-matchers
|
51
|
+
* https://www.relishapp.com/rspec/rspec-core/v/2-0/docs/hooks/before-and-after-hooks
|
52
|
+
|
53
|
+
Setting up the rake spec tasks:
|
54
|
+
|
55
|
+
* https://www.relishapp.com/rspec/rspec-core/v/2-4/docs/command-line/tag-option#filter-examples-with-a-simple-tag-and-@
|
56
|
+
* https://github.com/fgrehm/vagrant-lxc/blob/master/spec/acceptance_helper.rb
|
57
|
+
* https://www.relishapp.com/rspec/rspec-core/docs/command-line/rake-task
|
58
|
+
* https://github.com/fgrehm/vagrant-lxc/blob/master/tasks/spec.rake
|
File without changes
|
data/lib/vgrnt.rb
CHANGED
File without changes
|
data/lib/vgrnt/base.rb
CHANGED
@@ -1,18 +1,26 @@
|
|
1
1
|
require 'thor'
|
2
|
+
require 'open3'
|
3
|
+
require 'vgrnt/util/virtualbox'
|
4
|
+
require 'vgrnt/util/vagrantfile'
|
2
5
|
|
3
6
|
module Vgrnt
|
4
7
|
class Logger
|
5
8
|
|
9
|
+
def stdout(str)
|
10
|
+
$stdout.puts str unless str.empty?
|
11
|
+
end
|
12
|
+
|
6
13
|
def notice(str)
|
7
|
-
$stderr.puts str
|
14
|
+
$stderr.puts str unless str.empty?
|
8
15
|
end
|
9
16
|
|
10
17
|
def debug(str)
|
11
|
-
$stderr.puts str if ENV['VAGRANT_LOG'] == 'debug'
|
18
|
+
$stderr.puts str if !str.empty? && ENV['VAGRANT_LOG'] == 'debug'
|
12
19
|
end
|
13
20
|
|
14
21
|
def error(str)
|
15
|
-
|
22
|
+
# terminal codes for red
|
23
|
+
$stderr.puts "\e[31m" + str + "\e[0m" unless str.empty?
|
16
24
|
end
|
17
25
|
end
|
18
26
|
|
@@ -24,31 +32,6 @@ module Vgrnt
|
|
24
32
|
end
|
25
33
|
end
|
26
34
|
|
27
|
-
class Util
|
28
|
-
def self.getRunningMachines
|
29
|
-
machines = {}
|
30
|
-
|
31
|
-
ids = Dir.glob(".vagrant/machines/*/*/id")
|
32
|
-
ids.each do |id_file|
|
33
|
-
machine_name = id_file[ /^.vagrant\/machines\/(\w+)\/\w+\/id$/ ,1]
|
34
|
-
machine_id = IO.read(id_file)
|
35
|
-
machine_status = `VBoxManage showvminfo #{machine_id} --machinereadable`
|
36
|
-
|
37
|
-
# Forwarding(0)="ssh,tcp,127.0.0.1,2222,,22"
|
38
|
-
# Forwarding(1)="ssh,tcp,,2222,,22"
|
39
|
-
ssh_info = machine_status.scan( /^Forwarding\(\d+\)="ssh,tcp,([0-9.]*),([0-9]+),/ ).first
|
40
|
-
|
41
|
-
machines[machine_name] = {
|
42
|
-
:id => machine_id,
|
43
|
-
:ssh_ip => ssh_info[0].empty? ? '127.0.0.1' : ssh_info[0],
|
44
|
-
:ssh_port => ssh_info[1],
|
45
|
-
:state => machine_status.scan( /^VMState="(.*)"$/ ).first.first # VMState="running"
|
46
|
-
}
|
47
|
-
end
|
48
|
-
# puts machines.inspect
|
49
|
-
return machines
|
50
|
-
end
|
51
|
-
end
|
52
35
|
|
53
36
|
class App < Thor
|
54
37
|
|
@@ -69,15 +52,16 @@ module Vgrnt
|
|
69
52
|
if File.exists?(".vgrnt-sshconfig")
|
70
53
|
@logger.debug "Using .vgrnt-sshconf"
|
71
54
|
ssh_command = "ssh -F .vgrnt-sshconfig #{target_vm} #{args.join(' ')}"
|
72
|
-
else
|
55
|
+
else
|
73
56
|
@logger.debug ".vgrnt-sshconfig file not found; using VBoxManage to get connection info."
|
74
|
-
machine = Util::
|
57
|
+
machine = Util::VirtualBox::runningMachines()[target_vm]
|
58
|
+
ssh_info = Util::VirtualBox::machineSSH(target_vm)
|
75
59
|
|
76
|
-
if machine && machine[:state] == 'running'
|
60
|
+
if machine && machine[:state] == 'running'
|
77
61
|
# found by running "VAGRANT_LOG=debug vagrant ssh"
|
78
62
|
default_ssh_args = [
|
79
|
-
"vagrant@#{
|
80
|
-
"-p",
|
63
|
+
"vagrant@#{ssh_info[:ssh_ip]}",
|
64
|
+
"-p", ssh_info[:ssh_port],
|
81
65
|
"-o", "DSAAuthentication=yes", "-o", "LogLevel=FATAL", "-o", "StrictHostKeyChecking=no",
|
82
66
|
"-o", "UserKnownHostsFile=/dev/null", "-o", "IdentitiesOnly=yes",
|
83
67
|
"-i", "~/.vagrant.d/insecure_private_key"
|
@@ -90,21 +74,39 @@ module Vgrnt
|
|
90
74
|
end
|
91
75
|
end
|
92
76
|
|
93
|
-
|
77
|
+
puts `#{ssh_command}`
|
94
78
|
end
|
95
79
|
|
96
80
|
desc "ssh-config [vm-name]", "Store output of 'vagrant ssh-config' to .vgrnt-sshconfig"
|
97
81
|
def ssh_config(target="default")
|
98
|
-
output = `vagrant ssh-config #{target}`
|
82
|
+
output = `VAGRANT_NO_PLUGINS=1 vagrant ssh-config #{target}`
|
99
83
|
if $? && !output.empty?
|
100
84
|
IO.write('.vgrnt-sshconfig', output)
|
101
|
-
@logger.notice "Created ./.vgrnt-sshconfig with the following:"
|
85
|
+
@logger.notice "Created ./.vgrnt-sshconfig with the following: " + output
|
102
86
|
else
|
103
87
|
@logger.error "Call to 'vagrant ssh-config' failed."
|
104
88
|
exit 1
|
105
89
|
end
|
106
90
|
end
|
107
91
|
|
92
|
+
desc "status [vm-name]", "Wrapper on vagrant status"
|
93
|
+
def status(target_vm = 'default')
|
94
|
+
machines = Util::VirtualBox::runningMachines()
|
95
|
+
|
96
|
+
puts "Current machine states (as detected by vgrnt):\n\n"
|
97
|
+
machines.each do |machine_name, machine|
|
98
|
+
puts "#{machine_name.ljust(25)} #{machine[:state]} (virtualbox)"
|
99
|
+
end
|
100
|
+
defined_vms = Util::Vagrantfile::defined_vms()
|
101
|
+
|
102
|
+
defined_vms.each do |machine_name|
|
103
|
+
machine_name = machine_name.to_s # they're usually symbols
|
104
|
+
if !machines[machine_name]
|
105
|
+
puts "#{machine_name.ljust(25)} not created (virtualbox)"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
108
110
|
# desc "provision", "Run vagrant provision like last time"
|
109
111
|
# def provision
|
110
112
|
# raise "Not implemented yet. Likely requires creating vagrant-vgrnt."
|
@@ -134,7 +136,7 @@ module Vgrnt
|
|
134
136
|
|
135
137
|
# of form `VBoxManage showvminfo uuid|name ...`
|
136
138
|
vboxmanage_commands_standard = vboxmanage_commands_all - vboxmanage_commands_special
|
137
|
-
machine = Util::
|
139
|
+
machine = Util::VirtualBox::runningMachines()[target_vm]
|
138
140
|
if !machine
|
139
141
|
@logger.error "The specified target vm (#{target_vm}) has not been started."
|
140
142
|
exit 1
|
@@ -148,9 +150,6 @@ module Vgrnt
|
|
148
150
|
else
|
149
151
|
# TODO: handle substitution for commands like `usbfilter add 0 --target <uuid|name>`
|
150
152
|
|
151
|
-
# @logger.error "Support for non-standard vboxmanage commands (such as #{vboxmanage_subcommand}) is not implemented yet."
|
152
|
-
# exit 1
|
153
|
-
|
154
153
|
@logger.debug "Non-standard vboxmanage command detected (#{vboxmanage_subcommand}). Substituting 'VM_ID' for VM id."
|
155
154
|
|
156
155
|
# [VM_ID] is an optional literal token which will be replaced by the UUID of the VM referenced by Vagrant
|
@@ -160,12 +159,10 @@ module Vgrnt
|
|
160
159
|
|
161
160
|
@logger.debug "Executing: #{command}"
|
162
161
|
#TODO: windows support (path to VBoxManage.exe")
|
163
|
-
|
162
|
+
Open3.popen3(command) do |stdin, stdout, stderr|
|
163
|
+
@logger.stdout stdout.read
|
164
|
+
@logger.error stderr.read
|
165
|
+
end
|
164
166
|
end
|
165
167
|
end
|
166
168
|
end
|
167
|
-
|
168
|
-
# if __FILE__ == $0
|
169
|
-
# raise "vgrnt: must be run from vagrant project root" unless File.exists? 'Vagrantfile'
|
170
|
-
# Vgrnt::App.start
|
171
|
-
# end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# TODO: test me
|
2
|
+
|
3
|
+
$vagrant_config_vms = []
|
4
|
+
|
5
|
+
module Vagrant
|
6
|
+
def self.configure(*args, &block)
|
7
|
+
yield Vgrnt::Util::Vagrantfile::Proxy.new
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module Vgrnt
|
12
|
+
module Util
|
13
|
+
module Vagrantfile
|
14
|
+
class Proxy
|
15
|
+
def define(*args)
|
16
|
+
# $stderr.puts "Called define with args (#{args.join(", ")})"
|
17
|
+
$vagrant_config_vms << args.first
|
18
|
+
end
|
19
|
+
|
20
|
+
def method_missing(name, *args, &block)
|
21
|
+
return self
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.defined_vms
|
26
|
+
# clear any previous value (else tests fail depending on exec order)
|
27
|
+
# NOT PARALLEL SAFE (not sure how to do this given static methods)
|
28
|
+
$vagrant_config_vms = []
|
29
|
+
load('./Vagrantfile')
|
30
|
+
# puts $vagrant_config_vms.inspect
|
31
|
+
if $vagrant_config_vms.empty?
|
32
|
+
$vagrant_config_vms << :default
|
33
|
+
end
|
34
|
+
return $vagrant_config_vms
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# TODO testing - stubout VBoxManage showvminfo (use fixtures for its output),
|
2
|
+
# then write unit tests for getRunningMachines (consider using erb file for the fixtures)
|
3
|
+
module Vgrnt
|
4
|
+
module Util
|
5
|
+
class VirtualBox
|
6
|
+
def self.machineSSH(target)
|
7
|
+
machine = self.runningMachines()[target]
|
8
|
+
return nil unless machine && machine[:state] == 'running'
|
9
|
+
|
10
|
+
# Forwarding(0)="ssh,tcp,127.0.0.1,2222,,22"
|
11
|
+
# Forwarding(1)="ssh,tcp,,2222,,22"
|
12
|
+
ssh_info = machine[:showvminfo].scan( /^Forwarding\(\d+\)="ssh,tcp,([0-9.]*),([0-9]+),/ ).first
|
13
|
+
|
14
|
+
return {
|
15
|
+
:ssh_ip => ssh_info[0].empty? ? '127.0.0.1' : ssh_info[0],
|
16
|
+
:ssh_port => ssh_info[1]
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.showvminfo_command(machine_id)
|
21
|
+
return "VBoxManage showvminfo #{machine_id} --machinereadable"
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.showvminfo(machine_id)
|
25
|
+
return `#{self.showvminfo_command(machine_id)}`
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.runningMachines
|
29
|
+
machines = {}
|
30
|
+
|
31
|
+
ids = Dir.glob(".vagrant/machines/*/*/id")
|
32
|
+
ids.each do |id_file|
|
33
|
+
machine_name = id_file[ /^.vagrant\/machines\/(\w+)\/\w+\/id$/ ,1]
|
34
|
+
machine_id = IO.read(id_file)
|
35
|
+
|
36
|
+
machine_info = self.showvminfo(machine_id)
|
37
|
+
|
38
|
+
machines[machine_name] = {
|
39
|
+
:id => machine_id,
|
40
|
+
:showvminfo => machine_info,
|
41
|
+
:state => machine_info.scan( /^VMState="(.*)"$/ ).first.first # VMState="running"
|
42
|
+
}
|
43
|
+
end
|
44
|
+
return machines
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/vgrnt/version.rb
CHANGED
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'acceptance/support/acceptance_helper'
|
2
|
+
|
3
|
+
describe Vgrnt::App do
|
4
|
+
|
5
|
+
context "when running from ./spec/acceptance/fixtures/simple" do
|
6
|
+
before(:all) { vagrant_up('./spec/acceptance/fixtures/simple') }
|
7
|
+
let(:vagrant_path) { './spec/acceptance/fixtures/simple' }
|
8
|
+
|
9
|
+
describe "#ssh" do
|
10
|
+
it 'works with default VM specified explicitly' do
|
11
|
+
output = vagrant_stdout { Vgrnt::App.start(%w{ssh default -- whoami}) }
|
12
|
+
expect(output).to eq "vagrant\n"
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'works with default VM specified implicitly' do
|
16
|
+
output = vagrant_stdout { Vgrnt::App.start(%w{ssh -- whoami}) }
|
17
|
+
expect(output).to eq "vagrant\n"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "#ssh-config", :slow do
|
22
|
+
|
23
|
+
before(:each) { delete_in_vagrant_env '.vgrnt-sshconfig' }
|
24
|
+
after(:each) { delete_in_vagrant_env '.vgrnt-sshconfig' }
|
25
|
+
|
26
|
+
it 'with default VM specified explicitly' do
|
27
|
+
expect( in_vagrant_env { File.exists? '.vgrnt-sshconfig' } ).to be_false
|
28
|
+
stderr = vagrant_stderr { Vgrnt::App.start(%w{ssh-config default}) }
|
29
|
+
expect( in_vagrant_env { File.exists? '.vgrnt-sshconfig' } ).to be_true
|
30
|
+
end
|
31
|
+
|
32
|
+
it "works with #ssh" do
|
33
|
+
output = vagrant_stdout { Vgrnt::App.start(%w{ssh default -- whoami}) }
|
34
|
+
expect(output).to eq "vagrant\n"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "#vboxmanage" do
|
39
|
+
it "runs displays usage when run with no arguments" do
|
40
|
+
output = vagrant_stdout { Vgrnt::App.start(%w{vboxmanage}) }
|
41
|
+
expect(output).to include "Usage:"
|
42
|
+
end
|
43
|
+
|
44
|
+
it "runs regular command (showvminfo)" do
|
45
|
+
output = vagrant_stdout { Vgrnt::App.start(%w{vboxmanage default -- showvminfo}) }
|
46
|
+
expect(output).to match /Name.*vgrnt-test/
|
47
|
+
end
|
48
|
+
|
49
|
+
it "substitutes VM_UUID in irregular commands (guestproperty enumerate VM_UUID)" do
|
50
|
+
output = vagrant_stdout { Vgrnt::App.start(%w{vboxmanage default -- guestproperty enumerate VM_UUID}) }
|
51
|
+
expect(output).to include "Name: /VirtualBox/GuestInfo/OS/Product"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe "#status" do
|
56
|
+
it "correctly identifies running machines" do
|
57
|
+
# default saved (virtualbox)
|
58
|
+
output = vagrant_stdout { Vgrnt::App.start(%w{status}) }
|
59
|
+
expect(output).to match /default +running \(virtualbox\)/
|
60
|
+
end
|
61
|
+
|
62
|
+
it "is identical to 'vagrant status'", :slow do
|
63
|
+
# default saved (virtualbox)
|
64
|
+
vagrant_output = vagrant_stdout { puts `VAGRANT_NO_PLUGINS=1 vagrant status | grep '(virtualbox)$'` }
|
65
|
+
expect(vagrant_output).to match /default +running \(virtualbox\)/
|
66
|
+
|
67
|
+
vgrnt_output = vagrant_stdout { Vgrnt::App.start(%w{status}) }
|
68
|
+
expect(vgrnt_output).to include(vagrant_output)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context "when running from ./spec/acceptance/fixtures/neveron" do
|
74
|
+
before(:all) { vagrant_destroy('./spec/acceptance/fixtures/neveron') }
|
75
|
+
let(:vagrant_path) { './spec/acceptance/fixtures/neveron' }
|
76
|
+
|
77
|
+
describe "#status" do
|
78
|
+
it "correctly identifies not created machines" do
|
79
|
+
# default saved (virtualbox)
|
80
|
+
output = vagrant_stdout { Vgrnt::App.start(%w{status}) }
|
81
|
+
expect(output).to match /default +not created \(virtualbox\)/
|
82
|
+
end
|
83
|
+
|
84
|
+
it "is identical to 'vagrant status'", :slow do
|
85
|
+
# default saved (virtualbox)
|
86
|
+
vagrant_output = vagrant_stdout { puts `VAGRANT_NO_PLUGINS=1 vagrant status | grep '(virtualbox)$'` }
|
87
|
+
expect(vagrant_output).to match /default +not created \(virtualbox\)/
|
88
|
+
|
89
|
+
vgrnt_output = vagrant_stdout { Vgrnt::App.start(%w{status}) }
|
90
|
+
expect(vgrnt_output).to include(vagrant_output)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
context "when running in fixtures/multivm" do
|
96
|
+
before(:all) { vagrant_up('./spec/acceptance/fixtures/multivm') }
|
97
|
+
let(:vagrant_path) { './spec/acceptance/fixtures/multivm' }
|
98
|
+
|
99
|
+
describe "#status" do
|
100
|
+
it "correctly identifies multiple running machines" do
|
101
|
+
# default saved (virtualbox)
|
102
|
+
output = vagrant_stdout { Vgrnt::App.start(%w{status}) }
|
103
|
+
expect(output).to match /^vm1 +running \(virtualbox\)/
|
104
|
+
expect(output).to match /^vm2 +running \(virtualbox\)/
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
describe "#ssh" do
|
109
|
+
it 'works with multivm environments' do
|
110
|
+
expect(vagrant_stdout { Vgrnt::App.start(%w{ssh vm1 -- whoami}) }).to eq "vagrant\n"
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# -*- mode: ruby -*-
|
2
|
+
# vi: set ft=ruby :
|
3
|
+
|
4
|
+
Vagrant.configure("2") do |config|
|
5
|
+
|
6
|
+
config.vm.box = "precise64"
|
7
|
+
config.vm.box_url = "http://files.vagrantup.com/precise64.box"
|
8
|
+
|
9
|
+
config.vm.provider :virtualbox do |vb|
|
10
|
+
vb.customize ["modifyvm", :id, "--memory", "128"]
|
11
|
+
end
|
12
|
+
|
13
|
+
config.vm.define :vm1
|
14
|
+
config.vm.define :vm2
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
# -*- mode: ruby -*-
|
2
|
+
# vi: set ft=ruby :
|
3
|
+
|
4
|
+
# Fixture::neveron - all the tests will assume that we never turn on this fixture
|
5
|
+
|
6
|
+
Vagrant.configure('2') do |config|
|
7
|
+
config.vm.box = "precise64"
|
8
|
+
config.vm.box_url = "http://files.vagrantup.com/precise64.box"
|
9
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- mode: ruby -*-
|
2
|
+
# vi: set ft=ruby :
|
3
|
+
|
4
|
+
# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
|
5
|
+
Vagrant.configure("2") do |config|
|
6
|
+
config.vm.box = "precise64"
|
7
|
+
config.vm.box_url = "http://files.vagrantup.com/precise64.box"
|
8
|
+
|
9
|
+
# config.vm.network :private_network, ip: "192.168.33.10"
|
10
|
+
# config.vm.network :public_network
|
11
|
+
# config.ssh.forward_agent = true
|
12
|
+
|
13
|
+
config.vm.provider :virtualbox do |vb|
|
14
|
+
# vgrnt-test is hardcoded in util-spec.rb
|
15
|
+
vb.name = "vgrnt-test"
|
16
|
+
|
17
|
+
vb.customize ["modifyvm", :id, "--memory", "128"]
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module AcceptanceExampleGroup
|
4
|
+
# Captures and returns stream activity during block.
|
5
|
+
# Only supports :stdout and :stdin
|
6
|
+
# Note that it fails to capture output of exec or system calls.
|
7
|
+
def capture(stream)
|
8
|
+
begin
|
9
|
+
stream = stream.to_s
|
10
|
+
eval "$#{stream} = StringIO.new"
|
11
|
+
yield
|
12
|
+
result = eval("$#{stream}").string
|
13
|
+
ensure
|
14
|
+
eval("$#{stream} = #{stream.upcase}")
|
15
|
+
end
|
16
|
+
result
|
17
|
+
end
|
18
|
+
|
19
|
+
def in_vagrant_env_dir(vagrant_homedir, &block)
|
20
|
+
# unless (vagrant_homedir)
|
21
|
+
# vagrant_homedir = './spec/acceptance/fixtures/simple'
|
22
|
+
# end
|
23
|
+
Dir.chdir( vagrant_homedir, &block)
|
24
|
+
end
|
25
|
+
|
26
|
+
def capture_in_vagrant_env(stream=nil, &block)
|
27
|
+
capture(stream) { in_vagrant_env &block }
|
28
|
+
end
|
29
|
+
|
30
|
+
def vagrant_stderr(&block)
|
31
|
+
capture_in_vagrant_env(:stderr, &block)
|
32
|
+
end
|
33
|
+
|
34
|
+
def vagrant_stdout(&block)
|
35
|
+
capture_in_vagrant_env(:stdout, &block)
|
36
|
+
end
|
37
|
+
|
38
|
+
def delete_in_vagrant_env(file)
|
39
|
+
in_vagrant_env { File.delete(file) if File.exists?(file) }
|
40
|
+
end
|
41
|
+
|
42
|
+
def in_vagrant_env(&block)
|
43
|
+
in_vagrant_env_dir(vagrant_path, &block)
|
44
|
+
end
|
45
|
+
|
46
|
+
def vagrant_up(path)
|
47
|
+
in_vagrant_env_dir(path) do
|
48
|
+
s = Vgrnt::Util::VirtualBox::runningMachines()
|
49
|
+
# raise s['default'].inspect
|
50
|
+
unless s && s['default'] && s['default'][:state] == 'running'
|
51
|
+
`VAGRANT_NO_PLUGINS=1 vagrant up`
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def vagrant_destroy(path)
|
57
|
+
in_vagrant_env_dir(path) do
|
58
|
+
s = Vgrnt::Util::VirtualBox::runningMachines()
|
59
|
+
if s && s['default'] && s['default'] && ([nil, "poweroff"].include? s['default'][:state])
|
60
|
+
`VAGRANT_NO_PLUGINS=1 vagrant destroy -f`
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
RSpec.configure do |config|
|
68
|
+
config.include AcceptanceExampleGroup, :type => :acceptance, :example_group => {
|
69
|
+
:file_path => /\bspec\/acceptance\//
|
70
|
+
}
|
71
|
+
end
|
72
|
+
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'acceptance/support/acceptance_helper'
|
2
|
+
|
3
|
+
describe "VBoxManage environment" do
|
4
|
+
# def in_vagrant_env(&block)
|
5
|
+
# in_vagrant_env_dir './spec/acceptance/fixtures/simple', &block
|
6
|
+
# end
|
7
|
+
|
8
|
+
before(:all) { vagrant_up('./spec/acceptance/fixtures/simple') }
|
9
|
+
let(:vagrant_path) { './spec/acceptance/fixtures/simple' }
|
10
|
+
|
11
|
+
it 'ensure we can execute VBoxManage' do
|
12
|
+
return_code = system("VBoxManage list vms > /dev/null")
|
13
|
+
expect(return_code).to eq true
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'ensure vgrnt-test VM is recognized' do
|
17
|
+
expect(`VBoxManage list vms`).to include 'vgrnt-test'
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'ensure vgrnt-test VM is running' do
|
21
|
+
expect(`VBoxManage showvminfo vgrnt-test --machinereadable | grep VMState`).to include '"running"'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
describe Vgrnt::Util::VirtualBox do
|
27
|
+
|
28
|
+
context "when running from ./spec/acceptance/fixtures/simple" do
|
29
|
+
before(:all) { vagrant_up('./spec/acceptance/fixtures/simple') }
|
30
|
+
let(:vagrant_path) { './spec/acceptance/fixtures/simple' }
|
31
|
+
|
32
|
+
describe "::showvminfo" do
|
33
|
+
it 'ensure vgrnt-test VM is running' do
|
34
|
+
expect(Vgrnt::Util::VirtualBox::showvminfo('vgrnt-test')).to include 'UUID'
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "::runningMachines" do
|
39
|
+
it 'ensure .vgrnt directory exists' do
|
40
|
+
expect(in_vagrant_env { File.exists? '.vagrant' }).to be_true
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'ensure vgrnt-test VM is running' do
|
44
|
+
runningmachines = in_vagrant_env { Vgrnt::Util::VirtualBox::runningMachines() }
|
45
|
+
expect(runningmachines['default'][:state]).to eq 'running'
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "::machineSSH" do
|
50
|
+
let :machine_ssh_info do
|
51
|
+
in_vagrant_env { Vgrnt::Util::VirtualBox::machineSSH('default') }
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'extracts SSH hostname' do
|
55
|
+
expect(machine_ssh_info[:ssh_ip]).to eq '127.0.0.1'
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'extracts SSH port, within the 22xx range' do
|
59
|
+
expect(machine_ssh_info[:ssh_port]).to match /^22..$/
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
# RSpec.configure do |c|
|
4
|
+
# c.include Helpers
|
5
|
+
# end
|
6
|
+
|
7
|
+
describe Vgrnt::Util::VirtualBox do
|
8
|
+
describe "::showvminfo_command" do
|
9
|
+
it 'should generate VBoxManage command' do
|
10
|
+
expect(Vgrnt::Util::VirtualBox::showvminfo_command('test')).to eq 'VBoxManage showvminfo test --machinereadable'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/vgrnt.gemspec
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: vgrnt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alex Dergachev
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-
|
11
|
+
date: 2013-11-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|
@@ -52,6 +52,20 @@ dependencies:
|
|
52
52
|
- - '>='
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
55
69
|
description:
|
56
70
|
email: alex@evolvingweb.ca
|
57
71
|
executables:
|
@@ -60,6 +74,7 @@ extensions: []
|
|
60
74
|
extra_rdoc_files: []
|
61
75
|
files:
|
62
76
|
- .gitignore
|
77
|
+
- .travis.yml
|
63
78
|
- CHANGELOG.md
|
64
79
|
- Gemfile
|
65
80
|
- README.md
|
@@ -70,7 +85,17 @@ files:
|
|
70
85
|
- docs/vboxmanage-irregular-commands.txt
|
71
86
|
- lib/vgrnt.rb
|
72
87
|
- lib/vgrnt/base.rb
|
88
|
+
- lib/vgrnt/util/vagrantfile.rb
|
89
|
+
- lib/vgrnt/util/virtualbox.rb
|
73
90
|
- lib/vgrnt/version.rb
|
91
|
+
- spec/acceptance/app_spec.rb
|
92
|
+
- spec/acceptance/fixtures/multivm/Vagrantfile
|
93
|
+
- spec/acceptance/fixtures/neveron/Vagrantfile
|
94
|
+
- spec/acceptance/fixtures/simple/Vagrantfile
|
95
|
+
- spec/acceptance/support/acceptance_helper.rb
|
96
|
+
- spec/acceptance/util_spec.rb
|
97
|
+
- spec/spec_helper.rb
|
98
|
+
- spec/unit/util_spec.rb
|
74
99
|
- vgrnt.gemspec
|
75
100
|
homepage: https://github.com/dergachev/vgrnt
|
76
101
|
licenses:
|
@@ -96,4 +121,12 @@ rubygems_version: 2.0.3
|
|
96
121
|
signing_key:
|
97
122
|
specification_version: 4
|
98
123
|
summary: Speeds up a few common vagrant operations through dirty hacks.
|
99
|
-
test_files:
|
124
|
+
test_files:
|
125
|
+
- spec/acceptance/app_spec.rb
|
126
|
+
- spec/acceptance/fixtures/multivm/Vagrantfile
|
127
|
+
- spec/acceptance/fixtures/neveron/Vagrantfile
|
128
|
+
- spec/acceptance/fixtures/simple/Vagrantfile
|
129
|
+
- spec/acceptance/support/acceptance_helper.rb
|
130
|
+
- spec/acceptance/util_spec.rb
|
131
|
+
- spec/spec_helper.rb
|
132
|
+
- spec/unit/util_spec.rb
|