Flucti-flucti-cli 0.1.16

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.
Files changed (65) hide show
  1. data/LICENSE +7 -0
  2. data/README.mdown +24 -0
  3. data/Rakefile +56 -0
  4. data/TODO.txt +19 -0
  5. data/bin/flucti +4 -0
  6. data/flucti-cli.gemspec +46 -0
  7. data/lib/flucti.rb +20 -0
  8. data/lib/flucti/api_access.rb +56 -0
  9. data/lib/flucti/cli.rb +107 -0
  10. data/lib/flucti/parameters.rb +37 -0
  11. data/lib/flucti/resources.rb +41 -0
  12. data/lib/flucti/resources/account.rb +7 -0
  13. data/lib/flucti/resources/app_type.rb +18 -0
  14. data/lib/flucti/resources/backend.rb +28 -0
  15. data/lib/flucti/resources/basic_resource.rb +16 -0
  16. data/lib/flucti/resources/container.rb +15 -0
  17. data/lib/flucti/resources/db_server.rb +38 -0
  18. data/lib/flucti/resources/domain.rb +8 -0
  19. data/lib/flucti/resources/general.rb +14 -0
  20. data/lib/flucti/resources/mail_client.rb +7 -0
  21. data/lib/flucti/resources/mail_server.rb +7 -0
  22. data/lib/flucti/resources/port_forwarding.rb +15 -0
  23. data/lib/flucti/resources/port_forwarding/services +13921 -0
  24. data/lib/flucti/resources/ssh_details.rb +96 -0
  25. data/lib/flucti/resources/webserver.rb +9 -0
  26. data/lib/flucti/resources/website.rb +9 -0
  27. data/lib/flucti/tasks.rb +3 -0
  28. data/lib/flucti/tasks/apikey_tasks.rb +57 -0
  29. data/lib/flucti/tasks/apptype_tasks.rb +264 -0
  30. data/lib/flucti/tasks/connect_pack.rb +161 -0
  31. data/lib/flucti/tasks/db_tasks.rb +158 -0
  32. data/lib/flucti/tasks/mail_tasks.rb +104 -0
  33. data/lib/flucti/tasks/miscellaneous_tasks.rb +6 -0
  34. data/lib/flucti/tasks/progress_tasks.rb +24 -0
  35. data/lib/flucti/tasks/sshkey_tasks.rb +151 -0
  36. data/lib/flucti/tasks/vps/firewall_tasks.rb +84 -0
  37. data/lib/flucti/tasks/vps_tasks.rb +37 -0
  38. data/lib/flucti/tasks/webserver_tasks.rb +154 -0
  39. data/lib/flucti/tasks/website/apptype_tasks.rb +42 -0
  40. data/lib/flucti/tasks/website/backends/instances_tasks.rb +37 -0
  41. data/lib/flucti/tasks/website/backends_tasks.rb +254 -0
  42. data/lib/flucti/tasks/website/capfile_tasks.rb +41 -0
  43. data/lib/flucti/tasks/website/domains_tasks.rb +107 -0
  44. data/lib/flucti/tasks/website_tasks.rb +221 -0
  45. data/lib/flucti/utilities.rb +20 -0
  46. data/lib/flucti/utilities/connection_error_handling.rb +50 -0
  47. data/lib/flucti/utilities/core_ext.rb +35 -0
  48. data/lib/flucti/utilities/list_displayer.rb +57 -0
  49. data/lib/flucti/utilities/miscellaneous.rb +65 -0
  50. data/lib/flucti/utilities/progress_bar.rb +17 -0
  51. data/lib/flucti/utilities/table.rb +25 -0
  52. data/lib/flucti/utilities/task_packing.rb +10 -0
  53. data/lib/flucti/utilities/user_interface.rb +117 -0
  54. data/lib/vendor/ruby-progressbar-0.9/lib/ChangeLog +113 -0
  55. data/lib/vendor/ruby-progressbar-0.9/lib/progressbar.en.rd +103 -0
  56. data/lib/vendor/ruby-progressbar-0.9/lib/progressbar.ja.rd +100 -0
  57. data/lib/vendor/ruby-progressbar-0.9/lib/progressbar.rb +236 -0
  58. data/lib/vendor/ruby-progressbar-0.9/lib/test.rb +105 -0
  59. data/test/flucti/resources_test.rb +32 -0
  60. data/test/flucti/tasks_test.rb +28 -0
  61. data/test/flucti/utilities/miscellaneous_test.rb +54 -0
  62. data/test/flucti/utilities/table_test.rb +28 -0
  63. data/test/flucti/utilities/user_interface_test.rb +161 -0
  64. data/test/test_helper.rb +5 -0
  65. metadata +221 -0
