soloist 1.0.0.pre → 1.0.0
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 +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
|