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 +4 -4
- data/CHANGELOG.md +10 -5
- data/README.md +48 -2
- data/lib/discharger/setup_runner/commands/brew_command.rb +20 -1
- data/lib/discharger/setup_runner/commands/pg_tools_command.rb +94 -0
- data/lib/discharger/setup_runner/prerequisites_loader.rb +24 -2
- data/lib/discharger/task.rb +31 -2
- data/lib/discharger/version.rb +1 -1
- data/lib/generators/discharger/install/templates/setup.yml +5 -3
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 22ff9b0cc383ea213a6a8766f713ef4afa97a114c679c76b37954f73872b74e3
|
|
4
|
+
data.tar.gz: a3cfa43c5c9665704e13bce7a3bb19e36ccd4810b700b149a2646815fcca9f6a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|
|
8
|
+
## [0.3.3] - 2026-07-22
|
|
9
9
|
|
|
10
10
|
### Added
|
|
11
11
|
|
|
12
|
-
-
|
|
12
|
+
- Post-release runbook announcement in the Slack release thread, from Reissue's runbook_file (0cf9620)
|
|
13
13
|
|
|
14
|
-
## [0.2
|
|
14
|
+
## [0.3.2] - 2026-07-15
|
|
15
15
|
|
|
16
|
-
###
|
|
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
|
-
-
|
|
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` -
|
|
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
|
-
|
|
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
|
-
|
|
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["
|
|
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
|
data/lib/discharger/task.rb
CHANGED
|
@@ -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
|
-
|
|
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:",
|
|
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
|
data/lib/discharger/version.rb
CHANGED
|
@@ -24,7 +24,7 @@ redis:
|
|
|
24
24
|
#
|
|
25
25
|
# Built-in pre_steps:
|
|
26
26
|
# - homebrew # Installs Homebrew if not present
|
|
27
|
-
# - postgresql_tools #
|
|
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.
|
|
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
|