Flag Action should be called when value is set by alternative sources.
Checklist
- [x] Are you running the latest v3 release? The list of releases is here.
v3.0.0-beta1 - [x] Did you check the manual for your release? The v3 manual is here.
- [x] Did you perform a search about this feature? Here's the GitHub guide about searching.
What problem does this solve?
Fix action beeing called for all circumstances when a value is set by either a flag or alternative source. Currently the flag action is only executed when given as argument.
example:
Flags: []cli.Flag{
&cli.StringFlag{
Name: flag_log_level,
Usage: "The log level (trace, debug, info, warn, error, fatal, panic)",
Value: "info",
Sources: cli.EnvVars(flagEnv(flag_log_level)),
Action: func(ctx context.Context, cmd *cli.Command, value string) error {
log.Info("Setting log level to ", value)
level, err := log.ParseLevel(value)
if err != nil {
return err
}
log.SetLevel(level)
return nil
},
},
Solution description
When a env var is set, the action should be called.
Describe alternatives you've considered
not using in this case as it is has no consistent experience for users.
@project0 Can you set Local: true for cli.StringFlag and try ?
@dearchap indeed, setting Local true populates the value. Is this the intended behavior? If so, documenting it would be beneficial in this case.
The problem is that by default we arent running FlagAction for "non local" flags. Flags which are "non local" are available to all subcommands as well and there was a discussion as to how flag actions should be run for these kinds of flags. Local means that the flag is local only to current command and will have its flag actions run. Let me think how to proceed with this.
cc @urfave/cli
In this case suppose your flag is "non local" you could invoke it something like this
root cmd subcmd --log-level debug
we can run action for all flags(including log level ) for subcmd
but say you invoke it like this
LOG_LEVEL=debug root cmd subcmd
the log-level flag is set from environment when should the flag action be run ? or for example
LOG_LEVEL=debug root cmd --log-level info subcmd
It starts getting complicated.
I've run into this same situation. This seems to be an undocumented breaking change in behaviour between v2 and v3:
-
github.com/urfave/cli/v2Click to expand code
package main import ( "fmt" "log" "os" "github.com/urfave/cli/v2" ) func main() { app := cli.NewApp() app.Commands = []*cli.Command{ { Name: "serve", Flags: []cli.Flag{ &cli.StringFlag{ Name: "custom-var", EnvVars: []string{"CUSTOM_VAR"}, Required: false, Action: func(_ *cli.Context, s string) error { fmt.Println("CUSTOM_VAR Action") return nil }, }, }, Action: func(ctx *cli.Context) error { fmt.Println("Serve") return nil }, }, } if err := app.Run(os.Args); err != nil { log.Fatal(err) } }Output:
go run . serve CUSTOM_VAR Action Serve -
github.com/urfave/cli/v3Click to expand code
package main import ( "context" "fmt" "log" "os" "github.com/urfave/cli/v3" ) func main() { app := &cli.Command{ Name: "my app", Commands: []*cli.Command{ { Name: "serve", Flags: []cli.Flag{ &cli.StringFlag{ Name: "custom-var", Sources: cli.EnvVars("CUSTOM_VAR"), Required: false, Action: func(_ context.Context, _ *cli.Command, s string) error { fmt.Println("CUSTOM_VAR Action") return nil }, }, }, Action: func(_ context.Context, cmd *cli.Command) error { fmt.Println("Serve") return nil }, }, }, } if err := app.Run(context.Background(), os.Args); err != nil { log.Fatal(err) } }Output:
go run . serve Serve
I've also tried adding Local: true to &cli.StringFlag, but this doesn't change the behaviour:
Click to expand code
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/urfave/cli/v3"
)
func main() {
app := &cli.Command{
Name: "my app",
Commands: []*cli.Command{
{
Name: "serve",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "custom-var",
Sources: cli.EnvVars("CUSTOM_VAR"),
Local: true,
Required: false,
Action: func(_ context.Context, _ *cli.Command, s string) error {
fmt.Println("CUSTOM_VAR Action")
return nil
},
},
},
Action: func(_ context.Context, cmd *cli.Command) error {
fmt.Println("Serve")
return nil
},
},
},
}
if err := app.Run(context.Background(), os.Args); err != nil {
log.Fatal(err)
}
}
Output:
go run . serve
Serve
I have the following questions:
- Is there a reason this change of behaviour isn't documented in the v2-to-v3 migration guide?
- It was mentioned here that a workaround is to set
Local: true, why doesn't it work in the above example?
Thanks!