teleport 1.0.0 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +0 -1
- data/README.md +1 -1
- data/Rakefile +51 -3
- data/bin/teleport +0 -0
- data/lib/teleport.rb +1 -0
- data/lib/teleport/config.rb +9 -4
- data/lib/teleport/infer.rb +349 -0
- data/lib/teleport/install.rb +9 -3
- data/lib/teleport/main.rb +81 -38
- data/lib/teleport/mirror.rb +6 -2
- data/lib/teleport/run.sh +4 -4
- data/lib/teleport/util.rb +21 -2
- data/lib/teleport/version.rb +1 -1
- data/spec/end_to_end_spec.rb +39 -0
- data/spec/spec_helper.rb +25 -0
- data/spec/support/ec2.rb +115 -0
- data/spec/support/exit_code.rb +29 -0
- data/spec/support/telfile.rb +25 -0
- data/spec/unit/teleport/config_spec.rb +64 -0
- data/teleport.gemspec +10 -3
- metadata +84 -6
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -13,7 +13,7 @@ At the moment Teleport supports **Ubuntu 10.04/10.10/11.04 with Ruby 1.8.7, 1.9.
|
|
13
13
|
1. Install Teleport on your local machine.
|
14
14
|
|
15
15
|
```
|
16
|
-
$ sudo gem install teleport
|
16
|
+
$ sudo gem install teleport
|
17
17
|
```
|
18
18
|
|
19
19
|
1. Create a `Telfile` config file. Here's a simple example. Note that we actually define two machines, `server_app1` and `server_db1`:
|
data/Rakefile
CHANGED
@@ -1,10 +1,58 @@
|
|
1
1
|
require "bundler"
|
2
|
-
require "
|
2
|
+
require "bundler/setup"
|
3
3
|
|
4
|
-
|
4
|
+
require "rake"
|
5
|
+
require "rdoc/task"
|
6
|
+
require "rspec"
|
7
|
+
require "rspec/core/rake_task"
|
5
8
|
|
6
|
-
|
9
|
+
$LOAD_PATH << File.expand_path("../lib", __FILE__)
|
10
|
+
require "teleport/version"
|
11
|
+
|
12
|
+
#
|
13
|
+
# gem
|
14
|
+
#
|
15
|
+
|
16
|
+
task :gem => :build
|
17
|
+
task :build do
|
18
|
+
system "gem build --quiet teleport.gemspec"
|
19
|
+
end
|
20
|
+
|
21
|
+
task :install => :build do
|
22
|
+
system "sudo gem install --quiet teleport-#{Teleport::VERSION}.gem"
|
23
|
+
end
|
24
|
+
|
25
|
+
task :release => :build do
|
26
|
+
system "git tag -a #{Teleport::VERSION} -m 'Tagging #{Teleport::VERSION}'"
|
27
|
+
system "git push --tags"
|
28
|
+
system "gem push teleport-#{Teleport::VERSION}.gem"
|
29
|
+
end
|
30
|
+
|
31
|
+
#
|
32
|
+
# rspec
|
33
|
+
#
|
34
|
+
|
35
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
36
|
+
spec.rspec_opts = %w(--color --tty)
|
37
|
+
spec.pattern = "spec/**/*_spec.rb"
|
38
|
+
end
|
39
|
+
|
40
|
+
RSpec::Core::RakeTask.new("spec:unit") do |spec|
|
41
|
+
spec.pattern = "spec/unit/**/*_spec.rb"
|
42
|
+
end
|
43
|
+
|
44
|
+
RSpec::Core::RakeTask.new("spec:end") do |spec|
|
45
|
+
spec.pattern = "spec/end*_spec.rb"
|
46
|
+
end
|
47
|
+
|
48
|
+
#
|
49
|
+
# rdoc
|
50
|
+
#
|
51
|
+
|
52
|
+
RDoc::Task.new do |rdoc|
|
7
53
|
rdoc.rdoc_dir = "rdoc"
|
8
54
|
rdoc.title = "teleport #{Teleport::VERSION}"
|
9
55
|
rdoc.rdoc_files.include("lib/**/*.rb")
|
10
56
|
end
|
57
|
+
|
58
|
+
task :default => :spec
|
data/bin/teleport
CHANGED
File without changes
|
data/lib/teleport.rb
CHANGED
data/lib/teleport/config.rb
CHANGED
@@ -2,11 +2,10 @@ module Teleport
|
|
2
2
|
# This class parses Telfile, and includes DSL and the models.
|
3
3
|
class Config
|
4
4
|
RUBIES = ["1.9.2", "REE", "1.8.7"]
|
5
|
-
PATH = "Telfile"
|
6
5
|
|
7
|
-
attr_accessor :user, :ruby, :roles, :servers, :apt, :packages, :callbacks, :dsl
|
6
|
+
attr_accessor :user, :ruby, :ssh_options, :roles, :servers, :apt, :packages, :callbacks, :dsl
|
8
7
|
|
9
|
-
def initialize
|
8
|
+
def initialize(file = "Telfile")
|
10
9
|
@roles = []
|
11
10
|
@servers = []
|
12
11
|
@apt = []
|
@@ -14,7 +13,7 @@ module Teleport
|
|
14
13
|
@callbacks = { }
|
15
14
|
|
16
15
|
@dsl = DSL.new(self)
|
17
|
-
@dsl.instance_eval(File.read(
|
16
|
+
@dsl.instance_eval(File.read(file), file)
|
18
17
|
|
19
18
|
@user ||= Util.whoami
|
20
19
|
@ruby ||= RUBIES.first
|
@@ -95,6 +94,12 @@ module Teleport
|
|
95
94
|
@config.user = v
|
96
95
|
end
|
97
96
|
|
97
|
+
def ssh_options(v)
|
98
|
+
raise "ssh_options called twice" if @config.ssh_options
|
99
|
+
raise "ssh_options must be an Array" if !v.is_a?(Array)
|
100
|
+
@config.ssh_options = v
|
101
|
+
end
|
102
|
+
|
98
103
|
def role(name, options = {})
|
99
104
|
raise "role #{name.inspect} defined twice" if @config.roles.any? { |i| i.name == name }
|
100
105
|
@config.roles << Role.new(name, options)
|
@@ -0,0 +1,349 @@
|
|
1
|
+
require "set"
|
2
|
+
|
3
|
+
# Many, many thanks to Blueprint!
|
4
|
+
# https://github.com/devstructure/blueprint
|
5
|
+
|
6
|
+
module Teleport
|
7
|
+
class Infer
|
8
|
+
include Util
|
9
|
+
|
10
|
+
# Copyright 2011 DevStructure. All rights reserved.
|
11
|
+
#
|
12
|
+
# Redistribution and use in source and binary forms, with or without
|
13
|
+
# modification, are permitted provided that the following conditions are
|
14
|
+
# met:
|
15
|
+
#
|
16
|
+
# 1. Redistributions of source code must retain the above copyright
|
17
|
+
# notice, this list of conditions and the following disclaimer.
|
18
|
+
#
|
19
|
+
# 2. Redistributions in binary form must reproduce the above
|
20
|
+
# copyright notice, this list of conditions and the following
|
21
|
+
# disclaimer in the documentation and/or other materials provided
|
22
|
+
# with the distribution.
|
23
|
+
#
|
24
|
+
# THIS SOFTWARE IS PROVIDED BY DEVSTRUCTURE ``AS IS'' AND ANY EXPRESS
|
25
|
+
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
26
|
+
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
27
|
+
# DISCLAIMED. IN NO EVENT SHALL DEVSTRUCTURE OR CONTRIBUTORS BE LIABLE
|
28
|
+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
29
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
30
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
31
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
32
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
33
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
34
|
+
# THE POSSIBILITY OF SUCH DAMAGE.
|
35
|
+
#
|
36
|
+
# The views and conclusions contained in the software and documentation
|
37
|
+
# are those of the authors and should not be interpreted as representing
|
38
|
+
# official policies, either expressed or implied, of DevStructure.
|
39
|
+
#
|
40
|
+
# (for MD5SUMS)
|
41
|
+
|
42
|
+
MD5SUMS = {
|
43
|
+
'/etc/adduser.conf' => ['/usr/share/adduser/adduser.conf'],
|
44
|
+
'/etc/apparmor.d/tunables/home.d/ubuntu' =>
|
45
|
+
['2a88811f7b763daa96c20b20269294a4'],
|
46
|
+
'/etc/apt/apt.conf.d/00CDMountPoint' =>
|
47
|
+
['cb46a4e03f8c592ee9f56c948c14ea4e'],
|
48
|
+
'/etc/apt/apt.conf.d/00trustcdrom' =>
|
49
|
+
['a8df82e6e6774f817b500ee10202a968'],
|
50
|
+
'/etc/chatscripts/provider' => ['/usr/share/ppp/provider.chatscript'],
|
51
|
+
'/etc/default/console-setup' =>
|
52
|
+
['0fb6cec686d0410993bdf17192bee7d6',
|
53
|
+
'b684fd43b74ac60c6bdafafda8236ed3',
|
54
|
+
'/usr/share/console-setup/console-setup'],
|
55
|
+
'/etc/default/grub' => ['ee9df6805efb2a7d1ba3f8016754a119',
|
56
|
+
'ad9283019e54cedfc1f58bcc5e615dce'],
|
57
|
+
'/etc/default/irqbalance' => ['7e10d364b9f72b11d7bf7bd1cfaeb0ff'],
|
58
|
+
'/etc/default/keyboard' => ['06d66484edaa2fbf89aa0c1ec4989857'],
|
59
|
+
'/etc/default/locale' => ['164aba1ef1298affaa58761647f2ceba',
|
60
|
+
'7c32189e775ac93487aa4a01dffbbf76'],
|
61
|
+
'/etc/default/rcS' => ['/usr/share/initscripts/default.rcS'],
|
62
|
+
'/etc/environment' => ['44ad415fac749e0c39d6302a751db3f2'],
|
63
|
+
'/etc/hosts.allow' => ['8c44735847c4f69fb9e1f0d7a32e94c1'],
|
64
|
+
'/etc/hosts.deny' => ['92a0a19db9dc99488f00ac9e7b28eb3d'],
|
65
|
+
'/etc/initramfs-tools/modules' =>
|
66
|
+
['/usr/share/initramfs-tools/modules'],
|
67
|
+
'/etc/inputrc' => ['/usr/share/readline/inputrc'],
|
68
|
+
'/etc/iscsi/iscsid.conf' => ['6c6fd718faae84a4ab1b276e78fea471'],
|
69
|
+
'/etc/kernel-img.conf' => ['f1ed9c3e91816337aa7351bdf558a442'],
|
70
|
+
'/etc/ld.so.conf' => ['4317c6de8564b68d628c21efa96b37e4'],
|
71
|
+
'/etc/networks' => ['/usr/share/base-files/networks'],
|
72
|
+
'/etc/nsswitch.conf' => ['/usr/share/base-files/nsswitch.conf'],
|
73
|
+
'/etc/pam.d/common-account' => ['9d50c7dda6ba8b6a8422fd4453722324'],
|
74
|
+
'/etc/pam.d/common-auth' => ['a326c972f4f3d20e5f9e1b06eef4d620'],
|
75
|
+
'/etc/pam.d/common-password' => ['9f2fbf01b1a36a017b16ea62c7ff4c22'],
|
76
|
+
'/etc/pam.d/common-session' => ['e2b72dd3efb2d6b29698f944d8723ab1'],
|
77
|
+
'/etc/pam.d/common-session-noninteractive' =>
|
78
|
+
['508d44b6daafbc3d6bd587e357a6ff5b'],
|
79
|
+
'/etc/ppp/chap-secrets' => ['faac59e116399eadbb37644de6494cc4'],
|
80
|
+
'/etc/ppp/pap-secrets' => ['698c4d412deedc43dde8641f84e8b2fd'],
|
81
|
+
'/etc/ppp/peers/provider' => ['/usr/share/ppp/provider.peer'],
|
82
|
+
'/etc/profile' => ['/usr/share/base-files/profile'],
|
83
|
+
'/etc/python/debian_config' => ['7f4739eb8858d231601a5ed144099ac8'],
|
84
|
+
'/etc/rc.local' => ['10fd9f051accb6fd1f753f2d48371890'],
|
85
|
+
'/etc/rsyslog.d/50-default.conf' =>
|
86
|
+
['/usr/share/rsyslog/50-default.conf'],
|
87
|
+
'/etc/security/opasswd' => ['d41d8cd98f00b204e9800998ecf8427e'],
|
88
|
+
'/etc/sgml/xml-core.cat' => ['bcd454c9bf55a3816a134f9766f5928f'],
|
89
|
+
'/etc/shells' => ['0e85c87e09d716ecb03624ccff511760'],
|
90
|
+
'/etc/ssh/sshd_config' => ['e24f749808133a27d94fda84a89bb27b',
|
91
|
+
'8caefdd9e251b7cc1baa37874149a870'],
|
92
|
+
'/etc/sudoers' => ['02f74ccbec48997f402a063a172abb48'],
|
93
|
+
'/etc/ufw/after.rules' => ['/usr/share/ufw/after.rules'],
|
94
|
+
'/etc/ufw/after6.rules' => ['/usr/share/ufw/after6.rules'],
|
95
|
+
'/etc/ufw/before.rules' => ['/usr/share/ufw/before.rules'],
|
96
|
+
'/etc/ufw/before6.rules' => ['/usr/share/ufw/before6.rules'],
|
97
|
+
'/etc/ufw/ufw.conf' => ['/usr/share/ufw/ufw.conf']
|
98
|
+
}
|
99
|
+
|
100
|
+
NEW_FILES_WITHIN = %w(cron.d logrotate.d rsyslog.d init)
|
101
|
+
CHECKSUM_FILES = %w(bash.bashrc environment inputrc rc.local ssh/ssh_config ssh/sshd_config)
|
102
|
+
|
103
|
+
def initialize
|
104
|
+
@telfile = []
|
105
|
+
|
106
|
+
if fails?("grep -q Ubuntu /etc/lsb-release")
|
107
|
+
fatal "Sorry, --infer can only run on an Ubuntu machine."
|
108
|
+
end
|
109
|
+
|
110
|
+
append "#" * 72
|
111
|
+
append "# Telfile inferred from #{`hostname`.strip} at #{Time.now}"
|
112
|
+
append "#" * 72
|
113
|
+
append
|
114
|
+
|
115
|
+
user
|
116
|
+
ruby
|
117
|
+
apt
|
118
|
+
packages
|
119
|
+
files
|
120
|
+
|
121
|
+
banner "Done!"
|
122
|
+
$stderr.puts
|
123
|
+
@telfile.each { |i| puts i }
|
124
|
+
end
|
125
|
+
|
126
|
+
def append(s = nil)
|
127
|
+
@telfile << (s || "")
|
128
|
+
end
|
129
|
+
|
130
|
+
def user
|
131
|
+
append "user #{`whoami`.strip.inspect}"
|
132
|
+
end
|
133
|
+
|
134
|
+
def ruby
|
135
|
+
version = `ruby --version`
|
136
|
+
ruby = nil
|
137
|
+
case version
|
138
|
+
when /Ruby Enterprise Edition/ then ruby = "REE"
|
139
|
+
when /1\.8\.7/ then ruby = "1.8.7"
|
140
|
+
when /1\.9\.2/ then ruby = "1.9.2"
|
141
|
+
end
|
142
|
+
append "ruby #{ruby.inspect}" if ruby
|
143
|
+
end
|
144
|
+
|
145
|
+
def apt
|
146
|
+
banner "Calculating apt sources and keys..."
|
147
|
+
list = run_capture_lines("cat /etc/apt/sources.list /etc/apt/sources.list.d/*.list")
|
148
|
+
list = list.grep(/^deb /).sort
|
149
|
+
list.each do |line|
|
150
|
+
if line =~ /^deb http:\/\/(\S+)\s+(\S+)/
|
151
|
+
source, dist = $1, $2
|
152
|
+
file = source.chomp("/").gsub(/[^a-z0-9.-]/, "_")
|
153
|
+
file = "/var/lib/apt/lists/#{file}_dists_#{dist}_Release"
|
154
|
+
next if !File.exists?(file)
|
155
|
+
|
156
|
+
verify = run_capture("gpgv --keyring /etc/apt/trusted.gpg #{file}.gpg #{file} 2>&1")
|
157
|
+
key = verify[/key ID ([A-Z0-9]{8})$/, 1]
|
158
|
+
next if key == "437D05B5" # canonical key
|
159
|
+
append "apt #{line.inspect}, :key => #{key.inspect}"
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def packages
|
165
|
+
banner "Looking for interesting packages..."
|
166
|
+
@packages = Apt.new.added
|
167
|
+
if !@packages.empty?
|
168
|
+
append
|
169
|
+
append "# Note: You should read this package list very carefully and remove"
|
170
|
+
append "# packages that you don't want on your server."
|
171
|
+
append
|
172
|
+
append "packages %w(#{@packages.join(" ")})"
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def files
|
177
|
+
banner "Looking for interesting files..."
|
178
|
+
files = []
|
179
|
+
|
180
|
+
# read checksums from dpkg status
|
181
|
+
conf = { }
|
182
|
+
File.readlines("/var/lib/dpkg/status").each do |line|
|
183
|
+
if line =~ /^ (\S+) ([0-9a-f]{32})/
|
184
|
+
conf[$1] = $2
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
# look for changed conf files
|
189
|
+
$stderr.puts " scanning conf files from interesting packages..."
|
190
|
+
@packages.each do |pkg|
|
191
|
+
list = run_capture_lines("dpkg -L #{pkg}")
|
192
|
+
list = list.select { |i| i =~ /^\/etc/ }.sort
|
193
|
+
list = list.select { |i| File.file?(i) }
|
194
|
+
list = list.select { |i| conf[i] && conf[i] != md5sum(i) }
|
195
|
+
files += list
|
196
|
+
end
|
197
|
+
|
198
|
+
# look for new files in NEW_FILES_WITHIN
|
199
|
+
dirs = NEW_FILES_WITHIN.map { |i| "/etc/#{i}" }
|
200
|
+
dirs.sort.each do |dir|
|
201
|
+
$stderr.puts " scanning #{dir} for new files..."
|
202
|
+
list = Dir["#{dir}/*"].sort
|
203
|
+
list = list.select { |i| !MD5SUMS[i] }
|
204
|
+
list = list.select { |i| fails?("dpkg -S #{i}") }
|
205
|
+
files += list
|
206
|
+
end
|
207
|
+
|
208
|
+
# now look for changed files from CHECKSUM_FILES
|
209
|
+
scan = CHECKSUM_FILES.map { |i| "/etc/#{i}" }
|
210
|
+
scan = scan.select { |i| File.file?(i) }
|
211
|
+
scan.each do |i|
|
212
|
+
new_sum = md5sum(i)
|
213
|
+
if old_sum = MD5SUMS[i]
|
214
|
+
match = old_sum.any? do |sum|
|
215
|
+
sum = md5sum(sum) if sum =~ /^\//
|
216
|
+
new_sum == sum
|
217
|
+
end
|
218
|
+
files << i if !match
|
219
|
+
elsif old_sum = conf[i]
|
220
|
+
files << i if new_sum != old_sum
|
221
|
+
end
|
222
|
+
end
|
223
|
+
files = files.sort
|
224
|
+
|
225
|
+
if !files.empty?
|
226
|
+
append
|
227
|
+
append "#" * 72
|
228
|
+
append "# Also, I think these should be included in files/"
|
229
|
+
append "#" * 72
|
230
|
+
append
|
231
|
+
files.each { |i| append "# #{i}" }
|
232
|
+
append
|
233
|
+
append "# You can do that with this magical command:"
|
234
|
+
append "#"
|
235
|
+
append "# mkdir files && cd files && tar cf - #{files.join(" ")} | tar xf -"
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
class Apt
|
240
|
+
include Util
|
241
|
+
|
242
|
+
BLACKLIST = /^(linux-|grub-|cloud-init)/
|
243
|
+
|
244
|
+
Package = Struct.new(:name, :status, :deps, :base, :parents)
|
245
|
+
|
246
|
+
def initialize
|
247
|
+
@packages = nil
|
248
|
+
@map = nil
|
249
|
+
end
|
250
|
+
|
251
|
+
def packages
|
252
|
+
if !@packages
|
253
|
+
# run dpkg
|
254
|
+
lines = run_capture_lines("dpkg-query '-f=${Package}\t${Status}\t${Pre-Depends},${Depends},${Recommends}\t${Essential}\t${Priority}\n' -W")
|
255
|
+
@packages = lines.map do |line|
|
256
|
+
name, status, deps, essential, priority = line.split("\t")
|
257
|
+
deps = deps.gsub(/\([^)]+\)/, "")
|
258
|
+
deps = deps.split(/[,|]/)
|
259
|
+
deps = deps.map(&:strip).select { |i| !i.empty? }.sort
|
260
|
+
base = false
|
261
|
+
base = true if essential == "yes"
|
262
|
+
base = true if priority =~ /^(important|required|standard)$/
|
263
|
+
Package.new(name, status, deps, base, [])
|
264
|
+
end
|
265
|
+
|
266
|
+
# calculate ancestors
|
267
|
+
@packages.each do |pkg|
|
268
|
+
pkg.deps.each do |i|
|
269
|
+
if d = self[i]
|
270
|
+
d.parents << pkg.name
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
274
|
+
@packages.each do |pkg|
|
275
|
+
pkg.parents = pkg.parents.sort.uniq
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
@packages
|
280
|
+
end
|
281
|
+
|
282
|
+
def [](name)
|
283
|
+
if !@map
|
284
|
+
@map = { }
|
285
|
+
packages.each { |i| @map[i.name] = i }
|
286
|
+
end
|
287
|
+
@map[name]
|
288
|
+
end
|
289
|
+
|
290
|
+
def base_packages
|
291
|
+
packages.select { |i| i.base }.map(&:name)
|
292
|
+
end
|
293
|
+
|
294
|
+
def ignored_packages
|
295
|
+
list = packages.select { |i| i.base }.map(&:name)
|
296
|
+
list += %w(grub-pc installation-report language-pack-en language-pack-gnome-en linux-generic-pae linux-server os-prober ubuntu-desktop ubuntu-minimal ubuntu-standard wireless-crda)
|
297
|
+
dependencies(list)
|
298
|
+
end
|
299
|
+
|
300
|
+
def dependencies(list)
|
301
|
+
check = list
|
302
|
+
while !check.empty?
|
303
|
+
check = check.map do |i|
|
304
|
+
if pkg = self[i]
|
305
|
+
pkg.deps
|
306
|
+
end
|
307
|
+
end
|
308
|
+
check = check.compact.flatten.uniq.sort
|
309
|
+
check -= list
|
310
|
+
list += check
|
311
|
+
end
|
312
|
+
list.sort
|
313
|
+
end
|
314
|
+
|
315
|
+
def added
|
316
|
+
# calculate raw list
|
317
|
+
ignored = Set.new(ignored_packages)
|
318
|
+
list = packages.select do |i|
|
319
|
+
i.status == "install ok installed" && !ignored.include?(i.name)
|
320
|
+
end
|
321
|
+
list = list.map(&:name)
|
322
|
+
|
323
|
+
# now calculate parents
|
324
|
+
roots = []
|
325
|
+
check = list
|
326
|
+
while !check.empty?
|
327
|
+
check = check.map do |i|
|
328
|
+
if pkg = self[i]
|
329
|
+
if !pkg.parents.empty?
|
330
|
+
pkg.parents
|
331
|
+
else
|
332
|
+
roots << pkg.name
|
333
|
+
nil
|
334
|
+
end
|
335
|
+
end
|
336
|
+
end
|
337
|
+
check = check.compact.flatten.uniq.sort
|
338
|
+
check -= list
|
339
|
+
list += check
|
340
|
+
end
|
341
|
+
|
342
|
+
# blacklist
|
343
|
+
roots = roots.reject { |i| i =~ BLACKLIST }
|
344
|
+
|
345
|
+
roots.sort
|
346
|
+
end
|
347
|
+
end
|
348
|
+
end
|
349
|
+
end
|
data/lib/teleport/install.rb
CHANGED
@@ -51,14 +51,14 @@ module Teleport
|
|
51
51
|
# do we have a server object?
|
52
52
|
@server = @config.server(@host)
|
53
53
|
if !@server && !@config.servers.empty?
|
54
|
-
fatal "Hm. I couldn't find server #{@host.inspect} in
|
54
|
+
fatal "Hm. I couldn't find server #{@host.inspect} in Telfile."
|
55
55
|
end
|
56
56
|
|
57
57
|
@role = nil
|
58
58
|
if @server && (role_name = @server.options[:role])
|
59
59
|
@role = @config.role(role_name)
|
60
60
|
if !@role
|
61
|
-
fatal "Hm. I couldn't find role #{role_name.inspect} in
|
61
|
+
fatal "Hm. I couldn't find role #{role_name.inspect} in Telfile."
|
62
62
|
end
|
63
63
|
end
|
64
64
|
end
|
@@ -88,7 +88,13 @@ module Teleport
|
|
88
88
|
end
|
89
89
|
|
90
90
|
def _hostname
|
91
|
-
banner "Hostname..."
|
91
|
+
banner "Hostname..."
|
92
|
+
|
93
|
+
# ipv4?
|
94
|
+
return if @host =~ /^\d+(\.\d+){3}$/
|
95
|
+
# ipv6?
|
96
|
+
return if @host =~ /:/
|
97
|
+
|
92
98
|
old_hostname = `hostname`.strip
|
93
99
|
return if old_hostname == @host
|
94
100
|
|
data/lib/teleport/main.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
|
-
require "
|
2
|
-
require "getoptlong"
|
1
|
+
require "optparse"
|
3
2
|
|
4
3
|
module Teleport
|
5
4
|
# The main class for the teleport command line.
|
@@ -9,53 +8,83 @@ module Teleport
|
|
9
8
|
|
10
9
|
TAR = "#{DIR}.tgz"
|
11
10
|
|
12
|
-
attr_accessor :host, :options
|
13
|
-
|
14
11
|
def initialize(cmd = :teleport)
|
15
|
-
|
16
|
-
["--help", "-h", GetoptLong::NO_ARGUMENT]
|
17
|
-
)
|
18
|
-
opts.each do |opt, arg|
|
19
|
-
case opt
|
20
|
-
when "--help"
|
21
|
-
usage(0)
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
$stderr = $stdout
|
12
|
+
cli(cmd)
|
26
13
|
|
27
|
-
case cmd
|
14
|
+
case @options[:cmd]
|
28
15
|
when :teleport
|
29
|
-
|
16
|
+
$stderr = $stdout
|
17
|
+
teleport
|
30
18
|
when :install
|
19
|
+
$stderr = $stdout
|
31
20
|
install
|
21
|
+
when :infer
|
22
|
+
infer
|
32
23
|
end
|
33
24
|
end
|
25
|
+
|
26
|
+
# Parse ARGV.
|
27
|
+
def cli(cmd)
|
28
|
+
@options = { }
|
29
|
+
@options[:cmd] = cmd
|
30
|
+
@options[:file] = "Telfile"
|
31
|
+
|
32
|
+
opt = OptionParser.new do |o|
|
33
|
+
o.banner = "Usage: teleport <hostname>"
|
34
|
+
o.on("-f", "--file FILE", "use this file instead of Telfile") do |f|
|
35
|
+
@options[:file] = f
|
36
|
+
end
|
37
|
+
o.on("-i", "--infer", "infer a new Telfile from YOUR machine") do |f|
|
38
|
+
@options[:cmd] = :infer
|
39
|
+
end
|
40
|
+
o.on_tail("-h", "--help", "print this help text") do
|
41
|
+
puts opt
|
42
|
+
exit(0)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
begin
|
46
|
+
opt.parse!
|
47
|
+
rescue OptionParser::InvalidOption, OptionParser::MissingArgument
|
48
|
+
puts $!
|
49
|
+
puts opt
|
50
|
+
exit(1)
|
51
|
+
end
|
34
52
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
53
|
+
if @options[:cmd] == :teleport
|
54
|
+
# print this error message early, to give the user a hint
|
55
|
+
# instead of complaining about command line arguments
|
56
|
+
if ARGV.length != 1
|
57
|
+
puts opt
|
58
|
+
exit(1)
|
59
|
+
end
|
60
|
+
@options[:host] = ARGV.shift
|
61
|
+
end
|
39
62
|
end
|
40
63
|
|
64
|
+
# Read Telfile
|
41
65
|
def read_config
|
42
|
-
if !File.exists?(
|
43
|
-
fatal("Sadly, I can't find #{
|
66
|
+
if !File.exists?(@options[:file])
|
67
|
+
fatal("Sadly, I can't find #{@options[:file]} here. Please create one.")
|
44
68
|
end
|
45
|
-
@config = Config.new
|
69
|
+
@config = Config.new(@options[:file])
|
46
70
|
end
|
47
71
|
|
48
|
-
|
72
|
+
# Assemble the the tgz before we teleport to the host
|
73
|
+
def assemble_tgz
|
49
74
|
banner "Assembling #{TAR}..."
|
50
75
|
rm_and_mkdir(DIR)
|
51
76
|
|
52
77
|
# gem
|
53
78
|
run("cp", ["-r", "#{File.dirname(__FILE__)}/../../lib", GEM])
|
79
|
+
# Telfile, if necessary
|
80
|
+
if @options[:file] != "Telfile"
|
81
|
+
run("cp", [@options[:file], "Telfile"])
|
82
|
+
end
|
54
83
|
# data
|
55
84
|
run("cp", ["-r", ".", DATA])
|
56
85
|
# config.sh
|
57
86
|
File.open("#{DIR}/config", "w") do |f|
|
58
|
-
f.puts("CONFIG_HOST='#{host}'")
|
87
|
+
f.puts("CONFIG_HOST='#{@options[:host]}'")
|
59
88
|
f.puts("CONFIG_RUBY='#{@config.ruby}'")
|
60
89
|
f.puts("CONFIG_RUBYGEMS='#{RUBYGEMS}'")
|
61
90
|
end
|
@@ -63,8 +92,6 @@ module Teleport
|
|
63
92
|
ssh_key = "#{ENV["HOME"]}/.ssh/#{PUBKEY}"
|
64
93
|
if File.exists?(ssh_key)
|
65
94
|
run("cp", [ssh_key, DIR])
|
66
|
-
else
|
67
|
-
puts "Could not find #{ssh_key} - skipping."
|
68
95
|
end
|
69
96
|
|
70
97
|
Dir.chdir(File.dirname(DIR)) do
|
@@ -72,10 +99,16 @@ module Teleport
|
|
72
99
|
end
|
73
100
|
end
|
74
101
|
|
75
|
-
|
102
|
+
# Copy the tgz to the host, then run there.
|
103
|
+
def ssh_tgz
|
76
104
|
begin
|
77
|
-
banner "scp #{TAR} to #{host}:#{TAR}..."
|
78
|
-
|
105
|
+
banner "scp #{TAR} to #{@options[:host]}:#{TAR}..."
|
106
|
+
|
107
|
+
args = []
|
108
|
+
args += @config.ssh_options if @config.ssh_options
|
109
|
+
args << TAR
|
110
|
+
args << "#{@options[:host]}:#{TAR}"
|
111
|
+
run("scp", args)
|
79
112
|
|
80
113
|
cmd = [
|
81
114
|
"cd /tmp",
|
@@ -84,27 +117,37 @@ module Teleport
|
|
84
117
|
"sudo tar xfpz #{TAR}",
|
85
118
|
"sudo #{DIR}/gem/teleport/run.sh"
|
86
119
|
]
|
87
|
-
banner "ssh to #{host} and run..."
|
88
|
-
|
120
|
+
banner "ssh to #{@options[:host]} and run..."
|
121
|
+
|
122
|
+
args = []
|
123
|
+
args += @config.ssh_options if @config.ssh_options
|
124
|
+
args << @options[:host]
|
125
|
+
args << cmd.join(" && ")
|
126
|
+
run("ssh", args)
|
89
127
|
rescue RunError
|
90
128
|
fatal("Failed!")
|
91
129
|
end
|
92
130
|
banner "Success!"
|
93
131
|
end
|
94
132
|
|
95
|
-
|
133
|
+
# Teleport to the host.
|
134
|
+
def teleport
|
96
135
|
read_config
|
97
|
-
|
98
|
-
|
99
|
-
ssh_tgz(host)
|
136
|
+
assemble_tgz
|
137
|
+
ssh_tgz
|
100
138
|
end
|
101
139
|
|
140
|
+
# We're running on the host - install!
|
102
141
|
def install
|
103
142
|
Dir.chdir(DATA) do
|
104
143
|
read_config
|
105
144
|
end
|
106
145
|
Install.new(@config)
|
107
146
|
end
|
108
|
-
|
147
|
+
|
148
|
+
# try to infer a new Telfile based on the current machine
|
149
|
+
def infer
|
150
|
+
Infer.new
|
151
|
+
end
|
109
152
|
end
|
110
153
|
end
|
data/lib/teleport/mirror.rb
CHANGED
@@ -31,8 +31,12 @@ module Teleport
|
|
31
31
|
copy_metadata(path, tmp)
|
32
32
|
path = tmp
|
33
33
|
end
|
34
|
-
|
35
|
-
|
34
|
+
|
35
|
+
if !File.symlink?(path)
|
36
|
+
cp_if_necessary(path, dst, user_for_file(dst), mode_for_file(dst))
|
37
|
+
else
|
38
|
+
ln_if_necessary(File.readlink(path), dst)
|
39
|
+
end
|
36
40
|
end
|
37
41
|
|
38
42
|
# Install directory from the teleport data directory into the
|
data/lib/teleport/run.sh
CHANGED
@@ -90,15 +90,15 @@ function install_ruby_ree() {
|
|
90
90
|
#
|
91
91
|
|
92
92
|
# are we on Ubuntu?
|
93
|
-
if !
|
93
|
+
if ! grep -q Ubuntu /etc/lsb-release ; then
|
94
94
|
fatal "Teleport only works with Ubuntu"
|
95
95
|
fi
|
96
96
|
|
97
97
|
# which version?
|
98
98
|
. /etc/lsb-release
|
99
|
-
case $
|
100
|
-
10.*
|
101
|
-
11.04) ;; # nop
|
99
|
+
case $DISTRIB_RELEASE in
|
100
|
+
10.* ) ;; # nop
|
101
|
+
11.04 ) ;; # nop
|
102
102
|
*)
|
103
103
|
banner "warning - Ubuntu $DISTRIB_RELEASE hasn't been tested with Teleport yet"
|
104
104
|
esac
|
data/lib/teleport/util.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require "cgi"
|
2
|
+
require "digest/md5"
|
2
3
|
require "etc"
|
3
4
|
require "fileutils"
|
4
5
|
|
@@ -56,7 +57,7 @@ module Teleport
|
|
56
57
|
end
|
57
58
|
|
58
59
|
# Run a command, raise an error upon failure. The output is
|
59
|
-
#
|
60
|
+
# captured as a string and returned.
|
60
61
|
def run_capture(command, *args)
|
61
62
|
if !args.empty?
|
62
63
|
args = args.flatten.map { |i| shell_escape(i) }.join(" ")
|
@@ -72,6 +73,13 @@ module Teleport
|
|
72
73
|
result
|
73
74
|
end
|
74
75
|
|
76
|
+
# Run a command and split the result into lines, raise an error
|
77
|
+
# upon failure. The output is captured as an array of strings and
|
78
|
+
# returned.
|
79
|
+
def run_capture_lines(command, *args)
|
80
|
+
run_capture(command, args).split("\n")
|
81
|
+
end
|
82
|
+
|
75
83
|
# Run a command but don't send any output to $stdout/$stderr.
|
76
84
|
def run_quietly(command, *args)
|
77
85
|
if !args.empty?
|
@@ -304,7 +312,18 @@ module Teleport
|
|
304
312
|
end
|
305
313
|
false
|
306
314
|
end
|
307
|
-
|
315
|
+
|
316
|
+
# Calculate the md5 checksum for a file
|
317
|
+
def md5sum(path)
|
318
|
+
digest, buf = Digest::MD5.new, ""
|
319
|
+
File.open(path) do |f|
|
320
|
+
while f.read(4096, buf)
|
321
|
+
digest.update(buf)
|
322
|
+
end
|
323
|
+
end
|
324
|
+
digest.hexdigest
|
325
|
+
end
|
326
|
+
|
308
327
|
private
|
309
328
|
|
310
329
|
# Returns true if verbosity is turned on.
|
data/lib/teleport/version.rb
CHANGED
@@ -0,0 +1,39 @@
|
|
1
|
+
require "erb"
|
2
|
+
require "spec_helper"
|
3
|
+
|
4
|
+
describe "a new ec2 instance" do
|
5
|
+
ec2
|
6
|
+
|
7
|
+
telfile do
|
8
|
+
<<EOF
|
9
|
+
user "gub"
|
10
|
+
ruby "1.8.7"
|
11
|
+
ssh_options ["-o", "User=ubuntu", "-o", "StrictHostKeyChecking=no", "-o", "IdentityFile=#{ENV["TELEPORT_SSH_KEY"]}"]
|
12
|
+
|
13
|
+
role :master, :packages => %w(nginx)
|
14
|
+
server "#{$ec2_ip_address}", :role => :master, :packages => %w(strace)
|
15
|
+
packages %w(atop)
|
16
|
+
|
17
|
+
before_install do
|
18
|
+
puts "BEFORE_INSTALL"
|
19
|
+
end
|
20
|
+
|
21
|
+
after_install do
|
22
|
+
puts "AFTER_INSTALL"
|
23
|
+
run "touch /tmp/gub.txt"
|
24
|
+
end
|
25
|
+
EOF
|
26
|
+
end
|
27
|
+
|
28
|
+
it "installs properly" do
|
29
|
+
ARGV.clear
|
30
|
+
ARGV << $ec2_ip_address
|
31
|
+
Teleport::Main.new
|
32
|
+
end
|
33
|
+
|
34
|
+
it "installs again" do
|
35
|
+
ARGV.clear
|
36
|
+
ARGV << $ec2_ip_address
|
37
|
+
Teleport::Main.new
|
38
|
+
end
|
39
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
SUPPORT = "#{File.dirname(__FILE__)}/support"
|
2
|
+
|
3
|
+
$LOAD_PATH << "#{File.dirname(__FILE__)}/../lib"
|
4
|
+
$LOAD_PATH << File.dirname(__FILE__)
|
5
|
+
$LOAD_PATH << SUPPORT
|
6
|
+
|
7
|
+
require "awesome_print"
|
8
|
+
require "rspec"
|
9
|
+
require "teleport"
|
10
|
+
|
11
|
+
Dir["#{SUPPORT}/*.rb"].each { |i| require File.basename(i) }
|
12
|
+
|
13
|
+
TELDIRS = "#{File.dirname(__FILE__)}/teldirs"
|
14
|
+
|
15
|
+
RSpec.configure do |config|
|
16
|
+
config.extend Support::Telfile
|
17
|
+
config.extend Support::Ec2
|
18
|
+
|
19
|
+
ec2_configured = Support::Ec2.configured?
|
20
|
+
warn(Support::Ec2.message) if !ec2_configured
|
21
|
+
|
22
|
+
config.filter_run_excluding(:config => lambda { |value|
|
23
|
+
return true if value == :ec2 && !ec2_configured
|
24
|
+
})
|
25
|
+
end
|
data/spec/support/ec2.rb
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
require "AWS"
|
2
|
+
|
3
|
+
# spin up a fresh ec2 instance
|
4
|
+
module Support
|
5
|
+
module Ec2
|
6
|
+
AMI_10_04 = "fbbf7892"
|
7
|
+
AMI_10_10 = "08f40561"
|
8
|
+
AMI_11_04 = "68ad5201"
|
9
|
+
KEYPAIR = "teleport"
|
10
|
+
GROUP = "teleport"
|
11
|
+
|
12
|
+
AMI = "ami-#{AMI_10_04}"
|
13
|
+
|
14
|
+
def self.configured?
|
15
|
+
ENV["TELEPORT_ACCESS_KEY_ID"] && ENV["TELEPORT_SECRET_ACCESS_KEY"] && ENV["TELEPORT_SSH_KEY"]
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.message
|
19
|
+
<<EOF
|
20
|
+
------------------------------------------------------------------------
|
21
|
+
If you want to test against EC2, do the following:
|
22
|
+
|
23
|
+
1. Create a "teleport" keypair on EC2.
|
24
|
+
2. Set the TELEPORT_ACCESS_KEY_ID, TELEPORT_SECRET_ACCESS_KEY and
|
25
|
+
TELEPORT_SSH_KEY environment variables.
|
26
|
+
|
27
|
+
End-to-end tests that rely on EC2 will be skipped in the meantime.
|
28
|
+
------------------------------------------------------------------------
|
29
|
+
EOF
|
30
|
+
end
|
31
|
+
|
32
|
+
#
|
33
|
+
# specs call this
|
34
|
+
#
|
35
|
+
|
36
|
+
def ec2
|
37
|
+
controller = nil
|
38
|
+
before(:all) do
|
39
|
+
if ENV["TELEPORT_IP"]
|
40
|
+
$ec2_ip_address = ENV["TELEPORT_IP"]
|
41
|
+
else
|
42
|
+
controller = Controller.new
|
43
|
+
controller.stop
|
44
|
+
$ec2_ip_address = controller.start
|
45
|
+
end
|
46
|
+
end
|
47
|
+
after(:all) do
|
48
|
+
controller.stop if controller
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
#
|
53
|
+
# this controller class does all the work
|
54
|
+
#
|
55
|
+
|
56
|
+
class Controller
|
57
|
+
def initialize
|
58
|
+
raise "not configured" if !Support::Ec2::configured?
|
59
|
+
@ec2 = AWS::EC2::Base.new(:access_key_id => ENV["TELEPORT_ACCESS_KEY_ID"], :secret_access_key => ENV["TELEPORT_SECRET_ACCESS_KEY"])
|
60
|
+
end
|
61
|
+
|
62
|
+
def start
|
63
|
+
puts "Running new ec2 instance..."
|
64
|
+
# setup security group and allow ssh
|
65
|
+
begin
|
66
|
+
@ec2.create_security_group(:group_name => GROUP, :group_description => GROUP)
|
67
|
+
rescue AWS::InvalidGroupDuplicate
|
68
|
+
# ignore
|
69
|
+
end
|
70
|
+
@ec2.authorize_security_group_ingress(:group_name => GROUP, :ip_protocol => "tcp", :from_port => 22, :to_port => 22)
|
71
|
+
|
72
|
+
# create the instance
|
73
|
+
@ec2.run_instances(:image_id => AMI, :instance_type => "m1.large", :key_name => KEYPAIR, :security_group => GROUP)
|
74
|
+
|
75
|
+
# wait for the new instance to start
|
76
|
+
puts "Waiting for ec2 instance to start..."
|
77
|
+
while true
|
78
|
+
sleep 3
|
79
|
+
instance = describe_instances.first
|
80
|
+
status = instance["instanceState"]["name"]
|
81
|
+
puts " #{instance["instanceId"]}: #{status}"
|
82
|
+
break if status == "running"
|
83
|
+
end
|
84
|
+
|
85
|
+
# return the ip address
|
86
|
+
ip = instance["ipAddress"]
|
87
|
+
puts " #{instance["instanceId"]}: #{ip}"
|
88
|
+
puts " sleeping to give ssh a chance to start..."
|
89
|
+
sleep 10
|
90
|
+
ip
|
91
|
+
end
|
92
|
+
|
93
|
+
def stop
|
94
|
+
puts "Terminating existing ec2 instances..."
|
95
|
+
ids = describe_instances.map { |i| i["instanceId"] }
|
96
|
+
if !ids.empty?
|
97
|
+
puts " terminate: #{ids.join(" ")}"
|
98
|
+
@ec2.terminate_instances(:instance_id => ids)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def describe_instances
|
103
|
+
list = []
|
104
|
+
hash = @ec2.describe_instances
|
105
|
+
if hash = hash["reservationSet"]
|
106
|
+
list = hash["item"].map { |i| i["instancesSet"]["item"] }.flatten
|
107
|
+
end
|
108
|
+
# cull stuff we don't care about
|
109
|
+
list = list.select { |i| i["keyName"] == KEYPAIR }
|
110
|
+
list = list.select { |i| i["instanceState"]["name"] !~ /terminated|shutting-down/ }
|
111
|
+
list
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# http://stackoverflow.com/questions/5118745/is-systemexit-a-special-kind-of-exception
|
2
|
+
|
3
|
+
module ExitCodeMatchers
|
4
|
+
RSpec::Matchers.define :exit_with_code do |code|
|
5
|
+
actual = nil
|
6
|
+
match do |block|
|
7
|
+
begin
|
8
|
+
block.call
|
9
|
+
rescue SystemExit => e
|
10
|
+
actual = e.status
|
11
|
+
end
|
12
|
+
actual && actual == code
|
13
|
+
end
|
14
|
+
failure_message_for_should do |block|
|
15
|
+
"expected block to call exit(#{code}) but exit" +
|
16
|
+
(actual.nil? ? " not called" : "(#{actual}) was called")
|
17
|
+
end
|
18
|
+
failure_message_for_should_not do |block|
|
19
|
+
"expected block not to call exit(#{code})"
|
20
|
+
end
|
21
|
+
description do
|
22
|
+
"expect block to call exit(#{code})"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
RSpec.configure do |config|
|
28
|
+
config.include(ExitCodeMatchers)
|
29
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# run inside a specific dir
|
2
|
+
module Support
|
3
|
+
module Telfile
|
4
|
+
TMP = "/tmp/teleport_spec"
|
5
|
+
|
6
|
+
def telfile(contents = nil, &block)
|
7
|
+
pwd = nil
|
8
|
+
before(:all) do
|
9
|
+
pwd = Dir.pwd
|
10
|
+
`rm -rf #{TMP} && mkdir -p #{TMP}`
|
11
|
+
Dir.chdir(TMP)
|
12
|
+
File.open("Telfile", "w") do |f|
|
13
|
+
if block
|
14
|
+
contents = block.call
|
15
|
+
end
|
16
|
+
f.puts(contents)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
after(:all) do
|
21
|
+
Dir.chdir(pwd)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Teleport::Config do
|
4
|
+
context "with a blank Telfile" do
|
5
|
+
telfile("")
|
6
|
+
|
7
|
+
let(:config) do
|
8
|
+
Teleport::Config.new
|
9
|
+
end
|
10
|
+
it "defaults to the current username" do
|
11
|
+
config.user.should == `whoami`.strip
|
12
|
+
end
|
13
|
+
it "defaults to the first vm in RUBIES" do
|
14
|
+
config.ruby.should == Teleport::Config::RUBIES.first
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
context "with a simple Telfile" do
|
19
|
+
telfile do
|
20
|
+
<<EOF
|
21
|
+
user "somebody"
|
22
|
+
ruby "1.8.7"
|
23
|
+
|
24
|
+
role :master, :packages => %w(nginx)
|
25
|
+
role :slave, :packages => %w(memcached)
|
26
|
+
server "one", :role => :master, :packages => %w(strace)
|
27
|
+
server "two", :role => :slave, :packages => %w(telnet)
|
28
|
+
packages %w(atop)
|
29
|
+
apt "blah blah blah", :key => "123"
|
30
|
+
|
31
|
+
before_install do
|
32
|
+
puts "before_install running"
|
33
|
+
end
|
34
|
+
|
35
|
+
after_install do
|
36
|
+
puts "after_install running"
|
37
|
+
end
|
38
|
+
EOF
|
39
|
+
end
|
40
|
+
|
41
|
+
let(:config) do
|
42
|
+
Teleport::Config.new
|
43
|
+
end
|
44
|
+
it "has the master role" do
|
45
|
+
config.role(:master).name.should == :master
|
46
|
+
config.role(:master).packages.should == %w(nginx)
|
47
|
+
end
|
48
|
+
it "has server one" do
|
49
|
+
config.server("one").name.should == "one"
|
50
|
+
config.server("one").packages.should == %w(strace)
|
51
|
+
end
|
52
|
+
it "has default packages" do
|
53
|
+
config.packages.should == %w(atop)
|
54
|
+
end
|
55
|
+
it "has callbacks" do
|
56
|
+
config.callbacks[:before_install].should_not == nil
|
57
|
+
config.callbacks[:after_install].should_not == nil
|
58
|
+
end
|
59
|
+
it "has an apt line" do
|
60
|
+
config.apt.first.line.should == "blah blah blah"
|
61
|
+
config.apt.first.options[:key].should == "123"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
data/teleport.gemspec
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
|
1
|
+
$LOAD_PATH << File.expand_path("../lib", __FILE__)
|
2
|
+
|
2
3
|
require "teleport/version"
|
3
4
|
|
4
5
|
Gem::Specification.new do |s|
|
@@ -8,11 +9,17 @@ Gem::Specification.new do |s|
|
|
8
9
|
s.authors = ["Adam Doppelt"]
|
9
10
|
s.email = ["amd@gurge.com"]
|
10
11
|
s.homepage = "http://github.com/rglabs/teleport"
|
11
|
-
s.summary =
|
12
|
-
s.description =
|
12
|
+
s.summary = "Teleport - opinionated Ubuntu server setup with Ruby."
|
13
|
+
s.description = "Easy Ubuntu server setup via teleportation."
|
13
14
|
|
14
15
|
s.rubyforge_project = "teleport"
|
15
16
|
|
17
|
+
s.add_development_dependency("amazon-ec2")
|
18
|
+
s.add_development_dependency("awesome_print")
|
19
|
+
s.add_development_dependency("rake")
|
20
|
+
s.add_development_dependency("rdoc", ["~> 3.9"])
|
21
|
+
s.add_development_dependency("rspec", ["~> 2.6"])
|
22
|
+
|
16
23
|
s.files = `git ls-files`.split("\n")
|
17
24
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
25
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: teleport
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 21
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 1
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 1.0.
|
9
|
+
- 1
|
10
|
+
version: 1.0.1
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Adam Doppelt
|
@@ -15,10 +15,81 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-08-
|
18
|
+
date: 2011-08-10 00:00:00 -07:00
|
19
19
|
default_executable:
|
20
|
-
dependencies:
|
21
|
-
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: amazon-ec2
|
23
|
+
type: :development
|
24
|
+
version_requirements: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
version: "0"
|
33
|
+
requirement: *id001
|
34
|
+
prerelease: false
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: awesome_print
|
37
|
+
type: :development
|
38
|
+
version_requirements: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
hash: 3
|
44
|
+
segments:
|
45
|
+
- 0
|
46
|
+
version: "0"
|
47
|
+
requirement: *id002
|
48
|
+
prerelease: false
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: rake
|
51
|
+
type: :development
|
52
|
+
version_requirements: &id003 !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
hash: 3
|
58
|
+
segments:
|
59
|
+
- 0
|
60
|
+
version: "0"
|
61
|
+
requirement: *id003
|
62
|
+
prerelease: false
|
63
|
+
- !ruby/object:Gem::Dependency
|
64
|
+
name: rdoc
|
65
|
+
type: :development
|
66
|
+
version_requirements: &id004 !ruby/object:Gem::Requirement
|
67
|
+
none: false
|
68
|
+
requirements:
|
69
|
+
- - ~>
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
hash: 21
|
72
|
+
segments:
|
73
|
+
- 3
|
74
|
+
- 9
|
75
|
+
version: "3.9"
|
76
|
+
requirement: *id004
|
77
|
+
prerelease: false
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: rspec
|
80
|
+
type: :development
|
81
|
+
version_requirements: &id005 !ruby/object:Gem::Requirement
|
82
|
+
none: false
|
83
|
+
requirements:
|
84
|
+
- - ~>
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
hash: 15
|
87
|
+
segments:
|
88
|
+
- 2
|
89
|
+
- 6
|
90
|
+
version: "2.6"
|
91
|
+
requirement: *id005
|
92
|
+
prerelease: false
|
22
93
|
description: Easy Ubuntu server setup via teleportation.
|
23
94
|
email:
|
24
95
|
- amd@gurge.com
|
@@ -38,12 +109,19 @@ files:
|
|
38
109
|
- lib/teleport.rb
|
39
110
|
- lib/teleport/config.rb
|
40
111
|
- lib/teleport/constants.rb
|
112
|
+
- lib/teleport/infer.rb
|
41
113
|
- lib/teleport/install.rb
|
42
114
|
- lib/teleport/main.rb
|
43
115
|
- lib/teleport/mirror.rb
|
44
116
|
- lib/teleport/run.sh
|
45
117
|
- lib/teleport/util.rb
|
46
118
|
- lib/teleport/version.rb
|
119
|
+
- spec/end_to_end_spec.rb
|
120
|
+
- spec/spec_helper.rb
|
121
|
+
- spec/support/ec2.rb
|
122
|
+
- spec/support/exit_code.rb
|
123
|
+
- spec/support/telfile.rb
|
124
|
+
- spec/unit/teleport/config_spec.rb
|
47
125
|
- teleport.gemspec
|
48
126
|
has_rdoc: true
|
49
127
|
homepage: http://github.com/rglabs/teleport
|