-
Notifications
You must be signed in to change notification settings - Fork 261
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
Context Sharing POC #1855
Context Sharing POC #1855
Conversation
pkg/kn/plugin/context.go
Outdated
mockData := ` | ||
{ | ||
"service":"hello", | ||
"namespace":"default" | ||
} |
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 represents very minimal shared context between every Consumer and Producer to read/write. E.g. in a seems floating value changed on latest usage of kn
or plugins.
That's a looser contract then with predefined manifest etc.
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 could be further split into plugin specific sub-context and shared sub-context. Where plugin specific is only accessed by it's owner. In addition with read/write permission field maybe?
pkg/kn/plugin/context.go
Outdated
return ctxManager, nil | ||
} | ||
|
||
func (c *ContextDataManager) Find(key string) string { |
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.
Then defining the API as a set of access methods to retrieve and store the well-known values.
@dsimansk thanks for this initial work. I wouldn't mind to restrict ourselves to a set of fixed keys, but I'm not sure what this will buy us. Of course, the core commands themselves should check the Context for any mandatory argument that the user has not provided. These are are well known keys like "service" or "namespace". However, I would not prohibit that a plugin could add any key to the context manager, as this is not harmful as the core commands never will lookup this. However other plugins might make use of it, so that e.g. the function plugin could communicate with the event plugin. The tricky part really is, how external, non-inlined plugins can add to the context. For inline plugins, this is trivial as they just can add to a global map. For external plugins. that would need to an external call. For optimization, the core should know which plugin supports which context key, therefor the idea of the manifest that is obtained once during startup by calling every plugin that is reachable. This might be dangerous if plugins are looked up from the path and there are some dangling plugins or bogus plugins that would be called. So not really sure if this is the proper way. Alternatively we could keep a central registry remotely that contains this information in a plain file. We could fetch this once from a central place (e.g. GitHub) or fallback to a default one that we have compile into kn. This information ("which plugin supports which keys") should be quite static and don't changes very often. |
on the other hand, we also could reconsider to implement the "sharing via a local file" first, too. Not sure how far we should go. |
@rhuss updated with a building blocks to |
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 quiet sure how to allow external plugins to store any context data though. Other than by directly writting to context.json file.
|
||
// TODO: We should cautiously execute external binaries | ||
// fetchExternalManifest returns Manifest from external plugin by exec `$plugin manifest get` | ||
func fetchExternalManifest(p Plugin) *Manifest { |
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.
That's a bit tricky part. Executing binary half blind if it's desired one even?
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.
Preview of manifest flag: knative-extensions/kn-plugin-service-log@c214f72
That can be a standalone cmd. I.e. kn service log manifest
, but it kind of makes single purpose plugins grow the list of cmds. :)
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.
Indeed, the tricky part is not blindly calling each plugin in the path somehow, if possible. Of course if a plugin is in the path, it can be called anyway (maybe by accident), but chances are higher that a malicious plugin is called when it is called by itself. A simple solution could be to keep track of which plugins have been called explicitly and only can them when picking up all manifests (which might be hard to maintain, though ...)
pkg/kn/commands/service/describe.go
Outdated
c, _ := plugin.NewContextManager() | ||
serviceName = c.GetDefault()["service"] | ||
if serviceName == "" { | ||
return errors.New("'service describe' requires the service name given as single argument") | ||
} |
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.
@rhuss what would you say about using Golang's buily-in context.Context
since map[string]string
is our context now anyway? We can pass it through ExecuteContext()
function that's already part of Cobra lib.
Or shoud we be a bit more bold here and inject the params directly into args & flags?
Thanks! I hope i can review it today, if not, Iwill do it early next week. |
Codecov ReportAttention:
Additional details and impacted files@@ Coverage Diff @@
## main #1855 +/- ##
==========================================
- Coverage 79.62% 79.20% -0.43%
==========================================
Files 179 180 +1
Lines 13927 14075 +148
==========================================
+ Hits 11090 11148 +58
- Misses 2070 2155 +85
- Partials 767 772 +5
☔ View full report in Codecov by Sentry. |
03f2293
to
4e2e85f
Compare
63dded1
to
c8d0a42
Compare
@rhuss I'll be adding unit test, but it's ready for a review and feedback I believe. |
425df21
to
ec469a7
Compare
ec469a7
to
c0c511c
Compare
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.
Thank, I did a first round of review. Beside the usual bike shedding some more substantial comments inline:
- Consider cleaning the cache, too, if some external plugin is not in the list anymore.
- Only do the lookup dance and external call to a plugin if really needed. E.g. it should be lazily done, e.g. when the
service describe
does not find aservice
name provided, instead of doing it always. That would keep the current behavior and only fallback to a context lookup when needed.
|
||
defer func(ctxManager *pluginpkg.ContextDataManager) { | ||
if err := ctxManager.WriteCache(); err != nil { | ||
println("error during write") |
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 error message should contain more context (pun intended :-)
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.
also, you propbably want to write this to standard error.
@@ -182,7 +190,7 @@ func describe(w io.Writer, service *servingv1.Service, revisions []*revisionDesc | |||
return nil | |||
} | |||
|
|||
// Write out main service information. Use colors for major items. | |||
// WriteCache out main service information. Use colors for major items. |
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.
seems to be a wrong search/replace thingy. Same as in the comment below.
@@ -117,7 +117,7 @@ func setupConfig(t *testing.T, configContent string) (string, func()) { | |||
// Save old args | |||
backupArgs := os.Args | |||
|
|||
// Write out a temporary configContent file | |||
// WriteCache out a temporary configContent file |
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.
wrong comment
Path string `json:"path,omitempty"` | ||
|
||
// Plugin declares its own manifest to be included in Context Sharing feature | ||
HasManifest bool `json:"hasManifest"` |
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 flag really necessary or wouldn't it be just good enough to have the slices below, which then could be also be empty to indicate that the plugin does not support context data sharing.
func (c *ContextDataManager) FetchContextData(pluginManager *Manager) context.Context { | ||
ctx := context.Background() | ||
for _, p := range pluginManager.GetInternalPlugins() { | ||
if p.Name() == "kn func" { |
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 guess this is just a test case for the prototype and will be generalized later on ?
for _, plugin := range pluginManager.GetInternalPlugins() { | ||
manifest := &Manifest{} | ||
// For the integrity build the same name format as external plugins | ||
pluginName := "kn-" + strings.Join(plugin.CommandParts(), "-") |
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 don't remember whether we haven't added some more sophisticated name handling (e.g. like converting -
in command parts to _
before joining them), but I think we should do the same handling here.
// Add manifest to map | ||
c.Manifests[pluginName] = *manifest | ||
|
||
if manifest.HasManifest { |
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.
as said above, maybe just check if there are keys produced by a plugin, just to get rid of this boolean 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.
You could even just call populateDataKeys
as this is a noop if the slices for the keys are empty anyways.
if err != nil { | ||
return err | ||
} | ||
for _, plugin := range plugins { |
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 would be also cool, to remove plugins from the cache that are not listed anymore. So checking which keys of the c.Manifests[..] have been touched and remove at the end every entry with a key that is neither an internal or external plugin.
A corresponding client-pkg with types at least: knative/client-pkg#127. |
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 goes definitely in the write direction. See some comments inline (and the other comments I did before).
|
||
if config.GlobalConfig.ContextSharing() { | ||
if pwm, ok := plugin.(pluginpkg.PluginWithManifest); ok { | ||
data, _ := ctxManager.FetchContextData() |
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 probably should not ignore the error here. Either remove the error part of the FectContextData() signature or return the error (I hope, the second arg is an error ;-), nevertheless ignore this here has some smell)
return errors.New("'service describe' requires the service name given as single argument") | ||
} | ||
} else { | ||
serviceName = args[0] |
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 sure, but I have the feeling that his access pattern will be repeated in several commands (all that require a service name), so maybe we could extract this handling in a utility method. Usually, I wouldn't opt for this, but this looks complex enough to avoid a C&P approach.
Let's get it merged and iterate from there on. Not sure how to preserve the unresolved comments for the next update. But I think they are still kind of valid. /approve |
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: dsimansk, rhuss The full list of commands accepted by this bot can be found here. The pull request process is described here
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
/unhold |
Description
This's a bit simplified approach from original Feature Track proposal. I wonder if we should simply start with a few well known keys, that can be consumed in
kn
core + pluginsChanges
Reference
Fixes #
Release Note
/cc @rhuss