soloist-rvm 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/.pairs +14 -0
- data/.rspec +2 -0
- data/.rvmrc +1 -0
- data/.travis.yml +8 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +121 -0
- data/Guardfile +17 -0
- data/LICENSE +20 -0
- data/README.md +116 -0
- data/Vagrantfile +21 -0
- data/bin/soloist +4 -0
- data/examples/Guardfile +13 -0
- data/examples/soloistrc +2 -0
- data/lib/soloist.rb +2 -0
- data/lib/soloist/cli.rb +78 -0
- data/lib/soloist/config.rb +118 -0
- data/lib/soloist/remote.rb +66 -0
- data/lib/soloist/remote_config.rb +62 -0
- data/lib/soloist/royal_crown.rb +56 -0
- data/lib/soloist/spotlight.rb +41 -0
- data/lib/soloist/version.rb +3 -0
- data/script/bootstrap.sh +54 -0
- data/script/ci.sh +3 -0
- data/soloist-rvm.gemspec +34 -0
- data/spec/helpers/net_ssh_test_helper.rb +8 -0
- data/spec/helpers/net_ssh_test_patch.rb +38 -0
- data/spec/lib/soloist/cli_spec.rb +171 -0
- data/spec/lib/soloist/config_spec.rb +196 -0
- data/spec/lib/soloist/remote_config_spec.rb +84 -0
- data/spec/lib/soloist/remote_spec.rb +135 -0
- data/spec/lib/soloist/royal_crown_spec.rb +76 -0
- data/spec/lib/soloist/spotlight_spec.rb +66 -0
- data/spec/spec_helper.rb +15 -0
- metadata +297 -0
@@ -0,0 +1,38 @@
|
|
1
|
+
require "net/ssh/test"
|
2
|
+
|
3
|
+
class Net::SSH::Test::Packet
|
4
|
+
alias :original_types :types
|
5
|
+
def types
|
6
|
+
@types ||= case @type
|
7
|
+
when CHANNEL_EXTENDED_DATA then [:long, :long, :string]
|
8
|
+
else original_types
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class Net::SSH::Test::Script
|
14
|
+
def gets_channel_extended_data(channel, data)
|
15
|
+
events << Net::SSH::Test::RemotePacket.new(:channel_extended_data, channel.local_id, 1, data)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class Net::SSH::Test::Channel
|
20
|
+
def gets_extended_data(data)
|
21
|
+
script.gets_channel_extended_data(self, data)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class Net::SSH::Test::Kex
|
26
|
+
def exchange_keys
|
27
|
+
result = Net::SSH::Buffer.from(:byte, NEWKEYS)
|
28
|
+
@connection.send_message(result)
|
29
|
+
|
30
|
+
buffer = @connection.next_message
|
31
|
+
raise Net::SSH::Exception, "expected NEWKEYS" unless buffer.type == NEWKEYS
|
32
|
+
|
33
|
+
{ :session_id => "abc-xyz",
|
34
|
+
:server_key => OpenSSL::PKey::RSA.new(512),
|
35
|
+
:shared_secret => OpenSSL::BN.new("1234567890", 10),
|
36
|
+
:hashing_algorithm => OpenSSL::Digest::SHA1 }
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Soloist::CLI do
|
4
|
+
let(:cli) { Soloist::CLI.new }
|
5
|
+
let(:base_path) { RSpec.configuration.tempdir }
|
6
|
+
let(:soloistrc_path) { File.expand_path("soloistrc", base_path) }
|
7
|
+
|
8
|
+
before do
|
9
|
+
FileUtils.mkdir_p(base_path)
|
10
|
+
Soloist::Config.any_instance.stub(:exec)
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "#chef" do
|
14
|
+
it "receives the outside environment" do
|
15
|
+
FileUtils.touch(soloistrc_path)
|
16
|
+
Dir.chdir(base_path) do
|
17
|
+
ENV["AUTREYISM"] = "pathological-yodeling"
|
18
|
+
cli.soloist_config.should_receive(:exec) do |chef_solo|
|
19
|
+
`#{chef_solo}`.chomp.should == "pathological-yodeling"
|
20
|
+
end
|
21
|
+
cli.soloist_config.stub(:chef_solo).and_return('echo $AUTREYISM')
|
22
|
+
cli.chef
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context "when the soloistrc file does not exist" do
|
27
|
+
it "raises an error" do
|
28
|
+
expect do
|
29
|
+
begin
|
30
|
+
Dir.chdir(base_path) { cli.chef }
|
31
|
+
rescue Soloist::NotFound => e
|
32
|
+
e.message.should == "Could not find soloistrc or .soloistrc"
|
33
|
+
raise
|
34
|
+
end
|
35
|
+
end.to raise_error(Soloist::NotFound)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context "when the soloistrc file exists" do
|
40
|
+
before do
|
41
|
+
File.open(soloistrc_path, "w") do |file|
|
42
|
+
file.write(YAML.dump("recipes" => ["stinky::feet"]))
|
43
|
+
end
|
44
|
+
cli.soloist_config = nil
|
45
|
+
Dir.chdir(base_path) { cli.soloist_config.stub(:exec) }
|
46
|
+
end
|
47
|
+
|
48
|
+
it "runs the proper recipes" do
|
49
|
+
cli.chef
|
50
|
+
cli.soloist_config.royal_crown.recipes.should =~ ["stinky::feet"]
|
51
|
+
end
|
52
|
+
|
53
|
+
context "when a soloistrc_local file exists" do
|
54
|
+
let(:soloistrc_local_path) { File.expand_path("soloistrc_local", base_path) }
|
55
|
+
|
56
|
+
before do
|
57
|
+
File.open(soloistrc_local_path, "w") do |file|
|
58
|
+
file.write(YAML.dump("recipes" => ["stinky::socks"]))
|
59
|
+
end
|
60
|
+
cli.soloist_config = nil
|
61
|
+
Dir.chdir(base_path) { cli.soloist_config.stub(:exec) }
|
62
|
+
end
|
63
|
+
|
64
|
+
it "installs the proper recipes" do
|
65
|
+
cli.chef
|
66
|
+
cli.soloist_config.royal_crown.recipes.should =~ ["stinky::feet", "stinky::socks"]
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context "when the Cheffile does not exist" do
|
71
|
+
it "runs chef" do
|
72
|
+
cli.soloist_config.should_receive(:exec)
|
73
|
+
cli.chef
|
74
|
+
end
|
75
|
+
|
76
|
+
it "does not run librarian" do
|
77
|
+
Librarian::Chef::Cli.should_not_receive(:with_environment)
|
78
|
+
cli.chef
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
context "when the Cheffile exists" do
|
83
|
+
let(:cli_instance) { double(:cli_instance) }
|
84
|
+
|
85
|
+
before { FileUtils.touch(File.expand_path("Cheffile", base_path)) }
|
86
|
+
|
87
|
+
it "runs librarian" do
|
88
|
+
Librarian::Chef::Cli.should_receive(:with_environment).and_yield
|
89
|
+
Librarian::Chef::Cli.should_receive(:new).and_return(cli_instance)
|
90
|
+
cli_instance.should_receive(:install)
|
91
|
+
cli.chef
|
92
|
+
end
|
93
|
+
|
94
|
+
context "when the user is not root" do
|
95
|
+
context "when rvm is not present" do
|
96
|
+
before do
|
97
|
+
cli.soloist_config.stub(:rvm?).and_return(false)
|
98
|
+
end
|
99
|
+
it "creates the cache path using sudo" do
|
100
|
+
cli.soloist_config.should_receive(:exec) do |command|
|
101
|
+
command.should =~ /^sudo -E/
|
102
|
+
end
|
103
|
+
cli.chef
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
context "when rvm is present" do
|
108
|
+
before do
|
109
|
+
cli.soloist_config.stub(:rvm?).and_return(true)
|
110
|
+
end
|
111
|
+
it "creates the cache path using rvmsudo" do
|
112
|
+
cli.soloist_config.should_receive(:exec) do |command|
|
113
|
+
command.should =~ /rvmsudo -E/
|
114
|
+
end
|
115
|
+
cli.chef
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
context "when the user is root" do
|
121
|
+
before { Process.stub(:uid => 0) }
|
122
|
+
|
123
|
+
it "creates the cache path" do
|
124
|
+
cli.soloist_config.should_receive(:exec) do |command|
|
125
|
+
command.should_not =~ /^sudo -E/
|
126
|
+
end
|
127
|
+
cli.chef
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
describe "#run_recipe" do
|
135
|
+
context "when the soloistrc does not exist" do
|
136
|
+
it "raises an error" do
|
137
|
+
expect do
|
138
|
+
Dir.chdir(base_path) { cli.run_recipe("pineapple::wut") }
|
139
|
+
end.to raise_error(Soloist::NotFound)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
context "when the soloistrc file exists" do
|
144
|
+
before do
|
145
|
+
File.open(soloistrc_path, "w") do |file|
|
146
|
+
file.write(YAML.dump("recipes" => ["pineapple::wutcake"]))
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
it "sets a recipe to run" do
|
151
|
+
Dir.chdir(base_path) do
|
152
|
+
cli.should_receive(:chef)
|
153
|
+
cli.run_recipe("angst::teenage", "ennui::default")
|
154
|
+
cli.soloist_config.royal_crown.recipes.should =~ ["angst::teenage", "ennui::default"]
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
describe "#config" do
|
161
|
+
let(:royal_crown) { Soloist::RoyalCrown.new(:node_attributes => {"a" => "b"}) }
|
162
|
+
let(:config) { Soloist::Config.new(royal_crown) }
|
163
|
+
|
164
|
+
before { cli.stub(:soloist_config => config) }
|
165
|
+
|
166
|
+
it "prints the hash render of the RoyalCrown" do
|
167
|
+
Kernel.should_receive(:ap).with({"recipes"=>[], "a" => "b"})
|
168
|
+
cli.config
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
@@ -0,0 +1,196 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Soloist::Config do
|
4
|
+
let(:soloist_rc_path) { File.expand_path("soloistrc", RSpec.configuration.tempdir) }
|
5
|
+
let(:soloist_rc) { Soloist::RoyalCrown.new(:path => soloist_rc_path) }
|
6
|
+
let(:config) { Soloist::Config.new(soloist_rc) }
|
7
|
+
let(:cookbook_path) { File.expand_path("cookbooks", RSpec.configuration.tempdir) }
|
8
|
+
let(:nested_cookbook_path) { File.expand_path("whoa/cookbooks", RSpec.configuration.tempdir) }
|
9
|
+
|
10
|
+
describe "#as_solo_rb" do
|
11
|
+
subject { config.as_solo_rb }
|
12
|
+
|
13
|
+
it { should include 'file_cache_path "/var/chef/cache"' }
|
14
|
+
it { should include %(json_attribs "#{config.node_json_path}") }
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "#cookbook_paths" do
|
18
|
+
subject { config.cookbook_paths }
|
19
|
+
|
20
|
+
context "when the default cookbook path does not exist" do
|
21
|
+
it { should have(0).paths }
|
22
|
+
end
|
23
|
+
|
24
|
+
context "when the default cookbook path exists" do
|
25
|
+
before { FileUtils.mkdir_p(cookbook_path) }
|
26
|
+
|
27
|
+
it { should have(1).path }
|
28
|
+
it { should =~ [cookbook_path] }
|
29
|
+
|
30
|
+
context "when the default cookbook path is specified" do
|
31
|
+
before { soloist_rc.cookbook_paths = [cookbook_path] }
|
32
|
+
|
33
|
+
it { should have(1).path }
|
34
|
+
it { should =~ [cookbook_path] }
|
35
|
+
end
|
36
|
+
|
37
|
+
context "with a specified cookbook path" do
|
38
|
+
before { soloist_rc.cookbook_paths = [nested_cookbook_path] }
|
39
|
+
|
40
|
+
context "when the specified path exists" do
|
41
|
+
before { FileUtils.mkdir_p(nested_cookbook_path) }
|
42
|
+
|
43
|
+
it { should have(2).paths }
|
44
|
+
it { should =~ [cookbook_path, nested_cookbook_path] }
|
45
|
+
|
46
|
+
context "with duplicate cookbook paths" do
|
47
|
+
before { soloist_rc.cookbook_paths = [nested_cookbook_path, nested_cookbook_path] }
|
48
|
+
|
49
|
+
it { should have(2).paths }
|
50
|
+
it { should =~ [cookbook_path, nested_cookbook_path] }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context "when the specified path does not exist" do
|
55
|
+
it { should have(1).path }
|
56
|
+
it { should =~ [cookbook_path] }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context "with relative paths" do
|
62
|
+
before do
|
63
|
+
soloist_rc.cookbook_paths = ["./whoa/cookbooks"]
|
64
|
+
FileUtils.mkdir_p(nested_cookbook_path)
|
65
|
+
end
|
66
|
+
|
67
|
+
it { should have(1).path }
|
68
|
+
it { should =~ [nested_cookbook_path] }
|
69
|
+
end
|
70
|
+
|
71
|
+
context "with unixisms in the cookbook path" do
|
72
|
+
let(:home) { File.expand_path("~") }
|
73
|
+
|
74
|
+
before { soloist_rc.cookbook_paths = ["~"] }
|
75
|
+
|
76
|
+
it { should have(1).path }
|
77
|
+
it { should =~ [home] }
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe "#as_node_json" do
|
82
|
+
let(:soloist_rc) do
|
83
|
+
Soloist::RoyalCrown.new(
|
84
|
+
:path => soloist_rc_path,
|
85
|
+
:recipes => ["waffles"],
|
86
|
+
:node_attributes => { "gargling" => "cool", "birds" => {"nested" => "cheep"} }
|
87
|
+
)
|
88
|
+
end
|
89
|
+
|
90
|
+
describe "node_attributes" do
|
91
|
+
subject { config.as_node_json }
|
92
|
+
|
93
|
+
it { should include "gargling" => "cool" }
|
94
|
+
it { should include "birds" => { "nested" => "cheep" } }
|
95
|
+
end
|
96
|
+
|
97
|
+
describe "recipes" do
|
98
|
+
subject { config.as_node_json["recipes"] }
|
99
|
+
|
100
|
+
it { should have(1).recipe }
|
101
|
+
it { should =~ ["waffles"] }
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
describe "#compiled" do
|
106
|
+
let(:nested) { {} }
|
107
|
+
let(:switch) do
|
108
|
+
{
|
109
|
+
"TONGUES" => {
|
110
|
+
"FINE" => {
|
111
|
+
"recipes" => ["hobo_fist"],
|
112
|
+
"env_variable_switches" => nested
|
113
|
+
}
|
114
|
+
}
|
115
|
+
}
|
116
|
+
end
|
117
|
+
|
118
|
+
before { config.royal_crown.env_variable_switches = switch }
|
119
|
+
|
120
|
+
context "when the switch is inactive" do
|
121
|
+
before { ENV.stub(:[]).and_return("LOLWUT") }
|
122
|
+
|
123
|
+
it "does not merge the attribute" do
|
124
|
+
config.compiled["recipes"].should be_empty
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
context "when a switch is active" do
|
129
|
+
before { ENV.stub(:[]).and_return("FINE") }
|
130
|
+
|
131
|
+
it "merges the attributes" do
|
132
|
+
config.compiled.recipes.should =~ ["hobo_fist"]
|
133
|
+
end
|
134
|
+
|
135
|
+
context "when an inactive switch is nested" do
|
136
|
+
let(:nested) { {"BEANS" => {"EW" => {"recipes" => ["slammin"]}}} }
|
137
|
+
|
138
|
+
it "does not merge the attributes" do
|
139
|
+
config.compiled.recipes.should =~ ["hobo_fist"]
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
context "when an active switch is nested" do
|
144
|
+
let(:nested) { {"BEANS" => {"FINE" => {"recipes" => ["slammin"]}}} }
|
145
|
+
|
146
|
+
it "merges the attributes" do
|
147
|
+
config.compiled.recipes.should =~ ["slammin"]
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
describe "#merge!" do
|
154
|
+
let(:soloist_rc) { Soloist::RoyalCrown.new(:recipes => ["guts"], :node_attributes => {:reliable => "maybe"}) }
|
155
|
+
let(:other_rc) { Soloist::RoyalCrown.new(:recipes => ["chum"], :node_attributes => {:tasty => "maybe"}) }
|
156
|
+
let(:other_config) { Soloist::Config.new(other_rc) }
|
157
|
+
|
158
|
+
it "merges another config into the current one" do
|
159
|
+
config.merge!(other_config)
|
160
|
+
config.royal_crown.recipes.should =~ ["guts", "chum"]
|
161
|
+
config.royal_crown.node_attributes.keys.should =~ [:reliable, :tasty]
|
162
|
+
end
|
163
|
+
|
164
|
+
it "does not trample the other config" do
|
165
|
+
config.merge!(other_config)
|
166
|
+
other_config.royal_crown.recipes.should =~ ["chum"]
|
167
|
+
other_config.royal_crown.node_attributes.should == {:tasty => "maybe"}
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
describe "#log_level" do
|
172
|
+
subject { config.log_level }
|
173
|
+
|
174
|
+
context "when LOG_LEVEL is not set" do
|
175
|
+
it { should == "info" }
|
176
|
+
end
|
177
|
+
|
178
|
+
context "when LOG_LEVEL is set" do
|
179
|
+
before { ENV.stub(:[] => "BEANS") }
|
180
|
+
it { should == "BEANS" }
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
describe "#debug?" do
|
185
|
+
subject { config.debug? }
|
186
|
+
|
187
|
+
context "when log_level is not debug" do
|
188
|
+
it { should_not be }
|
189
|
+
end
|
190
|
+
|
191
|
+
context "when log_level is debug" do
|
192
|
+
before { config.stub(:log_level => "debug") }
|
193
|
+
it { should be }
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Soloist::RemoteConfig do
|
4
|
+
let(:royal_crown_path) { File.expand_path("soloistrc", RSpec.configuration.tempdir) }
|
5
|
+
let(:royal_crown) { Soloist::RoyalCrown.new(:path => royal_crown_path) }
|
6
|
+
let(:remote) { Soloist::Remote.new("user", "host", "key") }
|
7
|
+
let(:remote_config) { Soloist::RemoteConfig.new(royal_crown, remote) }
|
8
|
+
|
9
|
+
before { remote.stub(:backtick => "", :system => 0) }
|
10
|
+
|
11
|
+
def commands_for(method)
|
12
|
+
[].tap do |commands|
|
13
|
+
remote.stub(:system) { |c| commands << c; 0 }
|
14
|
+
remote.stub(:backtick) { |c| commands << c; "" }
|
15
|
+
remote_config.send(method)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "#run_chef" do
|
20
|
+
it "runs chef" do
|
21
|
+
commands_for(:run_chef).last.should include "chef-solo"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "#solo_rb_path" do
|
26
|
+
it "sets the path to /etc/chef/solo.rb" do
|
27
|
+
remote_config.solo_rb_path.should == "/etc/chef/solo.rb"
|
28
|
+
end
|
29
|
+
|
30
|
+
it "sets up solo.rb remotely" do
|
31
|
+
commands_for(:solo_rb_path).last.should =~ /sudo -E tee \/etc\/chef\/solo\.rb$/
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "#node_json_path" do
|
36
|
+
it "sets the path" do
|
37
|
+
remote_config.node_json_path.should == "/etc/chef/node.json"
|
38
|
+
end
|
39
|
+
|
40
|
+
it "sets up node.json remotely" do
|
41
|
+
commands_for(:node_json_path).last.should =~ /sudo -E tee \/etc\/chef\/node\.json$/
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "#chef_config_path" do
|
46
|
+
it "sets the path" do
|
47
|
+
remote_config.chef_config_path.should == "/etc/chef"
|
48
|
+
end
|
49
|
+
|
50
|
+
it "creates the path remotely" do
|
51
|
+
commands_for(:chef_config_path).tap do |commands|
|
52
|
+
commands.should have(1).command
|
53
|
+
commands.first.should =~ /mkdir .*? -p \/etc\/chef$/
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe "#chef_cache_path" do
|
59
|
+
it "sets the path" do
|
60
|
+
remote_config.chef_cache_path.should == "/var/chef/cache"
|
61
|
+
end
|
62
|
+
|
63
|
+
it "creates the path remotely" do
|
64
|
+
commands_for(:chef_cache_path).tap do |commands|
|
65
|
+
commands.should have(1).command
|
66
|
+
commands.first.should =~ /mkdir .*? -p \/var\/chef\/cache$/
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe "#cookbook_paths" do
|
72
|
+
it "sets the path" do
|
73
|
+
remote_config.cookbook_paths.should have(1).path
|
74
|
+
remote_config.cookbook_paths.should =~ ["/var/chef/cookbooks"]
|
75
|
+
end
|
76
|
+
|
77
|
+
it "creates the path remotely" do
|
78
|
+
commands_for(:cookbook_paths).tap do |commands|
|
79
|
+
commands.should have(1).command
|
80
|
+
commands.first.should =~ /mkdir .*? -p \/var\/chef\/cookbooks$/
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|