soloist-rvm 0.0.1
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.
- 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
|