Class: Sunshine::App

Inherits:
Object
  • Object
show all
Defined in:
lib/sunshine/app.rb

Overview

App objects are the core of sunshine deployment. The sunshine paradygm is to construct an app object, and run custom deploy code by passing a block to its deploy method:

options = {
   :name => 'myapp',
   :repo => {:type => :svn, :url => 'svn://blah...'},
   :root_path => '/usr/local/myapp',
   :remote_shells => ['[email protected]']
 }

 app = Sunshine::App.new(options)

 app.deploy do |app|

   app_server = Sunshine::Rainbows.new(app)
   app_server.restart

   Sunshine::Nginx.new(app, :point_to => app_server).restart

 end

Multiple apps can be defined, and deployed from a single deploy script. The constructor also supports passing a yaml file path:

Sunshine::App.new("path/to/config.yml")

Deployment can be expressed more concisely by calling App::deploy:

App.deploy("path/to/config.yml") do |app|
  Sunshine::Rainbows.new(app).restart
end

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config_file = Sunshine::DATA, options = {}) ⇒ App

Returns a new instance of App.



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
83
84
85
86
87
88
89
# File 'lib/sunshine/app.rb', line 55

def initialize config_file=Sunshine::DATA, options={}
  options, config_file = config_file, Sunshine::DATA if Hash === config_file


  @deploy_env = options[:deploy_env] || Sunshine.deploy_env

  binder  = Binder.new self
  binder.import_hash options
  binder.forward :deploy_env

  options = config_from_file(config_file, binder.get_binding).merge options


  @repo        = repo_from_config options[:repo]

  @name        = options[:name] || @repo.name

  @deploy_name = options[:deploy_name] || Time.now.to_i.to_s

  @server_app_filter = nil

  set_deploy_paths options[:root_path]

  @server_apps = server_apps_from_config options[:remote_shells]

  self.sudo = options[:sudo] || Sunshine.sudo

  @shell_env = {
    "RACK_ENV"  => @deploy_env.to_s,
    "RAILS_ENV" => @deploy_env.to_s
  }
  shell_env options[:shell_env]

  @post_user_lambdas = []
end

Instance Attribute Details

#checkout_pathObject (readonly)

Returns the value of attribute checkout_path.



51
52
53
# File 'lib/sunshine/app.rb', line 51

def checkout_path
  @checkout_path
end

#current_pathObject (readonly)

Returns the value of attribute current_path.



51
52
53
# File 'lib/sunshine/app.rb', line 51

def current_path
  @current_path
end

#deploy_envObject (readonly)

Returns the value of attribute deploy_env.



52
53
54
# File 'lib/sunshine/app.rb', line 52

def deploy_env
  @deploy_env
end

#deploy_nameObject (readonly)

Returns the value of attribute deploy_name.



52
53
54
# File 'lib/sunshine/app.rb', line 52

def deploy_name
  @deploy_name
end

#deploys_pathObject (readonly)

Returns the value of attribute deploys_path.



51
52
53
# File 'lib/sunshine/app.rb', line 51

def deploys_path
  @deploys_path
end

#log_pathObject (readonly)

Returns the value of attribute log_path.



52
53
54
# File 'lib/sunshine/app.rb', line 52

def log_path
  @log_path
end

#nameObject (readonly)

Returns the value of attribute name.



50
51
52
# File 'lib/sunshine/app.rb', line 50

def name
  @name
end

#repoObject (readonly)

Returns the value of attribute repo.



50
51
52
# File 'lib/sunshine/app.rb', line 50

def repo
  @repo
end

#root_pathObject (readonly)

Returns the value of attribute root_path.



51
52
53
# File 'lib/sunshine/app.rb', line 51

def root_path
  @root_path
end

#server_appsObject (readonly)

Returns the value of attribute server_apps.



50
51
52
# File 'lib/sunshine/app.rb', line 50

def server_apps
  @server_apps
end

#shared_pathObject (readonly)

Returns the value of attribute shared_path.



