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.
- data/LICENSE +7 -0
- data/README.mdown +24 -0
- data/Rakefile +56 -0
- data/TODO.txt +19 -0
- data/bin/flucti +4 -0
- data/flucti-cli.gemspec +46 -0
- data/lib/flucti.rb +20 -0
- data/lib/flucti/api_access.rb +56 -0
- data/lib/flucti/cli.rb +107 -0
- data/lib/flucti/parameters.rb +37 -0
- data/lib/flucti/resources.rb +41 -0
- data/lib/flucti/resources/account.rb +7 -0
- data/lib/flucti/resources/app_type.rb +18 -0
- data/lib/flucti/resources/backend.rb +28 -0
- data/lib/flucti/resources/basic_resource.rb +16 -0
- data/lib/flucti/resources/container.rb +15 -0
- data/lib/flucti/resources/db_server.rb +38 -0
- data/lib/flucti/resources/domain.rb +8 -0
- data/lib/flucti/resources/general.rb +14 -0
- data/lib/flucti/resources/mail_client.rb +7 -0
- data/lib/flucti/resources/mail_server.rb +7 -0
- data/lib/flucti/resources/port_forwarding.rb +15 -0
- data/lib/flucti/resources/port_forwarding/services +13921 -0
- data/lib/flucti/resources/ssh_details.rb +96 -0
- data/lib/flucti/resources/webserver.rb +9 -0
- data/lib/flucti/resources/website.rb +9 -0
- data/lib/flucti/tasks.rb +3 -0
- data/lib/flucti/tasks/apikey_tasks.rb +57 -0
- data/lib/flucti/tasks/apptype_tasks.rb +264 -0
- data/lib/flucti/tasks/connect_pack.rb +161 -0
- data/lib/flucti/tasks/db_tasks.rb +158 -0
- data/lib/flucti/tasks/mail_tasks.rb +104 -0
- data/lib/flucti/tasks/miscellaneous_tasks.rb +6 -0
- data/lib/flucti/tasks/progress_tasks.rb +24 -0
- data/lib/flucti/tasks/sshkey_tasks.rb +151 -0
- data/lib/flucti/tasks/vps/firewall_tasks.rb +84 -0
- data/lib/flucti/tasks/vps_tasks.rb +37 -0
- data/lib/flucti/tasks/webserver_tasks.rb +154 -0
- data/lib/flucti/tasks/website/apptype_tasks.rb +42 -0
- data/lib/flucti/tasks/website/backends/instances_tasks.rb +37 -0
- data/lib/flucti/tasks/website/backends_tasks.rb +254 -0
- data/lib/flucti/tasks/website/capfile_tasks.rb +41 -0
- data/lib/flucti/tasks/website/domains_tasks.rb +107 -0
- data/lib/flucti/tasks/website_tasks.rb +221 -0
- data/lib/flucti/utilities.rb +20 -0
- data/lib/flucti/utilities/connection_error_handling.rb +50 -0
- data/lib/flucti/utilities/core_ext.rb +35 -0
- data/lib/flucti/utilities/list_displayer.rb +57 -0
- data/lib/flucti/utilities/miscellaneous.rb +65 -0
- data/lib/flucti/utilities/progress_bar.rb +17 -0
- data/lib/flucti/utilities/table.rb +25 -0
- data/lib/flucti/utilities/task_packing.rb +10 -0
- data/lib/flucti/utilities/user_interface.rb +117 -0
- data/lib/vendor/ruby-progressbar-0.9/lib/ChangeLog +113 -0
- data/lib/vendor/ruby-progressbar-0.9/lib/progressbar.en.rd +103 -0
- data/lib/vendor/ruby-progressbar-0.9/lib/progressbar.ja.rd +100 -0
- data/lib/vendor/ruby-progressbar-0.9/lib/progressbar.rb +236 -0
- data/lib/vendor/ruby-progressbar-0.9/lib/test.rb +105 -0
- data/test/flucti/resources_test.rb +32 -0
- data/test/flucti/tasks_test.rb +28 -0
- data/test/flucti/utilities/miscellaneous_test.rb +54 -0
- data/test/flucti/utilities/table_test.rb +28 -0
- data/test/flucti/utilities/user_interface_test.rb +161 -0
- data/test/test_helper.rb +5 -0
- 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
|
data/lib/flucti/tasks.rb
ADDED
|
@@ -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
|