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

feat: Allow Kong to exit with semantic exit codes #507

Merged
merged 3 commits into from
Mar 11, 2025

Conversation

boblail
Copy link
Contributor

@boblail boblail commented Mar 7, 2025

feat: Allow Kong to exit with semantic exit codes

At Block, we've instrumented a number of commandline tools and set SLOs on some tools' reliability. To do that effectively, we had to partition usage errors from reliability issues. We looked at prior art and, taking inspiration from HTTP, defined a set of semantic exit codes in ranges: 80-99 for user errors, 100-119 for system errors.

We've been wrapping errors in exit.Error at whatever level of the stack can tell which class an error is and unwrapping them at exit (os.Exit(exit.FromError(err))).

This adds support for semantic exit codes to Kong, to FatalIfErrorf, which is used internally by kong.Parse and often used in Kong applications.

It also exits 80 (usage error) instead of 1 (generic error) when Kong parses a syntactically or structurally invalid command line.

Copy link
Owner

@alecthomas alecthomas left a comment

Choose a reason for hiding this comment

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

I don't mind the idea of errors being able to override exit codes, but the idiomatic way to do that would be having the error itself implement an interface, eg.

type ExitCode interface {
  ExitCode() int
}

var exitCode ExitCode
if errors.As(err, &exitCode) {
  os.Exit(exitCode.ExitCode())
}

This would allow Kong to opt in to error codes without pulling in an external dependency.

@boblail
Copy link
Contributor Author

boblail commented Mar 7, 2025

@alecthomas, makes sense, thanks!

I've updated this PR (and square/exit) accordingly.

Copy link
Owner

@alecthomas alecthomas left a comment

Choose a reason for hiding this comment

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

This functionality should also be documented in the Go docs/README, perhaps by making the interface public.

kong.go Outdated
return nil, err
}
if ctx.Error != nil {
return nil, &ParseError{error: ctx.Error, Context: ctx}
return nil, exitAsUsageError(&ParseError{error: ctx.Error, Context: ctx})
Copy link
Owner

Choose a reason for hiding this comment

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

Maybe ParseError could just implement ExitCode() int { return 80 }?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

At first, I wrapped all the ParseErrors but then I realized that the calls to k.applyHook invoked code outside of Kong — which could presumably fail for any reason, right?

But as I read Kong's code, it looked like the only reasons that Trace, Reset, Resolve, Apply, and Validate would fail would be malformed args or environment variables or config files it was trying to slurp in. Does that sound right?

Copy link
Owner

Choose a reason for hiding this comment

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

That's correct. Only hooks or Run() should execute code outside of Kong's control.

Copy link
Owner

Choose a reason for hiding this comment

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

Is that relevant to my comment though?

Copy link
Contributor Author

@boblail boblail Mar 8, 2025

Choose a reason for hiding this comment

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

Oh, yes! b/c Parse wraps user code in ParseError so not every ParseError is necessarily a usage error?

Copy link
Owner

Choose a reason for hiding this comment

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

Well, I think for those cases we shouldn't wrap in ParseError

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Gotcha!

I've added a commit to make that change: 2a7471f

  • ParseError implements ExitCode() and returns 80
  • Errors returned from k.applyHook are not wrapped in ParseError

This makes sense to me. Do you consider it a breaking change if the error type changes?

Copy link
Owner

Choose a reason for hiding this comment

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

Let's merge this in, then I'll have a think about it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks!

@boblail boblail force-pushed the lail/semantic-exit-codes branch from 89f99d1 to dd27ef0 Compare March 7, 2025 23:50
@boblail
Copy link
Contributor Author

boblail commented Mar 7, 2025

This functionality should also be documented in the Go docs/README, perhaps by making the interface public.

I made the interface public, gave it a docstring, and referenced it in FatalIfErrorf's doc string.

Do you think it warrants a mention in the README in addition?

boblail added 3 commits March 10, 2025 09:12
At Block, we've instrumented a number of commandline tools and set SLOs on some tools' reliability. To do that effectively, we had to partition usage errors from reliability issues. We looked at [prior art](https://github.com/square/exit?tab=readme-ov-file#reserved-codes-and-prior-art) and, taking inspiration from HTTP, defined [a set of semantic exit codes](https://github.com/square/exit?tab=readme-ov-file#about) in ranges: 80-99 for user errors, 100-119 for system errors.

We've been wrapping errors in `exit.Error` at whatever level of the stack can tell which class an error is and unwrapping them at exit (`os.Exit(exit.FromError(err))`).

This adds support for semantic exit codes to Kong, to `FatalIfErrorf`, which is used internally by `kong.Parse` and often used in Kong applications.
…ap errors from hooks in `ParseError`
@alecthomas alecthomas merged commit a86adbb into alecthomas:master Mar 11, 2025
5 checks passed
wsutina pushed a commit to cashapp/hermit that referenced this pull request Mar 12, 2025

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
This pull request has 3 commits. Kong v0.9.0 has a couple of breaking
changes for `hermit`, so:
1. The first commit updates from Kong 0.5.0 → 0.8.1 safely
2. The second commit updates to Kong 0.9.0 and sorts out the changes
3. The third commit updates from Kong 0.9.0 → 1.9.0 safely

- [x] (Ideally I'd merge alecthomas/kong#507 and
then bump this to the next version)

fixes #444
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