Class: Harrison::Deploy

Inherits:
Base
  • Object
show all
Defined in:
lib/harrison/deploy.rb

Defined Under Namespace

Classes: Phase

Instance Attribute Summary collapse

Attributes inherited from Base

#options

Instance Method Summary collapse

Methods inherited from Base

#download, #exec, #method_missing, option_helper, #upload

Constructor Details

#initialize(opts = {}) ⇒ Deploy

Returns a new instance of Deploy.



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/harrison/deploy.rb', line 13

def initialize(opts={})
  # Config helpers for Harrisonfile.
  self.class.option_helper(:hosts)
  self.class.option_helper(:env)
  self.class.option_helper(:base_dir)
  self.class.option_helper(:deploy_via)
  self.class.option_helper(:keep)

  # Command line opts for this action. Will be merged with common opts.
  arg_opts = [
    [ :hosts, "List of remote hosts to deploy to. Can also be specified in Harrisonfile.", :type => :strings ],
    [ :env, "Environment to deploy to. This can be examined in your Harrisonfile to calculate target hosts.", :type => :string ],
    [ :keep, "Number of recent deploys to keep after a successful deploy. (Including the most recent deploy.) Defaults to keeping all deploys forever.", :type => :integer ],
  ]

  super(arg_opts, opts)

  self.add_default_phases
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method in the class Harrison::Base

Instance Attribute Details

#artifactObject

Returns the value of attribute artifact.



3
4
5
# File 'lib/harrison/deploy.rb', line 3

def artifact
  @artifact
end

Returns the value of attribute deploy_link.



6
7
8
# File 'lib/harrison/deploy.rb', line 6

def deploy_link
  @deploy_link
end

#hostObject

The specific host among –hosts that we are currently working on.



4
5
6
# File 'lib/harrison/deploy.rb', line 4

def host
  @host
end

#phasesObject

Returns the value of attribute phases.



9
10
11
# File 'lib/harrison/deploy.rb', line 9

def phases
  @phases
end

#release_dirObject

Returns the value of attribute release_dir.



5
6
7
# File 'lib/harrison/deploy.rb', line 5

def release_dir
  @release_dir
end

#rollbackObject

Returns the value of attribute rollback.



8
9
10
# File 'lib/harrison/deploy.rb', line 8

def rollback
  @rollback
end

Instance Method Details

#add_phase(name, &block) ⇒ Object



47
48
49
50
51
# File 'lib/harrison/deploy.rb', line 47

def add_phase(name, &block)
  @_phases ||= Hash.new

  @_phases[name] = Harrison::Deploy::Phase.new(name, &block)
end

#cleanup_deploys(limit) ⇒ Object



155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/harrison/deploy.rb', line 155

def cleanup_deploys(limit)
  # Grab a list of deploys to be removed.
  purge_deploys = self.deploys.sort.reverse.slice(limit..-1) || []

  if purge_deploys.size > 0
    puts "[#{self.host}]   Purging #{purge_deploys.size} old deploys. (Keeping #{limit}...)"

    purge_deploys.each do |stale_deploy|
      remote_exec("cd deploys && rm -f #{stale_deploy}")
    end
  end
end

#cleanup_releasesObject



168
169
170
171
172
173
174
175
176
177
# File 'lib/harrison/deploy.rb', line 168

def cleanup_releases
  # Figure out which releases need to be kept.
  keep_releases = self.active_releases

  self.releases.each do |release|
    unless keep_releases.include?(release)
      remote_exec("cd releases && rm -rf #{release}")
    end
  end
end

#close(host = nil) ⇒ Object



179
180
181
182
183
184
185
186
187
# File 'lib/harrison/deploy.rb', line 179

def close(host=nil)
  if host
    @_conns[host].close if @_conns && @_conns[host]
  elsif @_conns
    @_conns.keys.each do |host|
      @_conns[host].close unless @_conns[host].closed?
    end
  end
end


57
58
59
# File 'lib/harrison/deploy.rb', line 57

def current_symlink
  "#{self.remote_project_dir}/current"
end

#invoke_user_blockObject



