Class: Dister::Core

Inherits:
Object
  • Object
show all
Defined in:
lib/dister/core.rb

Overview

Core functionality

Constant Summary collapse

APP_ROOT =

Absolute path to the root of the current application

File.expand_path('.')

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeCore

Connect to SUSE Studio and verify the user’s credentials. Sets @options, @shell and @connection for further use.



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/dister/core.rb', line 15

def initialize
  @options ||= Options.new
  @shell = Thor::Shell::Basic.new
  @connection = StudioApi::Connection.new(
    @options.username,
    @options.api_key,
    @options.api_path,
    :proxy   => @options.proxy,           # proxy can be nil
    :timeout => (@options.timeout || 60)  # default to 60s
  )
  # Try the connection once to determine whether credentials are correct.
  @connection.api_version
  StudioApi::Util.configure_studio_connection @connection

  # Ensure app_name is stored for further use.
  if @options.app_name.nil?
    @options.app_name = APP_ROOT.split(/(\/|\\)/).last
  end

  true
rescue ActiveResource::UnauthorizedAccess
  puts 'A connection to SUSE Studio could not be established.'
  keep_trying = @shell.yes?(
    'Would you like to re-enter your credentials and try again? (y/n)'
  )
  if keep_trying
    update_credentials
    retry
  else
    abort('Exiting dister.')
  end
end

Instance Attribute Details

#options (readonly)

Returns the value of attribute options.



8
9
10
# File 'lib/dister/core.rb', line 8

def options
  @options
end

#shell (readonly)

Returns the value of attribute shell.



8
9
10
# File 'lib/dister/core.rb', line 8

def shell
  @shell
end

Instance Method Details

#add_package(package)

Add a package to the appliance

Parameters:

  • package (String)

    the name of the package



305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
# File 'lib/dister/core.rb', line 305

def add_package package
  appliance_basesystem = appliance.basesystem
  result = appliance.search_software(package)#.find{|s| s.name == package }
  #TODO: better handling
  #Blocked by bnc#
  if result.empty? #it is not found in available repos
    puts "'#{package}' has not been found in the repositories currently "\
         "added to your appliance."
    keep_trying = @shell.yes?('Would you like to search for this package '\
                            'inside other repositories? (y/n)')
    if keep_trying
      matches = appliance.search_software(package, :all_repos => true)\
                         .find_all { |s| s.name == package }
      repositories = matches.map do |r|
        StudioApi::Repository.find r.repository_id
      end.find_all{|r| r.base_system == appliance_basesystem}

      if repositories.empty?
        puts "Cannot find #{package}, please look at this page: "
        puts URI.encode "http://software.opensuse.org/search?p=1&"\
                        "baseproject=ALL&q=#{package}"
      else
        puts "Package #{package} can be installed from one of the "\
             "following repositories:"
        repositories.each_with_index do |repo, index|
          puts "#{index+1} - #{repo.name} (#{repo.base_url})"
        end
        puts "#{repositories.size+1} - None of them."
        begin
          choice = @shell.ask("Which repo do you want to use? "\
                              "[1-#{repositories.size+1}]")
        end while (choice.to_i > (repositories.size+1) || choice.to_i < 1)
        if (choice.to_i == (repositories.size+1))
          abort("Package not added.")
        else
          repo_id = repositories[choice.to_i-1].id
        end
        appliance.add_repository repo_id
      end
    else
      exit 0
    end
    # add repo which contain samba
    #appliance.add_repository result.repository_id
  end
  Utils::execute_printing_progress "Adding #{package} package" do
    appliance.add_package(package)
  end
end

#add_packages(packages)

Add a list of packages at once

Parameters:

  • packages (Array<String>)


357
358
359
# File 'lib/dister/core.rb', line 357

def add_packages(packages)
  packages.each { |package| self.add_package(package) }
end

#applianceStudioApi::Appliance

Finds the appliance for the current app

Returns:

  • (StudioApi::Appliance)

    the app’s appliance (or nil if none exist).



144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/dister/core.rb', line 144

def appliance
  if @appliance.nil?
    begin
      appliance_id = self.options.appliance_id
      return nil if appliance_id.nil?
      @appliance = StudioApi::Appliance.find(appliance_id.to_i)
    rescue ActiveResource::BadRequest
      self.options.appliance_id = nil
      nil
    end
  else
    @appliance
  end
end

#basesystemsArray<String>

Find available base systems

Returns:

  • (Array<String>)

    a list of available base systems



178
179
180
# File 'lib/dister/core.rb', line 178

def basesystems
  templates.collect(&:basesystem).uniq
end

#build(build_options = {})

Builds the appliance

Parameters:

  • build_options (Hash) (defaults to: {})

