Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Feature post release hook #438

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

Person-93
Copy link
Contributor

@Person-93 Person-93 commented Mar 29, 2022

Here's my first crack at it. It needs some work, but I'm submitting it for feedback.

Open Questions

  • What environment variables should be set for the hook? For now, I copied the environment variables from the pre-release hook and added RELEASE_SHA.
    • Just RELEASE_TAG and DRY_RUN
  • What should happen if running git rev-parse HEAD to get the release commit fails? It should probably exit non-zero, but I see that you use specific exit codes. So the question is basically, what exit code?
    • We won't do this, we'll give the tag instead.
  • What should happen if the post-release hook exits non-zero? I think it makes sense to say that the hook is responsible for rolling back any changes it made and any changes made in the pre-release hook. Maybe cargo-release should yank the new release if it was already made?
    • Just exit with an error
  • Should the post release hook be run if the pre-release hook fails? If we consider the post-release hook to be responsible for rolling back changes, maybe it makes sense to run it even if the release was aborted?
  • Should there be a separate on-abort hook so the post-release hook isn't responsible for cleaning up an aborted release?
  • How should this be tested?
    • Dummy project and trycmd or snapbox.

Todo

  • Test
  • Update documentation
    • Add the new Config field post-release-hook to docs/reference.md
    • Document environment variables that are set
    • Document failure behaviour

@epage
Copy link
Collaborator

epage commented Mar 29, 2022

What should happen if running git rev-parse HEAD to get the release commit fails? It should probably exit non-zero, but I see that you use specific exit codes. So the question is basically, what exit code?

If the crate handles it, then its not our problem :)

Should the post release hook be run if the pre-release hook fails? If we consider the post-release hook to be responsible for rolling back changes, maybe it makes sense to run it even if the release was aborted?

Should there be a separate on-abort hook so the post-release hook isn't responsible for cleaning up an aborted release?

My thoughts

  • We shouldn't specialize behavior on pre-release failure
  • However, we could have an "abort" hook that runs if any step fails in releasing a crate
  • However, let's not worry about an abort hook until someone has a clear use case.

How should this be tested?

One option is to have a dummy project to release with hooks that print messages. We then use trycmd or snapbox to run a release and we then verify that the hook output came through.

@Person-93
Copy link
Contributor Author

What should happen if running git rev-parse HEAD to get the release commit fails? It should probably exit non-zero, but I see that you use specific exit codes. So the question is basically, what exit code?

If the crate handles it, then its not our problem :)

The way I set it up is that cargo-release sets an environment variable with the release commit. If commits are not consolidated, it could be tricky for the hook to figure out which commit it's supposed to operate on.

My thoughts

* We shouldn't specialize behavior on pre-release failure

* However, we could have an "abort" hook that runs if any step fails in releasing a crate

* However, let's not worry about an abort hook until someone has a clear use case.

Sounds good. Does this also mean that the release shouldn't be yanked on failure?

How should this be tested?

One option is to have a dummy project to release with hooks that print messages. We then use trycmd or snapbox to run a release and we then verify that the hook output came through.

Sounds good.

@epage
Copy link
Collaborator

epage commented Mar 29, 2022

The way I set it up is that cargo-release sets an environment variable with the release commit. If commits are not consolidated, it could be tricky for the hook to figure out which commit it's supposed to operate on.

What if we provide the tag instead?

Sounds good. Does this also mean that the release shouldn't be yanked on failure?

Yes, let's leave all failure policy to the script

@epage
Copy link
Collaborator

epage commented Mar 29, 2022

Oh, have you looked at how other release processes from other ecosystems deal with this? I'm assuming there is similar for node and others. We should make a list of them to regularly consult for ideas.

@Person-93
Copy link
Contributor Author

What if we provide the tag instead?

I added this function which gets the head sha. I'm not sure what to do if it fails.

Copy link
Collaborator

@epage epage left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Forgot to publish these

