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

Improve error message specificity for AWS initialization errors #10146

Conversation

quodlibetor
Copy link
Contributor

Previously the purification step would fail with a message that contained just
"Unable to build connector." Which doesn't explain why we're trying to build a
connector, what kind of connector it is, or provide resolution steps.

With this, we know that it's failing to validate, it will say something like
"Access Denied" if it's not possible to access STS, etc.

Previously the purification step would fail with a message that contained just
"Unable to build connector." Which doesn't explain why we're trying to build a
connector, what kind of connector it is, or provide resolution steps.

With this, we know that it's failing to validate, it will say something like
"Access Denied" if it's not possible to access STS, etc.
@quodlibetor quodlibetor requested review from guswynn and a team January 20, 2022 16:32
Copy link
Contributor

@cjubb39 cjubb39 left a comment

Choose a reason for hiding this comment

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

lgtm modulo inline comment

let sts_client = mz_aws_util::sts::client(&config)?;
let _ = sts_client.get_caller_identity().send().await?;
let _ok = mz_aws_util::sts::client(&config)
.map_err(|e| {
Copy link
Contributor

Choose a reason for hiding this comment

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

looks like client's error return type is anyhow::Error -- do you think it'd make sense to do mz_aws_util::sts::client(&config).context("Unable to build STS client to validate AWS credentials") instead of manually mapping the error?

Not a biggie but seems a little more best practices for using anyhow?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah anyhow's causes aren't printed unless you use the alternative format specifier ({:#} instead of just {}). We currently almost always use plain display, see my other comment here, so IMO the only way we can make context work correctly is to change all our error messages to not display their causes, and instead to always use the alternate form when formatting user-visible error messages. Which would be better, but is IMO out of scope for this PR.

playground

use anyhow::{anyhow, Context};

fn err() -> anyhow::Result<()> {
    Err(anyhow!("oh no!"))
}

fn main() {
    let result = err().context("this is helpful").unwrap_err();
    println!("basic: {}", result);
    println!("expanded: {:#}", result);
}

prints:

basic: this is helpful
expanded: this is helpful: oh no!

Copy link
Contributor

Choose a reason for hiding this comment

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

Ahh TIL -- thanks for the explanation and playground link!

Copy link
Member

Choose a reason for hiding this comment

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

IMO the only way we can make context work correctly is to change all our error messages to not display their causes, and instead to always use the alternate form when formatting user-visible error messages. Which would be better, but is IMO out of scope for this PR.

Yes. We should do this! We've been putting it off for way too long. I would much rather spend a few hours fixing this once and for all than continue to accumulate tech debt like this. I'm going to work on a PR for that.

Copy link
Member

Choose a reason for hiding this comment

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

I checked, and we already correctly use the {:#} specifier when printing the cause for this error, so we can use .context here like @cjubb39 suggested.

Copy link
Member

@benesch benesch left a comment

Choose a reason for hiding this comment

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

Gotta run, but I'd like to think a little more about the right way to add the right context; the change in aws-util/src/util.rs seems a little over-specific ATM!

let connector = mz_http_proxy::hyper::connector().map_err(|e| anyhow!("{}", e))?;
pub fn connector(service: &str) -> Result<DynConnector, anyhow::Error> {
let connector = mz_http_proxy::hyper::connector()
.map_err(|e| anyhow!("Unable to build AWS {} HTTP connector: {}", service, e))?;
Copy link
Member

Choose a reason for hiding this comment

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

This seems like the wrong place to add service-specific context to me. There's nothing service-specific about this connector.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right, it's just used by the various other methods in the aws-util crate. Originally I had added this method as a map_err to all of them, but that was pretty silly duplicated code. If you'd prefer I can bring that back.

Originally all these clients were generated from a macro that interpolated the service name in error messages, this seemed like the least intrusive change that still supports good error messages.

@benesch
Copy link
Member

benesch commented Jan 21, 2022

Ok, so I looked into this more, and we can make AWS client construction infallible to match upstream. I sent #10161 for that with justification.

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.

3 participants