Options Hash (build_options):

  • :force (Boolean)


86
87
88
89
90
91
92
93
94
95
96
97
98
99
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
137
138
139
140
# File 'lib/dister/core.rb', line 86

def build build_options = {}
  verify_status
  #TODO:
  # * build using another format
  force   = build_options[:force]
  version = nil
  begin
    params = {
               :appliance_id => @options.appliance_id,
               :image_type   => "oem"
             }
    params[:force]   = force if force
    params[:version] = version if version
    build = StudioApi::RunningBuild.create(params)
  rescue StudioApi::ImageAlreadyExists
    @shell.say 'An image with the same version already exists'
    if @shell.yes? 'Do you want to overwrite it? (y/n)'
      force = true
      retry
    else
      begin
        version = @shell.ask 'Enter new version number:'
      end until !version.blank?
      retry
    end
  end

  build.reload
  if build.state == "queued"
    puts "Your build is queued. It will be automatically processed by "\
         "SUSE Studio. You can keep waiting or you can exit from dister."
    puts "Exiting from dister won't remove your build from the queue."
    if @shell.no?('Do you want to keep waiting (y/n)')
      exit 0
    end

    Utils::execute_printing_progress "Build queued..." do
      while build.state == 'queued' do
        sleep 5
        build.reload
      end
    end
  end

  # build is no longer queued
  pbar = ProgressBar.new "Building", 100

  while not ['finished', 'error', 'failed', 'cancelled'].include?(build.state)
    pbar.set build.percent.to_i
    sleep 5
    build.reload
  end
  pbar.finish
  build.state == 'finished'
end

#buildsArray<StudioApi::Build>

Finds all builds

Returns:

  • (Array<StudioApi::Build>)


161
162
163
# File 'lib/dister/core.rb', line 161

def builds
  StudioApi::Build.find(:all, :params => {:appliance_id => @options.appliance_id})
end

#check_template_and_basesystem_availability(template, basesystem)



182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
# File 'lib/dister/core.rb', line 182

def check_template_and_basesystem_availability template, basesystem
  available_templates = self.templates
  match = available_templates.find do |t|
    t.basesystem == basesystem && t.name.downcase.include?(template.downcase)
  end

  if match.nil?
    STDERR.puts "The #{basesystem} doesn't have the #{template} template."
    STDERR.puts "Available templates are:"
    available_templates.find_all do |t|
      t.basesystem.downcase == basesystem.downcase
    end.each do |t|
      STDERR.puts "  - #{t.name}"
    end
  end
  match
end

#create_appliance(name, template, basesystem, arch) ⇒ StudioApi::Appliance

Creates a new appliance.

Parameters:

  • name (String)
  • template (String)
  • basesystem (String)
  • arch (String)

Returns:

  • (StudioApi::Appliance)

    the new appliance



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
# File 'lib/dister/core.rb', line 56

def create_appliance(name, template, basesystem, arch)
  match = check_template_and_basesystem_availability(template, basesystem)
  exit 1 if match.nil?

  @db_adapter = get_db_adapter
  app = Utils::execute_printing_progress "Cloning appliance" do
    StudioApi::Appliance.clone(match.appliance_id, {:name => name,
                                                    :arch => arch})
  end
  @options.appliance_id = app.id
  ensure_devel_languages_ruby_extensions_repo_is_added

  default_packages = %w(devel_C_C++ devel_ruby
                        rubygem-bundler rubygem-passenger-apache2)

  self.add_packages(default_packages)
  self.add_packages(@db_adapter.packages) unless @db_adapter.nil?

  Utils::execute_printing_progress "Uploading build scripts" do
    upload_configurations_scripts
  end
  puts "SUSE Studio appliance successfull created:"
  puts "  #{app.edit_url}"
  app
end

#download(build_set)

Parameters:

  • build_set (Array)


477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
# File 'lib/dister/core.rb', line 477

def download(build_set)
  # Choose the build(s) to download.
  to_download = []
  if build_set.size == 1
    to_download << build_set.first
  else
    to_download = choose do |menu|
      menu.choices *build_set do |i| [i] end # wrap choice in an array
      menu.choice("All of them.") { build_set }
      menu.choice("None.") { exit 0 }
      menu.prompt = "Which appliance do you want to download?"
    end
  end

  # Download selected builds.
  to_download.each do |b|
    puts "Going to download #{b.to_s}"
    d = Downloader.new(b.download_url.sub("https:", "http:"),"Downloading")
    if File.exists? d.filename
      if @shell.no?("Do you want to overwrite file #{d.filename}? (y/n)")
        exit 0
      end
    end
    begin
      d.start
      Utils::execute_printing_progress "Calculating md5sum" do
        digest = Digest::MD5.file d.filename
        raise "digest check not passed" if digest.to_s != b.checksum.md5
      end
    rescue
      STDOUT.puts
      STDERR.puts
      STDERR.flush
      STDERR.puts $!
      exit 1
    end
  end
end

#ensure_devel_languages_ruby_extensions_repo_is_added



398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
# File 'lib/dister/core.rb', line 398

def ensure_devel_languages_ruby_extensions_repo_is_added
  name = "devel:language:ruby:extensions"
  url = "http://download.opensuse.org/repositories/devel:/languages:/ruby:/extensions/"

  case appliance.basesystem
  when "11.1"
    url += "openSUSE_11.1"
    name += " 11.1"
  when "11.2"
    url += "openSUSE_11.2"
    name += " 11.2"
  when "11.3"
    url += "openSUSE_11.3"
    name += " 11.3"
  when "11.4"
    url += "openSUSE_11.4"
    name += " 11.4"
  when "12.1"
    url += "openSUSE_12.1"
    name += " 12.1"
  when "SLED10_SP2", "SLED10_SP3", "SLES10_SP2", "SLES10_SP3"
    url += "SLE_10/"
    name += " SLE10"
  when "SLED11", "SLES11"
    url += "SLE_11"
    name += " SLE 11"
  when "SLED11_SP1", "SLES11_SP1", "SLES11_SP1_VMware"
    url += "SLE_11_SP1"
    name += " SLE11 SP1"
  when "SLES11_SP2", "SLES11_SP2"
    url += "SLE_11_SP2"
    name += " SLE11 SP2"
  else
    STDERR.puts "#{appliance.basesystem}: unknown base system"
    exit 1
  end

  Utils::execute_printing_progress "Adding #{name} repository" do
    repos = StudioApi::Repository.find(:all, :params => {:filter => url.downcase})
    if repos.size > 0
      repo = repos.first
    else
      repo = import_repository url, name
    end
    appliance.add_repository repo.id
  end
end

#file_upload(filename, upload_options = {}) ⇒ Boolean

Uploads a file identified by filename to a SuSE Studio Appliance

Parameters:

  • filename (String)

    name of file to upload

  • upload_options (Hash) (defaults to: {})

    upload options (all parameters are optional)

Options Hash (upload_options):

  • filename (String)

    The name of the file in the filesystem

  • path (String)

    The path where the file will be stored

  • owner (String)

    The owner of the file

  • group (String)

    The group of the file

  • permissions (String)

    The permissions of the file

  • enabled (String)

    Used to enable/disable this file for the builds

  • url (String)

    The url of the file to add from the internet (HTTP and FTP are supported) when using the web upload method

Returns:

  • (Boolean)

    true if the file has been successfully uploaded



216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
# File 'lib/dister/core.rb', line 216

def file_upload filename, upload_options={}
  if File.exists? filename
    # Delete existing (obsolete) file.
    StudioApi::File.find(:all, :params => {
      :appliance_id => self.options.appliance_id
    }).select { |file|
      file.path == (upload_options[:path] || '/') and file.filename == File.basename(filename)
    }.each(&:destroy)
    # Upload new file.
    message =  "Uploading #{filename} "
    message += "(#{Utils.readable_file_size(File.size(filename),2)})"
    Utils::execute_printing_progress message do
      File.open(filename) do |file|
        StudioApi::File.upload file, @options.appliance_id, upload_options
      end
    end
    true
  else
    STDERR.puts "Cannot upload #{filename}, it doesn't exists."
    false
  end
end

#import_repository(url, name) ⇒ StudioApi::Repository

Asks Studio to mirror a repository.

Returns:

  • (StudioApi::Repository)


394
395
396
# File 'lib/dister/core.rb', line 394

def import_repository url, name
  StudioApi::Repository.import url, name
end

#package_app

Creates a tarball that holds the application’s source-files. Previously packaged versions get overwritten.



260
261
262
263
264
265
266
# File 'lib/dister/core.rb', line 260

def package_app
  puts 'Packaging application...'
  package = ".dister/#{@options.app_name}_application.tar.gz"
  system "rm #{package}" if File.exists?(package)
  system "tar -czf .dister/#{@options.app_name}_application.tar.gz ../#{@options.app_name}/ --exclude=.dister &> /dev/null"
  puts "Done!"
end

#package_config_files

Creates all relevant config files (e.g. apache.conf) for the appliance.



269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
# File 'lib/dister/core.rb', line 269

