soloist-rvm 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1,3 @@
1
+ module Soloist
2
+ VERSION = "0.0.1"
3
+ end
@@ -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
@@ -0,0 +1,3 @@
1
+ #!/bin/bash
2
+
3
+ bundle exec rspec
@@ -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