diff --git a/operator/plugin.go b/operator/plugin.go index 6e54f27d6..4441e7efe 100644 --- a/operator/plugin.go +++ b/operator/plugin.go @@ -21,13 +21,6 @@ type PluginConfig struct { Pipeline []Config } -// PluginParameter is a basic description of a plugin's parameter. -type PluginParameter struct { - Label string - Description string - Type string -} - // PluginRegistry is a registry of plugin templates. type PluginRegistry map[string]*template.Template @@ -63,6 +56,19 @@ func (r PluginRegistry) Render(pluginType string, params map[string]interface{}) ) } + for name, param := range config.Parameters { + if err := param.validate(); err != nil { + return PluginConfig{}, errors.NewError( + "invalid parameter found in plugin config", + "ensure that all parameters are valid for the plugin", + "plugin_type", pluginType, + "plugin_parameter", name, + "rendered_config", writer.String(), + "error_message", err.Error(), + ) + } + } + return config, nil } diff --git a/operator/plugin_parameter.go b/operator/plugin_parameter.go new file mode 100644 index 000000000..4c7cec4bd --- /dev/null +++ b/operator/plugin_parameter.go @@ -0,0 +1,147 @@ +package operator + +import ( + "fmt" + + "github.com/observiq/carbon/errors" +) + +// PluginParameter is a basic description of a plugin's parameter. +type PluginParameter struct { + Label string + Description string + Required bool + Type string // "string", "int", "bool", "strings", or "enum" + ValidValues []string `yaml:"valid_values"` // only useable if Type == "enum" + Default interface{} // Must be valid according to Type & ValidValues +} + +func (param PluginParameter) validate() error { + if param.Required && param.Default != nil { + return errors.NewError( + "required parameter cannot have a default value", + "ensure that required parameters do not have default values", + ) + } + + if err := param.validateType(); err != nil { + return err + } + + if err := param.validateValidValues(); err != nil { + return err + } + + if err := param.validateDefault(); err != nil { + return err + } + + return nil +} + +func (param PluginParameter) validateType() error { + switch param.Type { + case "string", "int", "bool", "strings", "enum": // ok + default: + return errors.NewError( + "invalid type for parameter", + "ensure that the type is one of 'string', 'int', 'bool', 'strings', or 'enum'", + ) + } + return nil +} + +func (param PluginParameter) validateValidValues() error { + switch param.Type { + case "string", "int", "bool", "strings": + if len(param.ValidValues) > 0 { + return errors.NewError( + fmt.Sprintf("valid_values is undefined for parameter of type '%s'", param.Type), + "remove 'valid_values' field or change type to 'enum'", + ) + } + case "enum": + if len(param.ValidValues) == 0 { + return errors.NewError( + "parameter of type 'enum' must have 'valid_values' specified", + "specify an array that includes one or more valid values", + ) + } + } + return nil +} + +func (param PluginParameter) validateDefault() error { + if param.Default == nil { + return nil + } + + // Validate that Default corresponds to Type + switch param.Type { + case "string": + if _, ok := param.Default.(string); !ok { + return errors.NewError( + "default value for a parameter of type 'string' must be a string", + "ensure that the default value is a string", + ) + } + case "int": + switch param.Default.(type) { + case int, int32, int64: // ok + default: + return errors.NewError( + "default value for a parameter of type 'int' must be an integer", + "ensure that the default value is an integer", + ) + } + case "bool": + if _, ok := param.Default.(bool); !ok { + return errors.NewError( + "default value for a parameter of type 'bool' must be a boolean", + "ensure that the default value is a boolean", + ) + } + case "strings": + defaultList, ok := param.Default.([]interface{}) + if !ok { + return errors.NewError( + "default value for a parameter of type 'strings' must be an array of strings", + "ensure that the default value is a string", + ) + } + for _, s := range defaultList { + if _, ok := s.(string); !ok { + return errors.NewError( + "default value for a parameter of type 'strings' must be an array of strings", + "ensure that the default value is an array of strings", + ) + } + } + case "enum": + if param.Default != nil { + // Validate that the default value is included in the enumeration + def, ok := param.Default.(string) + if !ok { + return errors.NewError( + "invalid default for enumerated parameter", + "ensure that the default value is a string", + ) + } + for _, val := range param.ValidValues { + if val == def { + return nil + } + } + return errors.NewError( + "invalid default value for enumerated parameter", + "ensure default value is listed as a valid value", + ) + } + default: + return errors.NewError( + "invalid type for parameter", + "ensure that the type is one of 'string', 'int', 'bool', 'strings', or 'enum'", + ) + } + return nil +} diff --git a/operator/plugin_test.go b/operator/plugin_test.go index 43d9b421e..a0b39bea2 100644 --- a/operator/plugin_test.go +++ b/operator/plugin_test.go @@ -135,8 +135,8 @@ func TestPluginMetadata(t *testing.T) { name: "full_meta", expectErr: false, template: `version: 0.0.0 -title: My Super Plugin -description: This is the best plugin ever +title: Test Plugin +description: This is a test plugin parameters: path: label: Path @@ -145,7 +145,7 @@ parameters: other: label: Other Thing description: Another parameter - type: integer + type: int pipeline: `, }, @@ -153,8 +153,8 @@ pipeline: name: "bad_version", expectErr: true, template: `version: [] -title: My Super Plugin -description: This is the best plugin ever +title: Test Plugin +description: This is a test plugin parameters: path: label: Path @@ -163,7 +163,7 @@ parameters: other: label: Other Thing description: Another parameter - type: integer + type: int pipeline: `, }, @@ -172,7 +172,7 @@ pipeline: expectErr: true, template: `version: 0.0.0 title: [] -description: This is the best plugin ever +description: This is a test plugin parameters: path: label: Path @@ -181,7 +181,7 @@ parameters: other: label: Other Thing description: Another parameter - type: integer + type: int pipeline: `, }, @@ -189,7 +189,7 @@ pipeline: name: "bad_description", expectErr: true, template: `version: 0.0.0 -title: My Super Plugin +title: Test Plugin description: [] parameters: path: @@ -199,16 +199,16 @@ parameters: other: label: Other Thing description: Another parameter - type: integer + type: int pipeline: `, }, { - name: "bad_parameters_type", + name: "bad_parameters", expectErr: true, template: `version: 0.0.0 -title: My Super Plugin -description: This is the best plugin ever +title: Test Plugin +description: This is a test plugin parameters: hello `, }, @@ -216,8 +216,8 @@ parameters: hello name: "bad_parameter_structure", expectErr: true, template: `version: 0.0.0 -title: My Super Plugin -description: This is the best plugin ever +title: Test Plugin +description: This is a test plugin parameters: path: this used to be supported pipeline: @@ -227,8 +227,8 @@ pipeline: name: "bad_parameter_label", expectErr: true, template: `version: 0.0.0 -title: My Super Plugin -description: This is the best plugin ever +title: Test Plugin +description: This is a test plugin parameters: path: label: [] @@ -241,8 +241,8 @@ pipeline: name: "bad_parameter_description", expectErr: true, template: `version: 0.0.0 -title: My Super Plugin -description: This is the best plugin ever +title: Test Plugin +description: This is a test plugin parameters: path: label: Path @@ -252,27 +252,362 @@ pipeline: `, }, { - name: "bad_parameter_type", + name: "bad_parameter", expectErr: true, template: `version: 0.0.0 -title: My Super Plugin -description: This is the best plugin ever +title: Test Plugin +description: This is a test plugin parameters: path: label: Path description: The path to a thing - type: [] + type: {} pipeline: `, }, { name: "empty_parameter", + expectErr: true, + template: `version: 0.0.0 +title: Test Plugin +description: This is a test plugin +parameters: + path: +pipeline: +`, + }, + { + name: "unknown_parameter", + expectErr: true, + template: `version: 0.0.0 +title: Test Plugin +description: This is a test plugin +parameters: + path: + label: Parameter + description: The thing of the thing + type: custom +pipeline: +`, + }, + { + name: "string_parameter", + expectErr: false, + template: `version: 0.0.0 +title: Test Plugin +description: This is a test plugin +parameters: + path: + label: Parameter + description: The thing of the thing + type: string +pipeline: +`, + }, + { + name: "string_parameter_default", + expectErr: false, + template: `version: 0.0.0 +title: Test Plugin +description: This is a test plugin +parameters: + path: + label: Parameter + description: The thing of the thing + type: string + default: hello +pipeline: +`, + }, + { + name: "string_parameter_default_invalid", + expectErr: true, + template: `version: 0.0.0 +title: Test Plugin +description: This is a test plugin +parameters: + path: + label: Parameter + description: The thing of the thing + type: string + default: 123 +pipeline: +`, + }, + { + name: "strings_parameter", + expectErr: false, + template: `version: 0.0.0 +title: Test Plugin +description: This is a test plugin +parameters: + path: + label: Parameter + description: The thing of the thing + type: strings +pipeline: +`, + }, + { + name: "strings_parameter_default", + expectErr: false, + template: `version: 0.0.0 +title: Test Plugin +description: This is a test plugin +parameters: + path: + label: Parameter + description: The thing of the thing + type: strings + default: + - hello +pipeline: +`, + }, + { + name: "strings_parameter_default_invalid", + expectErr: true, + template: `version: 0.0.0 +title: Test Plugin +description: This is a test plugin +parameters: + path: + label: Parameter + description: The thing of the thing + type: strings + default: hello +pipeline: +`, + }, + + { + name: "int_parameter", + expectErr: false, + template: `version: 0.0.0 +title: Test Plugin +description: This is a test plugin +parameters: + path: + label: Parameter + description: The thing of the thing + type: int +pipeline: +`, + }, + { + name: "int_parameter_default", + expectErr: false, + template: `version: 0.0.0 +title: Test Plugin +description: This is a test plugin +parameters: + path: + label: Parameter + description: The thing of the thing + type: int + default: 123 +pipeline: +`, + }, + { + name: "int_parameter_default_invalid", + expectErr: true, + template: `version: 0.0.0 +title: Test Plugin +description: This is a test plugin +parameters: + path: + label: Parameter + description: The thing of the thing + type: int + default: hello +pipeline: +`, + }, + { + name: "bool_parameter", + expectErr: false, + template: `version: 0.0.0 +title: Test Plugin +description: This is a test plugin +parameters: + path: + label: Parameter + description: The thing of the thing + type: bool +pipeline: +`, + }, + { + name: "bool_parameter_default_true", + expectErr: false, + template: `version: 0.0.0 +title: Test Plugin +description: This is a test plugin +parameters: + path: + label: Parameter + description: The thing of the thing + type: bool + default: true +pipeline: +`, + }, + { + name: "bool_parameter_default_false", + expectErr: false, + template: `version: 0.0.0 +title: Test Plugin +description: This is a test plugin +parameters: + path: + label: Parameter + description: The thing of the thing + type: bool + default: false +pipeline: +`, + }, + { + name: "bool_parameter_default_invalid", + expectErr: true, + template: `version: 0.0.0 +title: Test Plugin +description: This is a test plugin +parameters: + path: + label: Parameter + description: The thing of the thing + type: bool + default: 123 +pipeline: +`, + }, + { + name: "enum_parameter", + expectErr: false, + template: `version: 0.0.0 +title: Test Plugin +description: This is a test plugin +parameters: + path: + label: Parameter + description: The thing of the thing + type: enum + valid_values: ["one", "two"] +pipeline: +`, + }, + { + name: "enum_parameter_alternate", + expectErr: false, + template: `version: 0.0.0 +title: Test Plugin +description: This is a test plugin +parameters: + path: + label: Parameter + description: The thing of the thing + type: enum + valid_values: + - one + - two +pipeline: +`, + }, + { + name: "enum_parameter_default", expectErr: false, template: `version: 0.0.0 -title: My Super Plugin -description: This is the best plugin ever +title: Test Plugin +description: This is a test plugin +parameters: + path: + label: Parameter + description: The thing of the thing + type: enum + valid_values: + - one + - two + default: one +pipeline: +`, + }, + { + name: "enum_parameter_default_invalid", + expectErr: true, + template: `version: 0.0.0 +title: Test Plugin +description: This is a test plugin +parameters: + path: + label: Parameter + description: The thing of the thing + type: enum + valid_values: + - one + - two + default: three +pipeline: +`, + }, + { + name: "enum_parameter_no_valid_values", + expectErr: true, + template: `version: 0.0.0 +title: Test Plugin +description: This is a test plugin +parameters: + path: + label: Parameter + description: The thing of the thing + type: enum +pipeline: +`, + }, + { + name: "default_invalid", + expectErr: true, + template: `version: 0.0.0 +title: Test Plugin +description: This is a test plugin +parameters: + path: + label: Parameter + description: The thing of the thing + type: int + default: {} +pipeline: +`, + }, + { + name: "required_default", + expectErr: true, + template: `version: 0.0.0 +title: Test Plugin +description: This is a test plugin +parameters: + path: + label: Parameter + description: The thing of the thing + required: true + type: int + default: 123 +pipeline: +`, + }, + { + name: "non_enum_valid_values", + expectErr: true, + template: `version: 0.0.0 +title: Test Plugin +description: This is a test plugin parameters: path: + label: Parameter + description: The thing of the thing + type: int + valid_values: [1, 2, 3] pipeline: `, },