def package_config_files
  filename = File.expand_path('../../templates/passenger.erb', __FILE__)
  erb = ERB.new(File.read(filename))
  config_content = erb.result(binding)

  config_path = "#{APP_ROOT}/.dister/#{@options.app_name}_apache.conf"
  FileUtils.rm(config_path, :force => true)
  File.open(config_path, 'w') do |config_file|
    config_file.write(config_content)
  end

  @db_adapter = get_db_adapter
  unless @db_adapter.nil?
    create_db_user_file = "#{APP_ROOT}/.dister/create_db_user.sql"
    FileUtils.rm(create_db_user_file, :force => true)
    File.open(create_db_user_file, 'w') do |file|
      file.write(@db_adapter.create_user_cmd)
    end
  end
end

#package_gems

Use bundler to download and package all required gems for the app.



240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/dister/core.rb', line 240

def package_gems
  if !File.exists?("#{APP_ROOT}/Gemfile")
    puts "Gemfile missing, cannot use bundler"
    puts 'Either create a Gemfile or use "dister package add" command'
    return
  end

  puts 'Packaging gems...'
  system "cd #{APP_ROOT}"
  system "rm -R vendor/cache" if File.exists?("#{APP_ROOT}/vendor/cache")
  success = system 'bundle package'
  unless success
    STDERR.puts "`bundle package` failed, exiting"
    exit 1
  end
  puts "Done!"
end

#rm_package(package)

Remove a package from the appliance

Parameters:

  • package (String)

    the name of the package



363
364
365
366
367
# File 'lib/dister/core.rb', line 363

def rm_package package
  Utils::execute_printing_progress "Removing #{package} package" do
    appliance.remove_package(package)
  end
end

#templates



165
166
167
168
169
170
171
172
173
174
# File 'lib/dister/core.rb', line 165

def templates
  reply = StudioApi::TemplateSet.find(:first, :conditions => {:name => "default"})
  if reply.nil?
    STDERR.puts "There is no default template set named 'default'"
    STDERR.puts "Please contact SUSE Studio admin"
    exit 1
  else
    return reply.template
  end
end

#testdrive(build_set)

Parameters:

  • build_set (Array)


459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
# File 'lib/dister/core.rb', line 459

def testdrive(build_set)
  build = build_set[0] # for now we just take the first available build
  testdrive = Utils::execute_printing_progress "Starting testdrive" do
    begin
      StudioApi::Testdrive.create(:build_id => build.id)
    rescue
      STDERR.puts $!
      exit 1
    end
  end
  # NOTE can't get http to work, so lets just provide vnc info for now
  puts "Connect to your testdrive using VNC:"
  vnc = testdrive.server.vnc
  puts "Server: #{vnc.host}:#{vnc.port}"
  puts "Password: #{vnc.password}"
end

#upload_bundled_files

Uploads the app tarball and the config file to the appliance.



291
292
293
294
295
296
297
298
299
300
301
# File 'lib/dister/core.rb', line 291

def upload_bundled_files
  upload_options = {:path => "/srv/www", :owner => 'root', :group => 'root'}
  # Upload tarball.
  self.file_upload("#{APP_ROOT}/.dister/#{@options.app_name}_application.tar.gz", upload_options)
  # Upload config files to separate location.
  upload_options[:path] = "/etc/apache2/vhosts.d"
  self.file_upload("#{APP_ROOT}/.dister/#{@options.app_name}_apache.conf", upload_options)
  # Upload db related files to separate location.
  upload_options[:path] = "/root"
  self.file_upload("#{APP_ROOT}/.dister/create_db_user.sql", upload_options)
end

#upload_configurations_scriptstrue

Uploads our configuration scripts

Returns:

  • (true)

    if the scripts are successfully uploaded



371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
# File 'lib/dister/core.rb', line 371

def upload_configurations_scripts
  rails_root = "/srv/www/#{@options.app_name}"

  filename = File.expand_path('../../templates/boot_script.erb', __FILE__)
  erb = ERB.new(File.read(filename))
  boot_script = erb.result(binding)

  filename = File.expand_path('../../templates/build_script.erb', __FILE__)
  erb = ERB.new(File.read(filename))
  build_script = erb.result(binding)

  conf = appliance.configuration
  conf.scripts.boot.script = boot_script
  conf.scripts.boot.enabled = true

  conf.scripts.build.script = build_script
  conf.scripts.build.enabled = true
  conf.save
  true
end

#verify_status

Make sure the appliance doesn’t have conflicts. In this case an error message is shown and the program halts.



448
449
450
451
452
453
454
455
456
# File 'lib/dister/core.rb', line 448

def verify_status
  Utils::execute_printing_progress "Verifying appliance status" do
    if appliance.status.state != "ok"
       message = "Appliance is not OK - #{appliance.status.issues.inspect}"
       message += "\nVisit #{appliance.edit_url} to manually fix the issue."
       raise message
    end
  end
end