Class: Spout::Commands::Deploy

Inherits:
Object
  • Object
show all
Includes:
Helpers::Quietly
Defined in:
lib/spout/commands/deploy.rb

Overview

Deploys a data dictionary and associated dataset to the server.

Constant Summary collapse

INDENT_LENGTH =
23
INDENT =
" " * INDENT_LENGTH

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Helpers::Quietly

#quietly, #silence_stream

Constructor Details

#initialize(argv, version) ⇒ Deploy

Returns a new instance of Deploy.



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/spout/commands/deploy.rb', line 48

def initialize(argv, version)
  argv.shift # Remove "download" command from argv list
  @environment = argv.shift
  @version = version
  @skip_checks = !(argv.delete("--skip-checks").nil? && argv.delete("--no-checks").nil?)

  @skip_tests = !(argv.delete("--skip-tests").nil? && argv.delete("--no-tests").nil?)
  @skip_coverage = !(argv.delete("--skip-coverage").nil? && argv.delete("--no-coverage").nil?)

  @skip_variables = !(argv.delete("--skip-variables").nil? && argv.delete("--no-variables").nil?)
  @skip_dataset = !(argv.delete("--skip-dataset").nil? && argv.delete("--no-dataset").nil?)
  @skip_dictionary = !(argv.delete("--skip-dictionary").nil? && argv.delete("--no-dictionary").nil?)
  @skip_documentation = !(argv.delete("--skip-documentation").nil? && argv.delete("--no-documentation").nil?)
  @clean = !(argv.delete("--no-resume").nil? && argv.delete("--clean").nil?)
  @skip_server_scripts = !(argv.delete("--skip-server-scripts").nil? && argv.delete("--no-server-scripts").nil?)
  @archive_only = !(argv.delete("--archive-only").nil?)

  token_arg = argv.find { |arg| /^--token=/ =~ arg }
  argv.delete(token_arg)
  @token = token_arg.gsub(/^--token=/, "") if token_arg

  rows_arg = argv.find { |arg| /^--rows=(\d*)/ =~ arg }
  argv.delete(rows_arg)
  @number_of_rows = rows_arg.gsub(/--rows=/, "").to_i if rows_arg

  @argv = argv

  @created_folders = []

  begin
    run_all
  rescue Interrupt
    puts "\nINTERRUPTED".red
  end
end

Instance Attribute Details

#configObject

Returns the value of attribute config.



46
47
48
# File 'lib/spout/commands/deploy.rb', line 46

def config
  @config
end

#environmentObject

Returns the value of attribute environment.



46
47
48
# File 'lib/spout/commands/deploy.rb', line 46

def environment
  @environment
end

#slugObject

Returns the value of attribute slug.



46
47
48
# File 'lib/spout/commands/deploy.rb', line 46

def slug
  @slug
end

#subjectsObject

Returns the value of attribute subjects.



46
47
48
# File 'lib/spout/commands/deploy.rb', line 46

def subjects
  @subjects
end

#tokenObject

Returns the value of attribute token.



46
47
48
# File 'lib/spout/commands/deploy.rb', line 46

def token
  @token
end

#urlObject

Returns the value of attribute url.



46
47
48
# File 'lib/spout/commands/deploy.rb', line 46

def url
  @url
end

#versionObject

Returns the value of attribute version.



46
47
48
# File 'lib/spout/commands/deploy.rb', line 46

def version
  @version
end

#webserver_nameObject

Returns the value of attribute webserver_name.



46
47
48
# File 'lib/spout/commands/deploy.rb', line 46

def webserver_name
  @webserver_name
end

Instance Method Details

#config_file_loadObject



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/spout/commands/deploy.rb', line 100

