1
0
mirror of https://github.com/mainflux/mainflux.git synced 2025-04-24 13:48:49 +08:00

MF-1340 - Add CLI config TOML file (#1858)

* Add config

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Change key names

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Add config file path

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Add config file path

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Configure TOML parsing

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Add cli config command

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Remove debug log

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Use snake case

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Change from interactive command

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* use map for keys

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Add cli logger level

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Use mainflux logger

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Fix cli

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Remove unnecessary comments

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Fix cli error handling

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Remove fmt

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Update config

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Fix cli

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Fix cli

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Fix cli

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Fix cli

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Modify CLI

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Fix errors

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Fix cli

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Fix errors

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Remove user token

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Remove unused variables

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Add empty line

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Add url parsing

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* TEsts

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Make config path configurable

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Fix ci

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Remove empty toml

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Change url key identification

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Remove url parsing functions

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Handle parse error

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Handle url error

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Add marshal

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Update config

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Fix cli

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Handle file error

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Handle file error

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Modify url parsing

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Add usertoken

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Fix user token

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Fix errors

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Fix errors

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Remove string init

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Fix error

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Fix errors

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Remove config.toml from root

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Add empty line to config.toml

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Add empty line to config.toml

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Inline error handling

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Remove unnecessary type conversion

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Fix error handling

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Fix error handling

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

* Remove dynamic filters

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>

---------

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>
This commit is contained in:
Washington Kigani Kamadi 2023-08-08 13:01:59 +03:00 committed by GitHub
parent ca52a5a38d
commit 0f0d761a1b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 260 additions and 66 deletions

View File

@ -48,8 +48,8 @@ var cmdBootstrap = []cobra.Command{
return
}
pageMetadata := mfxsdk.PageMetadata{
Offset: uint64(Offset),
Limit: uint64(Limit),
Offset: Offset,
Limit: Limit,
State: State,
Name: Name,
}

View File

@ -57,8 +57,8 @@ var cmdChannels = []cobra.Command{
}
pageMetadata := mfxsdk.PageMetadata{
Name: "",
Offset: uint64(Offset),
Limit: uint64(Limit),
Offset: Offset,
Limit: Limit,
Metadata: metadata,
}
@ -116,8 +116,8 @@ var cmdChannels = []cobra.Command{
return
}
pm := mfxsdk.PageMetadata{
Offset: uint64(Offset),
Limit: uint64(Limit),
Offset: Offset,
Limit: Limit,
}
cl, err := sdk.ThingsByChannel(args[0], pm, args[1])
if err != nil {

View File

@ -4,65 +4,240 @@
package cli
import (
"fmt"
"io"
"net/url"
"os"
"reflect"
"strconv"
"strings"
"github.com/mainflux/mainflux/pkg/errors"
mfxsdk "github.com/mainflux/mainflux/pkg/sdk/go"
"github.com/pelletier/go-toml"
"github.com/spf13/cobra"
)
type Config struct {
Offset uint `toml:"offset"`
Limit uint `toml:"limit"`
Name string `toml:"name"`
RawOutput bool `toml:"raw_output"`
type remotes struct {
ThingsURL string `toml:"things_url"`
UsersURL string `toml:"users_url"`
ReaderURL string `toml:"reader_url"`
HTTPAdapterURL string `toml:"http_adapter_url"`
BootstrapURL string `toml:"bootstrap_url"`
CertsURL string `toml:"certs_url"`
TLSVerification bool `toml:"tls_verification"`
}
// read - retrieve config from a file.
func read(file string) (Config, error) {
data, err := os.ReadFile(file)
c := Config{}
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 = 0644
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.New(fmt.Sprintf("failed to read config file: %s", err))
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(data, &c); err != nil {
return Config{}, errors.New(fmt.Sprintf("failed to unmarshal config TOML: %s", err))
if err := toml.Unmarshal(buf, &c); err != nil {
return config{}, err
}
return c, nil
}
func ParseConfig() error {
// ParseConfig - parses the config file.
func ParseConfig(sdkConf mfxsdk.Config) (mfxsdk.Config, error) {
if ConfigPath == "" {
// No config file
return nil
ConfigPath = defaultConfigPath
}
if _, err := os.Stat(ConfigPath); os.IsNotExist(err) {
errConfigNotFound := errors.Wrap(errors.New("config file was not found"), err)
logError(errConfigNotFound)
return nil
_, err := os.Stat(ConfigPath)
switch {
// If the file does not exist, create it with default values.
case os.IsNotExist(err):
defaultConfig := config{
Remotes: remotes{
ThingsURL: "http://localhost:9000",
UsersURL: "http://localhost:9002",
ReaderURL: "http://localhost",
HTTPAdapterURL: "http://localhost/http:9016",
BootstrapURL: "http://localhost",
CertsURL: "https://localhost:9019",
TLSVerification: false,
},
}
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, err := strconv.ParseUint(config.Filter.Offset, 10, 64)
if err != nil {
return sdkConf, err
}
Offset = offset
}
if config.Filter.Limit != "" {
limit, err := strconv.ParseUint(config.Filter.Limit, 10, 64)
if err != nil {
return sdkConf, err
}
Limit = limit
}
if config.Filter.Topic != "" {
Topic = config.Filter.Topic
}
if config.RawOutput != "" {
rawOutput, err := strconv.ParseBool(config.RawOutput)
if err != nil {
return sdkConf, err
}
RawOutput = rawOutput
}
sdkConf.ThingsURL = config.Remotes.ThingsURL
sdkConf.UsersURL = config.Remotes.UsersURL
sdkConf.ReaderURL = config.Remotes.ReaderURL
sdkConf.HTTPAdapterURL = config.Remotes.HTTPAdapterURL
sdkConf.BootstrapURL = config.Remotes.BootstrapURL
sdkConf.CertsURL = config.Remotes.CertsURL
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 {
logUsage(cmd.Use)
return
}
if err := setConfigValue(args[0], args[1]); err != nil {
logError(err)
return
}
logOK()
},
}
}
func setConfigValue(key string, value string) error {
config, err := read(ConfigPath)
if err != nil {
return err
}
if config.Offset != 0 {
Offset = config.Offset
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)
}
}
if config.Limit != 0 {
Limit = config.Limit
var configKeyToField = map[string]interface{}{
"things_url": &config.Remotes.ThingsURL,
"users_url": &config.Remotes.UsersURL,
"reader_url": &config.Remotes.ReaderURL,
"http_adapter_url": &config.Remotes.HTTPAdapterURL,
"bootstrap_url": &config.Remotes.BootstrapURL,
"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,
}
if config.Name != "" {
Name = config.Name
fieldPtr, ok := configKeyToField[key]
if !ok {
return errNoKey
}
if config.RawOutput {
RawOutput = config.RawOutput
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
}

View File

@ -40,8 +40,8 @@ var cmdSubscription = []cobra.Command{
return
}
pageMetadata := mfxsdk.PageMetadata{
Offset: uint64(Offset),
Limit: uint64(Limit),
Offset: Offset,
Limit: Limit,
Topic: Topic,
Contact: Contact,
}

View File

@ -84,8 +84,8 @@ var cmdGroups = []cobra.Command{
return
}
pm := mfxsdk.PageMetadata{
Offset: uint64(Offset),
Limit: uint64(Limit),
Offset: Offset,
Limit: Limit,
}
l, err := sdk.Groups(pm, args[1])
if err != nil {
@ -101,8 +101,8 @@ var cmdGroups = []cobra.Command{
return
}
pm := mfxsdk.PageMetadata{
Offset: uint64(Offset),
Limit: uint64(Limit),
Offset: Offset,
Limit: Limit,
}
l, err := sdk.Children(args[1], pm, args[2])
if err != nil {
@ -118,8 +118,8 @@ var cmdGroups = []cobra.Command{
return
}
pm := mfxsdk.PageMetadata{
Offset: uint64(Offset),
Limit: uint64(Limit),
Offset: Offset,
Limit: Limit,
}
l, err := sdk.Parents(args[1], pm, args[2])
if err != nil {
@ -194,8 +194,8 @@ var cmdGroups = []cobra.Command{
return
}
pm := mfxsdk.PageMetadata{
Offset: uint64(Offset),
Limit: uint64(Limit),
Offset: Offset,
Limit: Limit,
Status: Status,
}
up, err := sdk.Members(args[0], pm, args[1])
@ -218,8 +218,8 @@ var cmdGroups = []cobra.Command{
return
}
pm := mfxsdk.PageMetadata{
Offset: uint64(Offset),
Limit: uint64(Limit),
Offset: Offset,
Limit: Limit,
}
up, err := sdk.Memberships(args[0], pm, args[1])
if err != nil {

View File

@ -111,8 +111,8 @@ var cmdPolicies = []cobra.Command{
return
}
pm := mfxsdk.PageMetadata{
Offset: uint64(Offset),
Limit: uint64(Limit),
Offset: Offset,
Limit: Limit,
}
switch args[0] {
case things:

View File

@ -58,9 +58,9 @@ var cmdThings = []cobra.Command{
return
}
pageMetadata := mfxsdk.PageMetadata{
Name: "",
Offset: uint64(Offset),
Limit: uint64(Limit),
Name: Name,
Offset: Offset,
Limit: Limit,
Metadata: metadata,
}
if args[0] == all {
@ -300,8 +300,8 @@ var cmdThings = []cobra.Command{
return
}
pm := mfxsdk.PageMetadata{
Offset: uint64(Offset),
Limit: uint64(Limit),
Offset: Offset,
Limit: Limit,
}
cl, err := sdk.ChannelsByThing(args[0], pm, args[1])
if err != nil {

View File

@ -63,9 +63,9 @@ var cmdUsers = []cobra.Command{
return
}
pageMetadata := mfxsdk.PageMetadata{
Email: "",
Offset: uint64(Offset),
Limit: uint64(Limit),
Email: Email,
Offset: Offset,
Limit: Limit,
Metadata: metadata,
Status: Status,
}

View File

@ -15,9 +15,9 @@ import (
var (
// Limit query parameter.
Limit uint = 10
Limit uint64 = 10
// Offset query parameter.
Offset uint = 0
Offset uint64 = 0
// Name query parameter.
Name string = ""
// Email query parameter.

View File

@ -39,16 +39,17 @@ func main() {
var rootCmd = &cobra.Command{
Use: "mainflux-cli",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
if err := cli.ParseConfig(); err != nil {
log.Fatal(err)
cliConf, err := cli.ParseConfig(sdkConf)
if err != nil {
log.Fatalf("Failed to parse config: %s", err)
}
sdkConf.MsgContentType = sdk.ContentType(msgContentType)
s := sdk.NewSDK(sdkConf)
if cliConf.MsgContentType == "" {
cliConf.MsgContentType = sdk.ContentType(msgContentType)
}
s := sdk.NewSDK(cliConf)
cli.SetSDK(s)
},
}
// API commands
healthCmd := cli.NewHealthCmd()
usersCmd := cli.NewUsersCmd()
@ -61,6 +62,7 @@ func main() {
certsCmd := cli.NewCertsCmd()
subscriptionsCmd := cli.NewSubscriptionCmd()
policiesCmd := cli.NewPolicyCmd()
configCmd := cli.NewConfigCmd()
// Root Commands
rootCmd.AddCommand(healthCmd)
@ -74,6 +76,7 @@ func main() {
rootCmd.AddCommand(certsCmd)
rootCmd.AddCommand(subscriptionsCmd)
rootCmd.AddCommand(policiesCmd)
rootCmd.AddCommand(configCmd)
// Root Flags
rootCmd.PersistentFlags().StringVarP(
@ -165,7 +168,7 @@ func main() {
)
// Client and Channels Flags
rootCmd.PersistentFlags().UintVarP(
rootCmd.PersistentFlags().Uint64VarP(
&cli.Limit,
"limit",
"l",
@ -173,7 +176,7 @@ func main() {
"Limit query parameter",
)
rootCmd.PersistentFlags().UintVarP(
rootCmd.PersistentFlags().Uint64VarP(
&cli.Offset,
"offset",
"o",

16
config.toml Normal file
View File

@ -0,0 +1,16 @@
raw_output = ""
user_token = ""
[filter]
limit = ""
offset = ""
topic = ""
[remotes]
bootstrap_url = "http://localhost"
certs_url = "https://localhost:9019"
http_adapter_url = "http://localhost/http:9016"
reader_url = "http://localhost"
things_url = "http://localhost:9000"
tls_verification = false
users_url = "http://localhost:9002"