Class: Shuttle::Strategy

Inherits:
Deploy
  • Object
show all
Defined in:
lib/shuttle/strategy.rb

Direct Known Subclasses

Nodejs, Php, Rails, Ruby, Static

Constant Summary

Constants included from Helpers

Helpers::LEVEL_COLORS

Instance Attribute Summary

Attributes inherited from Deploy

#config, #environment, #ssh, #target, #version

Instance Method Summary collapse

Methods inherited from Deploy

#available_releases, #initialize, #last_version

Methods included from PathHelpers

#current_path, #deploy_path, #release_path, #scm_path, #shared_path, #version_path

Methods included from Helpers

#deployer_hostname, #error, #git_installed?, #git_remote, #log, #release_exists?, #stream_output, #svn_installed?, #warn

Constructor Details

This class inherits a constructor from Shuttle::Deploy

Instance Method Details

#changes_at?(path) ⇒ Boolean

Returns:

  • (Boolean)


296
297
298
299
# File 'lib/shuttle/strategy.rb', line 296

def changes_at?(path)
  result = ssh.run(%{diff -r #{current_path}/#{path} #{release_path}/#{path} 2>/dev/null})
  result.success? ? false : true
end

#checkout_code(path = nil) ⇒ Object



150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/shuttle/strategy.rb', line 150

def checkout_code(path=nil)
  # Trigger hook before checking out code
  execute_hook(:before_checkout_code)

  checkout_path = [release_path, path].compact.join('/')
  res = ssh.run("cp -a #{scm_path} #{checkout_path}")
  
  if res.failure?
    error "Failed to checkout code. Reason: #{res.output}"
  else
    ssh.run("cd #{release_path} && rm -rf $(find . | grep .git)")
    ssh.run("cd #{release_path} && rm -rf $(find . -name .svn)")
  end

  # Trigger hook after checking out code
  execute_hook(:after_checkout_code)
end

#cleanup_releaseObject

Delete current session release



208
209
210
211
212
# File 'lib/shuttle/strategy.rb', line 208

def cleanup_release
  if ssh.directory_exists?(release_path)
    ssh.run("rm -rf #{release_path}")
  end
end

#cleanup_releasesObject



214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
# File 'lib/shuttle/strategy.rb', line 214

def cleanup_releases
  ssh.run("cd #{deploy_path('releases')}")
  ssh.run("count=`ls -1d [0-9]* | sort -rn | wc -l`")

  count = ssh.capture("echo $count")

  unless count.empty?
    num = Integer(count) - Integer(keep_releases)

    if num > 0
      log "Cleaning up old releases: #{num}" if num > 1

      ssh.run("remove=$((count > #{keep_releases} ? count - #{keep_releases} : 0))")
      ssh.run("ls -1d [0-9]* | sort -rn | tail -n $remove | xargs rm -rf {}")
    end
  end
end

#connectObject



288
289
290
# File 'lib/shuttle/strategy.rb', line 288

def connect
  exec("ssh #{target.user}@#{target.host}")
end

#debug?Boolean

Returns:

  • (Boolean)


305
306
307
# File 'lib/shuttle/strategy.rb', line 305

def debug?
  ENV["SHUTTLE_DEBUG"] ? true : false
end

#deployObject



20
21
22
23
24
25
26
# File 'lib/shuttle/strategy.rb', line 20

def deploy
  setup
  update_code
  checkout_code
  link_release
  cleanup_releases
end

#deploy_running?Boolean

Returns:

  • (Boolean)


284
285
286
# File 'lib/shuttle/strategy.rb', line 284

def deploy_running?
  ssh.file_exists?("#{deploy_path}/.lock")
end

#disable_historyObject



252
253
254
# File 'lib/shuttle/strategy.rb', line 252

def disable_history
  ssh.run("set +o history")
end

#execute_commands(commands = [], allow_failures = false) ⇒ Object



301
302
303
# File 'lib/shuttle/strategy.rb', line 301

def execute_commands(commands = [], allow_failures = false)
  Shuttle::Hook.new(self).run(commands, allow_failures)
end

#execute_hook(name, allow_failures = false) ⇒ Object



278
279
280
281
282
# File 'lib/shuttle/strategy.rb', line 278

def execute_hook(name, allow_failures = false)
  if config.hooks && config.hooks[name]
    execute_commands(config.hooks[name], allow_failures)
  end
end

#export_environmentObject



256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
# File 'lib/shuttle/strategy.rb', line 256

def export_environment
  ssh.export_hash(
    'DEPLOY_APP'          => config.app.name,
    'DEPLOY_APPLICATION'  => config.app.name,
    'DEPLOY_USER'         => target.user,
    'DEPLOY_PATH'         => deploy_path,
    'DEPLOY_RELEASE'      => version,
    'DEPLOY_RELEASE_PATH' => release_path,
    'DEPLOY_CURRENT_PATH' => current_path,
    'DEPLOY_SHARED_PATH'  => shared_path,
    'DEPLOY_SCM_PATH'     => scm_path
  )

  if config.env?
    log "Exporting environment variables"

    config.env.each_pair do |k, v|
      ssh.export(k, v)
    end
  end
end

#keep_releasesObject



292
293
294
# File 'lib/shuttle/strategy.rb', line 292

def keep_releases
  config.app.keep_releases || 10
end


168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/shuttle/strategy.rb', line 168

def link_release
  if !release_exists?
    error "Release does not exist"
  end

  # Execute before link_release hook
  execute_hook(:before_link_release)

  log "Linking release"

  # Check if `current` is a directory first
  if ssh.run("unlink #{current_path}").failure?
    ssh.run("rm -rf #{current_path}")
  end

  if ssh.run("ln -s #{release_path} #{current_path}").failure?
    error "Unable to create symlink to current path"
  end

  # Write version into RELEASE file
  write_release

  # Write version into current version file
  ssh.run "echo #{version} > #{version_path}"

  log "Release v#{version} has been deployed"

  # Execute after link_release hook, allow failures here
  execute_hook(:after_link_release, true)
end

#release_lockObject



203
204
205
# File 'lib/shuttle/strategy.rb', line 203

def release_lock
  ssh.run("rm #{deploy_path}/.lock")
end

#rollbackObject



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/shuttle/strategy.rb', line 28

def rollback
  execute_hook(:before_rollback)

  if last_version == 0
    error "There are no releases to rollback to"
  end

  release = available_releases.select { |v| v == last_version-1 }.first

  if release
    if ssh.run("unlink #{current_path}").failure?
      ssh.run("rm -rf #{current_path}")
    end

    if ssh.run("ln -s #{deploy_path}/releases/#{release} #{current_path}").failure?
      error "Unable to create symlink to current path"
    end

    ssh.run("echo #{release} > #{version_path}")
    ssh.run("rm -rf #{deploy_path}/releases/#{last_version}")

    log "Rolled back to release v#{release}"
  else
    error "There are no older releases"
  end

  execute_hook(:after_rollback)
end

#setupObject



5
6
7
8
9
10
11
12
13
14
15
16
17
18
# File 'lib/shuttle/strategy.rb', line 5

def setup
  log "Preparing application structure"

  execute_hook(:before_setup)

  ssh.run "mkdir -p #{deploy_path}"
  ssh.run "mkdir -p #{deploy_path('releases')}"
  ssh.run "mkdir -p #{deploy_path('shared')}"
  ssh.run "mkdir -p #{shared_path('tmp')}"
  ssh.run "mkdir -p #{shared_path('pids')}"
  ssh.run "mkdir -p #{shared_path('log')}"

  execute_hook(:after_setup)
end

#update_codeObject



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
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
# File 'lib/shuttle/strategy.rb', line 57

def update_code
  if config.app.svn
    return update_code_svn
  end

  error "Git is not installed" if !git_installed?
  error "Git source url is not defined. Please define :git option first" if config.app.git.nil?

  branch = config.app.branch || 'master'

  if ssh.directory_exists?(scm_path)
    # Check if git remote has changed
    current_remote = git_remote
    
    if current_remote != config.app.git
      log("Git remote change detected. Using #{config.app.git}", 'warning')

      res = ssh.run("cd #{scm_path} && git remote rm origin && git remote add origin #{config.app.git}")
      if res.failure?
        error("Failed to change git remote: #{res.output}")
      end
    end

    log "Fetching latest code"
    res = ssh.run "cd #{scm_path} && git pull origin #{branch}"

    if res.failure?
      error "Unable to fetch latest code: #{res.output}"
    end
  else
    log "Cloning repository #{config.app.git}"
    res = ssh.run "cd #{deploy_path} && git clone --depth 25 --recursive --quiet #{config.app.git} scm"

    if res.failure?
      error "Failed to clone repository: #{res.output}"
    end
  end

  ssh.run("cd #{scm_path} && git fetch")

  # Make sure to pull changes from current non-master branch
  if branch != 'master'
    ssh.run("cd #{scm_path} && git pull origin #{branch}")
  end

  log "Using branch '#{branch}'"
  result = ssh.run("cd #{scm_path} && git checkout -m #{branch}")

  if result.failure?
    error "Failed to checkout #{branch}: #{result.output}"
  end

  if ssh.file_exists?("#{scm_path}/.gitmodules")
    log "Updating git submodules"
    result = ssh.run("cd #{scm_path} && git submodule update --init --recursive")

    if result.failure?
      error "Failed to update submodules: #{result.output}"
    end
  end
end

#update_code_svnObject



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
# File 'lib/shuttle/strategy.rb', line 119

def update_code_svn
  error "Subversion is not installed" if !svn_installed?
  error "Subversion source is not defined. Please define :svn option first" if config.app.svn.nil?

  url = URI.parse(config.app.svn)
  repo_url = "#{url.scheme}://#{url.host}#{url.path}"

  opts = ["--non-interactive", "--quiet"]

  if url.user
    opts << "--username #{url.user}"
    opts << "--password #{url.password}" if url.password
  end

  if ssh.directory_exists?(scm_path)
    log "Fetching latest code"

    res = ssh.run("cd #{scm_path} && svn up #{opts.join(' ')}")
    if res.failure?
      error "Unable to fetch latest code: #{res.output}"
    end
  else
    log "Cloning repository #{config.app.svn}"
    res = ssh.run("cd #{deploy_path} && svn checkout #{opts.join(' ')} #{repo_url} scm")

    if res.failure?
      error "Failed to clone repository: #{res.output}"
    end
  end
end

#write_lockObject



199
200
201
# File 'lib/shuttle/strategy.rb', line 199

def write_lock
  ssh.run(%{echo #{deployer_hostname} > #{deploy_path}/.lock})
end

#write_releaseObject



248
249
250
# File 'lib/shuttle/strategy.rb', line 248

def write_release
  ssh.run("echo #{version} > #{release_path}/RELEASE")
end

#write_revisionObject



232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
# File 'lib/shuttle/strategy.rb', line 232

def write_revision
  if ssh.directory_exists?(deploy_path('scm'))
    command = nil

    if config.app.git
      command = "git log --format='%H' -n 1"
    elsif config.app.svn
      command = "svn info |grep Revision: |cut -c11-"
    end

    if command
      ssh.run("cd #{scm_path} && #{command} > #{release_path}/REVISION")
    end
  end
end