Files
Dušan Borovčanin 61d0427898 NOISSUE - Rename to Magistrala (#3427)
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2026-04-06 15:23:42 +02:00

301 lines
7.5 KiB
Go

// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package cli
import (
"io"
"net/url"
"os"
"reflect"
"strconv"
"strings"
"github.com/absmach/magistrala/pkg/errors"
smqsdk "github.com/absmach/magistrala/pkg/sdk"
"github.com/pelletier/go-toml"
"github.com/spf13/cobra"
)
const (
defURL string = "http://localhost"
defUsersURL string = defURL + ":9002"
defCLientsURL string = defURL + ":9006"
defDomainsURL string = defURL + ":9003"
defChannelsURL string = defURL + ":9005"
defGroupsURL string = defURL + ":9004"
defHTTPURL string = defURL + ":8008"
defJournalURL string = defURL + ":9021"
defTLSVerification bool = false
defOffset string = "0"
defLimit string = "10"
defTopic string = ""
defRawOutput string = "false"
)
type remotes struct {
ClientsURL string `toml:"clients_url"`
UsersURL string `toml:"users_url"`
DomainsURL string `toml:"domains_url"`
ChannelsURL string `toml:"channels_url"`
GroupsURL string `toml:"groups_url"`
HTTPAdapterURL string `toml:"http_adapter_url"`
CertsURL string `toml:"certs_url"`
JournalURL string `toml:"journal_url"`
HostURL string `toml:"host_url"`
TLSVerification bool `toml:"tls_verification"`
}
type filter struct {
Offset string `toml:"offset"`
Limit string `toml:"limit"`
Topic string `toml:"topic"`
}
type config struct {
Remotes remotes `toml:"remotes"`
Filter filter `toml:"filter"`
UserToken string `toml:"user_token"`
RawOutput string `toml:"raw_output"`
}
// Readable by all user groups but writeable by the user only.
const filePermission = 0o644
var (
errReadFail = errors.New("failed to read config file")
errNoKey = errors.New("no such key")
errUnsupportedKeyValue = errors.New("unsupported data type for key")
errWritingConfig = errors.New("error in writing the updated config to file")
errInvalidURL = errors.New("invalid url")
errURLParseFail = errors.New("failed to parse url")
defaultConfigPath = "./config.toml"
)
func read(file string) (config, error) {
c := config{}
data, err := os.Open(file)
if err != nil {
return c, errors.Wrap(errReadFail, err)
}
defer data.Close()
buf, err := io.ReadAll(data)
if err != nil {
return c, errors.Wrap(errReadFail, err)
}
if err := toml.Unmarshal(buf, &c); err != nil {
return config{}, err
}
return c, nil
}
// ParseConfig - parses the config file.
func ParseConfig(sdkConf smqsdk.Config) (smqsdk.Config, error) {
if ConfigPath == "" {
ConfigPath = defaultConfigPath
}
_, err := os.Stat(ConfigPath)
switch {
// If the file does not exist, create it with default values.
case os.IsNotExist(err):
defaultConfig := config{
Remotes: remotes{
ClientsURL: defCLientsURL,
UsersURL: defUsersURL,
DomainsURL: defDomainsURL,
ChannelsURL: defChannelsURL,
GroupsURL: defGroupsURL,
HTTPAdapterURL: defHTTPURL,
JournalURL: defJournalURL,
HostURL: defURL,
TLSVerification: defTLSVerification,
},
Filter: filter{
Offset: defOffset,
Limit: defLimit,
Topic: defTopic,
},
RawOutput: defRawOutput,
}
buf, err := toml.Marshal(defaultConfig)
if err != nil {
return sdkConf, err
}
if err = os.WriteFile(ConfigPath, buf, filePermission); err != nil {
return sdkConf, errors.Wrap(errWritingConfig, err)
}
case err != nil:
return sdkConf, err
}
config, err := read(ConfigPath)
if err != nil {
return sdkConf, err
}
if config.Filter.Offset != "" && Offset == 0 {
offset, err := strconv.ParseUint(config.Filter.Offset, 10, 64)
if err != nil {
return sdkConf, err
}
Offset = offset
}
if config.Filter.Limit != "" && Limit == 0 {
limit, err := strconv.ParseUint(config.Filter.Limit, 10, 64)
if err != nil {
return sdkConf, err
}
Limit = limit
}
if config.Filter.Topic != "" && Topic == "" {
Topic = config.Filter.Topic
}
if config.RawOutput != "" {
rawOutput, err := strconv.ParseBool(config.RawOutput)
if err != nil {
return sdkConf, err
}
// check for config file value or flag input value is true
RawOutput = rawOutput || RawOutput
}
if sdkConf.ClientsURL == "" && config.Remotes.ClientsURL != "" {
sdkConf.ClientsURL = config.Remotes.ClientsURL
}
if sdkConf.UsersURL == "" && config.Remotes.UsersURL != "" {
sdkConf.UsersURL = config.Remotes.UsersURL
}
if sdkConf.DomainsURL == "" && config.Remotes.DomainsURL != "" {
sdkConf.DomainsURL = config.Remotes.DomainsURL
}
if sdkConf.ChannelsURL == "" && config.Remotes.ChannelsURL != "" {
sdkConf.ChannelsURL = config.Remotes.ChannelsURL
}
if sdkConf.GroupsURL == "" && config.Remotes.GroupsURL != "" {
sdkConf.GroupsURL = config.Remotes.GroupsURL
}
if sdkConf.HTTPAdapterURL == "" && config.Remotes.HTTPAdapterURL != "" {
sdkConf.HTTPAdapterURL = config.Remotes.HTTPAdapterURL
}
if sdkConf.CertsURL == "" && config.Remotes.CertsURL != "" {
sdkConf.CertsURL = config.Remotes.CertsURL
}
if sdkConf.JournalURL == "" && config.Remotes.JournalURL != "" {
sdkConf.JournalURL = config.Remotes.JournalURL
}
if sdkConf.HostURL == "" && config.Remotes.HostURL != "" {
sdkConf.HostURL = config.Remotes.HostURL
}
sdkConf.TLSVerification = config.Remotes.TLSVerification || sdkConf.TLSVerification
return sdkConf, nil
}
// New config command to store params to local TOML file.
func NewConfigCmd() *cobra.Command {
return &cobra.Command{
Use: "config <key> <value>",
Short: "CLI local config",
Long: "Local param storage to prevent repetitive passing of keys",
Run: func(cmd *cobra.Command, args []string) {
if len(args) != 2 {
logUsageCmd(*cmd, cmd.Use)
return
}
if err := setConfigValue(args[0], args[1]); err != nil {
logErrorCmd(*cmd, err)
return
}
logOKCmd(*cmd)
},
}
}
func setConfigValue(key, value string) error {
config, err := read(ConfigPath)
if err != nil {
return err
}
if strings.Contains(key, "url") {
u, err := url.Parse(value)
if err != nil {
return errors.Wrap(errInvalidURL, err)
}
if u.Scheme == "" || u.Host == "" {
return errors.Wrap(errInvalidURL, err)
}
if u.Scheme != "http" && u.Scheme != "https" {
return errors.Wrap(errURLParseFail, err)
}
}
configKeyToField := map[string]any{
"clients_url": &config.Remotes.ClientsURL,
"users_url": &config.Remotes.UsersURL,
"http_adapter_url": &config.Remotes.HTTPAdapterURL,
"certs_url": &config.Remotes.CertsURL,
"tls_verification": &config.Remotes.TLSVerification,
"offset": &config.Filter.Offset,
"limit": &config.Filter.Limit,
"topic": &config.Filter.Topic,
"raw_output": &config.RawOutput,
"user_token": &config.UserToken,
}
fieldPtr, ok := configKeyToField[key]
if !ok {
return errNoKey
}
fieldValue := reflect.ValueOf(fieldPtr).Elem()
switch fieldValue.Kind() {
case reflect.String:
fieldValue.SetString(value)
case reflect.Int:
intValue, err := strconv.Atoi(value)
if err != nil {
return err
}
fieldValue.SetUint(uint64(intValue))
case reflect.Bool:
boolValue, err := strconv.ParseBool(value)
if err != nil {
return err
}
fieldValue.SetBool(boolValue)
default:
return errors.Wrap(errUnsupportedKeyValue, err)
}
buf, err := toml.Marshal(config)
if err != nil {
return err
}
if err = os.WriteFile(ConfigPath, buf, filePermission); err != nil {
return errors.Wrap(errWritingConfig, err)
}
return nil
}