I know that I can use keep_releases to keep a specific number of releases, but I have a series of releases that have symlinks to them. For example, I have:

http://www.example.com/version/1.0.0 which is a symlink to some release, say /var/www/example.com/releases/2019050101. I also have a series of what I'd call "transient" releases, which are linked using /current/. When one of these 'transient' releases is deployed, the /current symlink is overwritten to the most current release. What this amounts to is that there are some releases that don't have symlinks to them.

What I'd like to do is specify to Capistrano to only keep keep_releases number of these transient releases. In other words, all releases that are symlinked from the root directory should always be kept.

Can someone help me with a Capistrano recipe for accomplishing this?

1 Answers

0
jwir3 On

I ended up using the following recipe, which is mostly a copy of the cleanup recipe in the Capistrano source, except with some additional logic to determine and skip the symlinked directories:

desc "Clean up old releases"
  task :cleanup do
    on release_roles :all do |host|
      releases = capture(:ls, "-x", releases_path).split
      kept_paths = []
      within release_path.parent.parent do
        root_path = release_path.parent.parent
        links = capture(:ls, "-l", "|", "grep", "^l").split("\n")
        links.each { |x|
          next_link_target = x.split("->")[1]
          next_link_target = next_link_target.gsub("/build", "")
          target_components = next_link_target.split("/")
          next_link_target = target_components[target_components.size - 1]
          kept_paths.push(next_link_target)
        }
      end

      valid, invalid = releases.partition { |e| /^\d{14}$/ =~ e }

      warn t(:skip_cleanup, host: host.to_s) if invalid.any?

      valid, invalid = valid.partition { |v|
        split_path = v.split("/")
        !kept_paths.include? split_path[split_path.size - 1]
      }

      if invalid.any?
        info "Skipping cleanup of releases: #{invalid} because they are symlinked in the root directory"
      end

      if valid.count >= fetch(:keep_releases)
        info t(:keeping_releases, host: host.to_s, keep_releases: fetch(:keep_releases), releases: valid.count)
        directories = (valid - valid.last(fetch(:keep_releases))).map do |release|
          releases_path.join(release).to_s
        end
        if test("[ -d #{current_path} ]")
          current_release = capture(:readlink, current_path).to_s
          if directories.include?(current_release)
            warn t(:wont_delete_current_release, host: host.to_s)
            directories.delete(current_release)
          end
        else
          debug t(:no_current_release, host: host.to_s)
        end
        if directories.any?
          execute :rm, "-rf", *directories
        else
          info t(:no_old_releases, host: host.to_s, keep_releases: fetch(:keep_releases))
        end
      end
    end
  end