52
53
54
# File 'lib/sunshine/app.rb', line 52

def shared_path
  @shared_path
end

#sudoObject

Returns the value of attribute sudo.



50
51
52
# File 'lib/sunshine/app.rb', line 50

def sudo
  @sudo
end

Class Method Details

.deploy(*args, &block) ⇒ Object

Initialize and deploy an application. Takes any arguments supported by the constructor.



43
44
45
46
47
# File 'lib/sunshine/app.rb', line 43

def self.deploy(*args, &block)
  app = new(*args)
  app.deploy(&block)
  app
end

Instance Method Details

#add_shell_paths(*paths) ⇒ Object

Add paths the the shell $PATH env.



189
190
191
192
193
194
# File 'lib/sunshine/app.rb', line 189

def add_shell_paths(*paths)
  path = @shell_env["PATH"] || "$PATH"
  paths << path

  shell_env "PATH" => paths.join(":")
end

#add_to_crontab(name, cronjob, options = nil) ⇒ Object

Add a command to the crontab to be generated remotely:

add_to_crontab "reboot", "@reboot /path/to/app/start", :role => :web


201
202
203
204
205
# File 'lib/sunshine/app.rb', line 201

def add_to_crontab name, cronjob, options=nil
  with_server_apps options do |server_app|
    server_app.crontab[name] = cronjob
  end
end

#add_to_script(name, script, options = nil) ⇒ Object

Add a command to a control script to be generated remotely:

add_to_script :start, "do this on start"
add_to_script :start, "start_mail", :role => :mail


213
214
215
216
217
# File 'lib/sunshine/app.rb', line 213

def add_to_script name, script, options=nil
  with_server_apps options do |server_app|
    server_app.scripts[name] << script
  end
end

#after_user_script(&block) ⇒ Object

Define lambdas to run right after the user’s yield.

app.after_user_script do |app|
  ...
end


226
227
228
# File 'lib/sunshine/app.rb', line 226

def after_user_script &block
  @post_user_lambdas << block
end

#build_control_scripts(options = nil) ⇒ Object

Creates and uploads all control scripts for the application. To add to, or define a control script, see App#add_to_script.



235
236
237
238
239
# File 'lib/sunshine/app.rb', line 235

def build_control_scripts options=nil
  with_server_apps options,
    :msg  => "Building control scripts",
    :send => :build_control_scripts
end

#build_crontab(options = nil) ⇒ Object

Writes the crontab on all or selected server apps. To add or remove from the crontab, see App#add_to_crontab and App#remove_cronjob.



247
248
249
250
251
252
# File 'lib/sunshine/app.rb', line 247

def build_crontab options=nil
  with_server_apps options,
    :msg => "Building the crontab" do |server_app|
    server_app.crontab.write!
  end
end

#build_deploy_info_file(options = nil) ⇒ Object

Creates a yaml file with deploy information. To add custom information to the info file, use the app’s info hash attribute:

app.info[:key] = "some value"


260
261
262
263
264
# File 'lib/sunshine/app.rb', line 260

def build_deploy_info_file options=nil
  with_server_apps options,
    :msg  => "Creating info file",
    :send => :build_deploy_info_file
end

#build_erb(erb_file, custom_binding = nil) ⇒ Object

Parse an erb file and return the newly created string. Default binding is the app’s binding.



271
272
273
274
# File 'lib/sunshine/app.rb', line 271

def build_erb erb_file, custom_binding=nil
  str = File === erb_file ? erb_file.read : File.read(erb_file)
  ERB.new(str, nil, '-').result(custom_binding || binding)
end

#checkout_codebase(options = nil) ⇒ Object

Checks out the app’s codebase to one or all deploy servers.



280
281
282
283
284
285
286
287
# File 'lib/sunshine/app.rb', line 280

def checkout_codebase options=nil
  with_server_apps options,
    :msg  => "Checking out codebase",
    :send => [:checkout_repo, @repo]

rescue => e
  raise CriticalDeployError, e