src/release.rs Outdated Show resolved Hide resolved
src/release.rs Outdated
Comment on lines 698 to 682
if !cmd::call_with_env(post_rel_hook, envs, cwd, false)? {
todo!("handle non-zero exit from post-release hook")
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What should happen if the post-release hook exits non-zero? I think it makes sense to say that the hook is responsible for rolling back any changes it made and any changes made in the pre-release hook. Maybe cargo-release should yank the new release if it was already made?

I think we should log the error as a warning and document it as no-fail

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should log the error as a warning and document it as no-fail

I'm not so sure about that. If that's the desired behavior, the hook can do that. I think it should be up to the hook to decide.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm torn on this one. For a high quality one, they will write it out. For what most people will probably write, the assistance could be useful.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if we let it fail and add an error message stating that the hook should convert the error to a warning if that's the desired behavior?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, the hook, if its handling errors, can decide whether to give an error exit code.

Let's go ahead and warn if the hook fails but we shouldn't be telling people what to do about the hook.

src/config.rs Show resolved Hide resolved
@Person-93
Copy link
Contributor Author

Forgot to publish these

??

@Person-93
Copy link
Contributor Author

Oh, have you looked at how other release processes from other ecosystems deal with this? I'm assuming there is similar for node and others. We should make a list of them to regularly consult for ideas.

I looked briefly at npm. They have all the pre and post lifecycle scripts. If the postpublish script fails, it'll be reported as a failure. It won't automatically undo the release, but there's a separate unpublish command and there's nothing stopping the postpublish script from running it before exiting non-zero.

@Person-93
Copy link
Contributor Author

I've made the changes we discussed and added two failing tests. The hook does not run. I tried to copy the same logic that determines when to run the pre-release hook. I'm not sure where I went wrong.

1 similar comment
@Person-93
Copy link
Contributor Author

I've made the changes we discussed and added two failing tests. The hook does not run. I tried to copy the same logic that determines when to run the pre-release hook. I'm not sure where I went wrong.

// STEP 7: git push
// STEP 7: post-release hook
for pkg in pkgs {
if let (Some(post_rel_hook), Some(_)) =
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@epage, I think this if statement isn't checking the right thing. I don't know what it should be checking. Can you advise?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tests are not doing a version bump and most of actions are only on version bump.

let post_rel_hook = post_rel_hook.args();
log::debug!("Calling post-release hook: {:?}", post_rel_hook);
let envs = maplit::btreemap! {
OsStr::new("RELEASE_TAG") => OsStr::new(pkg.tag.as_ref().expect("post-release hook with no tag")),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

log::debug!("Calling post-release hook: {:?}", post_rel_hook);
let envs = maplit::btreemap! {
OsStr::new("RELEASE_TAG") => OsStr::new(pkg.tag.as_ref().expect("post-release hook with no tag")),
OsStr::new("DRY_RUN") => OsStr::new(if dry_run { "true" } else { "false" }),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How come a lot of the pre-release variables were removed?

@@ -0,0 +1,132 @@
use assert_fs::prelude::*;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer this to be tests/post-release-hook.rs

@@ -0,0 +1 @@
../crate/.gitignore
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please store all of these in the test/fixtures directory

}

fn git(dir: &Path, args: &[&str]) {
assert!(Command::new("git")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

snapbox::cmd::Command might be a good option

Comment on lines +55 to +86
fn run_cargo_release(dir: &Path) -> String {
let temp = TempDir::new().unwrap();
temp.copy_from(dir, &["**"]).unwrap();

git(temp.path(), &["init"]);
git(temp.path(), &["add", "."]);
git(
temp.path(),
&["commit", "--message", "this is a commit message"],
);

let mut cargo = env::var_os("CARGO").map_or_else(|| Command::new("cargo"), Command::new);
let output = cargo
.stderr(Stdio::piped())
.args(["run", "--manifest-path"])
.arg(Path::new(PROJECT_ROOT).join("Cargo.toml"))
.args(["--", "release", "-vv"])
.current_dir(&temp)
.spawn()
.unwrap()
.wait_with_output()
.unwrap();

temp.close().unwrap();

if !output.status.success() {
panic!("cargo release exited with {}", output.status);
}
let output = String::from_utf8(output.stderr).unwrap();
eprintln!("{output}");
output
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use snapbox::cmd::Command and snapbox::cmd::cargo_bin!

Comment on lines +55 to +78
fn run_cargo_release(dir: &Path) -> String {
let temp = TempDir::new().unwrap();
temp.copy_from(dir, &["**"]).unwrap();

git(temp.path(), &["init"]);
git(temp.path(), &["add", "."]);
git(
temp.path(),
&["commit", "--message", "this is a commit message"],
);

let mut cargo = env::var_os("CARGO").map_or_else(|| Command::new("cargo"), Command::new);
let output = cargo
.stderr(Stdio::piped())
.args(["run", "--manifest-path"])
.arg(Path::new(PROJECT_ROOT).join("Cargo.toml"))
.args(["--", "release", "-vv"])
.current_dir(&temp)
.spawn()
.unwrap()
.wait_with_output()
.unwrap();

temp.close().unwrap();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you have reusable fixture initialization, I'd recommend splitting that out into a dedicated function and have each test call cargo-release in its own way. This will make it easier to add new test cases in the future


fn main() {
eprintln!("START_ENV_VARS");
for (key, value) in env::vars_os() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we ensure these are sorted, we can use snapboxs built-in snapshot testing

We'd just define the following and snapbox's asserter will take the ... and treat it as a multi-line wildcard.

START_ENV_VARS
...
DRY_RUN=true
...
RELEASE_TAG=post-release-hook-ws-a-v42.42.42
...
END_ENV_VARS

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants