Class: Sunshine::ServerApp
- Inherits:
-
Object
- Object
- Sunshine::ServerApp
- Defined in:
- lib/sunshine/server_app.rb
Overview
Handles App deployment functionality for a single deploy server.
Server apps can be assigned any number of roles for classification.
- :roles
-
sym|array - roles assigned (web, db, app, etc…)
By default server apps get the special :all role which will always return true when calling:
server_app.has_roles? :some_role
ServerApp objects can be instantiated several ways:
ServerApp.new app_instance, shell_instance,
When passing an App instance, the new ServerApp will keep an active link to the app’s properties. Name, deploy, and path attributes will be actively linked.
Rely on ServerApp to create a RemoteShell instance to use:
ServerApp.new app_instance, "host.com",
Instantiate with app name and rely on Sunshine defaults for app paths:
ServerApp.new "app_name", shell_instance,
Explicitely assign the app’s root path:
ServerApp.new "app_name", ..., :root_path => "/path/to/app_root"
Assigning a specific deploy name to use can be done with the :deploy_name option:
ServerApp.new "app_name", ..., :deploy_name => "deploy"
Instance Attribute Summary collapse
-
#app ⇒ Object
Returns the value of attribute app.
-
#crontab ⇒ Object
Returns the value of attribute crontab.
-
#info ⇒ Object
Returns the value of attribute info.
-
#pkg_manager ⇒ Object
Returns the type of package management system to use.
-
#roles ⇒ Object
Returns the value of attribute roles.
-
#scripts ⇒ Object
Returns the value of attribute scripts.
-
#shell ⇒ Object
Returns the value of attribute shell.
Class Method Summary collapse
-
.app_attr(*attribs) ⇒ Object
Define an attribute that will get a value from app, or locally if.
-
.from_info_file(path, shell = nil) ⇒ Object
Creates a ServerApp instance from a deploy info file.
-
.register_dependency_type(dep_class) ⇒ Object
Creates dependency instance methods such as gem_install, yum_install, etc on both App and ServerApp classes.
Instance Method Summary collapse
-
#add_shell_paths(*paths) ⇒ Object
Add paths the the shell $PATH env.
-
#all_deploy_names(reload = false) ⇒ Object
Returns an array of all deploys in the deploys_path dir, starting with the oldest.
-
#build_control_scripts ⇒ Object
Creates and uploads all control scripts for the application.
-
#build_deploy_info_file ⇒ Object
Creates a yaml file with deploy information.
-
#checkout_repo(repo, scm_info = {}) ⇒ Object
Checks out the app’s codebase to the checkout path.
-
#deploy_details(reload = false) ⇒ Object
Get post-mortum information about the app’s deploy, from the generated deploy info file.
-
#deployed? ⇒ Boolean
Checks if the server_app’s current info file deploy_name matches the server_app’s deploy_name attribute.
-
#directories ⇒ Object
An array of all directories used by the app.
-
#get_deploy_info ⇒ Object
Builds a hash with information about the deploy at hand.
-
#has_roles?(roles, match_any = false) ⇒ Boolean
Check if this server app includes the specified roles: server_app.has_roles? :web server_app.has_roles? [:web, :app].
-
#initialize(app, host, options = {}) ⇒ ServerApp
constructor
Create a server app instance.
-
#install_deps(*deps) ⇒ Object
Install dependencies previously defined in Sunshine.dependencies.
-
#make_app_directories ⇒ Object
Creates the required application directories.
-
#make_bash_script(name, cmds) ⇒ Object
Makes an array of bash commands into a script that echoes ‘true’ on success.
-
#make_env_bash_script ⇒ Object
Creates the one-off env script that will be used by other scripts to correctly set their env variables.
-
#previous_deploy_name(reload = false) ⇒ Object
Returns the name of the previous deploy.
-
#rake(command) ⇒ Object
Run a rake task the deploy server.
-
#register_as_deployed ⇒ Object
Adds the app to the deploy server’s deployed-apps list.
-
#remove_old_deploys ⇒ Object
Removes old deploys from the checkout_dir based on Sunshine’s max_deploy_versions.
-
#restart ⇒ Object
Run the app’s restart script.
-
#restart! ⇒ Object
Run the app’s restart script.
-
#revert! ⇒ Object
Symlink current directory to previous checkout and remove the current deploy directory.
-
#run_bundler(options = {}) ⇒ Object
Runs bundler.
-
#run_geminstaller(options = {}) ⇒ Object
Runs geminstaller.
-
#run_script(name, options = nil, &block) ⇒ Object
Runs a script from the root_path.
-
#run_script!(name, options = nil, &block) ⇒ Object
Runs a script from the root_path.
-
#running? ⇒ Boolean
Check if the app pids are present.
-
#sass(*sass_names) ⇒ Object
Run a sass task on any or all deploy servers.
-
#shell_env ⇒ Object
Get the deploy server’s shell environment.
-
#start(options = nil) ⇒ Object
Run the app’s start script.
-
#start!(options = nil) ⇒ Object
Run the app’s start script.
-
#status ⇒ Object
Get the app’s status: :running or :down.
-
#stop ⇒ Object
Run the app’s stop script.
-
#stop! ⇒ Object
Run the app’s stop script.
-
#symlink_current_dir ⇒ Object
Creates a symlink to the app’s checkout path.
-
#symlink_scripts_to_root(*script_names) ⇒ Object
Creates a symlink of every script_name from the scripts_path dir to the app’s root directory for easy access.
-
#upload_codebase(code_dir, scm_info = {}) ⇒ Object
Assumes the passed code_dir is the root directory of the checked out codebase and uploads it to the checkout_path.
-
#write_script(name, contents) ⇒ Object
Write an executable bash script to the app’s scripts dir on the deploy server, and symlink them to the root dir.
Constructor Details
#initialize(app, host, options = {}) ⇒ ServerApp
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 |
# File 'lib/sunshine/server_app.rb', line 108 def initialize app, host, ={} @app = App === app ? app : nil name = @app && @app.name || app assign_local_app_attr name, @deploy_details = nil @roles = [:roles] || [:all] @roles = @roles.split(" ") if String === @roles @roles = [*@roles].compact.map{|r| r.to_sym } @scripts = Hash.new{|h, k| h[k] = []} @info = {:ports => {}} @pkg_manager = nil @shell = case host when String then RemoteShell.new host, when Shell then host else raise "Could not get remote shell '#{host}'" end @crontab = Crontab.new name, @shell @all_deploy_names = nil @previous_deploy_name = nil end |
Instance Attribute Details
#app ⇒ Object
Returns the value of attribute app.
98 99 100 |
# File 'lib/sunshine/server_app.rb', line 98 def app @app end |
#crontab ⇒ Object
Returns the value of attribute crontab.
98 99 100 |
# File 'lib/sunshine/server_app.rb', line 98 def crontab @crontab end |
#info ⇒ Object
Returns the value of attribute info.
98 99 100 |
# File 'lib/sunshine/server_app.rb', line 98 def info @info end |
#pkg_manager ⇒ Object
Returns the type of package management system to use.
352 353 354 355 356 357 |
# File 'lib/sunshine/server_app.rb', line 352 def pkg_manager @pkg_manager ||= DependencyLib.dependency_types.detect do |dt| dt.system_manager? @shell end end |
#roles ⇒ Object
Returns the value of attribute roles.
98 99 100 |
# File 'lib/sunshine/server_app.rb', line 98 def roles @roles end |
#scripts ⇒ Object
Returns the value of attribute scripts.
98 99 100 |
# File 'lib/sunshine/server_app.rb', line 98 def scripts @scripts end |
#shell ⇒ Object
Returns the value of attribute shell.
98 99 100 |
# File 'lib/sunshine/server_app.rb', line 98 def shell @shell end |
Class Method Details
.app_attr(*attribs) ⇒ Object
Define an attribute that will get a value from app, or locally if
39 40 41 42 43 44 45 46 47 |
# File 'lib/sunshine/server_app.rb', line 39 def self.app_attr *attribs attribs.each do |attrib| class_eval <<-STR, __FILE__, __LINE__ + 1 def #{attrib} @app ? @app.send(:#{attrib}) : @#{attrib} end STR end end |
.from_info_file(path, shell = nil) ⇒ Object
Creates a ServerApp instance from a deploy info file.
80 81 82 83 84 85 86 87 88 89 90 91 |
# File 'lib/sunshine/server_app.rb', line 80 def self.from_info_file path, shell=nil shell ||= Sunshine.shell opts = YAML.load shell.call("cat #{path}") opts[:root_path] = opts.delete :path sa_shell = shell.dup sa_shell.env = opts[:env] || Hash.new sa_shell.connect if shell.connected? new opts[:name], sa_shell, opts end |
.register_dependency_type(dep_class) ⇒ Object
Creates dependency instance methods such as gem_install, yum_install, etc on both App and ServerApp classes.
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
# File 'lib/sunshine/server_app.rb', line 54 def self.register_dependency_type dep_class class_eval <<-STR, __FILE__, __LINE__ + 1 def #{dep_class.short_name}_install(*names) options = Hash === names.last ? names.delete_at(-1) : Hash.new names.each do |name| dep = #{dep_class}.new(name, options) dep.install! :call => @shell end end STR App.class_eval <<-STR, __FILE__, __LINE__ + 1 def #{dep_class.short_name}_install(*names) options = names.last if Hash === names.last with_server_apps options, :msg => "Installing #{dep_class.short_name} packages", :send => [:#{dep_class.short_name}_install, *names] end STR end |
Instance Method Details
#add_shell_paths(*paths) ⇒ Object
Add paths the the shell $PATH env.
143 144 145 146 147 148 |
# File 'lib/sunshine/server_app.rb', line 143 def add_shell_paths(*paths) path = shell_env["PATH"] || "$PATH" paths << path shell_env.merge! "PATH" => paths.join(":") end |
#all_deploy_names(reload = false) ⇒ Object
Returns an array of all deploys in the deploys_path dir, starting with the oldest.
364 365 366 367 368 369 |
# File 'lib/sunshine/server_app.rb', line 364 def all_deploy_names reload=false return @all_deploy_names if @all_deploy_names && !reload @all_deploy_names = @shell.call("ls -rc1 #{self.deploys_path}").split("\n") end |
#build_control_scripts ⇒ Object
Creates and uploads all control scripts for the application. To add to, or define a control script, see App#add_to_script.
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 183 |
# File 'lib/sunshine/server_app.rb', line 155 def build_control_scripts @shell.call "mkdir -p #{self.scripts_path}" write_script "env", make_env_bash_script build_scripts = @scripts.dup if build_scripts[:restart].empty? && !build_scripts[:start].empty? && !build_scripts[:stop].empty? build_scripts[:restart] << "#{self.root_path}/stop" build_scripts[:restart] << "#{self.root_path}/start" end if build_scripts[:status].empty? build_scripts[:status] << "echo 'No status for #{self.name}'; exit 1;" end build_scripts.each do |name, cmds| if cmds.empty? Sunshine.logger.warn @shell.host, "#{name} script is empty" end bash = make_bash_script name, cmds write_script name, bash end symlink_scripts_to_root @scripts.keys, "env" end |
#build_deploy_info_file ⇒ 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"
191 192 193 194 195 196 197 198 199 |
# File 'lib/sunshine/server_app.rb', line 191 def build_deploy_info_file deploy_info = get_deploy_info.to_yaml info_filepath = "#{self.scripts_path}/info" @shell.make_file info_filepath, deploy_info @shell.symlink info_filepath, "#{self.root_path}/info" end |
#checkout_repo(repo, scm_info = {}) ⇒ Object
Checks out the app’s codebase to the checkout path.
205 206 207 208 209 210 211 212 213 214 |
# File 'lib/sunshine/server_app.rb', line 205 def checkout_repo repo, scm_info={} install_deps repo.scm Sunshine.logger.info repo.scm, "Checking out to #{@shell.host} #{self.checkout_path}" do @info[:scm] = repo.checkout_to self.checkout_path, @shell @info[:scm].merge! scm_info end end |
#deploy_details(reload = false) ⇒ Object
Get post-mortum information about the app’s deploy, from the generated deploy info file. Post-deploy only.
222 223 224 225 226 227 228 229 230 231 |
# File 'lib/sunshine/server_app.rb', line 222 def deploy_details reload=false return @deploy_details if @deploy_details && !reload @deploy_details = YAML.load @shell.call("cat #{self.root_path}/info") rescue nil @deploy_details = nil unless Hash === @deploy_details @deploy_details end |
#deployed? ⇒ Boolean
Checks if the server_app’s current info file deploy_name matches the server_app’s deploy_name attribute.
238 239 240 241 242 243 244 245 |
# File 'lib/sunshine/server_app.rb', line 238 def deployed? success = @deploy_details[:deploy_name] == self.deploy_name if @deploy_details return success if success deploy_details(true)[:deploy_name] == self.deploy_name rescue false end |
#directories ⇒ Object
An array of all directories used by the app. Does not include symlinked directories.
252 253 254 255 |
# File 'lib/sunshine/server_app.rb', line 252 def directories [root_path, deploys_path, shared_path, log_path, checkout_path, scripts_path] end |
#get_deploy_info ⇒ Object
Builds a hash with information about the deploy at hand.
261 262 263 264 265 266 267 268 269 270 271 272 |
# File 'lib/sunshine/server_app.rb', line 261 def get_deploy_info { :deployed_at => @shell.call("date"), :deployed_as => @shell.call("whoami"), :deployed_by => Sunshine.shell.user, :deploy_name => File.basename(self.checkout_path), :name => self.name, :env => shell_env, :roles => @roles, :path => self.root_path, :sunshine_version => Sunshine::VERSION }.merge @info end |
#has_roles?(roles, match_any = false) ⇒ Boolean
Check if this server app includes the specified roles:
server_app.has_roles? :web
server_app.has_roles? [:web, :app]
The boolean operator may be changed to OR by passing true as the second argument:
server_app.roles = [:web, :app]
server_app.has_roles? [:web, :db] #=> false
server_app.has_roles? [:web, :db], true #=> true
286 287 288 289 290 291 292 293 |
# File 'lib/sunshine/server_app.rb', line 286 def has_roles? roles, match_any=false roles = [*roles] return true if @roles.include? :all return !(roles & @roles).empty? if match_any (roles & @roles).length == roles.length end |
#install_deps(*deps) ⇒ Object
Install dependencies previously defined in Sunshine.dependencies. Will not execute if Sunshine.auto_dependencies? is false.
300 301 302 303 304 305 306 307 308 |
# File 'lib/sunshine/server_app.rb', line 300 def install_deps(*deps) return unless Sunshine.auto_dependencies? = {:call => @shell, :prefer => pkg_manager} .merge! deps.delete_at(-1) if Hash === deps.last args = deps << Sunshine.dependencies.install(*args) end |
#make_app_directories ⇒ Object
Creates the required application directories.
314 315 316 |
# File 'lib/sunshine/server_app.rb', line 314 def make_app_directories @shell.call "mkdir -p #{self.directories.join(" ")}" end |
#make_bash_script(name, cmds) ⇒ Object
Makes an array of bash commands into a script that echoes ‘true’ on success.
323 324 325 326 327 328 329 330 331 332 333 334 335 336 |
# File 'lib/sunshine/server_app.rb', line 323 def make_bash_script name, cmds cmds = cmds.map{|cmd| "(#{cmd})" } cmds << "echo true" bash = <<-STR #!/bin/bash if [ "$1" == "--no-env" ]; then #{cmds.flatten.join(" && ")} else #{self.root_path}/env #{self.root_path}/#{name} --no-env fi STR end |
#make_env_bash_script ⇒ Object
Creates the one-off env script that will be used by other scripts to correctly set their env variables.
343 344 345 346 |
# File 'lib/sunshine/server_app.rb', line 343 def make_env_bash_script env_str = shell_env.map{|e| e.join("=")}.join(" ") "#!/bin/bash\nenv #{env_str} \"$@\"" end |
#previous_deploy_name(reload = false) ⇒ Object
Returns the name of the previous deploy.
375 376 377 378 379 380 381 382 |
# File 'lib/sunshine/server_app.rb', line 375 def previous_deploy_name reload=false return @previous_deploy_name if @previous_deploy_name && !reload arr = all_deploy_names(reload) arr.delete(@deploy_name) @previous_deploy_name = arr.last end |
#rake(command) ⇒ Object
Run a rake task the deploy server.
388 389 390 391 |
# File 'lib/sunshine/server_app.rb', line 388 def rake command install_deps 'rake', :type => Gem @shell.call "cd #{self.checkout_path} && rake #{command}" end |
#register_as_deployed ⇒ Object
Adds the app to the deploy server’s deployed-apps list
397 398 399 |
# File 'lib/sunshine/server_app.rb', line 397 def register_as_deployed AddCommand.exec "#{self.name}:#{self.root_path}", 'servers' => [@shell] end |
#remove_old_deploys ⇒ Object
Removes old deploys from the checkout_dir based on Sunshine’s max_deploy_versions.
406 407 408 409 410 411 412 413 414 415 416 417 |
# File 'lib/sunshine/server_app.rb', line 406 def remove_old_deploys deploys = all_deploy_names true return unless deploys.length > Sunshine.max_deploy_versions lim = Sunshine.max_deploy_versions + 1 rm_deploys = deploys[0..-lim] rm_deploys.map!{|d| "#{self.deploys_path}/#{d}"} @shell.call "rm -rf #{rm_deploys.join(" ")}" end |
#restart ⇒ Object
Run the app’s restart script. Returns false on failure. Post-deploy only.
424 425 426 427 |
# File 'lib/sunshine/server_app.rb', line 424 def restart # Permissions are handled by the script, use: :sudo => false run_script :stop, :sudo => false end |
#restart! ⇒ Object
Run the app’s restart script. Raises an exception on failure. Post-deploy only.
434 435 436 437 |
# File 'lib/sunshine/server_app.rb', line 434 def restart! # Permissions are handled by the script, use: :sudo => false run_script! :restart, :sudo => false end |
#revert! ⇒ Object
Symlink current directory to previous checkout and remove the current deploy directory.
444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 |
# File 'lib/sunshine/server_app.rb', line 444 def revert! @shell.call "rm -rf #{self.checkout_path}" last_deploy = previous_deploy_name(true) if last_deploy && !last_deploy.empty? @shell.symlink "#{self.deploys_path}/#{last_deploy}", self.current_path Sunshine.logger.info @shell.host, "Reverted to #{last_deploy}" else @crontab.delete! Sunshine.logger.info @shell.host, "No previous deploy to revert to." end end |
#run_bundler(options = {}) ⇒ Object
Runs bundler. Installs the bundler gem if missing.
465 466 467 468 |
# File 'lib/sunshine/server_app.rb', line 465 def run_bundler ={} install_deps 'bundler', :type => Gem @shell.call "cd #{self.checkout_path} && gem bundle", end |
#run_geminstaller(options = {}) ⇒ Object
Runs geminstaller. :( Deprecated: how about trying bundler or isolate? If sudo is required to install to your GEM_HOME, make sure to pass it as an argument:
server_app.run_geminstaller :sudo => true
478 479 480 481 482 |
# File 'lib/sunshine/server_app.rb', line 478 def run_geminstaller ={} install_deps 'geminstaller', :type => Gem # Without sudo gems get installed to ~user/.gems @shell.call "cd #{self.checkout_path} && geminstaller -e", end |
#run_script(name, options = nil, &block) ⇒ Object
Runs a script from the root_path. Post-deploy only.
489 490 491 492 |
# File 'lib/sunshine/server_app.rb', line 489 def run_script name, =nil, &block ||= {} run_script! name, , &block rescue false end |
#run_script!(name, options = nil, &block) ⇒ Object
Runs a script from the root_path. Raises an exception if the status code is not 0. Post-deploy only.
500 501 502 503 504 505 |
# File 'lib/sunshine/server_app.rb', line 500 def run_script! name, =nil, &block ||= {} script_path = File.join self.scripts_path, name.to_s @shell.call script_path, , &block end |
#running? ⇒ Boolean
Check if the app pids are present. Post-deploy only.
512 513 514 515 516 517 518 519 520 |
# File 'lib/sunshine/server_app.rb', line 512 def running? # Permissions are handled by the script, use: :sudo => false run_script! :status, :sudo => false true rescue CmdError => e return false if e.exit_code == Daemon::STATUS_DOWN_CODE raise e end |
#sass(*sass_names) ⇒ Object
Run a sass task on any or all deploy servers.
526 527 528 529 530 531 532 533 534 535 536 |
# File 'lib/sunshine/server_app.rb', line 526 def sass *sass_names install_deps 'haml', :type => Gem sass_names.flatten.each do |name| sass_file = "public/stylesheets/sass/#{name}.sass" css_file = "public/stylesheets/#{name}.css" sass_cmd = "cd #{self.checkout_path} && sass #{sass_file} #{css_file}" @shell.call sass_cmd end end |
#shell_env ⇒ Object
Get the deploy server’s shell environment.
542 543 544 |
# File 'lib/sunshine/server_app.rb', line 542 def shell_env @shell.env end |
#start(options = nil) ⇒ Object
Run the app’s start script. Returns false on failure. Post-deploy only.
551 552 553 554 555 556 557 558 559 560 561 |
# File 'lib/sunshine/server_app.rb', line 551 def start =nil ||= {} if running? return unless [:force] stop end # Permissions are handled by the script, use: :sudo => false run_script :start, :sudo => false end |
#start!(options = nil) ⇒ Object
Run the app’s start script. Raises an exception on failure. Post-deploy only.
568 569 570 571 572 573 574 575 576 577 578 |
# File 'lib/sunshine/server_app.rb', line 568 def start! =nil ||= {} if running? return unless [:force] stop! end # Permissions are handled by the script, use: :sudo => false run_script! :start, :sudo => false end |
#status ⇒ Object
Get the app’s status: :running or :down.
584 585 586 |
# File 'lib/sunshine/server_app.rb', line 584 def status running? ? :running : :down end |
#stop ⇒ Object
Run the app’s stop script. Returns false on failure. Post-deploy only.
593 594 595 596 |
# File 'lib/sunshine/server_app.rb', line 593 def stop # Permissions are handled by the script, use: :sudo => false run_script :stop, :sudo => false end |
#stop! ⇒ Object
Run the app’s stop script. Raises an exception on failure. Post-deploy only.
603 604 605 606 |
# File 'lib/sunshine/server_app.rb', line 603 def stop! # Permissions are handled by the script, use: :sudo => false run_script! :stop, :sudo => false end |
#symlink_current_dir ⇒ Object
Creates a symlink to the app’s checkout path.
612 613 614 |
# File 'lib/sunshine/server_app.rb', line 612 def symlink_current_dir @shell.symlink self.checkout_path, self.current_path end |
#symlink_scripts_to_root(*script_names) ⇒ Object
Creates a symlink of every script_name from the scripts_path dir to the app’s root directory for easy access.
621 622 623 624 625 626 627 628 |
# File 'lib/sunshine/server_app.rb', line 621 def symlink_scripts_to_root *script_names script_names.flatten.each do |name, val| script_file = File.join self.scripts_path, name.to_s pointer_file = File.join self.root_path, name.to_s @shell.symlink script_file, pointer_file end end |
#upload_codebase(code_dir, scm_info = {}) ⇒ Object
Assumes the passed code_dir is the root directory of the checked out codebase and uploads it to the checkout_path.
635 636 637 638 639 640 641 642 643 644 |
# File 'lib/sunshine/server_app.rb', line 635 def upload_codebase code_dir, scm_info={} excludes = scm_info.delete :exclude if scm_info[:exclude] excludes = [excludes].flatten.compact excludes.map!{|e| "--exclude #{e}"} repo = RsyncRepo.new code_dir, :flags => excludes repo.checkout_to self.checkout_path, @shell @info[:scm] = scm_info end |
#write_script(name, contents) ⇒ Object
Write an executable bash script to the app’s scripts dir on the deploy server, and symlink them to the root dir.
651 652 653 654 655 656 |
# File 'lib/sunshine/server_app.rb', line 651 def write_script name, contents script_file = "#{self.scripts_path}/#{name}" @shell.make_file script_file, contents, :flags => '--chmod=ugo=rwx' unless @shell.file? script_file end |