module Autostager

Top-level module namespace. rubocop:disable Metrics/ModuleLength

Version constant for the gem.

Constants

VERSION

Public Instance Methods

access_token() click to toggle source
# File lib/autostager.rb, line 19
def access_token
  ENV['access_token']
end
alphafy(a_string) click to toggle source

Convert a string into purely alphanumeric characters

# File lib/autostager.rb, line 28
def alphafy(a_string)
  a_string.gsub(/[^a-z0-9_]/i, '_')
end
authenticated_url(s) click to toggle source

rubocop:enable MethodLength,Metrics/AbcSize

# File lib/autostager.rb, line 115
def authenticated_url(s)
  s.dup.sub!(%r{^(https://)(.*)}, '\1' + access_token + '@\2')
end
base_dir() click to toggle source
# File lib/autostager.rb, line 119
def base_dir
  ENV['base_dir'] || '/opt/puppet/environments'
end
client() click to toggle source
# File lib/autostager.rb, line 135
def client
  @client ||= Octokit::Client.new(access_token: access_token)
end
clone_dir(pr) click to toggle source
# File lib/autostager.rb, line 123
def clone_dir(pr)
  alphafy(pr.head.label)
end
comment_or_close(p, pr, add_comment = true) click to toggle source

rubocop:disable MethodLength,Metrics/AbcSize

# File lib/autostager.rb, line 90
def comment_or_close(p, pr, add_comment = true)
  if p.up2date?("upstream/#{pr.base.repo.default_branch}")
    if add_comment
      comment = format(
        ':bell: Staged `%s` at revision %s on %s',
        clone_dir(pr),
        p.local_sha,
        Socket.gethostname,
      )
      client.add_comment repo_slug, pr.number, comment
      log comment
    end
  else
    comment = format(
      ':boom: Unstaged since %s is dangerously behind upstream.',
      clone_dir(pr),
    )
    FileUtils.rm_rf staging_dir(pr), secure: true
    client.add_comment repo_slug, pr.number, comment
    client.close_issue repo_slug, pr.number
    log comment
  end
end
default_branch() click to toggle source

Get the name of the default branch for the repo. This is usually master in git, but could also be “production” for a puppet repo.

# File lib/autostager.rb, line 35
def default_branch
  @client.repo(repo_slug).default_branch
end
git_server() click to toggle source
# File lib/autostager.rb, line 23
def git_server
  ENV['git_server'] || 'github.com'
end
process_pull(pr) click to toggle source

rubocop:disable MethodLength,Metrics/AbcSize

# File lib/autostager.rb, line 63
def process_pull(pr)
  log "===> #{pr.number} #{clone_dir(pr)}"
  p = Autostager::PullRequest.new(
    pr.head.ref,
    authenticated_url(pr.head.repo.clone_url),
    base_dir,
    clone_dir(pr),
    authenticated_url(pr.base.repo.clone_url),
  )
  if p.staged?
    p.fetch
    if pr.head.sha != p.local_sha
      p.reset_hard
      add_comment = true
    else
      log "nothing to do on #{pr.number} #{staging_dir(pr)}"
      add_comment = false
    end
    comment_or_close(p, pr, add_comment)
  else
    p.clone
    comment_or_close(p, pr)
  end
end
repo_slug() click to toggle source
# File lib/autostager.rb, line 131
def repo_slug
  ENV['repo_slug']
end
run() click to toggle source

rubocop:disable MethodLength,Metrics/AbcSize

# File lib/autostager.rb, line 159
def run
  Octokit.auto_paginate = true
  user = client.user
  user.login

  # Handle the default branch differently because
  # we only ever rebase, never force-push.
  stage_upstream

  # Get open PRs.
  prs = client.pulls(repo_slug)

  # Set of PR clone dirs.
  new_clones = prs.map { |pr| clone_dir(pr) }

  # Discard directories that do not have open PRs.
  if File.exist?(base_dir)
    discard_dirs = Dir.entries(base_dir) - safe_dirs - new_clones
    discard_dirs.map { |d| File.join(base_dir, d) }.each do |dir|
      log "===> Unstage #{dir} since PR is closed."
      FileUtils.rm_rf dir, secure: true
    end
  end

  # Process current PRs.
  Autostager::Timeout.timeout(timeout_seconds, GitTimeout) do
    prs.each { |pr| process_pull pr }
  end
rescue Octokit::Unauthorized => e
  warn e.message
  warn 'Did you export "access_token" and "repo_slug"?'
  exit(1)
end
safe_dirs() click to toggle source

A list of directories we never discard.

# File lib/autostager.rb, line 149
def safe_dirs
  [
    '.',
    '..',
    'master',
    'production',
  ]
end
stage_upstream() click to toggle source

rubocop:disable MethodLength,Metrics/AbcSize

# File lib/autostager.rb, line 40
def stage_upstream
  log "===> begin #{default_branch}"
  p = Autostager::PullRequest.new(
    default_branch,
    authenticated_url("https://#{git_server}/#{repo_slug}"),
    base_dir,
    default_branch,
    authenticated_url("https://#{git_server}/#{repo_slug}"),
  )
  p.clone unless p.staged?
  p.fetch
  return if p.rebase

  # fast-forward failed, so raise awareness.
  @client.create_issue(
    repo_slug,
    "Failed to fast-forward #{default_branch} branch",
    ':bangbang: This probably means somebody force-pushed to the branch.',
  )
end
staging_dir(pr) click to toggle source
# File lib/autostager.rb, line 127
def staging_dir(pr)
  File.join base_dir, clone_dir(pr)
end
timeout_seconds() click to toggle source
# File lib/autostager.rb, line 139
def timeout_seconds
  result = 120
  if ENV.key?('timeout')
    result = ENV['timeout'].to_i
    raise 'timeout must be greater than zero seconds' if result <= 0
  end
  result
end