Module: ShellMethods

Defined in:
lib/chris_lib/shell_methods.rb

Overview

Helpers for shell-friendly Ruby scripts and deployment utilities.

Instance Method Summary collapse

Instance Method Details

#backup_databaseBoolean

Create a Postgres snapshot by delegating to ‘script/getSnapShot.sh`.

Returns:

  • (Boolean)

    command exit status



112
113
114
115
# File 'lib/chris_lib/shell_methods.rb', line 112

def backup_database
  file_path = "../backups/prod#{time_hash}.dump"
  system('./script/getSnapShot.sh production ' + file_path)
end

#check_chris_lib_statusObject

Verify that ‘chris_lib` is up to date before deploying.



175
176
177
178
179
180
181
# File 'lib/chris_lib/shell_methods.rb', line 175

def check_chris_lib_status
  gs=`cd ../chris_lib;git status`; lr=$?.successful?
  return unless gs['working tree clean'].nil? && gs['up-to-date'].nil?
  puts "Exiting, chris_lib is not up to date with master."
  exit 3
  system('cd $OLDPWD')
end

#check_git_cleanObject

Ensure git workspace is clean before deploying.



166
167
168
169
170
171
172
# File 'lib/chris_lib/shell_methods.rb', line 166

def check_git_clean
  puts "Checking git status"
  gs = `git status`
  return unless gs['working tree clean'].nil?
  puts "Exiting, you need to commit files"
  exit 1
end

#file_size(file_path) ⇒ Integer

Note:

Returns 0 and warns when the file is missing.

Returns file size in bytes.

Parameters:

  • file_path (String)

Returns:

  • (Integer)

    file size in bytes

Raises:

  • (ArgumentError)


28
29
30
31
32
33
34
35
36
# File 'lib/chris_lib/shell_methods.rb', line 28

def file_size(file_path)
  raise ArgumentError, 'file_path must be provided' unless file_path.respond_to?(:to_s)
  path = file_path.to_s
  unless File.exist?(path)
    warn "file_size: #{path} does not exist"
    return 0
  end
  `stat -f%z #{path}`.to_i
end

#migrate_if_necessary(remote: nil, skip_migration: false) ⇒ void

This method returns an undefined value.

Optionally run migrations if local and remote schema versions differ.

Parameters:

  • remote (String, nil) (defaults to: nil)
  • skip_migration (Boolean) (defaults to: false)


187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/chris_lib/shell_methods.rb', line 187

def migrate_if_necessary(remote: nil, skip_migration: false)
  if skip_migration
    puts "No migration will be performed due to --fast or --skip_migration options"
  else
    destination=(remote.nil? ? nil : "--remote #{remote}")
    puts "Checking local and remote databases have same version and migrates if necessary"
    if same_db_version(remote: remote)
       puts "No migration necessary"
    else
       puts "Warning, different db versions"
       system('tput bel')
       puts "Press m<cr> to migrate or q<cr> to exit"
       ans=$stdin.gets()
       exit 2 if ans[0]!='m'
       system("heroku run rake db:migrate #{destination}")
    end
  end
end

#notify_rollbar_of_deploy(access_token: nil) ⇒ void

This method returns an undefined value.

Notify Rollbar of a new deploy via its API.

Parameters:

  • access_token (String, nil) (defaults to: nil)


209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
# File 'lib/chris_lib/shell_methods.rb', line 209

def notify_rollbar_of_deploy(access_token: nil)
  warn 'notify_rollbar_of_deploy called without access token' if access_token.nil? || access_token.empty?
  system("ACCESS_TOKEN=#{access_token}")
  system("ENVIRONMENT=production")
  system("LOCAL_USERNAME=`whoami`")
  system("COMMENT=v#{TGM_VERSION}")
  sha = `git log -n 1 --pretty=format:'%H'`
  system("REVISION=sha")
  puts "Notifiying of revision #{sha}"
  cr = `curl https://api.rollbar.com/api/1/deploy/ \
  -F access_token=$ACCESS_TOKEN \
  -F environment=$ENVIRONMENT \
  -F revision=$REVISION \
  -F comment=$COMMENT \
  -F local_username=$LOCAL_USERNAME`
  if cr.class == Hash && cr['data'].empty?
    puts "Rollbar was notified of deploy of v#{TGM_VERSION} with SHA #{sha[0..5]}"
  else
    system('tput bel;tput bel')
    puts "Failure to notify Rollbar of deploy of v#{TGM_VERSION} with SHA #{sha[0..5]}", cr
  end
end

#osx_hostnameString

Returns hostname of the current machine.

Returns:

  • (String)

    hostname of the current machine



68
69
70
# File 'lib/chris_lib/shell_methods.rb', line 68

def osx_hostname
  `hostname`
end

#osx_imessage_admin(msg) ⇒ String

Note:

Warns and no-ops when ‘msg` is blank.

Send an iMessage to the “admin” buddy on macOS.

Parameters:

  • msg (String)

Returns:

  • (String)


42
43
44
45
46
47
48
# File 'lib/chris_lib/shell_methods.rb', line 42

def osx_imessage_admin(msg)
  if msg.nil? || msg.strip.empty?
    warn 'osx_imessage_admin called without a message; skipping'
    return nil
  end
  `osascript -e 'tell application "Messages" to send "#{msg}" to buddy "admin"'`
end

#osx_notification(msg, title) ⇒ String

Note:

Warns and no-ops when ‘msg` is blank. Supplies a default title when missing.

