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 CHANGED
@@ -1,3 +1,6 @@
1
1
  pkg/*
2
2
  *.gem
3
3
  .bundle
4
+ /.vagrant
5
+ /examples/cookbooks
6
+ /examples/tmp
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-p286@soloist
1
+ rvm --create use ruby-1.9.3-p327@soloist
data/.travis.yml CHANGED
@@ -4,7 +4,5 @@ rvm:
4
4
  - 1.9.3
5
5
  - rbx-19mode
6
6
  - rbx-18mode
7
- - jruby
8
- # - ruby-head
9
7
  - ree
10
8
  script: script/ci.sh
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.1.1)
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.5.3)
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.11.0)
90
- rspec-core (~> 2.11.0)
91
- rspec-expectations (~> 2.11.0)
92
- rspec-mocks (~> 2.11.0)
93
- rspec-core (2.11.1)
94
- rspec-expectations (2.11.3)
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.11.3)
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: '--fail-fast --tag ~@slow:true' do
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
@@ -1,20 +1,20 @@
1
- # Copyright (c) 2010, 2011 Matthew Kocher
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 DO_IT_LIVE lice::box
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 MIT-LICENSE for details.
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
@@ -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
@@ -0,0 +1,2 @@
1
+ recipes:
2
+ - pivotal_workstation::bash_it
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 "soloist/config"
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", "Runs chef-solo like a baws"
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
- exec("sudo -E bash -c '#{chef_solo}'")
17
+ soloist_config.run_chef
18
18
  end
19
19
 
20
- desc "run_recipe", "Runs an individual recipe with chef-solo"
21
- def DO_IT_LIVE(*recipes)
22
- config.royal_crown.recipes = recipes
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
- no_tasks do
27
- def write_solo_rb
28
- content = config.as_solo_rb
29
- content.each{ |line| puts line } if log_level == "debug"
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 config
54
- @config ||= Soloist::Config.from_file(rc_path)
55
- end
56
-
57
- def chef_solo
58
- "chef-solo -j '#{node_json.path}' -c '#{solo_rb.path}' -l '#{log_level}'"
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 cheffile_exists?
64
- File.exists?(File.expand_path("../Cheffile", rc_path))
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 solo_rb
72
- @solo_rb ||= Tempfile.new(["solo", ".rb"])
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 node_json
76
- @node_json ||= Tempfile.new(["node", ".json"])
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
@@ -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
- paths = cookbook_paths.uniq.map do |cookbook_path|
18
- File.expand_path(cookbook_path, bash_path)
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 as_json
24
- {
25
- "recipes" => compiled_rc.recipes
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 compiled_rc
30
- @compiled_rc ||= royal_crown.dup.tap do |rc|
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 bash_path
43
- File.dirname(royal_crown.path)
97
+ def conditional_sudo(command)
98
+ root? ? command : "sudo -E #{command}"
44
99
  end
45
100
 
46
- def cookbook_paths
47
- ["cookbooks"] + compiled_rc.cookbook_paths
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
@@ -1,3 +1,3 @@
1
1
  module Soloist
2
- VERSION = "1.0.0.pre"
2
+ VERSION = "1.0.0"
3
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