-
Notifications
You must be signed in to change notification settings - Fork 2.9k
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
How can I pass context.Context between pre/post-run hooks? #563
Comments
The way I've seen this done is to always wrap the func newXCommand(context CustomContext) *cobra.Command {
return &cobra.Command{
...
}
} In |
What do you think about offering a Context version of the lifecycle functions (Pre / Post) with a const (
Response = iota
Note
)
// preCmd always makes an HTTP request, which all of its children want to use
var preCmd = &cobra.Command {
// ...
PersistentPreRunCtx: func(cmd *cobra.Command) context.Context {
rsp, _ := http.Get("https://google.com")
return context.WithValue(context.WithTimeout(
// Response Body must be read within 5 seconds
cmd.Context(), 5*time.Second,
), Response, rsp.Body)
},
}
// preCmd is the parent of storeCmd
var storeCmd = &cobra.Command {
// ...
PreRun: func(cmd *cobra.Command) context.Context {
return context.WithValue(cmd.Context(), Note, "hi there")
},
RunCtx: func(cmd *cobra.Command) context.Context {
rc := cmd.Context().Value(Response).(io.ReadCloser)
// db.WriteAll(uuid.NewV4(), rc)
},
} Each lifecycle hook, if it returns a Context, could be threaded with something like: func ThreadCtx(cmd *Command, with context.Context, fs ...func(cmd *Command) context.Context) {
c := with
for _, f := range fs {
select {
case <-c.Done():
// fail gracefully
case err := <-c.Err():
// fail less gracefully
default:
// thread Context through next Command
}
if c = f(cmd); c == nil { continue } else { cmd.ctx = c }
}
} |
The problem with this approach, is that it requires a mutable context variable, which violates one of the core principles of the 'context' package. This can be done, if you're willing to hack your context beyond recognition, and well outside of best practices and concurrency safety (which may not matter in many CLI apps, anyway). To illustrate, suppose you want your If you follow normal To compensate, you must either pass a pointer to |
I think the only way to make this work properly would be to add ctx versions of the various
(In lieu of the ctx argument passed to each above function, Then cobra would take the
Logically, something like:
etc, etc I wonder if anyone else has thoughts about this? Are there implications I've failed to consider? Would a PR be welcome to add this functionality, or has a decision explicitly been made not to support context? |
Another approach, which might seem a bit more dangerous on the surface, but would make for a much smaller change to cobra, and mimics the standard library's http package, would be simply to add two methods to
This differs slightly from http.Request's approach, in that It feels a bit icky to me to be replacing a context like that, but I haven't yet imagined a scenario where it would actually break things. |
Or provide a new object like |
More than 2 years since this issue has been reported: users show interest, several solutions have been proposed and yet the problem remains fixed... Is there a point in keeping it open, aside from frustrating even more people coming here from a Google search? |
You keep issues open b/c it's not fixed. That said there is a push to make a 1.0 release, and with that some minimal changes have been merged recently, including support for context: #893 So this issue can be closed, I think. |
This issue is being marked as stale due to a long period of inactivity |
I just wanted to add a vote here. Cobra is great for the most part but it is definitely missing a way for us to provide cross cutting pieces of behavior such as a timeout/deadline or a configuration object we loaded from a file. Currently, it sort of force us to define globals (or singletons) that will then be used from within the commands, or to repeat the same logic on every command. |
@c4milo Support for context is there now. |
@cpuguy83, it is a bit limited, for instance, I want to receive a timeout value from a flag and set it in the context but i can't do that because the context gets set earlier than flag evaluation. And, there is no way to re-set that context in Cobra. |
@c4milo I'm not sure I understand, since you don't need to have a timeout for all commands, just the running command. |
I need the timeout in all commands, which means having to duplicate that code for every one of them. The same is true for any piece of shared code like creating gRPC clients, setting up logging. The alternative, as I said, is defining them as global variables in some package. If I could overwrite or re-set |
I'm not the only one struggling through this BTW: https://news.ycombinator.com/item?id=15717715 |
That's a post from 2017. I'm not arguing that exactly thing you are trying to do can't be done with cobra. It can't. |
Exactly.
and I'm telling you that I will have to do that for a 100 subcommands and duplicate code. |
My point is what is the difference between calling "cmd.Context()" and "GetContext(cmd)" in terms of duplication? I am not a maintainer here, I'm not saying one shouldn't be able to set contexts on the cmd, just someone who knows how difficult it was to get context support in to begin with (with setting one being contentious) offering up an alternative. |
the difference is that instead of writing this once:
I'm going to have to write it 100 times. It would be more useful if the context can be set after flags are parsed. It is basically the same that is being asked here: #893 (comment) |
@c4milo But that's not true.
|
|
This has been addressed with #1551 which adds a Thanks for all the great discussion and we hope this will prove helpful. |
Basically the same as spf13/cobra#1517 but uses the `Set<field>` naming convention instead of `WithContext` Context setting without execution is important because it means that more design patterns can be achieved. Currently I am using functional options in a project and I can add behaviour through functional options as such: ```go type GrptlOption func(*cobra.Command) error func WithFileDescriptors(descriptors ...protoreflect.FileDescriptor) GrptlOption { return func(cmd *cobra.Command) error { err := CommandFromFileDescriptors(cmd, descriptors...) if err != nil { return err } return nil } } ``` I've got a lot more options and this pattern allows me to have nice abstracted pieces of logic that interact with the cobra command. This Pattern also allows for adding extra information to a call through `PreRun` functions: ```go cmd := &cobra.Command{ Use: "Foobar", PersistentPreRun: func(cmd *cobra.Command, args []string) { err :=cmd.SetContext(metadata.AppendToOutgoingContext(context.Background(), "pre", "run")) }, } ``` This is a veer nice abstraction and allows for these functions to be very modular The issue I'm facing at the moment is that I can't write a nifty option (something like `WithAuthentication`) because that needs access to reading and setting the context. Currently I can only read the context. Needing to use `ExecuteContext` breaks this abstraction because I need to run it right at the end. Merge spf13/cobra#1551 Fixes spf13/cobra#1517 Fixes spf13/cobra#1118 Fixes spf13/cobra#563
In my pre-run hook, I'd like to set a value (someplace; maybe in the Command?) and have access to it in my
Run
. This seems like a good use-case forcontext.Context
. Is this supported somehow? I was thinking of setting a Flag value, but that seems awfully hackish, and I really don't want to use a global variable.The text was updated successfully, but these errors were encountered: