discharger 0.3.1 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 86a58f41a34e61bfc155a9563e55bbc1eb42d4bd6904c09fe4aafb9bc61f3411
4
- data.tar.gz: 62d3b3ad5ffec0dcdee073f5ca0109416b383dd2fe180f8501af47f2028d9122
3
+ metadata.gz: 22ff9b0cc383ea213a6a8766f713ef4afa97a114c679c76b37954f73872b74e3
4
+ data.tar.gz: a3cfa43c5c9665704e13bce7a3bb19e36ccd4810b700b149a2646815fcca9f6a
5
5
  SHA512:
6
- metadata.gz: d0f6c58d5fde62fb7dd63b5dbe56c867600eea46e5c8e74b9eee22025da5dd299bf9d0b1093a40a67411c5cc279b9ca826e0c6d0af8659635fb6692279fa8fab
7
- data.tar.gz: 4ef09b9ca157755c99aa693d3c768e11df6f4fb470ed2d3c4736cf7c2fb894fb46352e941e550c2c200ec95bfd98f40a5daf22ca0928431231fdad04e07fe1ca
6
+ metadata.gz: 0e01ea39ed9eba5c973951784d44b5488a1f6f00effc59a1192d5f1f3cfd2aafbe14895a1d6ce920520acb7c501bc79720febc7360bed35a301c742ab30728ad
7
+ data.tar.gz: 309f1fd0b7ea3d78814aedd8c8bb945e0c63806260007dc8391126863254d4f19d87adc0e9ec7cdd584c3173982fd69465e87c06a13a97f90870aa60d93b2f6d
data/CHANGELOG.md CHANGED
@@ -5,14 +5,19 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](http://keepachangelog.com/)
6
6
  and this project adheres to [Semantic Versioning](http://semver.org/).
7
7
 
8
- ## [0.3.1] - 2026-05-13
8
+ ## [0.3.3] - 2026-07-22
9
9
 
10
10
  ### Added
11
11
 
12
- - database.prefer_docker setup.yml option for explicit Docker vs native PostgreSQL selection (2fca81e)
12
+ - Post-release runbook announcement in the Slack release thread, from Reissue's runbook_file (0cf9620)
13
13
 
14
- ## [0.2.31] - 2026-04-09
14
+ ## [0.3.2] - 2026-07-15
15
15
 
16
- ### Added
16
+ ### Changed
17
+
18
+ - Treat generated pg_tools setup as opt-in for apps that use structure.sql or PostgreSQL CLI workflows.
19
+
20
+ ### Fixed
17
21
 
18
- - Unified setup entry point with pre-steps support (1459633)
22
+ - Retry `brew bundle` once before failing setup, so transient Homebrew lock contention (e.g. two `bin/setup` runs on the same host) doesn't hard-fail the whole run.
23
+ - pg-tools wrappers no longer forward localhost -h/-p flags into docker exec, where the host-mapped port doesn't exist; remote hosts still pass through untouched (a3869d2)
data/README.md CHANGED
@@ -89,6 +89,51 @@ changelog.d/
89
89
 
90
90
  Each fragment file should contain the changelog entry for a specific change or feature.
91
91
 
92
+ ### Post-release Runbook
93
+
94
+ Some releases need manual follow-up: run a rake task, backfill data, re-index documents.
95
+ Reissue collects those steps from `Runbook:` commit trailers, and Discharger announces
96
+ them to your team in the release thread.
97
+
98
+ ```ruby
99
+ Discharger::Task.create do |task|
100
+ # ... other configuration ...
101
+ task.runbook_file = "RUNBOOK.md" # Default: nil (disabled)
102
+ end
103
+ ```
104
+
105
+ Add steps as you work, in the same commit as the change that requires them:
106
+
107
+ ```bash
108
+ git commit -m "Add data migration
109
+
110
+ Fixed: Duplicate user records
111
+ Runbook: Run \`rake data:cleanup\` after deploy"
112
+ ```
113
+
114
+ `rake release:prepare` finalizes the runbook alongside the changelog, so the release tag
115
+ records exactly which steps that version needs. After the production release, Discharger
116
+ posts the checklist to Slack as a reply in the release thread, right after the changelog:
117
+
118
+ ```
119
+ *Post-release runbook for 1.2.3* — 2 steps
120
+
121
+ • Run `rake data:cleanup` (abc1234)
122
+ • Re-index search documents (def5678)
123
+ ```
124
+
125
+ When a release needs no follow-up, the thread says so explicitly rather than staying
126
+ silent, so nobody has to wonder whether the check was skipped:
127
+
128
+ ```
129
+ *Post-release runbook for 1.2.3*
130
+ No runbook tasks for this release.
131
+ ```
132
+
133
+ Leave `runbook_file` unset and Discharger posts nothing extra. See the
134
+ [Reissue runbook documentation](https://github.com/SOFware/reissue#post-release-runbook)
135
+ for how items are collected and merged.
136
+
92
137
  ## Available Tasks
93
138
 
94
139
  The gem creates several Rake tasks for managing your deployment workflow:
@@ -199,12 +244,12 @@ The `pre_steps` array defines commands that run **before Rails loads**. These ar
199
244
 
200
245
  Built-in pre_steps:
201
246
  - `homebrew` - Installs Homebrew if not present (macOS)
202
- - `postgresql_tools` - Installs PostgreSQL client tools (`pg_dump`, `psql`)
247
+ - `postgresql_tools` - Optionally installs PostgreSQL client tools (`pg_dump`, `psql`) for apps that use `structure.sql` or call those tools directly
203
248
 
204
249
  ```yaml
205
250
  pre_steps:
206
251
  - homebrew
207
- - postgresql_tools
252
+ # - postgresql_tools
208
253
  ```
209
254
 
210
255
  You can also define custom pre_steps with shell commands:
@@ -228,6 +273,7 @@ The `steps` array specifies which built-in setup commands to run. Available comm
228
273
  - `yarn` - Install JavaScript packages
229
274
  - `config` - Copy configuration files
230
275
  - `docker` - Setup Docker containers
276
+ - `pg_tools` - Create Docker-aware `pg_dump` and `psql` wrappers for apps that use `structure.sql` or call those tools directly
231
277
  - `env` - Configure environment variables
232
278
  - `database` - Setup and migrate database
233
279
 
@@ -6,10 +6,12 @@ module Discharger
6
6
  module SetupRunner
7
7
  module Commands
8
8
  class BrewCommand < BaseCommand
9
+ RETRY_DELAY_SECONDS = 2
10
+
9
11
  def execute
10
12
  proceed_with "brew bundle" do
11
13
  log "Ensuring brew dependencies"
12
- system! "brew bundle"
14
+ run_brew_bundle
13
15
  end
14
16
  end
15
17
 
@@ -20,6 +22,23 @@ module Discharger
20
22
  def description
21
23
  "Install Homebrew dependencies"
22
24
  end
25
+
26
+ private
27
+
28
+ # brew bundle can fail transiently (e.g. Homebrew lock contention from a
29
+ # concurrent `brew` invocation) unrelated to the app's actual dependencies.
30
+ # Retry once before treating it as a real setup failure.
31
+ def run_brew_bundle
32
+ system! "brew bundle"
33
+ rescue => e
34
+ log "brew bundle failed, retrying once: #{e.message}"
35
+ sleep retry_delay
36
+ system! "brew bundle"
37
+ end
38
+
39
+ def retry_delay
40
+ RETRY_DELAY_SECONDS
41
+ end
23
42
  end
24
43
  end
25
44
  end
@@ -62,6 +62,8 @@ module Discharger
62
62
  # When running in Docker, we need to output to stdout and redirect locally
63
63
  OUTPUT_FILE=""
64
64
  HAS_USER=""
65
+ HOST=""
66
+ PORT=""
65
67
  ARGS=()
66
68
  while [[ $# -gt 0 ]]; do
67
69
  case $1 in
@@ -82,6 +84,38 @@ module Discharger
82
84
  ARGS+=("$1")
83
85
  shift
84
86
  ;;
87
+ -h|--host)
88
+ if [[ $# -lt 2 ]]; then
89
+ echo "Error: $1 requires an argument" >&2
90
+ exit 2
91
+ fi
92
+ HOST="$2"
93
+ shift 2
94
+ ;;
95
+ -h?*)
96
+ HOST="${1#-h}"
97
+ shift
98
+ ;;
99
+ --host=*)
100
+ HOST="${1#*=}"
101
+ shift
102
+ ;;
103
+ -p|--port)
104
+ if [[ $# -lt 2 ]]; then
105
+ echo "Error: $1 requires an argument" >&2
106
+ exit 2
107
+ fi
108
+ PORT="$2"
109
+ shift 2
110
+ ;;
111
+ -p?*)
112
+ PORT="${1#-p}"
113
+ shift
114
+ ;;
115
+ --port=*)
116
+ PORT="${1#*=}"
117
+ shift
118
+ ;;
85
119
  *)
86
120
  ARGS+=("$1")
87
121
  shift
@@ -94,6 +128,19 @@ module Discharger
94
128
  ARGS=("-U" "postgres" "${ARGS[@]}")
95
129
  fi
96
130
 
131
+ # Drop localhost host/port flags: they target the host-mapped
132
+ # port, but inside the container Postgres listens on its default
133
+ # port. Remote hosts pass through untouched.
134
+ case "$HOST" in
135
+ ""|localhost|127.0.0.1|::1) ;;
136
+ *)
137
+ ARGS+=("-h" "$HOST")
138
+ if [[ -n "$PORT" ]]; then
139
+ ARGS+=("-p" "$PORT")
140
+ fi
141
+ ;;
142
+ esac
143
+
97
144
  if [[ -n "$OUTPUT_FILE" ]]; then