end

#connect(options = nil) ⇒ Object

Connect server apps.



95
96
97
98
99
100
# File 'lib/sunshine/app.rb', line 95

def connect options=nil
  with_server_apps options,
    :msg => "Connecting..." do |server_app|
    server_app.shell.connect
  end
end

#connected?(options = nil) ⇒ Boolean

Check if server apps are connected.

Returns:

  • (Boolean)


106
107
108
109
110
111
112
# File 'lib/sunshine/app.rb', line 106

def connected? options=nil
  with_server_apps options, :no_threads => true do |server_app|
    return false unless server_app.shell.connected?
  end

  true
end

#deploy(options = nil) ⇒ Object

Deploy the application to deploy servers and call user’s post-deploy code. Supports any App#find options.



130
131
132
133
134
135
136
137
138
139
140
141
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
# File 'lib/sunshine/app.rb', line 130

def deploy options=nil
  Sunshine.logger.info :app, "Beginning deploy of #{@name}" do
    connect options
  end

  deploy_trap = Sunshine.add_trap "Reverting deploy of #{@name}" do
    revert! options
  end

  with_filter options do |app|
    make_app_directories
    checkout_codebase
    symlink_current_dir

    yield(self) if block_given?

    run_post_user_lambdas

    health :enable

    build_control_scripts
    build_deploy_info_file
    build_crontab

    register_as_deployed
    remove_old_deploys
  end

rescue => e
  message = "#{e.class}: #{e.message}"

  Sunshine.logger.error :app, message do
    Sunshine.logger.error '>>', e.backtrace.join("\n")
    revert!
  end

ensure
  Sunshine.delete_trap deploy_trap

  Sunshine.logger.info :app, "Ending deploy of #{@name}" do
    disconnect options
  end
end

#deploy_details(options = nil) ⇒ Object

Get a hash of deploy information for each server app. Post-deploy only.



294
295
296
297
298
299
300
301
302
# File 'lib/sunshine/app.rb', line 294

def deploy_details options=nil
  details = {}

  with_server_apps options, :msg => "Getting deploy info..." do |server_app|
    details[server_app.shell.host] = server_app.deploy_details
  end

  details
end

#deployed?(options = nil) ⇒ Boolean

Check if app has been deployed successfully.

Returns:

  • (Boolean)


308
309
310
311
312
313
314
# File 'lib/sunshine/app.rb', line 308

def deployed? options=nil
  with_server_apps options, :no_threads => true do |server_app|
    return false unless server_app.deployed?
  end

  true
end

#disconnect(options = nil) ⇒ Object

Disconnect server apps.



118
119
120
121
122
123
# File 'lib/sunshine/app.rb', line 118

def disconnect options=nil
  with_server_apps options,
    :msg => "Disconnecting..." do |server_app|
    server_app.shell.disconnect
  end
end

#each(options = nil, &block) ⇒ Object

Iterate over each server app.



320
321
322
323
# File 'lib/sunshine/app.rb', line 320

def each(options=nil, &block)
  server_apps = find(options)
  server_apps.each(&block)
end

#find(query = nil) ⇒ Object

Find server apps matching the passed requirements. Returns an array of server apps.

find :user => 'db'
find :host => 'someserver.com'
find :role => :web


333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
# File 'lib/sunshine/app.rb', line 333

def find query=nil
  if @server_app_filter
    if Hash === query && Hash === @server_app_filter
      query.merge! @server_app_filter
    else
      query = @server_app_filter
    end
  end

  return @server_apps if query.nil? || query == :all

  @server_apps.select do |sa|
    next unless sa.shell.user == query[:user] if query[:user]
    next unless sa.shell.host == query[:host] if query[:host]

    next unless sa.has_roles?(query[:role])   if query[:role]

    true
  end
end

#gpg_decrypt(gpg_file, options = {}) ⇒ Object

Decrypt a file using gpg. Allows all DeployServerDispatcher#find options, plus:

:output

str - the path the output file should go to

