dev/import-beats/variables.go (169 lines of code) (raw):

// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one // or more contributor license agreements. Licensed under the Elastic License; // you may not use this file except in compliance with the Elastic License. package main import ( "fmt" "sort" "strings" "github.com/pkg/errors" yaml "gopkg.in/yaml.v3" "github.com/elastic/package-registry/packages" ) type manifestWithVars struct { Vars []packages.Variable `yaml:"var"` } var ignoredConfigOptions = []string{ "module", "metricsets", "enabled", } func createLogStreamVariables(manifestFile []byte) ([]packages.Variable, error) { var mwv manifestWithVars err := yaml.Unmarshal(manifestFile, &mwv) if err != nil { return nil, errors.Wrap(err, "unmarshalling manifest file failed") } adjusted, err := adjustVariablesFormat(mwv) if err != nil { return nil, errors.Wrap(err, "adjusting log stream variables failed") } return adjusted.Vars, nil } func createMetricStreamVariables(configFileContent []byte, moduleName, dataStreamName string) ([]packages.Variable, error) { var vars []packages.Variable if len(configFileContent) == 0 { return vars, nil } var moduleConfig []mapStr err := yaml.Unmarshal(configFileContent, &moduleConfig) if err != nil { return nil, errors.Wrap(err, "unmarshalling module config failed") } foundConfigEntries := map[string]bool{} for _, moduleConfigEntry := range moduleConfig { flatEntry := moduleConfigEntry.flatten() related, err := isConfigEntryRelatedToMetricset(flatEntry, dataStreamName) if err != nil { return nil, errors.Wrapf(err, "checking if config entry is related failed") } for name, value := range flatEntry { if shouldConfigOptionBeIgnored(name, value) { continue } if _, ok := foundConfigEntries[name]; ok { continue // already processed this config option } if related || strings.HasPrefix(name, fmt.Sprintf("%s.", dataStreamName)) { var isArray bool variableType := determineInputVariableType(name, value) if variableType == "yaml" { m, err := yaml.Marshal(value) if err != nil { return nil, errors.Wrapf(err, "marshalling object configuration variable failed") } value = string(m) } else { _, isArray = value.([]interface{}) } aVar := packages.Variable{ Name: name, Type: variableType, Title: toVariableTitle(name), Multi: isArray, Required: determineInputVariableIsRequired(value), ShowUser: true, Default: value, } vars = append(vars, aVar) foundConfigEntries[name] = true } } } // sort variables to keep them in order while using version control. sort.Slice(vars, func(i, j int) bool { return sort.StringsAreSorted([]string{vars[i].Name, vars[j].Name}) }) return vars, nil } // adjustVariablesFormat method adjusts the format of variables defined in manifest: // - ensure that all variable values are wrapped with a "default" field // - add field "multi: true" if value is an array func adjustVariablesFormat(mwvs manifestWithVars) (manifestWithVars, error) { var withDefaults manifestWithVars for _, aVar := range mwvs.Vars { var isArray bool variableType := determineInputVariableType(aVar.Name, aVar.Default) if variableType == "yaml" { m, err := yaml.Marshal(aVar.Default) if err != nil { return manifestWithVars{}, errors.Wrapf(err, "marshalling object configuration variable failed") } aVar.Default = string(m) } else { _, isArray = aVar.Default.([]interface{}) } aVarWithDefaults := aVar aVarWithDefaults.Title = toVariableTitle(aVar.Name) aVarWithDefaults.Type = variableType aVarWithDefaults.Required = determineInputVariableIsRequired(aVar.Default) aVarWithDefaults.ShowUser = true aVarWithDefaults.Multi = isArray withDefaults.Vars = append(withDefaults.Vars, aVarWithDefaults) } return withDefaults, nil } // shouldConfigOptionBeIgnored method checks if the configuration option name should be skipped (not used, duplicate, etc.) func shouldConfigOptionBeIgnored(optionName string, value interface{}) bool { if value == nil { return true } for _, ignored := range ignoredConfigOptions { if ignored == optionName { return true } } return false } // isConfigEntryRelatedToMetricset method checks if the configuration entry may affect the dataStream settings, // in other words, checks if the "metricsets" field is present and contains the given dataStreamName. func isConfigEntryRelatedToMetricset(entry mapStr, dataStreamName string) (bool, error) { var metricsetRelated bool if metricsets, ok := entry["metricsets"]; ok { metricsetsMapped, ok := metricsets.([]interface{}) // nats: connection data stream doesn't define a config, but this is fine if ok { for _, metricset := range metricsetsMapped { if metricset.(string) == dataStreamName { metricsetRelated = true break } } } } return metricsetRelated, nil } // determineInputVariableIsRequired method determines is the configuration variable should be marked as "required". // If the variable is string and its default value is empty, it can be assumed that isn't required. func determineInputVariableIsRequired(v interface{}) bool { if v == nil { return false } val, isString := v.(string) if isString && val == "" { return false } return true } // determineInputVariableType method determines the most appropriate type of the value or the value in array. // Support types: text, password, bool, integer func determineInputVariableType(name, v interface{}) string { if arr, isArray := v.([]interface{}); isArray { if len(arr) == 0 { return "text" // array doesn't contain any items, assuming default type } return determineInputVariableType(name, arr[0]) } if _, isBool := v.(bool); isBool { return "bool" } else if _, isInt := v.(int); isInt { return "integer" } if name == "password" { return "password" } if _, isString := v.(string); isString || v == nil { return "text" } return "yaml" } func toVariableTitle(name string) string { name = strings.ReplaceAll(name, "_", " ") name = strings.ReplaceAll(name, ".", " ") return strings.Title(name) }