11
# File 'lib/harrison/deploy.rb', line 11

alias :invoke_user_block :run

#parse(args) ⇒ Object



33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/harrison/deploy.rb', line 33

def parse(args)
  super

  # Preserve argv hosts if it's been passed.
  @_argv_hosts = self.hosts.dup if self.hosts

  self.rollback = args[0] == 'rollback'

  unless self.rollback
    # Make sure they passed an artifact.
    self.artifact = args[1] || abort("ERROR: You must specify the artifact to be deployed as an argument to this command.")
  end
end

#remote_exec(cmd) ⇒ Object



53
54
55
# File 'lib/harrison/deploy.rb', line 53

def remote_exec(cmd)
  super("cd #{remote_project_dir} && #{cmd}")
end


69
70
71
72
73
74
# File 'lib/harrison/deploy.rb', line 69

def revert_current_symlink
  # Restore current symlink to previous if set.
  if @_old_current
    self.remote_exec("ln -sfn #{@_old_current} #{self.current_symlink}")
  end
end

#runObject



76
77
78
79
80
81
82
83
84
85
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
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/harrison/deploy.rb', line 76

def run
  # Override Harrisonfile hosts if it was passed on argv.
  self.hosts = @_argv_hosts if @_argv_hosts

  # Require at least one host.
  if !self.hosts || self.hosts.empty?
    abort("ERROR: You must specify one or more hosts to deploy/rollback on, either in your Harrisonfile or via --hosts.")
  end

  # Default to just built in deployment phases.
  self.phases ||= [ :upload, :extract, :link, :cleanup ]

  # Default base_dir.
  self.base_dir ||= '/opt'

  if self.rollback
    puts "Rolling back \"#{project}\" to previously deployed release on #{hosts.size} hosts...\n\n"

    # Find the prior deploy on the first host.
    self.host = hosts[0]
    last_deploy = self.deploys.sort.reverse[1] || abort("ERROR: No previous deploy to rollback to.")
    self.release_dir = remote_exec("cd deploys && readlink -vn #{last_deploy}")

    # No need to upload or extract for rollback.
    self.phases.delete(:upload)
    self.phases.delete(:extract)

    # Don't cleanup old deploys either.
    self.phases.delete(:cleanup)
  else
    puts "Deploying #{artifact} for \"#{project}\" onto #{hosts.size} hosts...\n\n"
    self.release_dir = "#{remote_project_dir}/releases/" + File.basename(artifact, '.tar.gz')
  end

  self.deploy_link = "#{remote_project_dir}/deploys/" + Time.new.utc.strftime('%Y-%m-%d_%H%M%S')

  progress_stack = []

  failed = catch(:failure) do
    self.phases.each do |phase_name|
      phase = @_phases[phase_name] || abort("ERROR: Could not resolve \"#{phase_name}\" as a deployment phase.")

      self.hosts.each do |host|
        self.host = host

        phase._run(self)

        # Track what phases we have completed on which hosts, in a stack.
        progress_stack << { host: host, phase: phase_name }
      end
    end

    # We want "failed" to be false if nothing was caught.
    false
  end

  if failed
    print "\n"

    progress_stack.reverse.each do |progress|
      self.host = progress[:host]
      phase = @_phases[progress[:phase]]

      # Don't let failures interrupt the rest of the process.
      catch(:failure) do
        phase._fail(self)
      end
    end

    abort "\nDeployment failed, previously completed deployment actions have been reverted."
  else
    if self.rollback
      puts "\nSucessfully rolled back #{project} on #{hosts.join(', ')}."
    else
      puts "\nSucessfully deployed #{artifact} to #{hosts.join(', ')}."
    end
  end
end


61
62
63
64
65
66
67
# File 'lib/harrison/deploy.rb', line 61

def update_current_symlink
  @_old_current = self.remote_exec("if [ -L #{current_symlink} ]; then readlink -vn #{current_symlink}; fi")
  @_old_current = nil if @_old_current.empty?

  # Symlink current to new deploy.
  self.remote_exec("ln -sfn #{self.deploy_link} #{self.current_symlink}")
end