Module: Teleport::Util

Extended by:
Util
Included in:
Infer, Infer::Apt, Install, Main, Util
Defined in:
lib/teleport/util.rb

Overview

Helper module for executing commands and printing stuff out.

The general idea is to only print commands that are actually interesting. For example, mkdir_if_necessary won’t print anything if the directory already exists. That way we can scan teleport output and see what changes were made without getting lost in repetitive commands that had no actual effect.

Defined Under Namespace

Classes: RunError

Constant Summary collapse

RESET =
"\e[0m"
RED =
"\e[1;37;41m"
GREEN =
"\e[1;37;42m"
YELLOW =
"\e[1;37;43m"
BLUE =
"\e[1;37;44m"
MAGENTA =
"\e[1;37;45m"
CYAN =
"\e[1;37;46m"

Instance Method Summary collapse

Instance Method Details

A nice printout in green.



258
259
260
261
262
# File 'lib/teleport/util.rb', line 258

def banner(s, color = GREEN)
  s = "#{s} ".ljust(72, " ")
  $stderr.write "#{color}[#{Time.new.strftime('%H:%M:%S')}] #{s}#{RESET}\n"
  $stderr.flush
end

#chmod(file, mode) ⇒ Object

Chmod file to a new mode.



212
213
214
215
216
217
218
219
220
221
# File 'lib/teleport/util.rb', line 212

def chmod(file, mode)
  if File.stat(file).mode != mode
    begin
      FileUtils.chmod(mode, file, :verbose => verbose?)
    rescue NoMethodError
      # workaround https://bugs.ruby-lang.org/issues/8547
      FileUtils.chmod(mode, file)
    end
  end
end

#chown(file, user) ⇒ Object

Chown file to be owned by user.



200
201
202
203
204
205
206
207
208
209
# File 'lib/teleport/util.rb', line 200

def chown(file, user)
  user = user.to_s
  # who is the current owner?
  @uids ||= {}
  @uids[user] ||= Etc.getpwnam(user).uid
  uid = @uids[user]
  if File.stat(file).uid != uid
    run "chown #{user}:#{user} '#{file}'"
  end
end

#copy_metadata(src, dst) ⇒ Object

Copy perms and timestamps from src file to dst.



151
152
153
154
155
# File 'lib/teleport/util.rb', line 151

def (src, dst)
  stat = File.stat(src)
  File.chmod(stat.mode, dst)
  File.utime(stat.atime, stat.mtime, dst)
end

#copy_perms(src, dst) ⇒ Object

Copy perms from src file to dst.



145
146
147
148
# File 'lib/teleport/util.rb', line 145

def copy_perms(src, dst)
  stat = File.stat(src)
  File.chmod(stat.mode, dst)
end

#cp(src, dst, owner = nil, mode = nil) ⇒ Object

Copy file or dir from src to dst. Optionally, set the mode and owner of dst.



159
160
161
162
163
164
165
166
167
# File 'lib/teleport/util.rb', line 159

def cp(src, dst, owner = nil, mode = nil)
  FileUtils.cp_r(src, dst, :preserve => true, :verbose => verbose?)
  if owner && !File.symlink?(dst)
    chown(dst, owner)
  end
  if mode
    chmod(dst, mode)
  end
end

#cp_if_necessary(src, dst, owner = nil, mode = nil) ⇒ Object

Copy file or dir from src to dst, but ONLY if dst doesn’t exist or has different contents than src. Optionally, set the mode and owner of dst.



179
180
181
182
183
184
# File 'lib/teleport/util.rb', line 179

def cp_if_necessary(src, dst, owner = nil, mode = nil)
  if !File.exists?(dst) || different?(src, dst)
    cp(src, dst, owner, mode)
    true
  end
end

#cp_with_mkdir(src, dst, owner = nil, mode = nil) ⇒ Object

Copy file or dir from src to dst, but create the dst directory first if necessary. Optionally, set the mode and owner of dst.



171
172
173
174
# File 'lib/teleport/util.rb', line 171

def cp_with_mkdir(src, dst, owner = nil, mode = nil)
  mkdir_if_necessary(File.dirname(dst))
  cp(src, dst, owner, mode)
end

#different?(a, b) ⇒ Boolean

Are two files different?

Returns:

  • (Boolean)


140
141
142
# File 'lib/teleport/util.rb', line 140

def different?(a, b)
  !FileUtils.compare_file(a, b)
end

#fails?(command) ⇒ Boolean

Run a command, return true if it fails.

Returns:

  • (Boolean)


105
106
107
# File 'lib/teleport/util.rb', line 105