98
145
  # Run pg_dump in container, output to stdout, redirect to local file
99
146
  docker exec -i "$CONTAINER" pg_dump "${ARGS[@]}" > "$OUTPUT_FILE"
@@ -133,6 +180,8 @@ module Discharger
133
180
  # so we pipe file content via stdin instead
134
181
  INPUT_FILE=""
135
182
  HAS_USER=""
183
+ HOST=""
184
+ PORT=""
136
185
  ARGS=()
137
186
  while [[ $# -gt 0 ]]; do
138
187
  case $1 in
@@ -153,6 +202,38 @@ module Discharger
153
202
  ARGS+=("$1")
154
203
  shift
155
204
  ;;
205
+ -h|--host)
206
+ if [[ $# -lt 2 ]]; then
207
+ echo "Error: $1 requires an argument" >&2
208
+ exit 2
209
+ fi
210
+ HOST="$2"
211
+ shift 2
212
+ ;;
213
+ -h?*)
214
+ HOST="${1#-h}"
215
+ shift
216
+ ;;
217
+ --host=*)
218
+ HOST="${1#*=}"
219
+ shift
220
+ ;;
221
+ -p|--port)
222
+ if [[ $# -lt 2 ]]; then
223
+ echo "Error: $1 requires an argument" >&2
224
+ exit 2
225
+ fi
226
+ PORT="$2"
227
+ shift 2
228
+ ;;
229
+ -p?*)
230
+ PORT="${1#-p}"
231
+ shift
232
+ ;;
233
+ --port=*)
234
+ PORT="${1#*=}"
235
+ shift
236
+ ;;
156
237
  *)