:passphrase

str - the passphrase gpg should use



361
362
363
364
365
366
367
368
369
370
# File 'lib/sunshine/app.rb', line 361

def gpg_decrypt gpg_file, options={}
  options[:passphrase] ||=
    Sunshine.shell.ask("Enter gpg passphrase:") do |q|
    q.echo = false
  end

  with_server_apps options,
    :msg  => "Gpg decrypt: #{gpg_file}",
    :send => [:gpg_decrypt, gpg_file, options]
end

#health(method = nil, options = nil) ⇒ Object

Gets or sets the healthcheck state. Returns a hash of host/state pairs. State values are :enabled, :disabled, and :down. The method argument can be omitted or take a value of :enable, :disable, or :remove:

app.health
#=> Returns the health status for all server_apps

app.health :role => :web
#=> Returns the status of all server_apps of role :web

app.health :enable
#=> Enables all health checking and returns the status

app.health :disable, :role => :web
#=> Disables health checking for :web server_apps and returns the status


405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
# File 'lib/sunshine/app.rb', line 405

def health method=nil, options=nil
  valid_methods = [:enable, :disable, :remove]
  options = method if options.nil? && Hash === method

  valid_method = valid_methods.include? method

  message   = "#{method.to_s.capitalize[0..-2]}ing" if valid_method
  message ||= "Getting status of"
  message   = "#{message} healthcheck"

  statuses = {}
  with_server_apps options, :msg => message do |server_app|
    server_app.health.send method if valid_method

    statuses[server_app.shell.host] = server_app.health.status
  end

  statuses
end

#install_deps(*deps) ⇒ Object

Install dependencies defined as a Sunshine dependency object:

rake   = Sunshine.dependencies.gem 'rake', :version => '~>0.8'
apache = Sunshine.dependencies.yum 'apache'
app.install_deps rake, apache

Deploy servers can also be specified as a dispatcher, array, or single deploy server, by passing standard ‘find’ options:

postgres = Sunshine.dependencies.yum 'postgresql'
pgserver = Sunshine.dependencies.yum 'postgresql-server'
app.install_deps postgres, pgserver, :role => 'db'

If a dependency was already defined in the Sunshine dependency tree, the dependency name may be passed instead of the object:

app.install_deps 'nginx', 'ruby'


442
443
444
445
446
447
448
# File 'lib/sunshine/app.rb', line 442

def install_deps(*deps)
  options = Hash === deps[-1] ? deps.delete_at(-1) : {}

  with_server_apps options,
    :msg  => "Installing dependencies: #{deps.map{|d| d.to_s}.join(" ")}",
    :send => [:install_deps, *deps]
end

#make_app_directories(options = nil) ⇒ Object

Creates the required application directories.



454
455
456
457
458
459
460
461
# File 'lib/sunshine/app.rb', line 454

def make_app_directories options=nil
  with_server_apps options,
    :msg  => "Creating #{@name} directories",
    :send => :make_app_directories

rescue => e
  raise FatalDeployError, e
end

#prefer_pkg_manager(pkg_manager, options = nil) ⇒ Object

Assign the prefered package manager to all server_apps:

app.prefer_pkg_manager Settler::Yum

Package managers are typically detected automatically by each individual server_apps.



471
472
473
474
# File 'lib/sunshine/app.rb', line 471

def prefer_pkg_manager pkg_manager, options=nil
  with_server_apps options,
    :send => [:pkg_manager=, pkg_manager]
end

#rake(command, options = nil) ⇒ Object

Run a rake task on any or all deploy servers.



480
481
482
483
484
# File 'lib/sunshine/app.rb', line 480

def rake command, options=nil
  with_server_apps options,
    :msg  => "Running Rake task '#{command}'",
    :send => [:rake, command]
end

#register_as_deployed(options = nil) ⇒ Object

Adds the app to the deploy servers deployed-apps list.



490
491
492
493
494
# File 'lib/sunshine/app.rb', line 490