def fails?(command)
  !succeeds?(command)
end

#fatal(msg) ⇒ Object

Print a fatal error in red, then exit.



270
271
272
273
# File 'lib/teleport/util.rb', line 270

def fatal(msg)
  banner(msg, RED)
  exit(1)
end

#gem_if_necessary(gem) ⇒ Object

Install gem if necessary.



294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
# File 'lib/teleport/util.rb', line 294

def gem_if_necessary(gem)
  grep = args = nil
  if gem =~ /(.*)-(\d+\.\d+\.\d+)$/
    gem, version = $1, $2
    grep = "^#{gem}.*#{version}"
    args = " --version #{version}"
  else
    grep = "^#{gem}"
  end
  if fails?("gem list #{gem} | grep '#{grep}'")
    banner "#{gem}..."
    run "gem install #{gem} #{args} --no-rdoc --no-ri"
    return true
  end
  false
end

#gem_version(name) ⇒ Object

Returns the newest currently installed version of the named gem or false if the gem is not installed



313
314
315
316
317
318
319
# File 'lib/teleport/util.rb', line 313

def gem_version(name)
  spec_out = `gem specification #{name} 2> /dev/null`
  if !spec_out.empty?
    spec = Gem::Specification.from_yaml(spec_out)
    spec.version
  end
end

#ln(src, dst) ⇒ Object

Create a symlink from src to dst.



237
238
239
# File 'lib/teleport/util.rb', line 237

def ln(src, dst)
  FileUtils.ln_sf(src, dst, :verbose => verbose?)
end

#ln_if_necessary(src, dst) ⇒ Object

Create a symlink from src to dst, but only if it hasn’t already been created.



243
244
245
246
247
248
249
250
251
252
253
254
255
# File 'lib/teleport/util.rb', line 243

def ln_if_necessary(src, dst)
  ln = false
  if !File.symlink?(dst)
    ln = true
  elsif File.readlink(dst) != src
    rm(dst)
    ln = true
  end
  if ln
    ln(src, dst)
    true
  end
end

#md5sum(path) ⇒ Object

Calculate the md5 checksum for a file



338
339
340
341
342
343
344
345
346
# File 'lib/teleport/util.rb', line 338

def md5sum(path)
  digest, buf = Digest::MD5.new, ""
  File.open(path) do |f|
    while f.read(4096, buf)
      digest.update(buf)
    end
  end
  digest.hexdigest
end

#mkdir(dir, owner = nil, mode = nil) ⇒ Object

Like mkdir -p. Optionally, set the owner and mode.



121
122
123
124
125
# File 'lib/teleport/util.rb', line 121

def mkdir(dir, owner = nil, mode = nil)
  FileUtils.mkdir_p(dir, :verbose => verbose?)
  chmod(dir, mode) if mode
  chown(dir, owner) if owner
end

#mkdir_if_necessary(dir, owner = nil, mode = nil) ⇒ Object

mkdir only if the directory doesn’t already exist. Optionally, set the owner and mode.



129
130
131
# File 'lib/teleport/util.rb', line 129

def mkdir_if_necessary(dir, owner = nil, mode = nil)
  mkdir(dir, owner, mode) if !(File.exists?(dir) || File.symlink?(dir))
end

#mv(src, dst) ⇒ Object

Move src to dst. Because this uses FileUtils, it works even if dst is on a different partition.



188
189
190
# File 'lib/teleport/util.rb', line 188

def mv(src, dst)
  FileUtils.mv(src, dst, :verbose => verbose?)
end

#mv_with_mkdir(src, dst) ⇒ Object

Move src to dst, but create the dst directory first if necessary.



194
195
196
197
# File 'lib/teleport/util.rb', line 194

def mv_with_mkdir(src, dst)
  mkdir_if_necessary(File.dirname(dst))
  mv(src, dst)
end

#package_if_necessary(pkg) ⇒ Object

Install pkg if necessary.



286
287
288
289
290
291
# File 'lib/teleport/util.rb', line 286

def package_if_necessary(pkg)
  if !package_is_installed?(pkg)
    banner "#{pkg}..."
    run "apt-get -y install #{pkg}"
  end
end

#package_is_installed?(pkg) ⇒ Boolean

Returns true if the pkg is installed.

Returns:

  • (Boolean)


281
282
283
# File 'lib/teleport/util.rb', line 281

def package_is_installed?(pkg)
  succeeds?("dpkg-query -f='${Status}' -W #{pkg} 2>&1 | grep 'install ok installed'")
end

#process_by_pid?(pidfile) ⇒ Boolean

Returns true if the pidfile exists and that process id exists as well.