Trigger a macOS notification via AppleScript.

Parameters:

  • msg (String)
  • title (String)

Returns:

  • (String)


55
56
57
58
59
60
61
62
63
64
65
# File 'lib/chris_lib/shell_methods.rb', line 55

def osx_notification(msg, title)
  if msg.nil? || msg.strip.empty?
    warn 'osx_notification called without a message; skipping'
    return nil
  end
  if title.nil? || title.strip.empty?
    warn 'osx_notification called without a title; using default'
    title = 'Notification'
  end
  `osascript -e 'display notification "#{msg}" with title "#{title}"'`
end

#osx_send_mail(subject, body = nil) ⇒ String

Note:

Warns and no-ops when ‘subject` is blank.

Mail the signed-in macOS user using the BSD ‘mail` command.

Parameters:

  • subject (String)
  • body (String) (defaults to: nil)

Returns:

  • (String)


77
78
79
80
81
82
83
# File 'lib/chris_lib/shell_methods.rb', line 77

def osx_send_mail(subject, body = nil)
  if subject.nil? || subject.strip.empty?
    warn 'osx_send_mail called without a subject; skipping'
    return nil
  end
  `echo "#{body}" | mail -s "#{subject}" 'Chris'`
end

#parse_optionsHash

Parse CLI options for deployment scripts.

Returns:

  • (Hash)

    options hash with keys :skip_migration, :fast, :special_rake



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/chris_lib/shell_methods.rb', line 87

def parse_options
  @options = {}
  OptionParser.new do |opts|
    opts.on("-s", "--skip_migration", "Skip migrations") do
      @options[:skip_migration] = true
    end
    opts.on("-f", "--fast", "Deploy without warnings and skip migrations") do
      @options[:fast] = true
    end
    opts.on("-r", "--special_rake", "Run special rake task") do
      @options[:special_rake] = true
    end
  end.parse!
  @options[:skip_migration] = true   if @options[:fast]
end

#r_runner(script_path, arg1) ⇒ String

Note:

Warns and returns ‘nil` when the script is missing.

Run an R script via ‘Rscript –vanilla`

Parameters:

  • script_path (String)

    absolute path to the R script

  • arg1 (String)

    first argument passed to the script (accessible in R via ‘commandArgs(trailingOnly=TRUE)`)

Returns:

  • (String)

    stdout from the R script

Raises:

  • (ArgumentError)


14
15
16
17
18
19
20
21
22
23
# File 'lib/chris_lib/shell_methods.rb', line 14

def r_runner(script_path, arg1)
  raise ArgumentError, 'script_path must be provided' unless script_path.respond_to?(:to_s)
  path = script_path.to_s
  unless File.exist?(path)
    warn "r_runner: #{path} does not exist"
    return nil
  end
  arg = arg1.to_s
  `Rscript --vanilla #{path} #{arg}`
end

#run_special_rake_taskObject

Placeholder for future custom rake tasks.

Raises:

  • (RuntimeError)

    always, prompting implementers to override



105
106
107
108
# File 'lib/chris_lib/shell_methods.rb', line 105

def run_special_rake_task
  fail 'Need to implement by asking for name of rake task and
  also requiring confirmation'
end

#same_db_version(remote: nil) ⇒ Boolean?

Compare local and remote database schema versions.

Parameters:

  • remote (String, nil) (defaults to: nil)

    Heroku remote name

Returns:

  • (Boolean, nil)

    true when versions match, false when mismatched, nil when versions cannot be read



147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/chris_lib/shell_methods.rb', line 147

def same_db_version(remote: nil)
  destination = (remote.nil? ? nil : "--remote #{remote}")
  lv = `rake db:version`
  puts 'Local version: ', lv
  hv = Bundler.with_unbundled_env{ 
    `heroku run rake db:version #{destination}`
  }
  puts hv
  return nil if hv.nil? || hv.empty?
  return nil if lv.nil? || lv.empty?
  key = 'version: '
  nl = lv.index(key) + 9
  l_version = lv.slice(nl..-1)
  nh = hv.index(key) + 9
  h_version = hv.slice(nh..-1)
  l_version == h_version
end

#time_hashString

Timestamp helper used to build backup filenames.

Returns:

  • (String)


136
137
138
139
# File 'lib/chris_lib/shell_methods.rb', line 136

def time_hash
  time = Time.now
  time.day.to_s + time.month.to_s + time.year.to_s + '-' + time.hour.to_s + time.min.to_s
end

#warn_usersvoid

This method returns an undefined value.

Notify users of an impending deploy via Heroku and progress bar countdown.



119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/chris_lib/shell_methods.rb', line 119

def warn_users
  system('heroku run rake util:three_min_warning --remote production')
  # spend one minute precompiling 
  progress_bar = ProgressBar.create
  # now 2 minutes waiting
  increment = 3 * 60 / 100
  (1..100).each do |_i|
    sleep increment
    progress_bar.increment
    progress_bar.refresh
  end
  system('heroku run rake util:delete_newest_announcement --remote production')
  system('heroku run rake util:warn_under_maintenance --remote production')
end