def config_file_load
  print "   `.spout.yml` Check: "
  @config = Spout::Helpers::ConfigReader.new

  @slug = @config.slug

  if @slug == ""
    message = "#{INDENT}Please specify a dataset slug in your `.spout.yml` file!".red + " Ex:\n---\nslug: mydataset\n".gray
    failure(message)
  end

  if @config.webservers.empty?
    message = "#{INDENT}Please specify a webserver in your `.spout.yml` file!".red + " Ex:\n---\nwebservers:\n  - name: production\n    url: https://sleepdata.org\n  - name: staging\n    url: https://staging.sleepdata.org\n".gray
    failure(message)
  end

  matching_webservers = @config.webservers.select { |wh| /^#{@environment}/i =~ wh["name"].to_s.downcase }
  if matching_webservers.count == 0
    message = "#{INDENT}0 webservers match '#{@environment}'.".red + " The following webservers exist in your `.spout.yml` file:\n" + "#{INDENT}#{@config.webservers.collect{|wh| wh['name'].to_s.downcase}.join(', ')}".white
    failure(message)
  elsif matching_webservers.count > 1
    message = "#{INDENT}#{matching_webservers.count} webservers match '#{@environment}'.".red + " Did you mean one of the following?\n" + "#{INDENT}#{matching_webservers.collect{|wh| wh['name'].to_s.downcase}.join(', ')}".white
    failure(message)
  end

  @webserver_name = matching_webservers.first["name"].to_s.strip rescue @webserver_name = ""
  @url = URI.parse(matching_webservers.first["url"].to_s.strip) rescue @url = nil

  if @url.to_s == ""
    message = "#{INDENT}Invalid URL format for #{matching_webservers.first['name'].to_s.strip.downcase} webserver: ".red + "'#{matching_webservers.first['url'].to_s.strip}'".white
    failure(message)
  end

  puts "PASS".green
  puts "        Target Server: " + "#{@url}".white
  puts "       Target Dataset: " + "#{@slug}".white
end

#coverage_checkObject



204
205
206
207
208
209
210
211
# File 'lib/spout/commands/deploy.rb', line 204

def coverage_check
  if @skip_coverage
    puts "     Dataset Coverage: " + "SKIP".blue
    return
  end

  puts "     Dataset Coverage: " + "NOT IMPLEMENTED".yellow
end

#data_dictionary_uploadsObject



276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
# File 'lib/spout/commands/deploy.rb', line 276

def data_dictionary_uploads
  if @skip_dictionary
    puts "   Dictionary Uploads: " + "SKIP".blue
    return
  end

  print "   Dictionary Uploads:"

  require "spout/commands/exporter"
  Spout::Commands::Exporter.new(@version, ["--quiet"])

  csv_files = Dir.glob("exports/#{@version}/*.csv")
  csv_files.each_with_index do |csv_file, index|
    print "\r   Dictionary Uploads: " + "#{index + 1} of #{csv_files.count}".green
    @created_folders << "datasets"
    @created_folders << "datasets/archive"
    @created_folders << "datasets/archive/#{@version}"
    upload_file(csv_file, "datasets") unless @archive_only
    upload_file(csv_file, "datasets/archive/#{@version}")
  end
  puts "\r   Dictionary Uploads: " + "DONE          ".green
end

#dataset_uploadsObject



252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
# File 'lib/spout/commands/deploy.rb', line 252

def dataset_uploads
  if @skip_dataset
    puts "      Dataset Uploads: " + "SKIP".blue
    return
  end

  available_folders = (Dir.exist?("csvs") ? Dir.entries("csvs").select { |e| File.directory? File.join("csvs", e) }.reject { |e| [".", ".."].include?(e) }.sort : [])
  semantic = Spout::Helpers::Semantic.new(@version, available_folders)
  csv_directory = semantic.selected_folder
  csv_files = Dir.glob("csvs/#{csv_directory}/**/*.csv")

  csv_files.each_with_index do |csv_file, index|
    print "\r      Dataset Uploads: " + "#{index + 1} of #{csv_files.count}".green
    folder = csv_file.gsub(%r{^csvs/#{csv_directory}}, "").gsub(/#{File.basename(csv_file)}$/, "")
    folder = folder.gsub(%r{/$}, "")
    @created_folders << "datasets#{folder}"
    @created_folders << "datasets/archive"
    @created_folders << "datasets/archive/#{@version}#{folder}"
    upload_file(csv_file, "datasets#{folder}") unless @archive_only
    upload_file(csv_file, "datasets/archive/#{@version}#{folder}")
  end
  puts "\r      Dataset Uploads: " + "DONE          ".green
end

#failure(message) ⇒ Object

Raises:



352
353
354
355
356
# File 'lib/spout/commands/deploy.rb', line 352

def failure(message)
  puts "FAIL".red
  puts message
  raise DeployError
end

#graph_generationObject



244
245
246
247
248
249
250
# File 'lib/spout/commands/deploy.rb', line 244

def graph_generation
  # failure ""
  require "spout/commands/graphs"
  @argv << "--clean" if @clean
  Spout::Commands::Graphs.new(@argv, @version, true, @url, @slug, @token, @webserver_name, @subjects)
  puts "\r     Upload Variables: " + "DONE          ".green
end

#load_subjects_from_csvsObject



236
237
238
239
240
241
242
# File 'lib/spout/commands/deploy.rb', line 236

def load_subjects_from_csvs
  @dictionary_root = Dir.pwd
  @variable_files = Dir.glob(File.join(@dictionary_root, "variables", "**", "*.json"))
  @subject_loader = Spout::Helpers::SubjectLoader.new(@variable_files, [], @version, @number_of_rows, @config.visit)
  @subject_loader.load_subjects_from_csvs!
  @subjects = @subject_loader.subjects
end

#markdown_uploadsObject



299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
# File 'lib/spout/commands/deploy.rb', line 299

def markdown_uploads
  if @skip_documentation
    puts "Documentation Uploads: " + "SKIP".blue
    return
  end

  print "Documentation Uploads:"
  markdown_files = Dir.glob(%w(CHANGELOG.md KNOWNISSUES.md))
  markdown_files.each_with_index do |markdown_file, index|
    print "\rDocumentation Uploads: " + "#{index + 1} of #{markdown_files.count}".green
    @created_folders << "datasets"
    @created_folders << "datasets/archive"
    @created_folders << "datasets/archive/#{@version}"
    upload_file(markdown_file, "datasets") unless @archive_only
    upload_file(markdown_file, "datasets/archive/#{@version}")
  end
  puts "\rDocumentation Uploads: " + "DONE          ".green
end

#run_allObject



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/spout/commands/deploy.rb', line 84

def run_all
  config_file_load
  version_check
  test_check
  coverage_check
  user_authorization
  upload_variables
  dataset_uploads
  data_dictionary_uploads
  markdown_uploads
  trigger_server_updates
  set_default_dataset_version
rescue DeployError
  # Nothing on Deploy Error
end

#set_default_dataset_versionObject



335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
# File 'lib/spout/commands/deploy.rb', line 335

def set_default_dataset_version
  if @archive_only
    puts "  Set Default Version: " + "SKIP".blue
    return
  end
  print "  Set Default Version: "
  params = { auth_token: @token, dataset: @slug, version: @version }
  (json, _status) = Spout::Helpers::SendJson.post(
    "#{@url}/api/v1/dictionary/update_default_version.json", params
  )
  if json.is_a?(Hash) && json["version_update"] == "success"
    puts @version.to_s.green
  else
    failure("#{INDENT}Unable to set default version\n#{INDENT}to " + @version.to_s.white + " for " + @slug.to_s.white + " dataset.")
  end
end

#test_checkObject



184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/spout/commands/deploy.rb', line 184

def test_check
  if @skip_tests
    puts "          Spout Tests: " + "SKIP".blue
    return
  end

  print "          Spout Tests: "

  stdout = quietly do
    `spout t`
  end

  if stdout.match(/[^\d]0 failures, 0 errors,/)
    puts "PASS".green
  else
    message = "#{INDENT}spout t".white + " had errors or failures".red + "\n#{INDENT}Please fix all errors and failures and then run spout deploy again."
    failure message
  end
end

#trigger_server_updatesObject



318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
# File 'lib/spout/commands/deploy.rb', line 318

def trigger_server_updates
  if @skip_server_scripts
    puts "Launch Server Scripts: " + "SKIP".blue
    return
  end

  print "Launch Server Scripts: "
  params = { auth_token: @token, dataset: @slug, version: @version, folders: @created_folders.compact.uniq }
  (json, _status) = Spout::Helpers::SendJson.post("#{@url}/api/v1/dictionary/refresh.json", params)
  if json.is_a?(Hash) && json["refresh"] == "success"
    puts "DONE".green
  else
    puts "FAIL".red
    raise DeployError
  end
end

#upload_file(file, folder) ⇒ Object



358
359
360
# File 'lib/spout/commands/deploy.rb', line 358

def upload_file(file, folder)
  Spout::Helpers::SendFile.post("#{@url}/api/v1/dictionary/upload_file.json", file, @version, @token, @slug, folder)
end

#upload_variablesObject



227
228
229
230
231
232
233
234
# File 'lib/spout/commands/deploy.rb', line 227

def upload_variables
  if @skip_variables
    puts "     Upload Variables: " + "SKIP".blue
    return
  end
  load_subjects_from_csvs
  graph_generation
end

#user_authorizationObject



213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'lib/spout/commands/deploy.rb', line 213

def user_authorization
  puts  "  Get your token here: " + "#{@url}/token".blue.bg_gray.underline
  print "     Enter your token: "
  @token = STDIN.noecho(&:gets).chomp if @token.to_s.strip == ""
  (json, _status) = Spout::Helpers::JsonRequest.get("#{@url}/datasets/#{@slug}/a/#{@token}/editor.json")
  if json.is_a?(Hash) && json["editor"]
    puts "AUTHORIZED".green
  else
    puts "UNAUTHORIZED".red
    puts "#{INDENT}You are not set as an editor on the #{@slug} dataset or you mistyped your token."
    raise DeployError
  end
end

#version_checkObject

  • **Version Check**

    • Git Repo should have zero uncommitted changes

    • ‘CHANGELOG.md` top line should include version, ex: `## 0.1.0`

    • “v#VERSION” matches HEAD git tag annotation



142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/spout/commands/deploy.rb', line 142

def version_check
  if @skip_checks
    puts "        Version Check: " + "SKIP".blue
    return
  end

  stdout = quietly do
    `git status --porcelain`
  end

  print "     Git Status Check: "
  if stdout.to_s.strip == ""
    puts "PASS".green + " " + "nothing to commit, working directory clean".white
  else
    message = "#{INDENT}working directory contains uncomitted changes\n#{INDENT}use `".red + "--skip-checks".white + "` to ignore this step".red
    failure message
  end

  changelog = File.open("CHANGELOG.md", &:readline).strip rescue changelog = ""
  if changelog.match(/^## #{@version.split('.')[0..2].join('.')}/)
    puts "         CHANGELOG.md: " + "PASS".green + " " + changelog.white
  else
    print "         CHANGELOG.md: "
    message = "#{INDENT}Expected: ".red + "## #{@version}".white +
            "\n#{INDENT}  Actual: ".red + changelog.white
    failure message
  end

  stdout = quietly do
    `git describe --exact-match HEAD --tags`
  end

  print "        Version Check: "
  tag = stdout.to_s.strip
  if "v#{@version}" != tag
    message = "#{INDENT}Version specified in `VERSION` file ".red + "'v#{@version}'".white + " does not match git tag on HEAD commit ".red + "'#{tag}'".white
    failure message
  else
    puts "PASS".green + " VERSION " + "'v#{@version}'".white + " matches git tag " + "'#{tag}'".white
  end
end