Skip to content

Commit a805880

Browse files
authored
Add a way to get info about how flags were configured (#11282)
I've wanted to add this in a few cases in the past, but have avoided it because I've been able to find workarounds. Recently though I ran into a use case where I can't think of a good workaround (mutually exclusive flag groups where zero-value checks aren't sufficient). So I think maybe it's time to add this?
1 parent 9c0506c commit a805880

File tree

3 files changed

+100
-0
lines changed

3 files changed

+100
-0
lines changed

server/util/flag/flag.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ var Deprecated = flagtags.DeprecatedTag
1818
var YAMLIgnore = flagtags.YAMLIgnoreTag
1919
var Internal = flagtags.InternalTag
2020

21+
type Meta = flagtags.MetaTag
22+
2123
var NewFlagSet = flag.NewFlagSet
2224
var ContinueOnError = flag.ContinueOnError
2325
var ExitOnError = flag.ExitOnError

server/util/flagutil/types/autoflags/tags/tags.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,3 +389,52 @@ func (_ *yamlIgnoreTag) Tag(flagset *flag.FlagSet, name string, f Tagged) flag.V
389389
}
390390

391391
var YAMLIgnoreTag = &yamlIgnoreTag{}
392+
393+
// MetaTag allows tracking additional metadata about a flag. It's useful for
394+
// checking whether a flag was explicitly configured.
395+
//
396+
// Example: enforce mutually exclusive flag configuration in cases where
397+
// zero-value checks aren't possible (e.g. booleans):
398+
//
399+
// var (
400+
// compressMeta flag.Meta // Info about --compress flag configuration.
401+
// compress = flag.Bool("compress", true, "Enable compression.", &compressMeta)
402+
// resourceName = flag.String("resource_name", "", "Blob resource name.")
403+
// )
404+
//
405+
// // --resource_name and --compress flags should be mutually exclusive, since
406+
// // the resource name specifies the compression level within the string.
407+
// if *resourceName != "" && compressMeta.IsConfigured() {
408+
// log.Fatalf("Cannot specify both 'compress' and 'resource_name' options.")
409+
// }
410+
type MetaTag struct {
411+
isSetInCommandLine bool
412+
isSetInYAML bool
413+
isSetProgrammatically bool
414+
}
415+
416+
func (m *MetaTag) Tag(flagset *flag.FlagSet, name string, tagged Tagged) flag.Value {
417+
tagged.DesignateSetFunc(func(value string) error {
418+
m.isSetInCommandLine = true
419+
return tagged.Value().Set(value)
420+
})
421+
tagged.DesignateYAMLSetValueHookFunc(func() {
422+
m.isSetInYAML = true
423+
})
424+
tagged.DesignateSetValueForFlagNameHookFunc(func() {
425+
m.isSetProgrammatically = true
426+
})
427+
return tagged
428+
}
429+
430+
// IsConfigured returns true if the flag has been explicitly configured via the
431+
// flag API: either via the command line, via YAML, or via the
432+
// "SetValueForFlagName" API.
433+
//
434+
// Note: It does not return true if the flag was set via direct assignment (e.g.
435+
// *fooFlag = "bar").
436+
func (m *MetaTag) IsConfigured() bool {
437+
// isSetProgrammatically is checked here because in tests we set flags
438+
// programmatically to simulate flags being intentionally enabled by users.
439+
return m.isSetInCommandLine || m.isSetInYAML || m.isSetProgrammatically
440+
}

server/util/flagutil/types/autoflags/tags/tags_test.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,3 +304,52 @@ func TestYAMLIgnoreAndSecret(t *testing.T) {
304304
assert.Equal(t, "default", *v1)
305305
assert.Equal(t, "default2", *v2)
306306
}
307+
308+
func TestMeta_SetProgrammatically(t *testing.T) {
309+
flagset := replaceFlagsForTesting(t)
310+
311+
var meta MetaTag
312+
v := flagset.String("testflag", "test-default-value", "Test flag help")
313+
Tag[string, flag.Value](flagset, "testflag", &meta)
314+
315+
require.False(t, meta.IsConfigured(), "flag should not yet be configured")
316+
317+
require.NoError(t, common.SetValueForFlagName(flagset, "testflag", "test-value", nil, true))
318+
require.Equal(t, "test-value", *v)
319+
320+
require.True(t, meta.IsConfigured(), "flag should now be configured")
321+
}
322+
323+
func TestMeta_SetInCommandLine(t *testing.T) {
324+
flagset := replaceFlagsForTesting(t)
325+
326+
var meta MetaTag
327+
v := flagset.String("testflag", "test-default-value", "Test flag help")
328+
Tag[string, flag.Value](flagset, "testflag", &meta)
329+
330+
require.False(t, meta.IsConfigured(), "flag should not yet be configured")
331+
332+
require.NoError(t, flagset.Parse([]string{"--testflag=test-value"}))
333+
require.Equal(t, "test-value", *v)
334+
335+
require.True(t, meta.IsConfigured(), "flag should now be configured")
336+
}
337+
338+
func TestMeta_SetInYAML(t *testing.T) {
339+
flagset := replaceFlagsForTesting(t)
340+
_ = replaceIgnoreSetForTesting(t)
341+
342+
var meta MetaTag
343+
v := flagset.String("testflag", "test-default-value", "Test flag help")
344+
Tag[string, flag.Value](flagset, "testflag", &meta)
345+
346+
require.False(t, meta.IsConfigured(), "flag should not yet be configured")
347+
348+
yamlData := `
349+
testflag: test-value
350+
`
351+
require.NoError(t, flagyaml.PopulateFlagsFromData(yamlData))
352+
require.Equal(t, "test-value", *v)
353+
354+
require.True(t, meta.IsConfigured(), "flag should now be configured")
355+
}

0 commit comments

Comments
 (0)