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 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