mirror of https://github.com/knative/client.git
233 lines
6.4 KiB
Go
233 lines
6.4 KiB
Go
// 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/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))
|
|
}
|