Returns:

  • (Boolean)


323
324
325
326
327
328
329
330
331
332
333
334
335
# File 'lib/teleport/util.rb', line 323

def process_by_pid?(pidfile)
  begin
    if File.exists?(pidfile)
      pid = File.read(pidfile).to_i
      if pid != 0
        Process.kill(0, pid)
        return true
      end
    end
  rescue Errno::ENOENT, Errno::ESRCH
  end
  false
end

#rm(file) ⇒ Object

rm a file



224
225
226
# File 'lib/teleport/util.rb', line 224

def rm(file)
  FileUtils.rm(file, :force => true, :verbose => verbose?)
end

#rm_and_mkdir(dir) ⇒ Object

rm a dir and recreate it.



134
135
136
137
# File 'lib/teleport/util.rb', line 134

def rm_and_mkdir(dir)
  raise "don't do this" if dir == ""
  run "rm -rf #{dir} && mkdir -p #{dir}"
end

#rm_if_necessary(file) ⇒ Object

rm a file, but only if it exists.



229
230
231
232
233
234
# File 'lib/teleport/util.rb', line 229

def rm_if_necessary(file)
  if File.exists?(file)
    rm(file)
    true
  end
end

#run(command, args = nil) ⇒ Object

Run a command, raise an error upon failure. Output goes to $stdout/$stderr.



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/teleport/util.rb', line 40

def run(command, args = nil)
  line = nil
  if args
    args = args.map(&:to_s)
    line = "#{command} #{args.join(" ")}"
    vputs line
    system(command, *args)
  else
    line = command
    vputs line
    system(command)
  end
  if $? != 0
    if $?.termsig == Signal.list["INT"]
      raise "#{line} interrupted"
    end
    raise RunError, "#{line} failed : #{$?.to_i / 256}"
  end
end

#run_capture(command, *args) ⇒ Object

Run a command, raise an error upon failure. The output is captured as a string and returned.



62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/teleport/util.rb', line 62

def run_capture(command, *args)
  if !args.empty?
    args = args.flatten.map { |i| shell_escape(i) }.join(" ")
    command = "#{command} #{args}"
  end
  result = `#{command}`
  if $? != 0
    if $?.termsig == Signal.list["INT"]
      raise "#{command} interrupted"
    end
    raise RunError, "#{command} failed : #{$?.to_i / 256} #{result.inspect}"
  end
  result
end

#run_capture_lines(command, *args) ⇒ Object

Run a command and split the result into lines, raise an error upon failure. The output is captured as an array of strings and returned.



80
81
82
# File 'lib/teleport/util.rb', line 80

def run_capture_lines(command, *args)
  run_capture(command, args).split("\n")
end

#run_quietly(command, *args) ⇒ Object

Run a command but don’t send any output to $stdout/$stderr.



85
86
87
88
89
90
91
# File 'lib/teleport/util.rb', line 85

def run_quietly(command, *args)
  if !args.empty?
    args = args.flatten.map { |i| shell_escape(i) }.join(" ")
    command = "#{command} #{args}"
  end
  run("#{command} > /dev/null 2> /dev/null")
end

#run_verbose!Object

Make all commands echo before running.



34
35
36
# File 'lib/teleport/util.rb', line 34

def run_verbose!
  @run_verbose = true
end

#shell(commands) ⇒ Object

Run one or several commands, separate by newlines.



94
95
96
# File 'lib/teleport/util.rb', line 94

def shell(commands)
  commands.split("\n").each { |i| run(i) }
end

#shell_escape(s) ⇒ Object

Escape some text for the shell and enclose it in single quotes if necessary.



111
112
113
114
115
116
117
118
# File 'lib/teleport/util.rb', line 111

def shell_escape(s)
  s = s.to_s
  if s !~ /^[0-9A-Za-z+,.\/:=@_-]+$/
    s = s.gsub("'") { "'\\''" }
    s = "'#{s}'"
  end
  s
end

#succeeds?(command) ⇒ Boolean

Run a command, return true if it succeeds.

Returns:

  • (Boolean)


99
100
101
102
# File 'lib/teleport/util.rb', line 99

def succeeds?(command)
  system("#{command} > /dev/null 2> /dev/null")
  $? == 0
end

#warning(msg) ⇒ Object

Print a warning in yellow.



265
266
267
# File 'lib/teleport/util.rb', line 265

def warning(msg)
  banner("Warning: #{msg}", YELLOW)
end

#whoamiObject

Who owns this process?



276
277
278
# File 'lib/teleport/util.rb', line 276

def whoami
  @whoami ||= Etc.getpwuid(Process.uid).name
end