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,118 @@
|
|
1
|
+
require "soloist/royal_crown"
|
2
|
+
require "tempfile"
|
3
|
+
|
4
|
+
module Soloist
|
5
|
+
class Config
|
6
|
+
attr_writer :solo_rb_path, :node_json_path
|
7
|
+
attr_reader :royal_crown
|
8
|
+
|
9
|
+
def self.from_file(royal_crown_path)
|
10
|
+
rc = Soloist::RoyalCrown.from_file(royal_crown_path)
|
11
|
+
new(rc)
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(royal_crown)
|
15
|
+
@royal_crown = royal_crown
|
16
|
+
end
|
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
|
+
|
26
|
+
def as_solo_rb
|
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)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
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
|
67
|
+
end
|
68
|
+
|
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|
|
78
|
+
while rc["env_variable_switches"]
|
79
|
+
rc.delete("env_variable_switches").each do |variable, switch|
|
80
|
+
switch.each do |value, inner|
|
81
|
+
rc.merge!(inner) if ENV[variable] == value
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def log_level
|
89
|
+
ENV["LOG_LEVEL"] || "info"
|
90
|
+
end
|
91
|
+
|
92
|
+
def debug?
|
93
|
+
log_level == "debug"
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
def conditional_sudo(command)
|
98
|
+
sudo_command = rvm? ? "rvmsudo_secure_path=1 rvmsudo" : "sudo"
|
99
|
+
root? ? command : "#{sudo_command} -E #{command}"
|
100
|
+
end
|
101
|
+
|
102
|
+
def root?
|
103
|
+
Process.uid == 0
|
104
|
+
end
|
105
|
+
|
106
|
+
def rvm?
|
107
|
+
`which rvm`.length > 0
|
108
|
+
end
|
109
|
+
|
110
|
+
def royal_crown_cookbooks_directory
|
111
|
+
File.expand_path("cookbooks", royal_crown_path)
|
112
|
+
end
|
113
|
+
|
114
|
+
def royal_crown_path
|
115
|
+
File.dirname(royal_crown.path)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
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
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require "hashie"
|
2
|
+
|
3
|
+
module Soloist
|
4
|
+
class RoyalCrown < Hashie::Trash
|
5
|
+
property :path
|
6
|
+
property :recipes, :default => []
|
7
|
+
property :cookbook_paths, :default => []
|
8
|
+
property :node_attributes, :default => Hashie::Mash.new,
|
9
|
+
:transform_with => lambda { |v| Hashie::Mash.new(v) }
|
10
|
+
property :env_variable_switches, :default => Hashie::Mash.new,
|
11
|
+
:transform_with => lambda { |v| Hashie::Mash.new(v) }
|
12
|
+
|
13
|
+
def node_attributes=(hash)
|
14
|
+
self["node_attributes"] = Hashie::Mash.new(hash)
|
15
|
+
end
|
16
|
+
|
17
|
+
def env_variable_switches=(hash)
|
18
|
+
self["env_variable_switches"] = Hashie::Mash.new(hash)
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_yaml
|
22
|
+
to_hash.tap do |hash|
|
23
|
+
hash.delete("path")
|
24
|
+
self.class.nilable_properties.each { |k| hash[k] = nil if hash[k].empty? }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def save
|
29
|
+
return self unless path
|
30
|
+
File.open(path, "w+") { |file| file.write(YAML.dump(to_yaml)) }
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
def reload
|
35
|
+
self.class.from_file(path)
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.from_file(file_path)
|
39
|
+
new(read_config(file_path).merge("path" => file_path))
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.read_config(yaml_file)
|
43
|
+
content = File.read(yaml_file)
|
44
|
+
YAML.load(ERB.new(content).result).tap do |hash|
|
45
|
+
nilable_properties.each do |key|
|
46
|
+
hash.delete(key) if hash[key].nil?
|
47
|
+
end if hash
|
48
|
+
end || {}
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
def self.nilable_properties
|
53
|
+
(properties - [:path]).map(&:to_s)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
module Soloist
|
4
|
+
class NotFound < RuntimeError; end
|
5
|
+
|
6
|
+
class Spotlight
|
7
|
+
attr_reader :pathname
|
8
|
+
|
9
|
+
def self.find(*file_names)
|
10
|
+
new(Dir.pwd).find(*file_names)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.find!(*file_names)
|
14
|
+
new(Dir.pwd).find!(*file_names)
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(path)
|
18
|
+
@pathname = Pathname.new(path)
|
19
|
+
end
|
20
|
+
|
21
|
+
def find(*file_names)
|
22
|
+
pathname.ascend do |path|
|
23
|
+
file_name = file_names.detect { |fn| path.join(fn).file? }
|
24
|
+
break path.join(file_name) if file_name
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def find!(*file_names)
|
29
|
+
file_path = find(*file_names)
|
30
|
+
unless file_path
|
31
|
+
file_names = if file_names.length > 2
|
32
|
+
file_names[0...-1].join(", ") + " or " + file_names.last
|
33
|
+
else
|
34
|
+
file_names.join(" or ")
|
35
|
+
end
|
36
|
+
raise Soloist::NotFound.new("Could not find #{file_names}")
|
37
|
+
end
|
38
|
+
file_path
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
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
|
data/script/ci.sh
ADDED
data/soloist-rvm.gemspec
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "soloist/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "soloist-rvm"
|
7
|
+
s.version = Soloist::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Austin Putman"]
|
10
|
+
s.email = ["austin@rawfingertips.com"]
|
11
|
+
s.homepage = "http://github.com/austinfromboston/soloist"
|
12
|
+
s.summary = "Soloist-rvm is a way of running chef-solo under rvm"
|
13
|
+
s.description = "Makes running chef-solo possible."
|
14
|
+
|
15
|
+
s.files = `git ls-files`.split("\n")
|
16
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
17
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
18
|
+
s.require_paths = ["lib"]
|
19
|
+
|
20
|
+
s.add_dependency "chef"
|
21
|
+
s.add_dependency "librarian"
|
22
|
+
s.add_dependency "thor"
|
23
|
+
s.add_dependency "hashie", "~> 1.2"
|
24
|
+
s.add_dependency "net-ssh"
|
25
|
+
s.add_dependency "awesome_print"
|
26
|
+
|
27
|
+
s.add_development_dependency "rspec"
|
28
|
+
s.add_development_dependency "guard-rspec"
|
29
|
+
s.add_development_dependency "guard-bundler"
|
30
|
+
s.add_development_dependency "guard-shell"
|
31
|
+
s.add_development_dependency "rb-fsevent"
|
32
|
+
s.add_development_dependency "terminal-notifier-guard"
|
33
|
+
s.add_development_dependency "gem-release"
|
34
|
+
end
|