Skip to content

Commit c50a4a2

Browse files
committed
Ensure the release binary doesn't block when the primary is unavailable
If the primary is in read-only mode then the leader database in `DATABASE_URL` may be unavailable. To avoid blocking the release phase, the command exits with an error message. The error will prevent deploys and rollbacks (no change there), but Heroku will allow changes to config vars even if the release fails.
1 parent 7c548f4 commit c50a4a2

File tree

2 files changed

+21
-6
lines changed

2 files changed

+21
-6
lines changed

src/admin/migrate.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use anyhow::Error;
1+
use anyhow::{anyhow, Error};
22

33
static CATEGORIES_TOML: &'static str = include_str!("../boot/categories.toml");
44
diesel_migrations::embed_migrations!("./migrations");
@@ -8,7 +8,22 @@ diesel_migrations::embed_migrations!("./migrations");
88
pub struct Opts;
99

1010
pub fn run(_opts: Opts) -> Result<(), Error> {
11+
let config = crate::Config::default();
12+
13+
if config.db_primary_config.read_only_mode {
14+
// The service is undergoing maintenance or mitigating an outage.
15+
// Exit early to ensure we don't accidentally block the release phase.
16+
//
17+
// Heroku will update configuration variables even if the release command fails.
18+
// This will prevent deploys while in this mode, even if there are no new migrations.
19+
// https://devcenter.heroku.com/articles/release-phase#release-command-failure
20+
return Err(anyhow!(
21+
"Cannot run migrations or sync categories in read-only mode"
22+
));
23+
}
24+
1125
println!("==> migrating the database");
26+
// The primary is online, access directly via `DATABASE_URL`.
1227
let conn = crate::db::connect_now()?;
1328
embedded_migrations::run_with_output(&conn, &mut std::io::stdout())?;
1429

src/bin/crates-admin.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,17 @@ enum SubCommand {
2626
Migrate(migrate::Opts),
2727
}
2828

29-
fn main() {
29+
fn main() -> anyhow::Result<()> {
3030
let opts: Opts = Opts::parse();
3131

32-
match opts.command {
32+
Ok(match opts.command {
3333
SubCommand::DeleteCrate(opts) => delete_crate::run(opts),
3434
SubCommand::DeleteVersion(opts) => delete_version::run(opts),
3535
SubCommand::Populate(opts) => populate::run(opts),
3636
SubCommand::RenderReadmes(opts) => render_readmes::run(opts),
37-
SubCommand::TestPagerduty(opts) => test_pagerduty::run(opts).unwrap(),
37+
SubCommand::TestPagerduty(opts) => test_pagerduty::run(opts)?,
3838
SubCommand::TransferCrates(opts) => transfer_crates::run(opts),
3939
SubCommand::VerifyToken(opts) => verify_token::run(opts).unwrap(),
40-
SubCommand::Migrate(opts) => migrate::run(opts).unwrap(),
41-
}
40+
SubCommand::Migrate(opts) => migrate::run(opts)?,
41+
})
4242
}

0 commit comments

Comments
 (0)