@@ -0,0 +1,96 @@
1
+ module Flucti
2
+ module Resources
3
+ class SshDetails < BasicResource
4
+ def as(login)
5
+ AccountBinding.new(self, login)
6
+ end
7
+
8
+ def connection
9
+ as(login)
10
+ end
11
+
12
+ private
13
+
14
+ class AccountBinding < Struct.new(:details, :login)
15
+ delegate :host, :port, :to => :details
16
+
17
+ def connection_command
18
+ "ssh -p #{port} #{login}@#{host}"
19
+ end
20
+
21
+ def connect
22
+ check_for_presence_of_ssh do
23
+ command = connection_command
24
+ $stderr.puts "Connecting to VPS with: `#{command}'"
25
+ exec(command)
26
+ end
27
+ end
28
+
29
+ def execute_command(command)
30
+ %(#{connection_command} 'echo "#{command.gsub /"/, '\"'}" | bash -l -s')
31
+ end
32
+
33
+ def execute(command)
34
+ check_for_presence_of_ssh do
35
+ command = execute_command(command)
36
+ $stderr.puts "Executing: #{command}"
37
+
38
+ # For some reason, `exec' won't work, it throws the following error:
39
+ # `Operation not supported' (Errno::E045). `system' causes this too
40
+ # sometimes, but so much less often that it's bearable.
41
+ system(command)
42
+ end
43
+ end
44
+ alias run :execute
45
+
46
+ def upload(*files)
47
+ check_for_presence_of_scp do
48
+ command = %(scp -P #{port} #{files * ' '} #{login}@#{host}:~)
49
+ $stderr.puts "Executing: #{command}"
50
+ system(command)
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ def check_for_presence_of_ssh
57
+ if has_command? 'ssh'
58
+ yield
59
+ else
60
+ suggest_windows_alternative "ssh", "PuTTY", "http://www.chiark.greenend.org.uk/~sgtatham/putty"
61
+ end
62
+ end
63
+
64
+ def check_for_presence_of_scp
65
+ if has_command? 'scp'
66
+ yield
67
+ else
68
+ suggest_windows_alternative "scp", "WinSCP", "http://winscp.net"
69
+ end
70
+ end
71
+
72
+ def has_command?(command)
73
+ case RUBY_PLATFORM
74
+ when /mswin/
75
+ false
76
+ else
77
+ system("which ssh > /dev/null 2>&1")
78
+ end
79
+ end
80
+
81
+ def suggest_windows_alternative(missing, alternative, url)
82
+ Utilities.error! <<-MSG
83
+ The necessary `#{missing}' utility seems missing on your system. You
84
+ should connect to the VPS manually with #{alternative} (for Windows,
85
+ available at #{url}) using the following connection details :
86
+
87
+ * Host: #{host}
88
+ * Port: #{port}
89
+ * Login: #{login}
90
+ * Password: (not needed -- specify your public SSH key instead)
91
+ MSG
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,9 @@
1
+ module Flucti
2
+ module Resources
3
+ class Webserver < BasicResource
4
+ belongs_to :container
5
+ has_many :backends
6
+ has_one :ssh_details
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Flucti
2
+ module Resources
3
+ class Website < BasicResource
4
+ self.attribute_for_to_s = :name
5
+ has_many :domains
6
+ has_many :backends
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ Dir.glob(File.dirname(__FILE__) + "/tasks/**/*_tasks.rb") do |task_file|
2
+ load(task_file)
3
+ end
@@ -0,0 +1,57 @@
1
+ namespace :apikey do
2
+ desc <<-DESC
3
+ Reset your API key and generate a new one. The new key will be sent to you
4
+ by e-mail.
5
+ DESC
6
+ task :reset do
7
+ APIKey.put
8
+
9
+ puts_title "API key reset"
10
+ puts_long <<-MSG
11
+ Your API key has been reset. You will receive an e-mail with your new
12
+ API key shortly. After that, you will need to reconfigure this utility
13
+ to use the new key, by running #{qcommand "apikey:switch"}.
14
+ MSG
15
+ end
16
+
17
+ desc <<-DESC
18
+ Change the current API key. You should use this task after #{qcommand "apikey:reset"}
19
+ to use a newly generated API key.
20
+ DESC
21
+ task :switch do
22
+ puts_title "API key"
23
+ puts_long <<-MSG
24
+ In order to use this utility, you must provide your API key that must
25
+ have been sent to you in an e-mail after creating your account. Please
26
+ paste it at the prompt below and press <enter>.
27
+ MSG
28
+ prompt_and_store_key
29
+ end
30
+
31
+ def prompt_and_store_key
32
+ loop do
33
+ puts
34
+ print "API key: "
35
+ entered = $stdin.gets.strip
36
+ begin
37
+ APIKey.site = APIAccess.site_with_api_key(entered)
38
+ begin
39
+ APIKey.get
40
+ ensure
41
+ APIKey.site = nil
42
+ end
43
+ rescue WebService::UnauthorizedAccess
44
+ puts
45
+ puts_long <<-MSG
46
+ The key you entered apears to be invalid. Please double check it
47
+ and retry, or let us help you: #{SUPPORT_EMAIL_ADDR}.
48
+ MSG
49
+ else
50
+ Parameters.store(:api_key, entered)
51
+ puts
52
+ puts "* Key valid and saved."
53
+ break
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,264 @@
1
+ namespace :apptype do
2
+ task(:default) { list }
3
+
4
+ desc <<-DESC
5
+ Display a list of all the stock application types.
6
+
7
+ These are only suggestions that a website can be automatically configured
8
+ for running. Of course, you can configure your VPS's to run any kind of
9
+ site you want, either by customizing existing types (see #{qcommand \
10
+ 'apptype:import'} and #{qcommand 'apptype:push'}), or by configuring
11
+ everything manually.
12
+ DESC
13
+ task :list do
14
+ puts_title "Available application types"
15
+ puts_list AppType.all, :id => :short, :table => true do |t|
16
+ t.col("Name", :name)
17
+ t.col("Stock?") { |t| '*' if t.stock? }
18
+ end
19
+ end
20
+
21
+ desc <<-DESC
22
+ Display brief details about an application type.
23
+ DESC
24
+ task :show do
25
+ id = require_id!
26
+ display(AppType.find(id))
27
+ end
28
+
29
+ desc <<-DESC
30
+ Import files from an existing type into the current working directory.
31
+
32
+ This is usually the first step in creating a custom type. It's usually
33
+ followed by the customisation of the imported files, and finalised with a
34
+ #{qcommand 'apptype:push'}.
35
+ DESC
36
+ task :import do
37
+ id = require_id!
38
+
39
+ type = AppType.find(id)
40
+ puts_title "Importing type #{q type}"
41
+
42
+ # (utilities)
43
+ secure_write = lambda do |path, contents|
44
+ path = path.gsub(/\{(.*?)\}/) { $1.split(',').first }
45
+ if File.exist?(path) || File.exist?(path.downcase)
46
+ $stderr.puts "Warning: skipping #{q path} as it already exists"
47
+ else
48
+ puts "Writing #{q path}"
49
+ File.open(path, 'w') { |f| f << contents }
50
+ end
51
+ end
52
+
53
+ # Files
54
+ file_map_for_type.each do |attribute, glob|
55
+ secure_write[glob, type.send(attribute)]
56
+ end
57
+
58
+ # Metadata
59
+ filter_out = ['id', 'stock?', *file_map_for_type.stringify_keys.keys]
60
+ data =
61
+ type.attributes.
62
+ stringify_keys.
63
+ except(*filter_out).
64
+ merge('name' => name_of_current, 'short' => name_of_current)
65
+ secure_write["meta.yml", YAML.dump(data)]
66
+
67
+ # Services
68
+ type.service_configs.each do |service|
69
+ file_map_for_service.each do |attribute, glob|
70
+ path = ([service.name, glob] - %w(main)).join('_')
71
+ data = service.send(attribute)
72
+ secure_write[path, data]
73
+ end
74
+ end
75
+ end
76
+
77
+ desc <<-DESC
78
+ Create a new application type or update an existing one. The files in the
79
+ current working directory are used to build a new or updated type which is
80
+ then registered under the name of that directory.
81
+
82
+ Creating a new type usually goes somewhat like this:
83
+ $ mkdir foo-type && cd foo-type
84
+ $ #{command "apptype:import"} ID=sinatra
85
+ # ...Edit the imported files...
86
+ $ #{command "apptype:push"}
87
+ # ...Edit the files..
88
+ $ #{command "apptype:push"}
89
+
90
+ Custom types can be used like the stock ones. For a new site:
91
+ $ #{command "website:declare"} TYPE=foo
92
+
93
+ Or, for an existing site:
94
+ $ cd path/to/some-site
95
+ $ #{command "website:apptype:switch"} ID=foo
96
+ $ #{command "website:backends:reprepare"}
97
+
98
+ To update a type:
99
+ $ cd foo-type
100
+ # ...Edit the files...
101
+ $ #{command "apptype:push"}
102
+ # ...For each website of that type...
103
+ $ #{command "website:backends:reprepare"}
104
+ DESC
105
+ task :push do
106
+ if type = AppType[name_of_current]
107
+ update type
108
+ else
109
+ create type
110
+ end
111
+ end
112
+
113
+ desc <<-DESC
114
+ Delete an existing type. All website of this type must have been deleted
115
+ before running this tasks.
116
+ DESC
117
+ task :delete do
118
+ id = require_id!
119
+ type = AppType.find(id)
120
+ begin
121
+ type.destroy
122
+ rescue WebService::ResourceConflict
123
+ error! $!.response.data['errors']
124
+ else
125
+ puts_title "Application type deleted"
126
+ puts "Application type #{q type} deleted successfully."
127
+ end
128
+ end
129
+
130
+ def require_id!
131
+ ENV["ID"] or
132
+ error! <<-MSG
133
+ The ID or short name of the application type to deal with must be
134
+ specified in the $ID environment variable. To list all application
135
+ types, run #{qcommand "apptype:list"}.
136
+ MSG
137
+ end
138
+
139
+ def build_type_from_cwd
140
+ type = AppType.new
141
+ dir = Pathname.pwd
142
+
143
+ # (utilities)
144
+ assign_to = lambda do |object, map|
145
+ map.each do |attribute, glob|
146
+ file = Pathname.glob(dir.join(glob)).first
147
+ object.write_attribute(attribute, file.read) if file
148
+ end
149
+ end
150
+
151
+ # Metadata
152
+ meta = dir.join("meta.yml")
153
+ type.attributes = YAML.load(meta.read) if meta.file?
154
+
155
+ # Here is not really the place to assign a default name and short name but
156
+ # since callers of this method expect these attributes to be set, I think
157
+ # it's OK.
158
+ type.name = name_of_current unless type.attribute_set?(:name)
159
+ type.short = type.name unless type.attribute_set?(:short)
160
+
161
+ # Files
162
+ assign_to[type, file_map_for_type]
163
+
164
+ # Build the main ServiceConfig:
165
+ # start.sh => main.start_script
166
+ # finish.sh => main.finish_script
167
+ if file_map_for_service.values.any? { |f| File.file? f }
168
+ main = ServiceConfig.new(:name => "main")
169
+ assign_to[main, file_map_for_service]
170
+ end
171
+
172
+ # Build other ServiceConfig's:
173
+ # foo_start.sh => foo.start_script
174
+ # foo_install.sh => foo.finish_script
175
+ other =
176
+ Pathname.glob(dir.join("*_{#{file_map_for_service.values * ','}}")).
177
+ group_by { |p| p.basename.to_s.split('_', 2).first }.
178
+ map do |name, files|
179
+ service = ServiceConfig.new(:name => name)
180
+ file_map =
181
+ file_map_for_service.inject({}) do |m, (attr, file)|
182
+ if path = files.find { |path| path.basename.to_s.split('_', 2).last == file }
183
+ m[attr] = path
184
+ end
185
+ m
186
+ end
187
+ assign_to[service, file_map]
188
+ service
189
+ end
190
+
191
+ return type, [main, *other].compact
192
+ end
193
+
194
+ def display(type)
195
+ puts "Name: #{type.name}"
196
+ puts "Short name: #{type.short}"
197
+ puts "Stock?: #{type.stock? ? 'Yes' : 'No'}"
198
+ puts "Created: #{type.created_at}"
199
+ puts "Updated: #{type.updated_at}"
200
+ puts "Detection: #{(d = type.detection) ? "test -#{d}" : '(none)'}"
201
+ puts "Install script: (#{type.install_script.to_s.length} bytes)"
202
+ puts "Webserver conf.: (#{type.webserver_conf.to_s.length} bytes)"
203
+ puts "Capfile tmpl.: (#{type.capfile.to_s.length} bytes)"
204
+ if type.service_configs.any?
205
+ puts "Services:"
206
+ type.service_configs.each do |service|
207
+ puts "- #{service}"
208
+ end
209
+ else
210
+ puts "Services: (none)"
211
+ end
212
+ end
213
+
214
+ def name_of_current
215
+ ENV["NAME"] || clean_name(Pathname.pwd.expand_path.basename.to_s.gsub(/^type-|-type$/, ""))
216
+ end
217
+
218
+ def file_map_for_type
219
+ { :root_install_script => "root_install.sh", :install_script => "install.sh",
220
+ :webserver_conf => "nginx.conf", :capfile => "{C,c}apfile" }
221
+ end
222
+
223
+ def file_map_for_service
224
+ { :start_script => "start.sh", :finish_script => "finish.sh" }
225
+ end
226
+
227
+ def create(type)
228
+ type, services = build_type_from_cwd
229
+
230
+ try_save type do
231
+ puts_title "Application type created"
232
+ puts "Creating associated service configurations..."
233
+ services.each do |service|
234
+ service.app_type = type
235
+ try_save service do
236
+ puts "- Created service #{q service}"
237
+ end
238
+ end
239
+ puts
240
+ display(type.reload)
241
+ end
242
+ end
243
+
244
+ def update(type)
245
+ updated, services = build_type_from_cwd
246
+ updated.attributes.except('id').each do |attribute, value|
247
+ type.write_attribute(attribute, value)
248
+ end
249
+
250
+ try_save type do
251
+ puts_title "Application type updated"
252
+ puts "Updating associated service configurations..."
253
+ type.service_configs.each &:destroy
254
+ services.each do |service|
255
+ service.app_type = type
256
+ try_save service do
257
+ puts "- Updated service #{q service}"
258
+ end
259
+ end
260
+ puts
261
+ display(type.reload)
262
+ end
263
+ end
264
+ end
@@ -0,0 +1,161 @@
1
+ desc <<-DESC
2
+ Open a new shell in the specified #{resource_type}'s UNIX account.
3
+ For password-less access, be sure to run #{qcommand "sshkey:register"}
4
+ first.
5
+
6
+ Environment variables:
7
+ (optional) $ID: the ID of the #{resource_type} to connect to.
8
+ (optional) $AS: the login of the UNIX account to connect as.
9
+ DESC
10
+ namespace :connect do
11
+ task :default do
12
+ resource = fetch_current
13
+ ssh_details_for(resource).connect
14
+ end
15
+
16
+ desc <<-DESC
17
+ Display SSH connection details. You can then use these details to connect
18
+ to the #{resource_type} either from the command line (using the standard
19
+ `ssh', `scp' or `sftp' tool suite), programmatically using Net::SSH, or
20
+ graphically using WinSCP or Cyberduck.
21
+
22
+ Environment variables:
23
+ (optional) $ID: the ID of the #{resource_type} to get the
24
+ connection details for.
25
+ DESC
26
+ task :details do
27
+ resource = fetch_current
28
+ details = ssh_details_for(resource)
29
+
30
+ puts_long <<-EOS
31
+ SSH connection details for #{resource_type} #{resource}:
32
+ * Host: #{details.host}
33
+ * Port: #{details.port}
34
+ * Login: #{details.login}
35
+
36
+ Remote command execution:
37
+ $ ssh -p #{details.port} #{details.login}@#{details.host} <command>
38
+
39
+ File upload:
40
+ $ scp -P #{details.port} #{details.login}@#{details.host} <local-path> <remote-path>
41
+ EOS
42
+ end
43
+
44
+ # Allow for defining a method named `command'.
45
+ class << self
46
+ undef_method :command if method_defined? :command
47
+ end
48
+
49
+ desc <<-DESC
50
+ Output an SSH connection command, ready to run.
51
+
52
+ Environment variables:
53
+ (optional) $ID: the ID of the #{resource_type} to get the connection
54
+ command for.
55
+ DESC
56
+ task :command do
57
+ resource = fetch_current
58
+ puts ssh_details_for(resource).connection_command
59
+ end
60
+
61
+ def ssh_details_for(resource)
62
+ details = resource.ssh_details
63
+ details.login = ENV["AS"] || 'root' unless details.attribute_set? :login
64
+
65
+ # Return `details.connection' instead of `details' so that methods
66
+ # of both SshDetails and SshDetails::AccountBinding are made
67
+ # available from the same object.
68
+ details.connection
69
+ rescue WebService::NotAcceptable
70
+ vps = resource.attribute_set?(:vps) ? resource.vps : resource
71
+ reference = vps == resource ? "VPS" : "#{resource_type}'s VPS"
72
+ error! <<-MSG
73
+ The SSH port of the #{reference} (#{q vps}) is not accessible
74
+ from the Internet. You need to open it in order to be able
75
+ to connect to the #{resource_type}'s UNIX account. To do so,
76
+ run #{qcommand "vps:firewall:open PORT=22"}.
77
+ MSG
78
+ end
79
+ end
80
+
81
+ desc "Alias for task `connect'."
82
+ task(:enter) { connect.default }
83
+
84
+ desc <<-DESC
85
+ Run a command in the #{resource_type} environment. Default working
86
+ directory is the home directory.
87
+
88
+ Environment variables:
89
+ (mandatory) $CMD: the command to run.
90
+
91
+ (optional) $ID: the ID of the #{resource_type} to run the command
92
+ in.
93
+ DESC
94
+ task :run do
95
+ cmd = ENV['CMD'] or error! <<-MSG
96
+ The command to execute must be specified in the $CMD environment
97
+ variable.
98
+ MSG
99
+ resource = fetch_current
100
+ connect.ssh_details_for(resource).run(cmd)
101
+ end
102
+
103
+ desc <<-DESC
104
+ Upload a local file to the home directory of the #{resource_type}.
105
+
106
+ Environment variables:
107
+ (mandatory) $FILE: the path to the file to upload.
108
+
109
+ (optional) $ID: the ID of the #{resource_type} to upload the
110
+ file to.
111
+ DESC
112
+ task :upload do
113
+ files = ENV['FILE'] || ENV['FILES'] or error! <<-MSG
114
+ The file to upload must be specified in the $FILE environment
115
+ variable.
116
+ MSG
117
+ resource = fetch_current
118
+ connect.ssh_details_for(resource).upload(*Dir[files])
119
+ end
120
+
121
+ namespace :rubygems do
122
+ desc <<-DESC
123
+ Install or update RubyGems (latest version) on the UNIX account of the
124
+ specified #{resource_type}. Ruby will be installed if necessary (latest
125
+ 1.8).
126
+
127
+ Once you have done that, you can connect to the #{resource_type}'s
128
+ UNIX account and start installing gems. For example:
129
+ $ #{command "db:server:rubygems:install"}
130
+ $ #{command "progress"}
131
+ $ #{command "db:server:enter"}
132
+ mysql@filiberto ~ $ gem install rails
133
+
134
+ Environment variables:
135
+ (optional) $ID: the ID of the #{resource_type} to install RubyGems onto.
136
+ Default: the last #{resource_type}.
137
+ DESC
138
+ task :install do
139
+ resource = fetch_current
140
+
141
+ resource.post(:rubygems)
142
+
143
+ puts_title "Request sent"
144
+ puts "RubyGems (latest version) has been scheduled for installation or update on #{resource_type} #{resource}."
145
+ end
146
+
147
+ desc "Alias for `install'."
148
+ task(:setup) { install }
149
+
150
+ desc <<-DESC
151
+ Update RubyGems to the latest version. Only the package management
152
+ system is updated, not the gems themselves.
153
+
154
+ Environment variables:
155
+ (optional) $ID: the ID of the #{resource_type} to update RubyGems on.
156
+ Default: the last #{resource_type}.
157
+ DESC
158
+ task(:update) do
159
+ install
160
+ end
161
+ end