def register_as_deployed options=nil
  with_server_apps options,
    :msg  => "Registering app with deploy servers",
    :send => :register_as_deployed
end

#remove_cronjob(name, options = nil) ⇒ Object

Remove a cron job from the remote crontabs:

remove_cronjob "reboot", :role => :web
remove_cronjob :all
#=> deletes all cronjobs related to this app


503
504
505
506
507
508
509
510
511
512
# File 'lib/sunshine/app.rb', line 503

def remove_cronjob name, options=nil
  with_server_apps options,
    :msg => "Removing cronjob #{name.inspect}" do |server_app|
    if name == :all
      server_app.crontab.clear
    else
      server_app.crontab.delete(name)
    end
  end
end

#remove_old_deploys(options = nil) ⇒ Object

Removes old deploys from the checkout_dir based on Sunshine’s max_deploy_versions.



519
520
521
522
523
# File 'lib/sunshine/app.rb', line 519

def remove_old_deploys options=nil
  with_server_apps options,
    :msg  => "Removing old deploys (max = #{Sunshine.max_deploy_versions})",
    :send => :remove_old_deploys
end

#restart(options = nil) ⇒ Object

Run the restart script of a deployed app on the specified deploy servers. Post-deploy only.



531
532
533
534
535
# File 'lib/sunshine/app.rb', line 531

def restart options=nil
  with_server_apps options,
    :msg  => "Running restart script",
    :send => :restart
end

#revert!(options = nil) ⇒ Object

Symlink current directory to previous checkout and remove the current deploy directory.



179
180
181
182
183
# File 'lib/sunshine/app.rb', line 179

def revert!(options=nil)
  with_server_apps options,
    :msg  => "Reverting to previous deploy.",
    :send => :revert!
end

#run_bundler(options = nil) ⇒ Object

Runs bundler on deploy servers.



541
542
543
544
545
546
547
548
# File 'lib/sunshine/app.rb', line 541

def run_bundler options=nil
  with_server_apps options,
    :msg  => "Running Bundler",
    :send => :run_bundler

rescue => e
  raise CriticalDeployError, e
end

#run_geminstaller(options = nil) ⇒ Object

Runs GemInstaller on deploy servers.



554
555
556
557
558
559
560
561
# File 'lib/sunshine/app.rb', line 554

def run_geminstaller options=nil
  with_server_apps options,
    :msg  => "Running GemInstaller",
    :send => :run_geminstaller

rescue => e
  raise CriticalDeployError, e
end

#run_post_user_lambdasObject

Run lambdas that were saved for after the user’s script. See #after_user_script.



568
569
570
# File 'lib/sunshine/app.rb', line 568

def run_post_user_lambdas
  @post_user_lambdas.each{|l| l.call self}
end

#sass(*sass_names) ⇒ Object

Run a sass task on any or all deploy servers.



576
577
578
579
580
581
582
# File 'lib/sunshine/app.rb', line 576

def sass *sass_names
  options = sass_names.delete_at(-1) if Hash === sass_names.last

  with_server_apps options,
    :msg  => "Running Sass for #{sass_names.join(' ')}",
    :send => [:sass, *sass_names]
end

#shell_env(env_hash = nil) ⇒ Object

Set and return the remote shell env variables. Also assigns shell environment to the app’s deploy servers.



589
590
591
592
593
594
595
596
597
598
599
600
# File 'lib/sunshine/app.rb', line 589

def shell_env env_hash=nil
  env_hash ||= {}

  @shell_env.merge!(env_hash)

  with_server_apps :all,
    :msg => "Shell env: #{@shell_env.inspect}" do |server_app|
    server_app.shell_env.merge!(@shell_env)
  end

  @shell_env.dup
end

#start(options = nil) ⇒ Object

Run the start script of a deployed app on the specified deploy servers. Post-deploy only.



608
609
610
611
612
# File 'lib/sunshine/app.rb', line 608

def start options=nil
  with_server_apps options,
    :msg  => "Running start script",
    :send => [:start, options]
