soloist 1.0.0.pre → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/.pairs +14 -0
- data/.rvmrc +1 -1
- data/.travis.yml +0 -2
- data/Gemfile.lock +23 -9
- data/Guardfile +6 -1
- data/{MIT-LICENSE → LICENSE} +20 -20
- data/{README.markdown → README.md} +2 -2
- data/Vagrantfile +21 -0
- data/examples/Guardfile +13 -0
- data/examples/soloistrc +2 -0
- data/lib/soloist/cli.rb +39 -44
- data/lib/soloist/config.rb +76 -13
- data/lib/soloist/remote.rb +66 -0
- data/lib/soloist/remote_config.rb +62 -0
- data/lib/soloist/version.rb +1 -1
- data/script/bootstrap.sh +54 -0
- data/soloist.gemspec +3 -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 +72 -51
- data/spec/lib/soloist/config_spec.rb +147 -58
- data/spec/lib/soloist/remote_config_spec.rb +84 -0
- data/spec/lib/soloist/remote_spec.rb +135 -0
- data/spec/lib/soloist/spotlight_spec.rb +2 -3
- data/spec/spec_helper.rb +9 -0
- metadata +70 -7
data/.gitignore
CHANGED
data/.pairs
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# This file is managed by chef! Override node.default["git_pairs_authors"] in attributes/git_pairs.rb to modify.
|
2
|
+
|
3
|
+
pairs:
|
4
|
+
mk: Matthew Kocher; mkocher
|
5
|
+
bc: Brian Cunnie; cunnie
|
6
|
+
dr: Doc Ritezel; doc
|
7
|
+
ap: aram price; aram
|
8
|
+
sc: Santa Claus
|
9
|
+
|
10
|
+
email:
|
11
|
+
prefix: pair
|
12
|
+
domain: pivotallabs.com
|
13
|
+
|
14
|
+
global: true
|
data/.rvmrc
CHANGED
@@ -1 +1 @@
|
|
1
|
-
rvm --create use ruby-1.9.3-
|
1
|
+
rvm --create use ruby-1.9.3-p327@soloist
|
data/.travis.yml
CHANGED
data/Gemfile.lock
CHANGED
@@ -2,15 +2,18 @@ PATH
|
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
4
|
soloist (1.0.0.pre)
|
5
|
+
awesome_print
|
5
6
|
chef
|
6
7
|
hashie
|
7
8
|
librarian
|
9
|
+
net-ssh
|
8
10
|
thor
|
9
11
|
|
10
12
|
GEM
|
11
13
|
remote: http://rubygems.org/
|
12
14
|
specs:
|
13
15
|
archive-tar-minitar (0.5.2)
|
16
|
+
awesome_print (1.1.0)
|
14
17
|
bunny (0.7.9)
|
15
18
|
chef (10.16.2)
|
16
19
|
bunny (>= 0.6.0, < 0.8.0)
|
@@ -42,19 +45,22 @@ GEM
|
|
42
45
|
guard-bundler (1.0.0)
|
43
46
|
bundler (~> 1.0)
|
44
47
|
guard (~> 1.1)
|
45
|
-
guard-rspec (2.
|
48
|
+
guard-rspec (2.2.0)
|
46
49
|
guard (>= 1.1)
|
47
50
|
rspec (~> 2.11)
|
51
|
+
guard-shell (0.5.1)
|
52
|
+
guard (>= 1.1.0)
|
48
53
|
hashie (1.2.0)
|
49
54
|
highline (1.6.15)
|
50
55
|
ipaddress (0.8.0)
|
51
56
|
json (1.6.1)
|
57
|
+
json (1.6.1-java)
|
52
58
|
librarian (0.0.25)
|
53
59
|
archive-tar-minitar (>= 0.5.2)
|
54
60
|
chef (>= 0.10)
|
55
61
|
highline
|
56
62
|
thor (~> 0.15)
|
57
|
-
listen (0.
|
63
|
+
listen (0.6.0)
|
58
64
|
lumberjack (1.0.2)
|
59
65
|
method_source (0.8.1)
|
60
66
|
mime-types (1.19)
|
@@ -83,18 +89,24 @@ GEM
|
|
83
89
|
coderay (~> 1.0.5)
|
84
90
|
method_source (~> 0.8)
|
85
91
|
slop (~> 3.3.1)
|
92
|
+
pry (0.9.10-java)
|
93
|
+
coderay (~> 1.0.5)
|
94
|
+
method_source (~> 0.8)
|
95
|
+
slop (~> 3.3.1)
|
96
|
+
spoon (~> 0.0)
|
86
97
|
rb-fsevent (0.9.2)
|
87
98
|
rest-client (1.6.7)
|
88
99
|
mime-types (>= 1.16)
|
89
|
-
rspec (2.
|
90
|
-
rspec-core (~> 2.
|
91
|
-
rspec-expectations (~> 2.
|
92
|
-
rspec-mocks (~> 2.
|
93
|
-
rspec-core (2.
|
94
|
-
rspec-expectations (2.
|
100
|
+
rspec (2.12.0)
|
101
|
+
rspec-core (~> 2.12.0)
|
102
|
+
rspec-expectations (~> 2.12.0)
|
103
|
+
rspec-mocks (~> 2.12.0)
|
104
|
+
rspec-core (2.12.0)
|
105
|
+
rspec-expectations (2.12.0)
|
95
106
|
diff-lcs (~> 1.1.3)
|
96
|
-
rspec-mocks (2.
|
107
|
+
rspec-mocks (2.12.0)
|
97
108
|
slop (3.3.3)
|
109
|
+
spoon (0.0.1)
|
98
110
|
systemu (2.5.2)
|
99
111
|
terminal-notifier-guard (1.5.3)
|
100
112
|
thor (0.16.0)
|
@@ -105,12 +117,14 @@ GEM
|
|
105
117
|
yajl-ruby (1.1.0)
|
106
118
|
|
107
119
|
PLATFORMS
|
120
|
+
java
|
108
121
|
ruby
|
109
122
|
|
110
123
|
DEPENDENCIES
|
111
124
|
gem-release
|
112
125
|
guard-bundler
|
113
126
|
guard-rspec
|
127
|
+
guard-shell
|
114
128
|
rb-fsevent
|
115
129
|
rspec
|
116
130
|
soloist!
|
data/Guardfile
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
guard 'rspec', cli
|
3
|
+
guard 'rspec', :cli => '--fail-fast --tag ~@slow:true' do
|
4
4
|
watch(%r{^spec/.+_spec\.rb$})
|
5
5
|
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
6
6
|
watch('spec/spec_helper.rb') { "spec" }
|
@@ -10,3 +10,8 @@ guard 'bundler' do
|
|
10
10
|
watch('Gemfile')
|
11
11
|
watch('soloist.gemspec')
|
12
12
|
end
|
13
|
+
|
14
|
+
guard 'shell' do
|
15
|
+
watch('Vagrantfile') { system("unset RUBYOPT; vagrant provision") }
|
16
|
+
watch('script/bootstrap.sh') { system("unset RUBYOPT; vagrant provision") }
|
17
|
+
end
|
data/{MIT-LICENSE → LICENSE}
RENAMED
@@ -1,20 +1,20 @@
|
|
1
|
-
# Copyright (c)
|
2
|
-
#
|
3
|
-
# Permission is hereby granted, free of charge, to any person obtaining
|
4
|
-
# a copy of this software and associated documentation files (the
|
5
|
-
# "Software"), to deal in the Software without restriction, including
|
6
|
-
# without limitation the rights to use, copy, modify, merge, publish,
|
7
|
-
# distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
-
# permit persons to whom the Software is furnished to do so, subject to
|
9
|
-
# the following conditions:
|
10
|
-
#
|
11
|
-
# The above copyright notice and this permission notice shall be
|
12
|
-
# included in all copies or substantial portions of the Software.
|
13
|
-
#
|
14
|
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
-
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
-
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
-
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
-
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
-
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
-
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
1
|
+
# Copyright (c) 2012 Matthew Kocher and Doc Ritezel
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
# a copy of this software and associated documentation files (the
|
5
|
+
# "Software"), to deal in the Software without restriction, including
|
6
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
# the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be
|
12
|
+
# included in all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
@@ -94,7 +94,7 @@ If we set `MEGA_PRODUCTION=godspeed`, the `embarrassment::parental` recipe is no
|
|
94
94
|
|
95
95
|
Soloist can also run one-off recipes:
|
96
96
|
|
97
|
-
$ soloist
|
97
|
+
$ soloist run_recipe lice::box
|
98
98
|
Installing lice (1.0.0)
|
99
99
|
… chef output …
|
100
100
|
INFO: Run List expands to [lice::box]
|
@@ -113,4 +113,4 @@ Soloist runs `chef-solo` at log level `info` by default, which is helpful when y
|
|
113
113
|
License
|
114
114
|
=======
|
115
115
|
|
116
|
-
See
|
116
|
+
See LICENSE for details.
|
data/Vagrantfile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
Vagrant::Config.run do |config|
|
4
|
+
ssh_key = File.read(File.expand_path("~/.ssh/id_rsa.pub"))
|
5
|
+
|
6
|
+
config.vm.box = "precise64"
|
7
|
+
config.vm.box_url = "http://files.vagrantup.com/#{config.vm.box}.box"
|
8
|
+
config.vm.network :hostonly, "192.168.6.66"
|
9
|
+
|
10
|
+
config.vm.provision :shell, :inline => "test -d /etc/skel/.ssh || mkdir /etc/skel/.ssh"
|
11
|
+
config.vm.provision :shell do |shell|
|
12
|
+
shell.inline = "echo $@ | tee /etc/skel/.ssh/authorized_keys"
|
13
|
+
shell.args = ssh_key
|
14
|
+
end
|
15
|
+
config.vm.provision :shell do |shell|
|
16
|
+
shell.path = File.expand_path("../script/bootstrap.sh", __FILE__)
|
17
|
+
shell.args = `whoami`.chomp
|
18
|
+
end
|
19
|
+
config.vm.provision :shell, :inline => "bash -lc 'rvm use --install --default ruby-1.9.3'"
|
20
|
+
config.vm.provision :shell, :inline => "bash -lc 'gem install chef --no-rdoc --no-ri'"
|
21
|
+
end
|
data/examples/Guardfile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
guard 'bundler' do
|
4
|
+
watch('../Gemfile')
|
5
|
+
watch('../soloist.gemspec')
|
6
|
+
end
|
7
|
+
|
8
|
+
guard 'shell' do
|
9
|
+
watch('soloistrc') { system("../bin/soloist -r 192.168.6.66") }
|
10
|
+
watch(%r{^cookbooks/(.+)\.rb$}) { system("unset RUBYOPT; bundle exec ../bin/soloist -r 192.168.6.66") }
|
11
|
+
watch('Vagrantfile') { system("unset RUBYOPT; vagrant provision") }
|
12
|
+
watch('script/bootstrap.sh') { system("unset RUBYOPT; vagrant provision") }
|
13
|
+
end
|
data/examples/soloistrc
ADDED
data/lib/soloist/cli.rb
CHANGED
@@ -1,47 +1,36 @@
|
|
1
1
|
require "librarian/chef/cli"
|
2
|
+
require "soloist/remote_config"
|
2
3
|
require "soloist/spotlight"
|
3
|
-
require "
|
4
|
-
require "tempfile"
|
4
|
+
require "awesome_print"
|
5
5
|
require "thor"
|
6
6
|
|
7
7
|
module Soloist
|
8
8
|
class CLI < Thor
|
9
|
+
attr_writer :soloist_config
|
9
10
|
default_task :chef
|
10
11
|
|
11
|
-
desc "chef", "
|
12
|
+
desc "chef", "Run chef-solo"
|
13
|
+
method_option :remote, :aliases => "-r", :desc => "Run chef-solo on user@host"
|
14
|
+
method_option :identity, :aliases => "-i", :desc => "The SSH identity file"
|
12
15
|
def chef
|
13
|
-
ensure_chef_cache_path
|
14
|
-
write_solo_rb
|
15
|
-
write_node_json
|
16
16
|
install_cookbooks if cheffile_exists?
|
17
|
-
|
17
|
+
soloist_config.run_chef
|
18
18
|
end
|
19
19
|
|
20
|
-
desc "run_recipe", "
|
21
|
-
|
22
|
-
|
20
|
+
desc "run_recipe [cookbook::recipe, ...]", "Run individual recipes"
|
21
|
+
method_option :remote, :aliases => "-r", :desc => "Run recipes on user@host"
|
22
|
+
method_option :identity, :aliases => "-i", :desc => "The SSH identity file"
|
23
|
+
def run_recipe(*recipes)
|
24
|
+
soloist_config.royal_crown.recipes = recipes
|
23
25
|
chef
|
24
26
|
end
|
25
27
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
File.open(solo_rb.path, "w") { |f| f.write(content) }
|
31
|
-
end
|
32
|
-
|
33
|
-
def write_node_json
|
34
|
-
content = config.as_json
|
35
|
-
puts JSON.pretty_generate(content) if log_level == "debug"
|
36
|
-
File.open(node_json.path, "w") { |f| f.write(JSON.dump(content)) }
|
37
|
-
end
|
38
|
-
|
39
|
-
def ensure_chef_cache_path
|
40
|
-
unless File.directory?("/var/chef/cache")
|
41
|
-
system("sudo mkdir -p /var/chef/cache")
|
42
|
-
end
|
43
|
-
end
|
28
|
+
desc "config", "Dumps configuration data for Soloist"
|
29
|
+
def config
|
30
|
+
Kernel.ap(soloist_config.as_node_json)
|
31
|
+
end
|
44
32
|
|
33
|
+
no_tasks do
|
45
34
|
def install_cookbooks
|
46
35
|
Dir.chdir(File.dirname(rc_path)) do
|
47
36
|
Librarian::Chef::Cli.with_environment do
|
@@ -50,34 +39,40 @@ module Soloist
|
|
50
39
|
end
|
51
40
|
end
|
52
41
|
|
53
|
-
def
|
54
|
-
@
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
42
|
+
def soloist_config
|
43
|
+
@soloist_config ||= if options[:remote]
|
44
|
+
Soloist::RemoteConfig.from_file(rc_path, remote)
|
45
|
+
else
|
46
|
+
Soloist::Config.from_file(rc_path)
|
47
|
+
end.tap do |config|
|
48
|
+
config.merge!(rc_local) if rc_local_path
|
49
|
+
end
|
59
50
|
end
|
60
51
|
end
|
61
52
|
|
62
53
|
private
|
63
|
-
def
|
64
|
-
|
65
|
-
end
|
66
|
-
|
67
|
-
def log_level
|
68
|
-
ENV["LOG_LEVEL"] || "info"
|
54
|
+
def rc_local
|
55
|
+
Soloist::Config.from_file(rc_local_path)
|
69
56
|
end
|
70
57
|
|
71
|
-
def
|
72
|
-
@
|
58
|
+
def remote
|
59
|
+
@remote ||= if options[:identity]
|
60
|
+
Soloist::Remote.from_uri(options[:remote], options[:identity])
|
61
|
+
else
|
62
|
+
Soloist::Remote.from_uri(options[:remote])
|
63
|
+
end
|
73
64
|
end
|
74
65
|
|
75
|
-
def
|
76
|
-
|
66
|
+
def cheffile_exists?
|
67
|
+
File.exists?(File.expand_path("../Cheffile", rc_path))
|
77
68
|
end
|
78
69
|
|
79
70
|
def rc_path
|
80
71
|
@rc_path ||= Soloist::Spotlight.find!("soloistrc", ".soloistrc")
|
81
72
|
end
|
73
|
+
|
74
|
+
def rc_local_path
|
75
|
+
@rc_local_path ||= Soloist::Spotlight.find("soloistrc_local", ".soloistrc_local")
|
76
|
+
end
|
82
77
|
end
|
83
78
|
end
|
data/lib/soloist/config.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
require "soloist/royal_crown"
|
2
|
+
require "tempfile"
|
2
3
|
|
3
4
|
module Soloist
|
4
5
|
class Config
|
6
|
+
attr_writer :solo_rb_path, :node_json_path
|
5
7
|
attr_reader :royal_crown
|
6
8
|
|
7
9
|
def self.from_file(royal_crown_path)
|
@@ -13,21 +15,66 @@ module Soloist
|
|
13
15
|
@royal_crown = royal_crown
|
14
16
|
end
|
15
17
|
|
18
|
+
def run_chef
|
19
|
+
exec(conditional_sudo("bash -c '#{chef_solo}'"))
|
20
|
+
end
|
21
|
+
|
22
|
+
def chef_solo
|
23
|
+
"chef-solo -c '#{solo_rb_path}' -l '#{log_level}'"
|
24
|
+
end
|
25
|
+
|
16
26
|
def as_solo_rb
|
17
|
-
|
18
|
-
|
27
|
+
<<-SOLO_RB
|
28
|
+
file_cache_path "#{chef_cache_path}"
|
29
|
+
cookbook_path #{cookbook_paths.inspect}
|
30
|
+
json_attribs "#{node_json_path}"
|
31
|
+
SOLO_RB
|
32
|
+
end
|
33
|
+
|
34
|
+
def as_node_json
|
35
|
+
compiled.node_attributes.to_hash.merge({ "recipes" => compiled.recipes })
|
36
|
+
end
|
37
|
+
|
38
|
+
def chef_cache_path
|
39
|
+
"/var/chef/cache".tap do |cache_path|
|
40
|
+
system(conditional_sudo("mkdir -p #{cache_path}")) \
|
41
|
+
unless File.directory?(cache_path)
|
19
42
|
end
|
20
|
-
"cookbook_path #{paths.inspect}"
|
21
43
|
end
|
22
44
|
|
23
|
-
def
|
24
|
-
|
25
|
-
|
26
|
-
|
45
|
+
def cookbook_paths
|
46
|
+
([royal_crown_cookbooks_directory] + compiled.cookbook_paths).map do |path|
|
47
|
+
File.expand_path(path, royal_crown_path)
|
48
|
+
end.uniq.select do |path|
|
49
|
+
File.directory?(path)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def solo_rb_path
|
54
|
+
@solo_rb_path ||= Tempfile.new(["solo", ".rb"]).tap do |file|
|
55
|
+
puts as_solo_rb if debug?
|
56
|
+
file.write(as_solo_rb)
|
57
|
+
file.close
|
58
|
+
end.path
|
59
|
+
end
|
60
|
+
|
61
|
+
def node_json_path
|
62
|
+
@node_json_path ||= Tempfile.new(["node", ".json"]).tap do |file|
|
63
|
+
puts JSON.pretty_generate(as_node_json) if debug?
|
64
|
+
file.write(JSON.dump(as_node_json))
|
65
|
+
file.close
|
66
|
+
end.path
|
27
67
|
end
|
28
68
|
|
29
|
-
def
|
30
|
-
|
69
|
+
def merge!(other)
|
70
|
+
royal_crown.recipes += other.royal_crown.recipes
|
71
|
+
royal_crown.cookbook_paths += other.royal_crown.cookbook_paths
|
72
|
+
royal_crown.node_attributes.merge!(other.royal_crown.node_attributes)
|
73
|
+
royal_crown.env_variable_switches.merge!(other.royal_crown.env_variable_switches)
|
74
|
+
end
|
75
|
+
|
76
|
+
def compiled
|
77
|
+
@compiled ||= royal_crown.dup.tap do |rc|
|
31
78
|
while rc["env_variable_switches"]
|
32
79
|
rc.delete("env_variable_switches").each do |variable, switch|
|
33
80
|
switch.each do |value, inner|
|
@@ -38,13 +85,29 @@ module Soloist
|
|
38
85
|
end
|
39
86
|
end
|
40
87
|
|
88
|
+
def log_level
|
89
|
+
ENV["LOG_LEVEL"] || "info"
|
90
|
+
end
|
91
|
+
|
92
|
+
def debug?
|
93
|
+
log_level == "debug"
|
94
|
+
end
|
95
|
+
|
41
96
|
private
|
42
|
-
def
|
43
|
-
|
97
|
+
def conditional_sudo(command)
|
98
|
+
root? ? command : "sudo -E #{command}"
|
44
99
|
end
|
45
100
|
|
46
|
-
def
|
47
|
-
|
101
|
+
def root?
|
102
|
+
Process.uid == 0
|
103
|
+
end
|
104
|
+
|
105
|
+
def royal_crown_cookbooks_directory
|
106
|
+
File.expand_path("cookbooks", royal_crown_path)
|
107
|
+
end
|
108
|
+
|
109
|
+
def royal_crown_path
|
110
|
+
File.dirname(royal_crown.path)
|
48
111
|
end
|
49
112
|
end
|
50
113
|
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require "net/ssh"
|
2
|
+
require "shellwords"
|
3
|
+
require "etc"
|
4
|
+
|
5
|
+
module Soloist
|
6
|
+
class RemoteError < RuntimeError; end
|
7
|
+
|
8
|
+
class Remote
|
9
|
+
attr_reader :user, :host, :key, :timeout, :stdout, :stderr, :exitstatus
|
10
|
+
attr_writer :connection
|
11
|
+
|
12
|
+
def self.from_uri(uri, key = "~/.ssh/id_rsa")
|
13
|
+
parsed = URI.parse("ssh://#{uri}")
|
14
|
+
new(parsed.user || Etc.getlogin, parsed.host, key)
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(user, host, key, options = {})
|
18
|
+
@user = user
|
19
|
+
@host = host
|
20
|
+
@key = key
|
21
|
+
@timeout = options[:timeout] || 10000
|
22
|
+
end
|
23
|
+
|
24
|
+
def backtick(command)
|
25
|
+
@stdout = ""
|
26
|
+
@stderr = ""
|
27
|
+
exec(command)
|
28
|
+
@stdout
|
29
|
+
end
|
30
|
+
|
31
|
+
def system(command)
|
32
|
+
@stdout = STDOUT
|
33
|
+
@stderr = STDERR
|
34
|
+
exec(command)
|
35
|
+
exitstatus
|
36
|
+
end
|
37
|
+
|
38
|
+
def system!(*command)
|
39
|
+
system(*command).tap do |status|
|
40
|
+
raise RemoteError.new("#{command.join(" ")} exited #{status}") unless status == 0
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def upload(from, to, opts = "--exclude .git")
|
45
|
+
Kernel.system("rsync -e 'ssh -i #{key}' -avz --delete #{from} #{user}@#{host}:#{to} #{opts}")
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
def connection
|
50
|
+
@connection ||= Net::SSH.start(host, user, :keys => [key], :timeout => timeout)
|
51
|
+
end
|
52
|
+
|
53
|
+
def exec(*command)
|
54
|
+
connection.open_channel do |channel|
|
55
|
+
channel.exec(*command) do |stream, success|
|
56
|
+
raise RemoteError.new("Could not run #{command.join(" ")}") unless success
|
57
|
+
stream.on_data { |_, data| stdout << data }
|
58
|
+
stream.on_extended_data { |_, type, data| stderr << data }
|
59
|
+
stream.on_request("exit-status") { |_, data| @exitstatus = data.read_long }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
connection.loop
|
63
|
+
@exitstatus ||= 0
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require "soloist/config"
|
2
|
+
require "soloist/remote"
|
3
|
+
|
4
|
+
module Soloist
|
5
|
+
class RemoteConfig < Config
|
6
|
+
attr_reader :remote
|
7
|
+
|
8
|
+
def self.from_file(royal_crown_path, remote)
|
9
|
+
rc = Soloist::RoyalCrown.from_file(royal_crown_path)
|
10
|
+
new(rc, remote)
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(royal_crown, remote)
|
14
|
+
@royal_crown = royal_crown
|
15
|
+
@remote = remote
|
16
|
+
end
|
17
|
+
|
18
|
+
def run_chef
|
19
|
+
remote.system!(conditional_sudo(%(/bin/bash -lc "#{chef_solo}")))
|
20
|
+
end
|
21
|
+
|
22
|
+
def node_json_path
|
23
|
+
@node_json_path ||= File.expand_path("node.json", chef_config_path).tap do |path|
|
24
|
+
remote.system!(%(echo '#{JSON.dump(as_node_json)}' | #{conditional_sudo("tee #{path}")}))
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def solo_rb_path
|
29
|
+
@solo_rb_path ||= File.expand_path("solo.rb", chef_config_path).tap do |path|
|
30
|
+
remote.system!(%(echo '#{as_solo_rb}' | #{conditional_sudo("tee #{path}")}))
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def chef_cache_path
|
35
|
+
@chef_cache_path ||= "/var/chef/cache".tap do |cache_path|
|
36
|
+
remote.system!(conditional_sudo("/bin/mkdir -m 777 -p #{cache_path}"))
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def chef_config_path
|
41
|
+
@chef_config_path ||= "/etc/chef".tap do |path|
|
42
|
+
remote.system!(conditional_sudo("/bin/mkdir -m 777 -p #{path}"))
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def cookbook_paths
|
47
|
+
@cookbook_paths ||= ["/var/chef/cookbooks".tap do |remote_path|
|
48
|
+
remote.system!(conditional_sudo("/bin/mkdir -m 777 -p #{remote_path}"))
|
49
|
+
super.each { |path| remote.upload("#{path}/", remote_path) }
|
50
|
+
end]
|
51
|
+
end
|
52
|
+
|
53
|
+
protected
|
54
|
+
def conditional_sudo(command)
|
55
|
+
root? ? command : "/usr/bin/sudo -E #{command}"
|
56
|
+
end
|
57
|
+
|
58
|
+
def root?
|
59
|
+
remote.user == "root"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
data/lib/soloist/version.rb
CHANGED
data/script/bootstrap.sh
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
set -e
|
4
|
+
|
5
|
+
echo "Updating package list"
|
6
|
+
sudo apt-get update > /dev/null
|
7
|
+
|
8
|
+
echo "Ensuring curl is available"
|
9
|
+
sudo apt-get install -y curl > /dev/null
|
10
|
+
|
11
|
+
echo "Setting up RVM"
|
12
|
+
|
13
|
+
user=$1
|
14
|
+
[ -z "$user" ] && user="ubuntu"
|
15
|
+
|
16
|
+
test -d /usr/local/rvm || curl -L https://get.rvm.io | sudo bash -s stable
|
17
|
+
|
18
|
+
test -e /usr/local/rvm || sudo tee /etc/profile.d/rvm.sh > /dev/null <<RVMSH_CONTENT
|
19
|
+
[[ -s "/usr/local/rvm/scripts/rvm" ]] && source "/usr/local/rvm/scripts/rvm"
|
20
|
+
RVMSH_CONTENT
|
21
|
+
|
22
|
+
test -x /usr/local/rvm || sudo chmod +x /etc/profile.d/rvm.sh
|
23
|
+
|
24
|
+
grep "^$user:" /etc/passwd > /dev/null || sudo useradd -m $user -G sudo,rvm,admin -s /bin/bash
|
25
|
+
|
26
|
+
test -e /etc/rvmrc || sudo tee /etc/rvmrc > /dev/null <<RVMRC_CONTENTS
|
27
|
+
rvm_install_on_use_flag=1
|
28
|
+
rvm_trust_rvmrcs_flag=1
|
29
|
+
rvm_gemset_create_on_use_flag=1
|
30
|
+
RVMRC_CONTENTS
|
31
|
+
|
32
|
+
echo "Detecting RVM requirements"
|
33
|
+
|
34
|
+
bash -lc 'rvm requirements' | tee /tmp/rvm-requirements > /dev/null
|
35
|
+
packages=`grep " ruby: /usr/bin/apt-get install" /tmp/rvm-requirements | sed "s/ ruby: \/usr\/bin\/apt-get install //g"`
|
36
|
+
|
37
|
+
echo "Detected RVM requirements: $packages"
|
38
|
+
|
39
|
+
selections=`dpkg --get-selections`
|
40
|
+
for package in $packages
|
41
|
+
do
|
42
|
+
if ! echo "$selections" | grep "^$package\s" > /dev/null
|
43
|
+
then
|
44
|
+
to_install="$to_install $package"
|
45
|
+
fi
|
46
|
+
done
|
47
|
+
|
48
|
+
if [ -z "$to_install" ]
|
49
|
+
then
|
50
|
+
echo "Satisfied RVM requirements"
|
51
|
+
else
|
52
|
+
echo "Installing missing RVM requirements: $to_install"
|
53
|
+
sudo apt-get install -y $to_install
|
54
|
+
fi
|