157
238
  ARGS+=("$1")
158
239
  shift
@@ -165,6 +246,19 @@ module Discharger
165
246
  ARGS=("-U" "postgres" "${ARGS[@]}")
166
247
  fi
167
248
 
249
+ # Drop localhost host/port flags: they target the host-mapped
250
+ # port, but inside the container Postgres listens on its default
251
+ # port. Remote hosts pass through untouched.
252
+ case "$HOST" in
253
+ ""|localhost|127.0.0.1|::1) ;;
254
+ *)
255
+ ARGS+=("-h" "$HOST")
256
+ if [[ -n "$PORT" ]]; then
257
+ ARGS+=("-p" "$PORT")
258
+ fi
259
+ ;;
260
+ esac
261
+
168
262
  if [[ -n "$INPUT_FILE" ]]; then
169
263
  docker exec -i "$CONTAINER" #{tool} "${ARGS[@]}" < "$INPUT_FILE"
170
264
  else
@@ -55,14 +55,36 @@ module Discharger
55
55
  def set_db_port(db_config)
56
56
  if db_config["port"] && !ENV["DB_PORT"]
57
57
  ENV["DB_PORT"] = db_config["port"].to_s
58
- ENV["PGPORT"] = db_config["port"].to_s
58
+ set_pgport_if_needed(db_config["port"].to_s)
59
59
  puts " Setting DB_PORT=#{db_config["port"]} from config/setup.yml"
60
60
  elsif ENV["DB_PORT"]
61
61
  warn_if_mismatch("DB_PORT", ENV["DB_PORT"], db_config["port"].to_s)
62
- ENV["PGPORT"] = ENV["DB_PORT"]
62
+ set_pgport_if_needed(ENV["DB_PORT"])
63
63
  end
64
64
  end
65
65
 
66
+ def set_pgport_if_needed(port)
67
+ ENV["PGPORT"] = port if postgresql_cli_setup_enabled?
68
+ end
69
+
70
+ def postgresql_cli_setup_enabled?
71
+ configured_pre_steps.include?("postgresql_tools") ||
72
+ configured_steps.include?("pg_tools") ||
73
+ all_regular_steps_enabled?
74
+ end
75
+
76
+ def configured_pre_steps
77
+ Array(config["pre_steps"]).select { |step| step.is_a?(String) }
78
+ end
79
+
80
+ def configured_steps
81
+ Array(config["steps"]).select { |step| step.is_a?(String) }
82
+ end
83
+
84
+ def all_regular_steps_enabled?
85
+ !config.key?("steps") || Array(config["steps"]).empty?
86
+ end
87
+
66
88
  def set_db_name(db_config)
67
89
  if db_config["name"] && !ENV["DB_NAME"]
68
90
  container_name = db_config["name"].to_s
