Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify configuration properties #21

Merged
merged 4 commits into from
Jul 15, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions client/uploadable.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,20 +43,20 @@ const (

// UploadableConfig contains configuration for the AutoUploadable feature
type UploadableConfig struct {
Name string `json:"name,omitempty" def:"Autouploadable" descr:"Name for the Uploadable feature. Should conform to https://www.eclipse.org/ditto/basic-namespaces-and-names.html#name"`
Context string `json:"context,omitempty" def:"edge" descr:"ID of the Uploadable feature."`
Type string `json:"type,omitempty" def:"file" descr:"Type of the Uploadable feature."`
Period Duration `json:"period,omitempty" def:"10h" descr:"Upload period. Should be a sequence of decimal numbers, each with optional fraction and a unit suffix, such as '300ms', '1.5h', '10m30s', etc. Valid time units are 'ns', 'us' (or 'µs'), 'ms', 's', 'm', 'h'"`
Name string `json:"name,omitempty" def:"{name}" descr:"Name for the {feature} feature.\nShould conform to https://docs.bosch-iot-suite.com/things/basic-concepts/namespace-thing-feature/#characters-allowed-in-a-feature-id"`
Context string `json:"context,omitempty" def:"edge" descr:"ID of the {feature} feature."`
Type string `json:"type,omitempty" def:"file" descr:"Type of the {feature} feature."`
Period Duration `json:"period,omitempty" def:"10h" descr:"{period}. Should be a sequence of decimal numbers, each with optional fraction and a unit suffix, such as '300ms', '1.5h', '10m30s', etc. Valid time units are 'ns', 'us' (or 'µs'), 'ms', 's', 'm', 'h'"`

Active bool `json:"active,omitempty" def:"false" descr:"Activate periodic uploads"`
ActiveFrom Xtime `json:"activeFrom,omitempty" descr:"Time from which periodic uploads should be active, in RFC 3339 format. If ommitted (and 'active' flag is set) current time will be used as start of the periodic uploads."`
ActiveTill Xtime `json:"activeTill,omitempty" descr:"Time till which periodic uploads should be active, in RFC 3339 format. If ommitted (and 'active' flag is set) periodic uploads will be active indefinitely."`
Active bool `json:"active,omitempty" def:"false" descr:"Activate periodic {actions}"`
ActiveFrom Xtime `json:"activeFrom,omitempty" descr:"Time from which periodic {actions} should be active, in RFC 3339 format (2006-01-02T15:04:05Z07:00). If omitted (and 'active' flag is set) current time will be used as start of the periodic {actions}."`
ActiveTill Xtime `json:"activeTill,omitempty" descr:"Time till which periodic {actions} should be active, in RFC 3339 format (2006-01-02T15:04:05Z07:00). If omitted (and 'active' flag is set) periodic {actions} will be active indefinitely."`

Delete bool `json:"delete,omitempty" def:"false" descr:"Delete successfully uploaded files"`
Checksum bool `json:"checksum,omitempty" def:"false" descr:"Send MD5 checksum for uploaded files to ensure data integrity. Computing checksums incurs additional CPU/disk usage."`
SingleUpload bool `json:"singleUpload,omitempty" def:"false" descr:"Forbid triggering of new uploads when there is upload in progress. Trigger can be forced from the backend with the 'force' option."`

StopTimeout Duration `json:"stopTimeout,omitempty" def:"30s" descr:"Time to wait for running uploads to finish. Should be a sequence of decimal numbers, each with optional fraction and a unit suffix, such as '300ms', '1.5h', '10m30s', etc. Valid time units are 'ns', 'us' (or 'µs'), 'ms', 's', 'm', 'h'"`
StopTimeout Duration `json:"stopTimeout,omitempty" def:"30s" descr:"Time to wait for running {running_actions} to finish when stopping. Should be a sequence of decimal numbers, each with optional fraction and a unit suffix, such as '300ms', '1.5h', '10m30s', etc. Valid time units are 'ns', 'us' (or 'µs'), 'ms', 's', 'm', 'h'"`
}

// AutoUploadableState is used for serializing the state property of the AutoUploadable feature
Expand Down
220 changes: 96 additions & 124 deletions flagparse/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import (
"path/filepath"
"reflect"
"strconv"
"time"
"strings"
"unicode"

"github.com/eclipse-kanto/file-upload/client"
Expand All @@ -32,49 +32,36 @@ import (
const (
ConfigFile = "configFile"

Files = "files"
DefaultFiles = ""
Files = "files"
)

//UploadFileConfig describes file config of uploadable
type UploadFileConfig struct {
gboyvalenkov-bosch marked this conversation as resolved.
Show resolved Hide resolved
*client.BrokerConfig
*client.UploadableConfig
*logger.LogConfig
client.BrokerConfig
client.UploadableConfig
logger.LogConfig

Files string `json:"files,omitempty"`
Files string `json:"files,omitempty" descr:"Glob pattern for the files to upload"`
}

//ConfigNames contains template names to be replaced in config properties descriptions and default values
var ConfigNames = map[string]string{
"name": "Autouploadable", "feature": "Uploadable", "period": "Upload period",
"action": "upload", "actions": "uploads", "running_actions": "uploads",
gboyvalenkov-bosch marked this conversation as resolved.
Show resolved Hide resolved
}

//ConfigFileMissing error, which represents a warning for missing config file
type ConfigFileMissing error

//NewUploadFileConfig return initialized UploadFileConfig
e-grigorov marked this conversation as resolved.
Show resolved Hide resolved
func NewUploadFileConfig() *UploadFileConfig {
return &UploadFileConfig{
&client.BrokerConfig{},
&client.UploadableConfig{},
&logger.LogConfig{},
"",
}
}

//ParseFlags Define & Parse all flags
e-grigorov marked this conversation as resolved.
Show resolved Hide resolved
func ParseFlags(version string) (*client.BrokerConfig, *client.UploadableConfig, *logger.LogConfig, string, ConfigFileMissing) {
func ParseFlags(version string) (*UploadFileConfig, ConfigFileMissing) {
dumpFiles := flag.Bool("dumpFiles", false, "On startup dump the file paths matching the '-files' glob pattern to standard output.")

config := NewUploadFileConfig()
flagsConfig := &UploadFileConfig{}
printVersion := flag.Bool("version", false, "Prints current version and exits")
configFile := flag.String(ConfigFile, "", "Defines the configuration file")

brokerConfig := &client.BrokerConfig{}
uploadConfig := &client.UploadableConfig{}
logConfig := &logger.LogConfig{}
filesGlob := ""

FlagBroker(config)
FlagUploadable(config)
FlagLogger(config)
flag.StringVar(&config.Files, Files, DefaultFiles, "Glob pattern for the files to upload")
InitFlagVars(flagsConfig, ConfigNames, nil)

flag.Parse()

Expand All @@ -83,59 +70,58 @@ func ParseFlags(version string) (*client.BrokerConfig, *client.UploadableConfig,
os.Exit(0)
}

warn := applyConfigurationFile(*configFile, brokerConfig, uploadConfig, logConfig, &filesGlob)
ApplyFlags(config, brokerConfig, uploadConfig, logConfig, &filesGlob)
config := &UploadFileConfig{}
warn := LoadConfigFromFile(*configFile, config, ConfigNames, nil)
ApplyFlags(config, *flagsConfig)

if *dumpFiles {
if config.Files == "" {
fmt.Println("No glob filter provided!")
fmt.Println("no glob filter provided!")
} else {
files, err := filepath.Glob(config.Files)
if err != nil {
log.Fatalln(err)
}
fmt.Printf("Files matching glob filter '%s': %v\n", config.Files, files)
fmt.Printf("files matching glob filter '%s': %v\n", config.Files, files)
}
}

return brokerConfig, uploadConfig, logConfig, filesGlob, warn
return config, warn
}

// ApplyFlags applies cli values over config values
func ApplyFlags(config *UploadFileConfig, brokerConfig *client.BrokerConfig,
e-grigorov marked this conversation as resolved.
Show resolved Hide resolved
uploadConfig *client.UploadableConfig, logConfig *logger.LogConfig, filesGlob *string) {

srcBroker := reflect.ValueOf(config.BrokerConfig).Elem()
valBroker := reflect.ValueOf(brokerConfig).Elem()

srcUpload := reflect.ValueOf(config.UploadableConfig).Elem()
valUpload := reflect.ValueOf(uploadConfig).Elem()
func ApplyFlags(config interface{}, flagsConfig interface{}) {
srcVal := reflect.ValueOf(flagsConfig)
dstVal := reflect.ValueOf(config).Elem()
flag.Visit(func(f *flag.Flag) {
name := ToFieldName(f.Name)

srcLog := reflect.ValueOf(config.LogConfig).Elem()
valLog := reflect.ValueOf(logConfig).Elem()
srcField := srcVal.FieldByName(name)
if srcField.Kind() != reflect.Invalid {
dstField := dstVal.FieldByName(name)

flag.Visit(func(f *flag.Flag) {
upperCaseName := toUpper(f.Name)
switch {
case copyFieldIfExists(upperCaseName, srcBroker, valBroker): // copying done, if check is ok, otherwise continue
case copyFieldIfExists(upperCaseName, srcUpload, valUpload): // copying done, if check is ok, otherwise continue
case copyFieldIfExists(upperCaseName, srcLog, valLog): // copying done, if check is ok, otherwise continue
case f.Name == Files:
*filesGlob = config.Files
default:
// Unknown flag
dstField.Set(srcField)
}
})
}

func applyConfigurationFile(configFile string, brokerConfig *client.BrokerConfig,
uploadConfig *client.UploadableConfig, logConfig *logger.LogConfig, filesGlob *string) ConfigFileMissing {
def := GetUploadFileConfigDefaults()
//InitFlagVars parses the 'cfg' structure and defines flag variables for its fields.
//The 'cfg' parameter should be a pointer to structure. Flag names are taken from field names (with the first letter lowercased).
//Flag descriptions are taken from 'descr' field tags, default values are taken from 'def' fielt tags
func InitFlagVars(cfg interface{}, names map[string]string, skip map[string]bool) {
e-grigorov marked this conversation as resolved.
Show resolved Hide resolved
initConfigValues(reflect.ValueOf(cfg).Elem(), names, skip, true)
}

//LoadConfigFromFile load the config from the specified file into the given config structure.
e-grigorov marked this conversation as resolved.
Show resolved Hide resolved
//The 'cfg' parameter should be a pointer to structure
e-grigorov marked this conversation as resolved.
Show resolved Hide resolved
func LoadConfigFromFile(configFile string, cfg interface{}, names map[string]string, skip map[string]bool) ConfigFileMissing {
initConfigValues(reflect.ValueOf(cfg).Elem(), names, skip, false)

var warn ConfigFileMissing

// Load configuration file (if possible)
// Load configuration file (if posible)
if len(configFile) > 0 {
err := LoadJSON(configFile, def)
err := LoadJSON(configFile, cfg)

if err != nil {
if os.IsNotExist(err) {
Expand All @@ -145,22 +131,37 @@ func applyConfigurationFile(configFile string, brokerConfig *client.BrokerConfig
}
}
}
SetBrokerConfig(def, brokerConfig)
SetUploadableConfig(def, uploadConfig)
SetLoggerConfig(def, logConfig)
*filesGlob = def.Files

return warn
}

func initConfigValues(typeOfConfig reflect.Type, valueOfConfig reflect.Value, flagIt bool) {
func initConfigValues(valueOfConfig reflect.Value, names map[string]string, skip map[string]bool, flagIt bool) {
r := getReplacer(names)

typeOfConfig := valueOfConfig.Type()
for i := 0; i < typeOfConfig.NumField(); i++ {
gboyvalenkov-bosch marked this conversation as resolved.
Show resolved Hide resolved
fieldType := typeOfConfig.Field(i)
argName := ToFlagName(fieldType.Name)

if skip != nil && skip[argName] {
continue
}

if !fieldType.IsExported() {
continue
}

defaultValue := fieldType.Tag.Get("def")
description := fieldType.Tag.Get("descr")

if r != nil {
defaultValue = r.Replace(defaultValue)
description = r.Replace(description)
}

fieldValue := valueOfConfig.FieldByName(fieldType.Name)
pointer := fieldValue.Addr().Interface()
argName := toLower(fieldType.Name)

switch val := fieldValue.Interface(); val.(type) {
case string:
if flagIt {
Expand Down Expand Up @@ -189,60 +190,40 @@ func initConfigValues(typeOfConfig reflect.Type, valueOfConfig reflect.Value, fl
fieldValue.SetInt(int64(defaultIntValue))
}
default:
if flagIt {
flag.Var(pointer.(flag.Value), argName, description)
} else {
if fieldType.Type.Name() == "Duration" {
duration, err := time.ParseDuration(defaultValue)
if err != nil {
log.Println(fmt.Sprintf("Error parsing duration argument %v with value %v", fieldType.Name, defaultValue))
}
fieldValue.Set(reflect.ValueOf(client.Duration(duration)))
v, ok := pointer.(flag.Value)

if ok {
if flagIt {
flag.Var(v, argName, description)
} else if err := v.Set(defaultValue); err == nil {
fieldValue.Set(reflect.ValueOf(v).Elem())
} else {
log.Println(fmt.Sprintf("Error parsing argument %v with value %v - %v", fieldType.Name, defaultValue, err))
}
} else if fieldType.Type.Kind() == reflect.Struct {
initConfigValues(fieldValue, names, skip, flagIt)
}
}
}
}

//FlagUploadable flags uploadable
func FlagUploadable(config *UploadFileConfig) {
initConfigValues(reflect.TypeOf(*config.UploadableConfig), reflect.ValueOf(config.UploadableConfig).Elem(), true)
}

//FlagBroker flags broker
func FlagBroker(config *UploadFileConfig) {
initConfigValues(reflect.TypeOf(*config.BrokerConfig), reflect.ValueOf(config.BrokerConfig).Elem(), true)
}

//FlagLogger flags logger
func FlagLogger(config *UploadFileConfig) {
initConfigValues(reflect.TypeOf(*config.LogConfig), reflect.ValueOf(config.LogConfig).Elem(), true)
}

//GetUploadFileConfigDefaults returns new *UploadFileConfig with default values set.
func GetUploadFileConfigDefaults() *UploadFileConfig {
brokerConfig := client.BrokerConfig{}
uploadConfig := client.UploadableConfig{}
logConfig := logger.LogConfig{}
initConfigValues(reflect.TypeOf(brokerConfig), reflect.ValueOf(&brokerConfig).Elem(), false)
initConfigValues(reflect.TypeOf(uploadConfig), reflect.ValueOf(&uploadConfig).Elem(), false)
initConfigValues(reflect.TypeOf(logConfig), reflect.ValueOf(&logConfig).Elem(), false)
return &UploadFileConfig{&brokerConfig, &uploadConfig, &logConfig, DefaultFiles}
}

//SetBrokerConfig sets BrokerConfig values from file config
func SetBrokerConfig(def *UploadFileConfig, brokerConfig *client.BrokerConfig) {
copyConfigData(def.BrokerConfig, brokerConfig)
}
func getReplacer(names map[string]string) *strings.Replacer {
if names == nil {
return nil
}
oldnew := make([]string, 0, len(names)*2)
for k, v := range names {
oldnew = append(oldnew, "{"+k+"}")
oldnew = append(oldnew, v)
}

//SetUploadableConfig sets UploadableConfig from file config
func SetUploadableConfig(def *UploadFileConfig, uploadConfig *client.UploadableConfig) {
copyConfigData(def.UploadableConfig, uploadConfig)
return strings.NewReplacer(oldnew...)
}

//SetLoggerConfig sets LogConfig from file config
func SetLoggerConfig(def *UploadFileConfig, logConfig *logger.LogConfig) {
copyConfigData(def.LogConfig, logConfig)
//InitConfigDefaults sets the default field values of the passed config.
//The 'cfg' should be a pointer to config. Default values are extracted from 'def' field tags
func InitConfigDefaults(cfg interface{}, mapping map[string]string, skip map[string]bool) {
initConfigValues(reflect.ValueOf(cfg).Elem(), mapping, skip, false)
}

//LoadJSON loads a json file from path into a given interface
Expand All @@ -258,29 +239,20 @@ func LoadJSON(file string, v interface{}) error {
func copyConfigData(sourceConfig interface{}, targetConfig interface{}) {
source := reflect.ValueOf(sourceConfig).Elem()
target := reflect.ValueOf(targetConfig).Elem()
typeOfSource := source.Type()
for i := 0; i < source.NumField(); i++ {
fieldTarget := target.FieldByName(typeOfSource.Field(i).Name)
fieldTarget.Set(reflect.ValueOf(source.Field(i).Interface()))
}

target.Set(source)
}

func toLower(s string) string {
//ToFlagName converts config structure field name to command-line flag name
func ToFlagName(s string) string {
rn := []rune(s)
rn[0] = unicode.ToLower(rn[0])
return string(rn)
}

func toUpper(s string) string {
//ToFieldName converts command-line flag name to config structure field name
func ToFieldName(s string) string {
rn := []rune(s)
rn[0] = unicode.ToUpper(rn[0])
return string(rn)
}

func copyFieldIfExists(name string, source, target reflect.Value) bool {
if field := target.FieldByName(name); field != reflect.Zero(reflect.TypeOf(field)).Interface() {
field.Set(reflect.ValueOf(source.FieldByName(name).Interface()))
return true
}
return false
}
Loading