end

#status(options = nil) ⇒ Object

Get a hash of which deploy server apps are :running or :down. Post-deploy only.



619
620
621
622
623
624
625
626
627
# File 'lib/sunshine/app.rb', line 619

def status options=nil
  statuses = {}

  with_server_apps options, :msg => "Querying app status..." do |server_app|
    statuses[server_app.shell.host] = server_app.status
  end

  statuses
end

#stop(options = nil) ⇒ Object

Run the stop script of a deployed app on the specified deploy servers. Post-deploy only.



635
636
637
638
639
# File 'lib/sunshine/app.rb', line 635

def stop options=nil
  with_server_apps options,
    :msg  => "Running stop script",
    :send => :stop
end

Creates a symlink to the app’s checkout path.



659
660
661
662
663
664
665
666
# File 'lib/sunshine/app.rb', line 659

def symlink_current_dir options=nil
  with_server_apps options,
    :msg  => "Symlinking #{@checkout_path} -> #{@current_path}",
    :send => :symlink_current_dir

rescue => e
  raise CriticalDeployError, e
end

#threaded_each(options = nil, &block) ⇒ Object

Iterate over all deploy servers but create a thread for each deploy server. Means you can’t return from the passed block!



673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
# File 'lib/sunshine/app.rb', line 673

def threaded_each(options=nil, &block)
  mutex   = Mutex.new
  threads = []

  return_val = each(options) do |server_app|

    thread = Thread.new do
      server_app.shell.with_mutex mutex do
        yield server_app
      end
    end

    # We don't want deploy servers to keep doing things if one fails
    thread.abort_on_exception = true

    threads << thread
  end

  threads.each{|t| t.join }

  return_val
end

#upload_tasks(*files) ⇒ Object

Upload common rake tasks from the sunshine lib.

app.upload_tasks
  #=> upload all tasks
app.upload_tasks 'app', 'common', :role => :web
  #=> upload app and common rake files

Allows standard DeployServerDispatcher#find options, plus:

:remote_path

str - the remote absolute path to upload the files to



707
708
709
710
711
712
713
714
715
716
# File 'lib/sunshine/app.rb', line 707

def upload_tasks *files
  options = Hash === files.last ? files.last.dup : {}

  options.delete(:remote_path)
  options = :all if options.empty?

  with_server_apps options,
    :msg  => "Uploading tasks: #{files.join(" ")}",
    :send => [:upload_tasks, *files]
end

#with_filter(filter_hash) {|_self| ... } ⇒ Object

Execute a block with a specified server app filter:

app.with_filter :role => :cdn do |app|
  app.sass 'file1', 'file2', 'file3'
  app.rake 'asset:packager:build_all'
end

Yields:

  • (_self)

Yield Parameters:

  • _self (Sunshine::App)

    the object that the method was called on



726
727
728
729
730
731
732
# File 'lib/sunshine/app.rb', line 726

def with_filter filter_hash
  old_filter, @server_app_filter = @server_app_filter, filter_hash

  yield self

  @server_app_filter = old_filter
end

#with_server_apps(search_options, options = {}) ⇒ Object

Calls a method for server_apps found with the passed options, and with an optional log message. Supports all App#find options, plus:

:no_threads

bool - disable threaded execution

:msg

“some message” - log message

app.with_server_apps :all, :msg => "doing something" do |server_app|
  # do something here
end

app.with_server_apps :role => :db, :user => "bob" do |server_app|
  # do something here
end


750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
# File 'lib/sunshine/app.rb', line 750

def with_server_apps search_options, options={}
  options = search_options.merge options if Hash === search_options

  message = options[:msg]
  method  = options[:no_threads] ? :each : :threaded_each

  block = lambda do
    send(method, search_options) do |server_app|

      if block_given?
        yield(server_app)

      elsif options[:send]
        server_app.send(*options[:send])
      end
    end
  end


  if message
    Sunshine.logger.info(:app, message, &block)

  else
    block.call
  end
end