mirror of https://github.com/knative/client.git
Context Sharing POC (#1855)
* WIP: Context Sharing POC * More lazy init * Fix imports
This commit is contained in:
parent
c318a2224c
commit
b658574638
|
|
@ -23,7 +23,7 @@ import (
|
|||
|
||||
"github.com/spf13/cobra"
|
||||
"knative.dev/client/pkg/kn/config"
|
||||
"knative.dev/client/pkg/kn/plugin"
|
||||
pluginpkg "knative.dev/client/pkg/kn/plugin"
|
||||
"knative.dev/client/pkg/kn/root"
|
||||
)
|
||||
|
||||
|
|
@ -60,7 +60,7 @@ func run(args []string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
pluginManager := plugin.NewManager(config.GlobalConfig.PluginsDir(), config.GlobalConfig.LookupPluginsInPath())
|
||||
pluginManager := pluginpkg.NewManager(config.GlobalConfig.PluginsDir(), config.GlobalConfig.LookupPluginsInPath())
|
||||
|
||||
// Create kn root command and all sub-commands
|
||||
rootCmd, err := root.NewRootCommand(pluginManager.HelpTemplateFuncs())
|
||||
|
|
@ -84,13 +84,37 @@ func run(args []string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// FT: Context Sharing
|
||||
var ctxManager *pluginpkg.ContextDataManager
|
||||
if config.GlobalConfig.ContextSharing() {
|
||||
ctxManager, err = pluginpkg.NewContextManager(pluginManager)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func(ctxManager *pluginpkg.ContextDataManager) {
|
||||
if err := ctxManager.WriteCache(); err != nil {
|
||||
println("error during write")
|
||||
}
|
||||
}(ctxManager)
|
||||
}
|
||||
|
||||
if plugin != nil {
|
||||
// Validate & Execute plugin
|
||||
err = validatePlugin(rootCmd, plugin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if config.GlobalConfig.ContextSharing() {
|
||||
if pwm, ok := plugin.(pluginpkg.PluginWithManifest); ok {
|
||||
data, _ := ctxManager.FetchContextData()
|
||||
err := pwm.ExecuteWithContext(data, argsWithoutCommands(args, plugin.CommandParts()))
|
||||
if err != nil {
|
||||
return &runError{err: err}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
err := plugin.Execute(argsWithoutCommands(args, plugin.CommandParts()))
|
||||
if err != nil {
|
||||
return &runError{err: err}
|
||||
|
|
@ -140,7 +164,7 @@ func filterHelpOptions(args []string) []string {
|
|||
}
|
||||
|
||||
// Check if the plugin collides with any command specified in the root command
|
||||
func validatePlugin(root *cobra.Command, plugin plugin.Plugin) error {
|
||||
func validatePlugin(root *cobra.Command, plugin pluginpkg.Plugin) error {
|
||||
// Check if a given plugin can be identified as a command
|
||||
cmd, args, err := root.Find(plugin.CommandParts())
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"knative.dev/client/pkg/kn/plugin"
|
||||
pluginpkg "knative.dev/client/pkg/kn/plugin"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"gotest.tools/v3/assert"
|
||||
|
|
@ -361,7 +361,7 @@ func TestRun(t *testing.T) {
|
|||
args []string
|
||||
expectedOut []string
|
||||
expectedErrOut []string
|
||||
plugin plugin.Plugin
|
||||
plugin pluginpkg.Plugin
|
||||
}{
|
||||
{
|
||||
[]string{"kn", "version"},
|
||||
|
|
@ -425,8 +425,8 @@ func TestRun(t *testing.T) {
|
|||
for _, tc := range testCases {
|
||||
os.Args = tc.args
|
||||
if tc.plugin != nil {
|
||||
plugin.InternalPlugins = plugin.PluginList{}
|
||||
plugin.InternalPlugins = append(plugin.InternalPlugins, tc.plugin)
|
||||
pluginpkg.InternalPlugins = pluginpkg.PluginList{}
|
||||
pluginpkg.InternalPlugins = append(pluginpkg.InternalPlugins, tc.plugin)
|
||||
}
|
||||
capture := test.CaptureOutput(t)
|
||||
err := run(tc.args[1:])
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import (
|
|||
"knative.dev/serving/pkg/apis/serving"
|
||||
|
||||
"knative.dev/client/pkg/kn/commands/revision"
|
||||
"knative.dev/client/pkg/kn/plugin"
|
||||
"knative.dev/client/pkg/printers"
|
||||
clientservingv1 "knative.dev/client/pkg/serving/v1"
|
||||
|
||||
|
|
@ -99,10 +100,21 @@ func NewServiceDescribeCommand(p *commands.KnParams) *cobra.Command {
|
|||
Example: describe_example,
|
||||
ValidArgsFunction: commands.ResourceNameCompletionFunc(p),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
var serviceName string
|
||||
if len(args) != 1 {
|
||||
if plugin.CtxManager != nil {
|
||||
data, err := plugin.CtxManager.FetchContextData()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
serviceName = data["service"]
|
||||
}
|
||||
if serviceName == "" {
|
||||
return errors.New("'service describe' requires the service name given as single argument")
|
||||
}
|
||||
serviceName := args[0]
|
||||
} else {
|
||||
serviceName = args[0]
|
||||
}
|
||||
|
||||
namespace, err := p.GetNamespace(cmd)
|
||||
if err != nil {
|
||||
|
|
@ -182,7 +194,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.
|
||||
func writeService(dw printers.PrefixWriter, service *servingv1.Service) {
|
||||
commands.WriteMetadata(dw, &service.ObjectMeta, printDetails)
|
||||
dw.WriteAttribute("URL", extractURL(service))
|
||||
|
|
@ -200,7 +212,7 @@ func writeService(dw printers.PrefixWriter, service *servingv1.Service) {
|
|||
}
|
||||
}
|
||||
|
||||
// Write out revisions associated with this service. By default only active
|
||||
// WriteCache out revisions associated with this service. By default only active
|
||||
// target revisions are printed, but with --verbose also inactive revisions
|
||||
// created by this services are shown
|
||||
func writeRevisions(dw printers.PrefixWriter, revisions []*revisionDesc, printDetails bool) {
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ const configContentDefaults = `# Taken from https://github.com/knative/client/bl
|
|||
|
||||
// config contains the variables for the Kn config
|
||||
type config struct {
|
||||
|
||||
// configFile is the config file location
|
||||
configFile string
|
||||
|
||||
|
|
@ -65,6 +66,10 @@ type config struct {
|
|||
channelTypeMappings []ChannelTypeMapping
|
||||
}
|
||||
|
||||
func (c *config) ContextSharing() bool {
|
||||
return viper.GetBool(keyFeaturesContextSharing)
|
||||
}
|
||||
|
||||
// ConfigFile returns the config file which is either the default XDG conform
|
||||
// config file location or the one set with --config
|
||||
func (c *config) ConfigFile() string {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
var cfgFile string
|
||||
if configContent != "" {
|
||||
cfgFile = filepath.Join(tmpDir, "config.yaml")
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ package config
|
|||
// Set an instance of this for config.GlobalConfig to mock
|
||||
// your own configuration setup
|
||||
type TestConfig struct {
|
||||
TestContextSharing bool
|
||||
TestPluginsDir string
|
||||
TestConfigFile string
|
||||
TestLookupPluginsInPath bool
|
||||
|
|
@ -28,6 +29,7 @@ type TestConfig struct {
|
|||
// Ensure that TestConfig implements the configuration interface
|
||||
var _ Config = &TestConfig{}
|
||||
|
||||
func (t TestConfig) ContextSharing() bool { return t.TestContextSharing }
|
||||
func (t TestConfig) PluginsDir() string { return t.TestPluginsDir }
|
||||
func (t TestConfig) ConfigFile() string { return t.TestConfigFile }
|
||||
func (t TestConfig) LookupPluginsInPath() bool { return t.TestLookupPluginsInPath }
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import (
|
|||
// Test to keep code coverage quality gate happy.
|
||||
func TestTestConfig(t *testing.T) {
|
||||
cfg := TestConfig{
|
||||
TestContextSharing: false,
|
||||
TestPluginsDir: "pluginsDir",
|
||||
TestConfigFile: "configFile",
|
||||
TestLookupPluginsInPath: true,
|
||||
|
|
@ -30,6 +31,7 @@ func TestTestConfig(t *testing.T) {
|
|||
TestChannelTypeMappings: nil,
|
||||
}
|
||||
|
||||
assert.Equal(t, cfg.ContextSharing(), false)
|
||||
assert.Equal(t, cfg.PluginsDir(), "pluginsDir")
|
||||
assert.Equal(t, cfg.ConfigFile(), "configFile")
|
||||
assert.Assert(t, cfg.LookupPluginsInPath())
|
||||
|
|
|
|||
|
|
@ -19,6 +19,9 @@ package config
|
|||
|
||||
type Config interface {
|
||||
|
||||
// ContextSharing represents feature flag enabling context sharing
|
||||
ContextSharing() bool
|
||||
|
||||
// ConfigFile returns the location of the configuration file
|
||||
ConfigFile() string
|
||||
|
||||
|
|
@ -70,6 +73,7 @@ type ChannelTypeMapping struct {
|
|||
|
||||
// config Keys for looking up in viper
|
||||
const (
|
||||
keyFeaturesContextSharing = "features.context-sharing"
|
||||
keyPluginsDirectory = "plugins.directory"
|
||||
keySinkMappings = "eventing.sink-mappings"
|
||||
keyChannelTypeMappings = "eventing.channel-type-mappings"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,232 @@
|
|||
// Copyright © 2023 The Knative Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io/fs"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"knative.dev/client/pkg/kn/config"
|
||||
)
|
||||
|
||||
//--TYPES--
|
||||
|
||||
// Manifest represents plugin metadata
|
||||
type Manifest struct {
|
||||
// Path to external plugin binary. Always empty for inlined plugins.
|
||||
Path string `json:"path,omitempty"`
|
||||
|
||||
// Plugin declares its own manifest to be included in Context Sharing feature
|
||||
HasManifest bool `json:"hasManifest"`
|
||||
|
||||
// ProducesContextDataKeys is a list of keys for the ContextData that
|
||||
// a plugin can produce. Nil or an empty list declares that this
|
||||
// plugin is not ContextDataProducer
|
||||
ProducesContextDataKeys []string `json:"producesKeys,omitempty"`
|
||||
|
||||
// ConsumesContextDataKeys is a list of keys from a ContextData that a
|
||||
// plugin is interested in to consume. Nil or an empty list declares
|
||||
// that this plugin is not a ContextDataConsumer
|
||||
ConsumesContextDataKeys []string `json:"consumesKeys,omitempty"`
|
||||
}
|
||||
|
||||
// PluginWithManifest represents extended plugin support for Manifest and Context Sharing feature
|
||||
type PluginWithManifest interface {
|
||||
// Plugin original interface wrapper
|
||||
Plugin
|
||||
|
||||
// GetManifest
|
||||
GetManifest() *Manifest
|
||||
|
||||
// GetContextData
|
||||
GetContextData() map[string]string
|
||||
|
||||
// ExecuteWithContext
|
||||
ExecuteWithContext(ctx map[string]string, args []string) error
|
||||
}
|
||||
|
||||
//--TYPES--
|
||||
|
||||
var CtxManager *ContextDataManager
|
||||
|
||||
type ContextDataManager struct {
|
||||
//ContextData map[string]ContextData `json:"-"`
|
||||
PluginManager *Manager `json:"-"`
|
||||
Producers map[string][]string `json:"producers"`
|
||||
Consumers map[string][]string `json:"consumers"`
|
||||
Manifests map[string]Manifest `json:"manifests"`
|
||||
}
|
||||
|
||||
func NewContextManager(pluginManager *Manager) (*ContextDataManager, error) {
|
||||
if CtxManager == nil {
|
||||
CtxManager = &ContextDataManager{
|
||||
PluginManager: pluginManager,
|
||||
Producers: map[string][]string{},
|
||||
Consumers: map[string][]string{},
|
||||
Manifests: map[string]Manifest{},
|
||||
}
|
||||
}
|
||||
return CtxManager, nil
|
||||
}
|
||||
|
||||
// GetConsumesKeys returns array of keys consumed by plugin
|
||||
func (c *ContextDataManager) GetConsumesKeys(pluginName string) []string {
|
||||
return c.Manifests[pluginName].ConsumesContextDataKeys
|
||||
}
|
||||
|
||||
// GetProducesKeys returns array of keys produced by plugin
|
||||
func (c *ContextDataManager) GetProducesKeys(pluginName string) []string {
|
||||
return c.Manifests[pluginName].ProducesContextDataKeys
|
||||
}
|
||||
|
||||
func (c *ContextDataManager) FetchContextData() (map[string]string, error) {
|
||||
// Load cached data first
|
||||
if err := c.loadCache(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Fetch manifests
|
||||
if err := c.FetchManifests(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get context data, limited to func only
|
||||
for _, p := range c.PluginManager.GetInternalPlugins() {
|
||||
if p.Name() == "kn func" {
|
||||
if pwm, ok := p.(PluginWithManifest); ok {
|
||||
return pwm.GetContextData(), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return map[string]string{}, nil
|
||||
}
|
||||
|
||||
// FetchManifests it tries to retrieve manifest from both inlined and external plugins
|
||||
func (c *ContextDataManager) FetchManifests() error {
|
||||
for _, plugin := range c.PluginManager.GetInternalPlugins() {
|
||||
manifest := &Manifest{}
|
||||
// For the integrity build the same name format as external plugins
|
||||
pluginName := "kn-" + strings.Join(plugin.CommandParts(), "-")
|
||||
if pwm, ok := plugin.(PluginWithManifest); ok {
|
||||
manifest = pwm.GetManifest()
|
||||
}
|
||||
|
||||
// Add manifest to map
|
||||
c.Manifests[pluginName] = *manifest
|
||||
|
||||
if manifest.HasManifest {
|
||||
c.populateDataKeys(manifest, pluginName)
|
||||
}
|
||||
}
|
||||
plugins, err := c.PluginManager.ListPlugins()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, plugin := range plugins {
|
||||
// Add new plugins only
|
||||
if _, exists := c.Manifests[plugin.Name()]; !exists {
|
||||
|
||||
if plugin.Path() != "" {
|
||||
manifest := &Manifest{
|
||||
Path: plugin.Path(),
|
||||
}
|
||||
// Fetch from external plugin
|
||||
if m := fetchExternalManifest(plugin); m != nil {
|
||||
manifest = m
|
||||
}
|
||||
|
||||
// Add manifest to map
|
||||
c.Manifests[plugin.Name()] = *manifest
|
||||
|
||||
if manifest.HasManifest {
|
||||
c.populateDataKeys(manifest, plugin.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ContextDataManager) populateDataKeys(manifest *Manifest, pluginName string) {
|
||||
// Build producers mapping
|
||||
for _, key := range manifest.ProducesContextDataKeys {
|
||||
c.Producers[key] = append(c.Producers[key], pluginName)
|
||||
}
|
||||
// Build consumers mapping
|
||||
for _, key := range manifest.ConsumesContextDataKeys {
|
||||
c.Consumers[key] = append(c.Consumers[key], pluginName)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: We should cautiously execute external binaries
|
||||
// fetchExternalManifest returns Manifest from external plugin by exec `$plugin manifest get`
|
||||
func fetchExternalManifest(p Plugin) *Manifest {
|
||||
cmd := exec.Command(p.Path(), "manifest") //nolint:gosec
|
||||
stdOut := new(bytes.Buffer)
|
||||
cmd.Stdout = stdOut
|
||||
manifest := &Manifest{
|
||||
Path: p.Path(),
|
||||
}
|
||||
if err := cmd.Run(); err != nil {
|
||||
return nil
|
||||
}
|
||||
d := json.NewDecoder(stdOut)
|
||||
if err := d.Decode(manifest); err != nil {
|
||||
return nil
|
||||
}
|
||||
manifest.HasManifest = true
|
||||
return manifest
|
||||
}
|
||||
|
||||
func (c *ContextDataManager) loadCache() error {
|
||||
cacheFile := filepath.Join(filepath.Dir(config.GlobalConfig.ConfigFile()), "context.json")
|
||||
if _, err := os.Stat(cacheFile); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil // No cache file yet
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
file, err := os.Open(cacheFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
decoder := json.NewDecoder(file)
|
||||
ctxManager := &ContextDataManager{}
|
||||
if err := decoder.Decode(ctxManager); err != nil {
|
||||
return err
|
||||
}
|
||||
c.Manifests = ctxManager.Manifests
|
||||
c.Producers = ctxManager.Producers
|
||||
c.Consumers = ctxManager.Consumers
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteCache store data back to cache file
|
||||
func (c *ContextDataManager) WriteCache() error {
|
||||
out := new(bytes.Buffer)
|
||||
enc := json.NewEncoder(out)
|
||||
enc.SetIndent("", " ")
|
||||
if err := enc.Encode(c); err != nil {
|
||||
return nil
|
||||
}
|
||||
return os.WriteFile(filepath.Join(filepath.Dir(config.GlobalConfig.ConfigFile()), "context.json"), out.Bytes(), fs.FileMode(0664))
|
||||
}
|
||||
|
|
@ -0,0 +1,184 @@
|
|||
// Copyright © 2023 The Knative Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build !windows
|
||||
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
type testPluginWithManifest struct {
|
||||
testPlugin
|
||||
manifest *Manifest
|
||||
contextData map[string]string
|
||||
}
|
||||
|
||||
func (t testPluginWithManifest) GetManifest() *Manifest { return t.manifest }
|
||||
func (t testPluginWithManifest) GetContextData() map[string]string { return t.contextData }
|
||||
func (t testPluginWithManifest) ExecuteWithContext(ctx map[string]string, args []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Verify both interfaces are implemented
|
||||
var _ Plugin = testPluginWithManifest{}
|
||||
var _ PluginWithManifest = testPluginWithManifest{}
|
||||
|
||||
func TestFetchManifest(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
cmdPart []string
|
||||
hasManifest bool
|
||||
expectedManifest *Manifest
|
||||
}{
|
||||
{
|
||||
name: "Inlined with manifest",
|
||||
cmdPart: []string{"cmd"},
|
||||
hasManifest: true,
|
||||
expectedManifest: &Manifest{
|
||||
Path: "",
|
||||
HasManifest: true,
|
||||
ProducesContextDataKeys: []string{"service"},
|
||||
ConsumesContextDataKeys: []string{"service"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Inlined no manifest",
|
||||
cmdPart: []string{"no", "manifest"},
|
||||
hasManifest: false,
|
||||
expectedManifest: &Manifest{
|
||||
HasManifest: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "No plugins",
|
||||
cmdPart: []string{},
|
||||
hasManifest: false,
|
||||
expectedManifest: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
c := setup(t)
|
||||
t.Cleanup(func() {
|
||||
cleanup(t, c)
|
||||
InternalPlugins = PluginList{}
|
||||
CtxManager = nil
|
||||
})
|
||||
|
||||
if len(tc.cmdPart) == 0 {
|
||||
ctxManager, err := NewContextManager(c.pluginManager)
|
||||
assert.NilError(t, err)
|
||||
|
||||
err = ctxManager.FetchManifests()
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, len(ctxManager.Manifests) == 0)
|
||||
return
|
||||
}
|
||||
|
||||
if tc.hasManifest {
|
||||
prepareInternalPlugins(testPluginWithManifest{
|
||||
testPlugin: testPlugin{parts: tc.cmdPart},
|
||||
manifest: tc.expectedManifest,
|
||||
contextData: map[string]string{},
|
||||
})
|
||||
} else {
|
||||
prepareInternalPlugins(testPlugin{parts: tc.cmdPart})
|
||||
}
|
||||
|
||||
ctxManager, err := NewContextManager(c.pluginManager)
|
||||
assert.NilError(t, err)
|
||||
|
||||
err = ctxManager.FetchManifests()
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, len(ctxManager.Manifests) == 1)
|
||||
|
||||
expectedKey := "kn-" + strings.Join(tc.cmdPart, "-")
|
||||
assert.DeepEqual(t, ctxManager.Manifests[expectedKey], *tc.expectedManifest)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestContextFetchExternalManifests(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
testScript string
|
||||
expectedManifest *Manifest
|
||||
}{
|
||||
{
|
||||
name: "manifest",
|
||||
testScript: `#!/bin/bash
|
||||
echo '{"hasManifest":true,"consumesKeys":["service"]}'\n`,
|
||||
expectedManifest: &Manifest{
|
||||
HasManifest: true,
|
||||
ConsumesContextDataKeys: []string{"service"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "badjson",
|
||||
testScript: `#!/bin/bash
|
||||
echo '{hasManifest:true,"consumesKeys":["service"]}'\n`,
|
||||
expectedManifest: nil,
|
||||
},
|
||||
{
|
||||
name: "badscript",
|
||||
testScript: `#!/bin/bash
|
||||
exit 1\n`,
|
||||
expectedManifest: nil,
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ct := setup(t)
|
||||
defer cleanup(t, ct)
|
||||
|
||||
fullPath := createTestPluginInDirectoryFromScript(t, "kn-"+tc.name, ct.pluginsDir, tc.testScript)
|
||||
// Set expected path
|
||||
if tc.expectedManifest != nil {
|
||||
tc.expectedManifest.Path = fullPath
|
||||
}
|
||||
|
||||
testPlugin, err := ct.pluginManager.FindPlugin([]string{tc.name})
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, testPlugin != nil)
|
||||
|
||||
actual := fetchExternalManifest(testPlugin)
|
||||
assert.DeepEqual(t, actual, tc.expectedManifest)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// CreateTestPluginInPath with name, path, script, and fileMode and return the tmp random path
|
||||
func createTestPluginInDirectoryFromScript(t *testing.T, name string, dir string, script string) string {
|
||||
fullPath := filepath.Join(dir, name)
|
||||
err := os.WriteFile(fullPath, []byte(script), 0777)
|
||||
assert.NilError(t, err)
|
||||
// Some extra files to feed the tests
|
||||
err = os.WriteFile(filepath.Join(dir, "non-plugin-prefix-"+name), []byte{}, 0555)
|
||||
assert.NilError(t, err)
|
||||
_, err = os.CreateTemp(dir, "bogus-dir")
|
||||
assert.NilError(t, err)
|
||||
|
||||
return fullPath
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
// Copyright © 2023 The Knative Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package plugin
|
||||
|
|
@ -55,26 +55,6 @@ type Plugin interface {
|
|||
Path() string
|
||||
}
|
||||
|
||||
type ContextData map[string]string
|
||||
|
||||
type PluginManifest struct {
|
||||
// ProducesContextDataKeys is a list of keys for the ContextData that
|
||||
// a plugin can produce. Nil or an empty list declares that this
|
||||
// plugin is not ContextDataProducer
|
||||
ProducesContextDataKeys []string
|
||||
|
||||
// ConsumesContextDataKeys is a list of keys from a ContextData that a
|
||||
// plugin is interested in to consume. Nil or an empty list declares
|
||||
// that this plugin is not a ContextDataConsumer
|
||||
ConsumesContextDataKeys []string
|
||||
}
|
||||
|
||||
type ContextDataConsumer interface {
|
||||
// ExecuteWithContextData executes the plugin with the given args much like
|
||||
// Execute() but with an additional argument that holds the ContextData
|
||||
ExecuteWithContextData(args []string, data ContextData) error
|
||||
}
|
||||
|
||||
type Manager struct {
|
||||
// Dedicated plugin directory as configured
|
||||
pluginsDir string
|
||||
|
|
@ -125,6 +105,10 @@ func (manager *Manager) AppendPlugin(plugin Plugin) {
|
|||
InternalPlugins = append(InternalPlugins, plugin)
|
||||
}
|
||||
|
||||
func (manager *Manager) GetInternalPlugins() PluginList {
|
||||
return InternalPlugins
|
||||
}
|
||||
|
||||
// FindPlugin checks if a plugin for the given parts exist and return it.
|
||||
// The args given must not contain any options and contain only
|
||||
// the commands (like in [ "source", "github" ] for a plugin called 'kn-source-github'
|
||||
|
|
|
|||
Loading…
Reference in New Issue