-
Notifications
You must be signed in to change notification settings - Fork 103
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
Introducing Flags interface, with implementation for control server related flags #1114
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the general shape of this. Cool to see generics in action. Look forward to more tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a lot of meat here. But it feels very hard to understand.
Co-authored-by: seph <seph@kolide.co>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
... still digesting
return k.flags.DebugServerData() | ||
} | ||
|
||
func (k *knapsack) SetForceControlSubsystems(force bool) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The above funcs to retrieve KVStores are suffixed with Store
should we consider suffixing the flags with Flag
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I could do that if folks think it's more readable (or, conversely remove the Store
suffix) from the ones above. Always a balance between readability and conciseness.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a lot here, so I'm not confident I absorbed it all. But this looks like it's a huge improvement. I say we merge, and iterate based on what we learn.
Caveat -- don't merge until we cut 1.0.7, since I'm hoping we can come up with that tomorrow
return k.getKVStore(storage.ServerProvidedDataStore) | ||
} | ||
|
||
func (k *knapsack) RegisterChangeObserver(observer types.FlagsChangeObserver, flagKeys ...keys.FlagKey) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not totally sold on this -- I think exposing keys.FlagKey
feels wrong, it's abstraction breaking in a way. But I think it's fine to keep it in this PR
@@ -0,0 +1,189 @@ | |||
package flags |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we'll want to keep this, vs absorbing it into knapsack. I guess we'll see!
agentFlagsStore types.KVStore | ||
overrideMutex sync.RWMutex | ||
controlRequestOverride FlagValueOverride | ||
observers map[types.FlagsChangeObserver][]keys.FlagKey |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does observers need a mutex?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't hurt to add one - in practice I think most observers will register early and wait for notifications, so modifications to the map would be rare later on. But better to be safe.
// getControlServerValue looks for a control-server-provided value for the key and returns it. | ||
// If a control server value is not found, nil is returned. | ||
func (fc *FlagController) getControlServerValue(key keys.FlagKey) []byte { | ||
value, err := fc.agentFlagsStore.Get([]byte(key)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment and name is control server
, but the underlying store is simply agentFlagsStore
. This is a bit confusing, given the next set
function, which uses the same store, but does not use the control server name
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I can clean up the language inconsistency here.
// Extract just the keys from the key-value pairs | ||
updatedKeys := make([]string, len(kvPairs)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is fine, but FYI there's now an x/exp/maps
. (no idea how the generics work there, or if it would need casting, as said, this is fine)
} | ||
} | ||
|
||
func WithSanitizer(sanitizer func(value string) string) stringFlagValueOption { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense. Though I wonder if we should write a WithEnum
as a precanned sanitizer. Eh, wait till we need it...
|
||
// When adding a new FlagKey: | ||
// 1. Define the FlagKey identifier, and the string key value it corresponds to, in the block below | ||
// 2. Add a getter and setter to the Flags interface (flags.go) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this still correct?
desktopRunner.WithLogger(logger), | ||
desktopRunner.WithUpdateInterval(time.Second*5), | ||
desktopRunner.WithMenuRefreshInterval(time.Minute*15), | ||
desktopRunner.WithHostname(opts.KolideServerURL), | ||
desktopRunner.WithAuthToken(ulid.New()), | ||
desktopRunner.WithUsersFilesRoot(rootDirectory), | ||
desktopRunner.WithProcessSpawningEnabled(desktopProcessSpawningEnabled), | ||
desktopRunner.WithGetter(k.AgentFlagsStore()), | ||
desktopRunner.WithProcessSpawningEnabled(k.DesktopEnabled()), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think, long term, we should replace things like desktopRunner.WithProcessSpawningEnabled(k.DesktopEnabled())
with having the underlying thing just call k.DesktopEnabled()
directly.
We don't expect there to be other consumers of desktopRunner
and the functional arguments exist mostly as a style to manage the developer experience around them. I think knapsack replaces them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah that's a good point, I'll add this to a list of cleanup items. I'll check and see if there are other option style functions that can be eliminated as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it's all the long arc of using knapsack.
|
||
## Retrieving Flags | ||
|
||
```mermaid |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This diagram is different than the implementation. It's semantically the same, but the implementation calculates each value, and then adds the next overlay. This diagram implies early returns.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, I'll make this more accurate.
type Option func(*FlagController) | ||
|
||
// WithStore sets the key/value store for control data | ||
func WithCmdLineOpts(cmdLineOpts *launcher.Options) Option { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this basically required? Do you think we'll have more options? (eg: should we just add it to the method signature)
Related to #1086, and #1083
Motivation for these changes:
We have a number of launcher flags controlling various behaviors, specified through different means, and generally accessed and modified in non-standard and inconsistent ways. This PR lays the foundation for streamlining:
Knapsack
to enable disparate areas of the code base to utilize common flag functionality easily and with low developer overhead.What is in this PR:
Flags
interface: provides an easy-to-use and readable API for setting/getting launcher flags. It's been added to theKnapsack
struct, so anywhereKnapsack
is passed to, you can accessFlags
just as easily.types.Updater
interface to better separate JSON parsing from the logic of bulk-replacing the contents of theagent_flags
key-value store. This was necessary to enable clients of the interface to have better control and knowledge of what keys are being changed.FlagKey
and receive notifications when they change.Flags
implementation, notification framework, and common temporary override functionality.DesktopEnabled
,DebugServerData
,ForceControlSubsystems
,ControlServerURL
,ControlRequestInterval
,DisableControlTLS
,InsecureControlTLS