@@ -24,6 +24,7 @@ module Discharger
24
24
  reissue.fragment = task.fragment
25
25
  reissue.clear_fragments = task.clear_fragments
26
26
  reissue.tag_pattern = task.tag_pattern
27
+ reissue.runbook_file = task.runbook_file
27
28
  end
28
29
  task.define
29
30
  task
@@ -179,6 +180,27 @@ module Discharger
179
180
  stdout.strip
180
181
  end
181
182
 
183
+ # The post-release steps collected from "Runbook:" commit trailers.
184
+ # Empty when no runbook is configured or the file has yet to be written.
185
+ def runbook_items
186
+ return [] unless runbook_file
187
+
188
+ Reissue::Runbook.new(Rails.root.join(runbook_file).to_s).items
189
+ end
190
+
191
+ # Build the Slack message listing the post-release steps for a version.
192
+ # Returns nil when the project does not use a runbook, which is distinct
193
+ # from a configured runbook that happens to have no steps this release.
194
+ def runbook_announcement(version, items = runbook_items)
195
+ return nil unless runbook_file
196
+
197
+ header = "*Post-release runbook for #{version}*"
198
+ return "#{header}\nNo runbook tasks for this release." if items.empty?
199
+
200
+ "#{header} — #{items.size} step#{"s" unless items.size == 1}\n\n" +
201
+ items.map { |item| "• #{item}" }.join("\n")
202
+ end
203
+
182
204
  def existing_pr_number(base, head)
183
205
  stdout, _, status = Open3.capture3(
184
206
  "gh", "pr", "list",
@@ -286,10 +308,17 @@ module Discharger
286
308
  ["git push origin v#{current_version}"]
287
309
  ) do
288
310
  tasker["#{name}:slack"].invoke("Released #{app_name} #{current_version} (#{commit_identifier.call}) to production.", release_message_channel, ":chipmunk:")
289
- if last_message_ts.present?
311
+ # Capture the root before replying; each post overwrites last_message_ts.
312
+ thread_ts = last_message_ts
313
+ if thread_ts.present?
290
314
  text = File.read(Rails.root.join(changelog_file))
291
315
  tasker["#{name}:slack"].reenable
292
- tasker["#{name}:slack"].invoke(text, release_message_channel, ":log:", last_message_ts)
316
+ tasker["#{name}:slack"].invoke(text, release_message_channel, ":log:", thread_ts)
317
+
318
+ if (runbook_message = runbook_announcement(current_version))
319
+ tasker["#{name}:slack"].reenable
320
+ tasker["#{name}:slack"].invoke(runbook_message, release_message_channel, ":clipboard:", thread_ts)
321
+ end
293
322
  end
294
323
  # Signal success — no branch switch needed since we stay on working_branch throughout
295
324
  true
@@ -1,3 +1,3 @@
1
1
  module Discharger
2
- VERSION = "0.3.1"
2
+ VERSION = "0.3.3"
3
3
  end
@@ -24,7 +24,7 @@ redis:
24
24
  #
25
25
  # Built-in pre_steps:
26
26
  # - homebrew # Installs Homebrew if not present
27
- # - postgresql_tools # Installs PostgreSQL client tools (pg_dump, psql)
27
+ # - postgresql_tools # Optional: installs PostgreSQL client tools for structure.sql workflows
28
28
  #
29
29
  # Custom pre_steps (run shell commands):
30
30
  # - description: "Description of step"
@@ -33,7 +33,7 @@ redis:
33
33
  #
34
34
  pre_steps: []
35
35
  # - homebrew
36
- # - postgresql_tools
36
+ # - postgresql_tools # Optional: for apps that use structure.sql or pg_dump/psql directly
37
37
 
38
38
  # Built-in commands to run (empty array runs all available commands)
39
39
  # Available commands: brew, asdf, git, bundler, yarn, config, docker, pg_tools, env, database
@@ -45,10 +45,12 @@ steps:
45
45
  - yarn
46
46
  - config
47
47
  - docker
48
- - pg_tools
49
48
  - env
50
49
  - database
51
50
 
51
+ # Optional for apps that use structure.sql or other pg_dump/psql workflows:
52
+ # - pg_tools
53
+
52
54
  # Custom commands for application-specific setup (run AFTER Rails loads)
53
55
  custom_steps:
54
56
  - description: "Seed application data"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: discharger
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.3.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jim Gay
@@ -72,14 +72,14 @@ dependencies:
72
72
  requirements:
73
73
  - - ">="
74
74
  - !ruby/object:Gem::Version
75
- version: '0'
75
+ version: '0.5'
76
76
  type: :runtime
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - ">="
81
81
  - !ruby/object:Gem::Version
82
- version: '0'
82
+ version: '0.5'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: slack-ruby-client
85
85
  requirement: !ruby/object:Gem::Requirement