mirror of
https://github.com/helm/helm.git
synced 2026-05-28 04:35:48 -04:00
Cleanup plugin config
Signed-off-by: George Jenkins <gvjenkins@gmail.com>
This commit is contained in:
parent
ed6cab39c6
commit
a8151ef4fe
17 changed files with 163 additions and 95 deletions
|
|
@ -16,72 +16,39 @@ limitations under the License.
|
|||
package plugin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"go.yaml.in/yaml/v3"
|
||||
)
|
||||
|
||||
// Config interface defines the methods that all plugin type configurations must implement
|
||||
// Config represents an plugin type specific configuration
|
||||
// It is expected to type assert (cast) the a Config to its expected underlying type (schema.ConfigCLIV1, schema.ConfigGetterV1, etc).
|
||||
type Config interface {
|
||||
Validate() error
|
||||
}
|
||||
|
||||
// ConfigCLI represents the configuration for CLI plugins
|
||||
type ConfigCLI struct {
|
||||
// Usage is the single-line usage text shown in help
|
||||
// For recommended syntax, see [spf13/cobra.command.Command] Use field comment:
|
||||
// https://pkg.go.dev/github.com/spf13/cobra#Command
|
||||
Usage string `yaml:"usage"`
|
||||
// ShortHelp is the short description shown in the 'helm help' output
|
||||
ShortHelp string `yaml:"shortHelp"`
|
||||
// LongHelp is the long message shown in the 'helm help <this-command>' output
|
||||
LongHelp string `yaml:"longHelp"`
|
||||
// IgnoreFlags ignores any flags passed in from Helm
|
||||
IgnoreFlags bool `yaml:"ignoreFlags"`
|
||||
}
|
||||
func unmarshaConfig(pluginType string, configData map[string]any) (Config, error) {
|
||||
|
||||
// ConfigGetter represents the configuration for download plugins
|
||||
type ConfigGetter struct {
|
||||
// Protocols are the list of URL schemes supported by this downloader
|
||||
Protocols []string `yaml:"protocols"`
|
||||
}
|
||||
|
||||
// ConfigPostrenderer represents the configuration for postrenderer plugins
|
||||
// there are no runtime-independent configurations for postrenderer/v1 plugin type
|
||||
type ConfigPostrenderer struct{}
|
||||
|
||||
func (c *ConfigCLI) Validate() error {
|
||||
// Config validation for CLI plugins
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ConfigGetter) Validate() error {
|
||||
if len(c.Protocols) == 0 {
|
||||
return fmt.Errorf("getter has no protocols")
|
||||
pluginTypeMeta, ok := pluginTypesIndex[pluginType]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown plugin type %q", pluginType)
|
||||
}
|
||||
for i, protocol := range c.Protocols {
|
||||
if protocol == "" {
|
||||
return fmt.Errorf("getter has empty protocol at index %d", i)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ConfigPostrenderer) Validate() error {
|
||||
// Config validation for postrenderer plugins
|
||||
return nil
|
||||
}
|
||||
// TODO: Avoid (yaml) serialization/deserialization for type conversion here
|
||||
|
||||
func remarshalConfig[T Config](configData map[string]any) (Config, error) {
|
||||
data, err := yaml.Marshal(configData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshel config data (plugin type %s): %w", pluginType, err)
|
||||
}
|
||||
|
||||
config := reflect.New(pluginTypeMeta.configType)
|
||||
d := yaml.NewDecoder(bytes.NewReader(data))
|
||||
d.KnownFields(true)
|
||||
if err := d.Decode(config.Interface()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var config T
|
||||
if err := yaml.Unmarshal(data, &config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return config, nil
|
||||
return config.Interface().(Config), nil
|
||||
}
|
||||
|
|
|
|||
56
internal/plugin/config_test.go
Normal file
56
internal/plugin/config_test.go
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
Copyright The Helm 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 (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"helm.sh/helm/v4/internal/plugin/schema"
|
||||
)
|
||||
|
||||
func TestUnmarshaConfig(t *testing.T) {
|
||||
// Test unmarshalling a CLI plugin config
|
||||
{
|
||||
config, err := unmarshaConfig("cli/v1", map[string]any{
|
||||
"usage": "usage string",
|
||||
"shortHelp": "short help string",
|
||||
"longHelp": "long help string",
|
||||
"ignoreFlags": true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.IsType(t, &schema.ConfigCLIV1{}, config)
|
||||
assert.Equal(t, schema.ConfigCLIV1{
|
||||
Usage: "usage string",
|
||||
ShortHelp: "short help string",
|
||||
LongHelp: "long help string",
|
||||
IgnoreFlags: true,
|
||||
}, *(config.(*schema.ConfigCLIV1)))
|
||||
}
|
||||
|
||||
// Test unmarshalling invalid config data
|
||||
{
|
||||
config, err := unmarshaConfig("cli/v1", map[string]any{
|
||||
"invalid field": "foo",
|
||||
})
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "field not found")
|
||||
assert.Nil(t, config)
|
||||
}
|
||||
}
|
||||
|
|
@ -22,6 +22,8 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"helm.sh/helm/v4/internal/plugin/schema"
|
||||
)
|
||||
|
||||
func TestPeekAPIVersion(t *testing.T) {
|
||||
|
|
@ -73,7 +75,7 @@ func TestLoadDir(t *testing.T) {
|
|||
Version: "0.1.0",
|
||||
Type: "cli/v1",
|
||||
Runtime: "subprocess",
|
||||
Config: &ConfigCLI{
|
||||
Config: &schema.ConfigCLIV1{
|
||||
Usage: usage,
|
||||
ShortHelp: "echo hello message",
|
||||
LongHelp: "description",
|
||||
|
|
@ -145,7 +147,7 @@ func TestLoadDirGetter(t *testing.T) {
|
|||
Type: "getter/v1",
|
||||
APIVersion: "v1",
|
||||
Runtime: "subprocess",
|
||||
Config: &ConfigGetter{
|
||||
Config: &schema.ConfigGetterV1{
|
||||
Protocols: []string{"myprotocol", "myprotocols"},
|
||||
},
|
||||
RuntimeConfig: &RuntimeConfigSubprocess{
|
||||
|
|
@ -173,7 +175,7 @@ func TestPostRenderer(t *testing.T) {
|
|||
Type: "postrenderer/v1",
|
||||
APIVersion: "v1",
|
||||
Runtime: "subprocess",
|
||||
Config: &ConfigPostrenderer{},
|
||||
Config: &schema.ConfigPostRendererV1{},
|
||||
RuntimeConfig: &RuntimeConfigSubprocess{
|
||||
PlatformCommand: []PlatformCommand{
|
||||
{
|
||||
|
|
|
|||
|
|
@ -123,11 +123,11 @@ func buildLegacyConfig(m MetadataLegacy, pluginType string) Config {
|
|||
for _, d := range m.Downloaders {
|
||||
protocols = append(protocols, d.Protocols...)
|
||||
}
|
||||
return &ConfigGetter{
|
||||
return &schema.ConfigGetterV1{
|
||||
Protocols: protocols,
|
||||
}
|
||||
case "cli/v1":
|
||||
return &ConfigCLI{
|
||||
return &schema.ConfigCLIV1{
|
||||
Usage: "", // Legacy plugins don't have Usage field for command syntax
|
||||
ShortHelp: m.Usage, // Map legacy usage to shortHelp
|
||||
LongHelp: m.Description, // Map legacy description to longHelp
|
||||
|
|
@ -175,7 +175,7 @@ func buildLegacyRuntimeConfig(m MetadataLegacy) RuntimeConfig {
|
|||
|
||||
func fromMetadataV1(mv1 MetadataV1) (*Metadata, error) {
|
||||
|
||||
config, err := convertMetadataConfig(mv1.Type, mv1.Config)
|
||||
config, err := unmarshaConfig(mv1.Type, mv1.Config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -197,30 +197,6 @@ func fromMetadataV1(mv1 MetadataV1) (*Metadata, error) {
|
|||
}, nil
|
||||
}
|
||||
|
||||
func convertMetadataConfig(pluginType string, configRaw map[string]any) (Config, error) {
|
||||
var err error
|
||||
var config Config
|
||||
|
||||
switch pluginType {
|
||||
case "test/v1":
|
||||
config, err = remarshalConfig[*schema.ConfigTestV1](configRaw)
|
||||
case "cli/v1":
|
||||
config, err = remarshalConfig[*ConfigCLI](configRaw)
|
||||
case "getter/v1":
|
||||
config, err = remarshalConfig[*ConfigGetter](configRaw)
|
||||
case "postrenderer/v1":
|
||||
config, err = remarshalConfig[*ConfigPostrenderer](configRaw)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported plugin type: %s", pluginType)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal config for %s plugin type: %w", pluginType, err)
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func convertMetdataRuntimeConfig(runtimeType string, runtimeConfigRaw map[string]any) (RuntimeConfig, error) {
|
||||
var runtimeConfig RuntimeConfig
|
||||
var err error
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ package plugin
|
|||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"helm.sh/helm/v4/internal/plugin/schema"
|
||||
)
|
||||
|
||||
func mockSubprocessCLIPlugin(t *testing.T, pluginName string) *SubprocessPluginRuntime {
|
||||
|
|
@ -46,7 +48,7 @@ func mockSubprocessCLIPlugin(t *testing.T, pluginName string) *SubprocessPluginR
|
|||
Type: "cli/v1",
|
||||
APIVersion: "v1",
|
||||
Runtime: "subprocess",
|
||||
Config: &ConfigCLI{
|
||||
Config: &schema.ConfigCLIV1{
|
||||
Usage: "Mock plugin",
|
||||
ShortHelp: "Mock plugin",
|
||||
LongHelp: "Mock plugin for testing",
|
||||
|
|
|
|||
|
|
@ -81,13 +81,19 @@ var pluginTypes = []pluginTypeMeta{
|
|||
pluginType: "cli/v1",
|
||||
inputType: reflect.TypeOf(schema.InputMessageCLIV1{}),
|
||||
outputType: reflect.TypeOf(schema.OutputMessageCLIV1{}),
|
||||
configType: reflect.TypeOf(ConfigCLI{}),
|
||||
configType: reflect.TypeOf(schema.ConfigCLIV1{}),
|
||||
},
|
||||
{
|
||||
pluginType: "getter/v1",
|
||||
inputType: reflect.TypeOf(schema.InputMessageGetterV1{}),
|
||||
outputType: reflect.TypeOf(schema.OutputMessageGetterV1{}),
|
||||
configType: reflect.TypeOf(ConfigGetter{}),
|
||||
configType: reflect.TypeOf(schema.ConfigGetterV1{}),
|
||||
},
|
||||
{
|
||||
pluginType: "postrenderer/v1",
|
||||
inputType: reflect.TypeOf(schema.InputMessagePostRendererV1{}),
|
||||
outputType: reflect.TypeOf(schema.OutputMessagePostRendererV1{}),
|
||||
configType: reflect.TypeOf(schema.ConfigPostRendererV1{}),
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -34,5 +34,5 @@ func TestMakeOutputMessage(t *testing.T) {
|
|||
func TestMakeConfig(t *testing.T) {
|
||||
ptm := pluginTypesIndex["getter/v1"]
|
||||
config := reflect.New(ptm.configType).Interface().(Config)
|
||||
assert.IsType(t, &ConfigGetter{}, config)
|
||||
assert.IsType(t, &schema.ConfigGetterV1{}, config)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ func mockSubprocessCLIPluginErrorExit(t *testing.T, pluginName string, exitCode
|
|||
Type: "cli/v1",
|
||||
APIVersion: "v1",
|
||||
Runtime: "subprocess",
|
||||
Config: &ConfigCLI{
|
||||
Config: &schema.ConfigCLIV1{
|
||||
Usage: "Mock plugin",
|
||||
ShortHelp: "Mock plugin",
|
||||
LongHelp: "Mock plugin for testing",
|
||||
|
|
|
|||
|
|
@ -27,3 +27,22 @@ type InputMessageCLIV1 struct {
|
|||
type OutputMessageCLIV1 struct {
|
||||
Data *bytes.Buffer `json:"data"`
|
||||
}
|
||||
|
||||
// ConfigCLIV1 represents the configuration for CLI plugins
|
||||
type ConfigCLIV1 struct {
|
||||
// Usage is the single-line usage text shown in help
|
||||
// For recommended syntax, see [spf13/cobra.command.Command] Use field comment:
|
||||
// https://pkg.go.dev/github.com/spf13/cobra#Command
|
||||
Usage string `yaml:"usage"`
|
||||
// ShortHelp is the short description shown in the 'helm help' output
|
||||
ShortHelp string `yaml:"shortHelp"`
|
||||
// LongHelp is the long message shown in the 'helm help <this-command>' output
|
||||
LongHelp string `yaml:"longHelp"`
|
||||
// IgnoreFlags ignores any flags passed in from Helm
|
||||
IgnoreFlags bool `yaml:"ignoreFlags"`
|
||||
}
|
||||
|
||||
func (c *ConfigCLIV1) Validate() error {
|
||||
// Config validation for CLI plugins
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
18
internal/plugin/schema/doc.go
Normal file
18
internal/plugin/schema/doc.go
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
Copyright The Helm 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 schema
|
||||
|
|
@ -14,10 +14,11 @@
|
|||
package schema
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TODO: can we generate these plugin input/outputs?
|
||||
// TODO: can we generate these plugin input/output messages?
|
||||
|
||||
type GetterOptionsV1 struct {
|
||||
URL string
|
||||
|
|
@ -45,3 +46,21 @@ type InputMessageGetterV1 struct {
|
|||
type OutputMessageGetterV1 struct {
|
||||
Data []byte `json:"data"`
|
||||
}
|
||||
|
||||
// ConfigGetterV1 represents the configuration for download plugins
|
||||
type ConfigGetterV1 struct {
|
||||
// Protocols are the list of URL schemes supported by this downloader
|
||||
Protocols []string `yaml:"protocols"`
|
||||
}
|
||||
|
||||
func (c *ConfigGetterV1) Validate() error {
|
||||
if len(c.Protocols) == 0 {
|
||||
return fmt.Errorf("getter has no protocols")
|
||||
}
|
||||
for i, protocol := range c.Protocols {
|
||||
if protocol == "" {
|
||||
return fmt.Errorf("getter has empty protocol at index %d", i)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,3 +30,9 @@ type InputMessagePostRendererV1 struct {
|
|||
type OutputMessagePostRendererV1 struct {
|
||||
Manifests *bytes.Buffer `json:"manifests"`
|
||||
}
|
||||
|
||||
type ConfigPostRendererV1 struct{}
|
||||
|
||||
func (c *ConfigPostRendererV1) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ func loadCLIPlugins(baseCmd *cobra.Command, out io.Writer) {
|
|||
for _, plug := range found {
|
||||
var use, short, long string
|
||||
var ignoreFlags bool
|
||||
if cliConfig, ok := plug.Metadata().Config.(*plugin.ConfigCLI); ok {
|
||||
if cliConfig, ok := plug.Metadata().Config.(*schema.ConfigCLIV1); ok {
|
||||
use = cliConfig.Usage
|
||||
short = cliConfig.ShortHelp
|
||||
long = cliConfig.LongHelp
|
||||
|
|
@ -340,7 +340,7 @@ func pluginDynamicComp(plug plugin.Plugin, cmd *cobra.Command, args []string, to
|
|||
}
|
||||
|
||||
var ignoreFlags bool
|
||||
if cliConfig, ok := subprocessPlug.Metadata().Config.(*plugin.ConfigCLI); ok {
|
||||
if cliConfig, ok := subprocessPlug.Metadata().Config.(*schema.ConfigCLIV1); ok {
|
||||
ignoreFlags = cliConfig.IgnoreFlags
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
|
||||
"helm.sh/helm/v4/internal/plugin"
|
||||
"helm.sh/helm/v4/internal/plugin/schema"
|
||||
)
|
||||
|
||||
func newPluginListCmd(out io.Writer) *cobra.Command {
|
||||
|
|
@ -106,7 +107,7 @@ func compListPlugins(_ string, ignoredPluginNames []string) []string {
|
|||
for _, p := range filteredPlugins {
|
||||
m := p.Metadata()
|
||||
var shortHelp string
|
||||
if config, ok := m.Config.(*plugin.ConfigCLI); ok {
|
||||
if config, ok := m.Config.(*schema.ConfigCLIV1); ok {
|
||||
shortHelp = config.ShortHelp
|
||||
}
|
||||
pNames = append(pNames, fmt.Sprintf("%s\t%s", p.Metadata().Name, shortHelp))
|
||||
|
|
|
|||
|
|
@ -4,10 +4,6 @@ name: "postrenderer-v1"
|
|||
version: "1.2.3"
|
||||
type: postrenderer/v1
|
||||
runtime: subprocess
|
||||
config:
|
||||
shortHelp: "echo test"
|
||||
longHelp: "This echos test"
|
||||
ignoreFlags: false
|
||||
runtimeConfig:
|
||||
platformCommand:
|
||||
- command: "${HELM_PLUGIN_DIR}/sed-test.sh"
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ func collectGetterPlugins(settings *cli.EnvSettings) (Providers, error) {
|
|||
}
|
||||
results := make([]Provider, 0, len(plgs))
|
||||
for _, plg := range plgs {
|
||||
if c, ok := plg.Metadata().Config.(*plugin.ConfigGetter); ok {
|
||||
if c, ok := plg.Metadata().Config.(*schema.ConfigGetterV1); ok {
|
||||
results = append(results, Provider{
|
||||
Schemes: c.Protocols,
|
||||
New: pluginConstructorBuilder(plg),
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ func (t *testPlugin) Metadata() plugin.Metadata {
|
|||
Type: "cli/v1",
|
||||
APIVersion: "v1",
|
||||
Runtime: "subprocess",
|
||||
Config: &plugin.ConfigCLI{},
|
||||
Config: &schema.ConfigCLIV1{},
|
||||
RuntimeConfig: &plugin.RuntimeConfigSubprocess{
|
||||
PlatformCommand: []plugin.PlatformCommand{
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue