Module: Autostager

Extended by:
Logger
Defined in:
lib/autostager.rb,
lib/autostager/cli.rb,
lib/autostager/logger.rb,
lib/autostager/timeout.rb,
lib/autostager/version.rb,
lib/autostager/git_timeout.rb,
lib/autostager/pull_request.rb

Overview

Version constant for the gem.

Defined Under Namespace

Modules: Logger, Timeout Classes: CLI, GitTimeout, PullRequest

Constant Summary collapse

VERSION =
'0.0.16'.freeze

Class Method Summary collapse

Methods included from Logger

log, safe

Class Method Details

.access_tokenObject



19
20
21
# File 'lib/autostager.rb', line 19

def access_token
  ENV['access_token']
end

.alphafy(a_string) ⇒ Object

Convert a string into purely alphanumeric characters



28
29
30
# File 'lib/autostager.rb', line 28

def alphafy(a_string)
  a_string.gsub(/[^a-z0-9_]/i, '_')
end

.authenticated_url(s) ⇒ Object

rubocop:enable MethodLength,Metrics/AbcSize



115
116
117
# File 'lib/autostager.rb', line 115

def authenticated_url(s)
  s.dup.sub!(%r{^(https://)(.*)}, '\1' + access_token + '@\2')
end

.base_dirObject



119
120
121
# File 'lib/autostager.rb', line 119

def base_dir
  ENV['base_dir'] || '/opt/puppet/environments'
end

.clientObject



135
136
137
# File 'lib/autostager.rb', line 135

def client
  @client ||= Octokit::Client.new(access_token: access_token)
end

.clone_dir(pr) ⇒ Object



123
124
125
# File 'lib/autostager.rb', line 123

def clone_dir(pr)
  alphafy(pr.head.label)
end

.comment_or_close(p, pr, add_comment = true) ⇒ Object

rubocop:disable MethodLength,Metrics/AbcSize



90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# 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_branchObject

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.



35
36
37
# File 'lib/autostager.rb', line 35

def default_branch
  @client.repo(repo_slug).default_branch
end

.git_serverObject



23
24
25
# File 'lib/autostager.rb', line 23

def git_server
  ENV['git_server'] || 'github.com'
end

.process_pull(pr) ⇒ Object

rubocop:disable MethodLength,Metrics/AbcSize



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# 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_slugObject



131
132
133
# File 'lib/autostager.rb', line 131

def repo_slug
  ENV['repo_slug']
end

.runObject

rubocop:disable MethodLength,Metrics/AbcSize



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
184
185
186
187
188
189
190
191
# File 'lib/autostager.rb', line 159

def run
  Octokit.auto_paginate = true
  user = client.user
  user.

  # 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_dirsObject

A list of directories we never discard.



149
150
151
152
153
154
155
156
# File 'lib/autostager.rb', line 149

def safe_dirs
  [
    '.',
    '..',
    'master',
    'production',
  ]
end

.stage_upstreamObject

rubocop:disable MethodLength,Metrics/AbcSize



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# 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) ⇒ Object



127
128
129
# File 'lib/autostager.rb', line 127

def staging_dir(pr)
  File.join base_dir, clone_dir(pr)
end

.timeout_secondsObject



139
140
141
142
143
144
145
146
# 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