mirror of
https://github.com/absmach/supermq.git
synced 2026-06-23 04:10:34 +00:00
NOISSUE - Add Magistrala CLI (#40)
Signed-off-by: Felix Gateru <felix.gateru@gmail.com>
This commit is contained in:
@@ -68,6 +68,11 @@ jobs:
|
||||
- "pkg/sdk/**"
|
||||
- "pkg/events/**"
|
||||
|
||||
cli:
|
||||
- "cli/**"
|
||||
- "cmd/cli/**"
|
||||
- "pkg/sdk/**"
|
||||
|
||||
consumers:
|
||||
- "consumers/**"
|
||||
- "cmd/postgres-writer/**"
|
||||
@@ -109,6 +114,11 @@ jobs:
|
||||
run: |
|
||||
go test --race -v -count=1 -coverprofile=coverage/bootstrap.out ./bootstrap/...
|
||||
|
||||
- name: Run cli tests
|
||||
if: steps.changes.outputs.cli == 'true' || steps.changes.outputs.workflow == 'true'
|
||||
run: |
|
||||
go test --race -v -count=1 -coverprofile=coverage/cli.out ./cli/...
|
||||
|
||||
- name: Run consumers tests
|
||||
if: steps.changes.outputs.consumers == 'true' || steps.changes.outputs.workflow == 'true'
|
||||
run: |
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
MG_DOCKER_IMAGE_NAME_PREFIX ?= ghcr.io/absmach/magistrala
|
||||
BUILD_DIR = build
|
||||
SERVICES = bootstrap provision re postgres-writer postgres-reader timescale-writer timescale-reader
|
||||
SERVICES = bootstrap provision re postgres-writer postgres-reader timescale-writer timescale-reader cli
|
||||
DOCKERS = $(addprefix docker_,$(SERVICES))
|
||||
DOCKERS_DEV = $(addprefix docker_dev_,$(SERVICES))
|
||||
CGO_ENABLED ?= 0
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
mgsdk "github.com/absmach/magistrala/pkg/sdk"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var cmdBootstrap = []cobra.Command{
|
||||
{
|
||||
Use: "create <JSON_config> <domain_id> <user_auth_token>",
|
||||
Short: "Create config",
|
||||
Long: `Create new Client Bootstrap Config to the user identified by the provided key`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 3 {
|
||||
logUsageCmd(*cmd, cmd.Use)
|
||||
return
|
||||
}
|
||||
|
||||
var cfg mgsdk.BootstrapConfig
|
||||
if err := json.Unmarshal([]byte(args[0]), &cfg); err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
|
||||
id, err := sdk.AddBootstrap(cfg, args[1], args[2])
|
||||
if err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
|
||||
logCreatedCmd(*cmd, id)
|
||||
},
|
||||
},
|
||||
{
|
||||
Use: "get [all | <client_id>] <domain_id> <user_auth_token>",
|
||||
Short: "Get config",
|
||||
Long: `Get Client Config with given ID belonging to the user identified by the given key.
|
||||
all - lists all config
|
||||
<client_id> - view config of <client_id>`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 3 {
|
||||
logUsageCmd(*cmd, cmd.Use)
|
||||
return
|
||||
}
|
||||
pageMetadata := mgsdk.PageMetadata{
|
||||
Offset: Offset,
|
||||
Limit: Limit,
|
||||
State: State,
|
||||
Name: Name,
|
||||
}
|
||||
if args[0] == "all" {
|
||||
l, err := sdk.Bootstraps(pageMetadata, args[1], args[2])
|
||||
if err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
logJSONCmd(*cmd, l)
|
||||
return
|
||||
}
|
||||
|
||||
c, err := sdk.ViewBootstrap(args[0], args[1], args[2])
|
||||
if err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
|
||||
logJSONCmd(*cmd, c)
|
||||
},
|
||||
},
|
||||
{
|
||||
Use: "update [config <JSON_config> | connection <id> <channel_ids> | certs <id> <client_cert> <client_key> <ca> ] <domain_id> <user_auth_token>",
|
||||
Short: "Update config",
|
||||
Long: `Updates editable fields of the provided Config.
|
||||
config <JSON_config> - Updates editable fields of the provided Config.
|
||||
connection <id> <channel_ids> - Updates connections performs update of the channel list corresponding Client is connected to.
|
||||
channel_ids - '["channel_id1", ...]'
|
||||
certs <id> <client_cert> <client_key> <ca> - Update bootstrap config certificates.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) < 4 {
|
||||
logUsageCmd(*cmd, cmd.Use)
|
||||
return
|
||||
}
|
||||
if args[0] == "config" {
|
||||
var cfg mgsdk.BootstrapConfig
|
||||
if err := json.Unmarshal([]byte(args[1]), &cfg); err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := sdk.UpdateBootstrap(cfg, args[1], args[2]); err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
|
||||
logOKCmd(*cmd)
|
||||
return
|
||||
}
|
||||
if args[0] == "connection" {
|
||||
var ids []string
|
||||
if err := json.Unmarshal([]byte(args[2]), &ids); err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
if err := sdk.UpdateBootstrapConnection(args[1], ids, args[3], args[4]); err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
|
||||
logOKCmd(*cmd)
|
||||
return
|
||||
}
|
||||
if args[0] == "certs" {
|
||||
cfg, err := sdk.UpdateBootstrapCerts(args[0], args[1], args[2], args[3], args[4], args[5])
|
||||
if err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
|
||||
logJSONCmd(*cmd, cfg)
|
||||
return
|
||||
}
|
||||
logUsageCmd(*cmd, cmd.Use)
|
||||
},
|
||||
},
|
||||
{
|
||||
Use: "remove <client_id> <domain_id> <user_auth_token>",
|
||||
Short: "Remove config",
|
||||
Long: `Removes Config with specified key that belongs to the user identified by the given key`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 3 {
|
||||
logUsageCmd(*cmd, cmd.Use)
|
||||
return
|
||||
}
|
||||
|
||||
if err := sdk.RemoveBootstrap(args[0], args[1], args[2]); err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
|
||||
logOKCmd(*cmd)
|
||||
},
|
||||
},
|
||||
{
|
||||
Use: "bootstrap [<external_id> <external_key> | secure <external_id> <external_key> <crypto_key> ]",
|
||||
Short: "Bootstrap config",
|
||||
Long: `Returns Config to the Client with provided external ID using external key.
|
||||
secure - Retrieves a configuration with given external ID and encrypted external key.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) < 2 {
|
||||
logUsageCmd(*cmd, cmd.Use)
|
||||
return
|
||||
}
|
||||
if args[0] == "secure" {
|
||||
c, err := sdk.BootstrapSecure(args[1], args[2], args[3])
|
||||
if err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
|
||||
logJSONCmd(*cmd, c)
|
||||
return
|
||||
}
|
||||
c, err := sdk.Bootstrap(args[0], args[1])
|
||||
if err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
|
||||
logJSONCmd(*cmd, c)
|
||||
},
|
||||
},
|
||||
{
|
||||
Use: "whitelist <JSON_config> <domain_id> <user_auth_token>",
|
||||
Short: "Whitelist config",
|
||||
Long: `Whitelist updates client state config with given id from the authenticated user`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 3 {
|
||||
logUsageCmd(*cmd, cmd.Use)
|
||||
return
|
||||
}
|
||||
|
||||
var cfg mgsdk.BootstrapConfig
|
||||
if err := json.Unmarshal([]byte(args[0]), &cfg); err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := sdk.Whitelist(cfg.ClientID, cfg.State, args[1], args[2]); err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
|
||||
logOKCmd(*cmd)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// NewBootstrapCmd returns bootstrap command.
|
||||
func NewBootstrapCmd() *cobra.Command {
|
||||
cmd := cobra.Command{
|
||||
Use: "bootstrap [create | get | update | remove | bootstrap | whitelist]",
|
||||
Short: "Bootstrap management",
|
||||
Long: `Bootstrap management: create, get, update, delete or whitelist Bootstrap config`,
|
||||
}
|
||||
|
||||
for i := range cmdBootstrap {
|
||||
cmd.AddCommand(&cmdBootstrap[i])
|
||||
}
|
||||
|
||||
return &cmd
|
||||
}
|
||||
@@ -0,0 +1,633 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cli_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/absmach/magistrala/cli"
|
||||
"github.com/absmach/magistrala/internal/testsutil"
|
||||
mgsdk "github.com/absmach/magistrala/pkg/sdk"
|
||||
sdkmocks "github.com/absmach/magistrala/pkg/sdk/mocks"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
svcerr "github.com/absmach/supermq/pkg/errors/service"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
var (
|
||||
clientID = testsutil.GenerateUUID(&testing.T{})
|
||||
channelID = testsutil.GenerateUUID(&testing.T{})
|
||||
domainID = testsutil.GenerateUUID(&testing.T{})
|
||||
bootConfig = mgsdk.BootstrapConfig{
|
||||
ClientID: clientID,
|
||||
Channels: []string{channelID},
|
||||
Name: "Test Bootstrap",
|
||||
ExternalID: "09:6:0:sb:sa",
|
||||
ExternalKey: "key",
|
||||
}
|
||||
validToken = "validToken"
|
||||
invalidToken = "invalidToken"
|
||||
extraArg = "extra-arg"
|
||||
invalidID = "invalidID"
|
||||
all = "all"
|
||||
)
|
||||
|
||||
func TestCreateBootstrapConfigCmd(t *testing.T) {
|
||||
sdkMock := new(sdkmocks.SDK)
|
||||
cli.SetSDK(sdkMock)
|
||||
bootCmd := cli.NewBootstrapCmd()
|
||||
rootCmd := setFlags(bootCmd)
|
||||
|
||||
jsonConfig := fmt.Sprintf("{\"external_id\":\"09:6:0:sb:sa\", \"client_id\": \"%s\", \"external_key\":\"key\", \"name\": \"%s\", \"channels\":[\"%s\"]}", clientID, "Test Bootstrap", channelID)
|
||||
invalidJson := fmt.Sprintf("{\"external_id\":\"09:6:0:sb:sa\", \"client_id\": \"%s\", \"external_key\":\"key\", \"name\": \"%s\", \"channels\":[\"%s\"]", clientID, "Test Bootdtrap", channelID)
|
||||
cases := []struct {
|
||||
desc string
|
||||
args []string
|
||||
logType outputLog
|
||||
response string
|
||||
sdkErr errors.SDKError
|
||||
errLogMessage string
|
||||
id string
|
||||
}{
|
||||
{
|
||||
desc: "create bootstrap config successfully",
|
||||
args: []string{
|
||||
jsonConfig,
|
||||
domainID,
|
||||
validToken,
|
||||
},
|
||||
logType: createLog,
|
||||
id: clientID,
|
||||
response: fmt.Sprintf("\ncreated: %s\n\n", clientID),
|
||||
},
|
||||
{
|
||||
desc: "create bootstrap config with invald args",
|
||||
args: []string{
|
||||
jsonConfig,
|
||||
domainID,
|
||||
validToken,
|
||||
extraArg,
|
||||
},
|
||||
logType: usageLog,
|
||||
},
|
||||
{
|
||||
desc: "create bootstrap config with invald json",
|
||||
args: []string{
|
||||
invalidJson,
|
||||
domainID,
|
||||
validToken,
|
||||
},
|
||||
sdkErr: errors.NewSDKError(errors.New("unexpected end of JSON input")),
|
||||
errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.New("unexpected end of JSON input")),
|
||||
logType: errLog,
|
||||
},
|
||||
{
|
||||
desc: "create bootstrap config with invald token",
|
||||
args: []string{
|
||||
jsonConfig,
|
||||
domainID,
|
||||
invalidToken,
|
||||
},
|
||||
sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusUnauthorized),
|
||||
errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusUnauthorized)),
|
||||
logType: errLog,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
sdkCall := sdkMock.On("AddBootstrap", mock.Anything, mock.Anything, mock.Anything).Return(tc.id, tc.sdkErr)
|
||||
out := executeCommand(t, rootCmd, append([]string{createCmd}, tc.args...)...)
|
||||
|
||||
switch tc.logType {
|
||||
case createLog:
|
||||
assert.Equal(t, tc.response, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.response, out))
|
||||
case errLog:
|
||||
assert.Equal(t, tc.errLogMessage, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.errLogMessage, out))
|
||||
case usageLog:
|
||||
assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out))
|
||||
}
|
||||
sdkCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetBootstrapConfigCmd(t *testing.T) {
|
||||
sdkMock := new(sdkmocks.SDK)
|
||||
cli.SetSDK(sdkMock)
|
||||
bootCmd := cli.NewBootstrapCmd()
|
||||
rootCmd := setFlags(bootCmd)
|
||||
|
||||
var boot mgsdk.BootstrapConfig
|
||||
var page mgsdk.BootstrapPage
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
args []string
|
||||
sdkErr errors.SDKError
|
||||
page mgsdk.BootstrapPage
|
||||
boot mgsdk.BootstrapConfig
|
||||
logType outputLog
|
||||
errLogMessage string
|
||||
}{
|
||||
{
|
||||
desc: "get all bootstrap config successfully",
|
||||
args: []string{
|
||||
all,
|
||||
domainID,
|
||||
validToken,
|
||||
},
|
||||
page: mgsdk.BootstrapPage{
|
||||
PageRes: mgsdk.PageRes{
|
||||
Total: 1,
|
||||
Offset: 0,
|
||||
Limit: 10,
|
||||
},
|
||||
Configs: []mgsdk.BootstrapConfig{bootConfig},
|
||||
},
|
||||
logType: entityLog,
|
||||
},
|
||||
{
|
||||
desc: "get bootstrap config with id",
|
||||
args: []string{
|
||||
channelID,
|
||||
domainID,
|
||||
validToken,
|
||||
},
|
||||
logType: entityLog,
|
||||
boot: bootConfig,
|
||||
},
|
||||
{
|
||||
desc: "get bootstrap config with invalid args",
|
||||
args: []string{
|
||||
all,
|
||||
domainID,
|
||||
validToken,
|
||||
extraArg,
|
||||
},
|
||||
logType: usageLog,
|
||||
},
|
||||
{
|
||||
desc: "get all bootstrap config with invalid token",
|
||||
args: []string{
|
||||
all,
|
||||
domainID,
|
||||
invalidToken,
|
||||
},
|
||||
logType: errLog,
|
||||
sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden),
|
||||
errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)),
|
||||
},
|
||||
{
|
||||
desc: "get bootstrap config with invalid id",
|
||||
args: []string{
|
||||
invalidID,
|
||||
domainID,
|
||||
validToken,
|
||||
},
|
||||
sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden),
|
||||
errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)),
|
||||
logType: errLog,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
sdkCall := sdkMock.On("ViewBootstrap", tc.args[0], tc.args[1], tc.args[2]).Return(tc.boot, tc.sdkErr)
|
||||
sdkCall1 := sdkMock.On("Bootstraps", mock.Anything, tc.args[1], tc.args[2]).Return(tc.page, tc.sdkErr)
|
||||
|
||||
out := executeCommand(t, rootCmd, append([]string{getCmd}, tc.args...)...)
|
||||
|
||||
switch tc.logType {
|
||||
case entityLog:
|
||||
if tc.args[0] == all {
|
||||
err := json.Unmarshal([]byte(out), &page)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, tc.page, page, fmt.Sprintf("%v unexpected response, expected: %v, got: %v", tc.desc, tc.page, page))
|
||||
} else {
|
||||
err := json.Unmarshal([]byte(out), &boot)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, tc.boot, boot, fmt.Sprintf("%v unexpected response, expected: %v, got: %v", tc.desc, tc.boot, boot))
|
||||
}
|
||||
case errLog:
|
||||
assert.Equal(t, tc.errLogMessage, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.errLogMessage, out))
|
||||
case usageLog:
|
||||
assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out))
|
||||
}
|
||||
sdkCall.Unset()
|
||||
sdkCall1.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveBootstrapConfigCmd(t *testing.T) {
|
||||
sdkMock := new(sdkmocks.SDK)
|
||||
cli.SetSDK(sdkMock)
|
||||
bootCmd := cli.NewBootstrapCmd()
|
||||
rootCmd := setFlags(bootCmd)
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
args []string
|
||||
sdkErr errors.SDKError
|
||||
logType outputLog
|
||||
errLogMessage string
|
||||
}{
|
||||
{
|
||||
desc: "remove bootstrap config successfully",
|
||||
args: []string{
|
||||
clientID,
|
||||
domainID,
|
||||
validToken,
|
||||
},
|
||||
logType: okLog,
|
||||
},
|
||||
{
|
||||
desc: "remove bootstrap config with invalid args",
|
||||
args: []string{
|
||||
clientID,
|
||||
domainID,
|
||||
validToken,
|
||||
extraArg,
|
||||
},
|
||||
logType: usageLog,
|
||||
},
|
||||
{
|
||||
desc: "remove bootstrap config with invalid client id",
|
||||
args: []string{
|
||||
invalidID,
|
||||
domainID,
|
||||
validToken,
|
||||
},
|
||||
sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden),
|
||||
errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)),
|
||||
logType: errLog,
|
||||
},
|
||||
{
|
||||
desc: "remove bootstrap config with invalid token",
|
||||
args: []string{
|
||||
clientID,
|
||||
domainID,
|
||||
invalidToken,
|
||||
},
|
||||
sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden),
|
||||
errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)),
|
||||
logType: errLog,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
sdkCall := sdkMock.On("RemoveBootstrap", tc.args[0], tc.args[1], tc.args[2]).Return(tc.sdkErr)
|
||||
out := executeCommand(t, rootCmd, append([]string{rmCmd}, tc.args...)...)
|
||||
|
||||
switch tc.logType {
|
||||
case okLog:
|
||||
assert.True(t, strings.Contains(out, "ok"), fmt.Sprintf("%s unexpected response: expected success message, got: %v", tc.desc, out))
|
||||
case errLog:
|
||||
assert.Equal(t, tc.errLogMessage, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.errLogMessage, out))
|
||||
case usageLog:
|
||||
assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out))
|
||||
}
|
||||
sdkCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateBootstrapConfigCmd(t *testing.T) {
|
||||
sdkMock := new(sdkmocks.SDK)
|
||||
cli.SetSDK(sdkMock)
|
||||
bootCmd := cli.NewBootstrapCmd()
|
||||
rootCmd := setFlags(bootCmd)
|
||||
|
||||
config := "config"
|
||||
connection := "connection"
|
||||
|
||||
newConfigJson := "{\"name\" : \"New Bootstrap\"}"
|
||||
chanIDsJson := fmt.Sprintf("[\"%s\"]", channelID)
|
||||
cases := []struct {
|
||||
desc string
|
||||
args []string
|
||||
boot mgsdk.BootstrapConfig
|
||||
sdkErr errors.SDKError
|
||||
errLogMessage string
|
||||
logType outputLog
|
||||
}{
|
||||
{
|
||||
desc: "update bootstrap config successfully",
|
||||
args: []string{
|
||||
config,
|
||||
newConfigJson,
|
||||
domainID,
|
||||
validToken,
|
||||
},
|
||||
logType: okLog,
|
||||
},
|
||||
{
|
||||
desc: "update bootstrap config with invalid token",
|
||||
args: []string{
|
||||
config,
|
||||
newConfigJson,
|
||||
domainID,
|
||||
invalidToken,
|
||||
},
|
||||
logType: errLog,
|
||||
sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden),
|
||||
errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)),
|
||||
},
|
||||
{
|
||||
desc: "update bootstrap connections successfully",
|
||||
args: []string{
|
||||
connection,
|
||||
clientID,
|
||||
chanIDsJson,
|
||||
domainID,
|
||||
validToken,
|
||||
},
|
||||
logType: okLog,
|
||||
},
|
||||
{
|
||||
desc: "update bootstrap connections with invalid json",
|
||||
args: []string{
|
||||
connection,
|
||||
clientID,
|
||||
fmt.Sprintf("[\"%s\"", clientID),
|
||||
domainID,
|
||||
validToken,
|
||||
},
|
||||
sdkErr: errors.NewSDKError(errors.New("unexpected end of JSON input")),
|
||||
errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.New("unexpected end of JSON input")),
|
||||
logType: errLog,
|
||||
},
|
||||
{
|
||||
desc: "update bootstrap connections with invalid token",
|
||||
args: []string{
|
||||
connection,
|
||||
clientID,
|
||||
chanIDsJson,
|
||||
domainID,
|
||||
invalidToken,
|
||||
},
|
||||
logType: errLog,
|
||||
sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden),
|
||||
errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)),
|
||||
},
|
||||
{
|
||||
desc: "update bootstrap certs successfully",
|
||||
args: []string{
|
||||
"certs",
|
||||
clientID,
|
||||
"client cert",
|
||||
"client key",
|
||||
"ca",
|
||||
domainID,
|
||||
validToken,
|
||||
},
|
||||
boot: bootConfig,
|
||||
logType: entityLog,
|
||||
},
|
||||
{
|
||||
desc: "update bootstrap certs with invalid token",
|
||||
args: []string{
|
||||
"certs",
|
||||
clientID,
|
||||
"client cert",
|
||||
"client key",
|
||||
"ca",
|
||||
domainID,
|
||||
invalidToken,
|
||||
},
|
||||
logType: errLog,
|
||||
sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden),
|
||||
errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)),
|
||||
},
|
||||
{
|
||||
desc: "update bootstrap config with invalid args",
|
||||
args: []string{
|
||||
newConfigJson,
|
||||
domainID,
|
||||
validToken,
|
||||
},
|
||||
logType: usageLog,
|
||||
},
|
||||
{
|
||||
desc: "update bootstrap config with invalid json",
|
||||
args: []string{
|
||||
config,
|
||||
"{\"name\" : \"New Bootstrap\"",
|
||||
domainID,
|
||||
validToken,
|
||||
},
|
||||
sdkErr: errors.NewSDKError(errors.New("unexpected end of JSON input")),
|
||||
errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.New("unexpected end of JSON input")),
|
||||
logType: errLog,
|
||||
},
|
||||
{
|
||||
desc: "update bootstrap with invalid args",
|
||||
args: []string{
|
||||
extraArg,
|
||||
extraArg,
|
||||
extraArg,
|
||||
extraArg,
|
||||
extraArg,
|
||||
},
|
||||
logType: usageLog,
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
var boot mgsdk.BootstrapConfig
|
||||
sdkCall := sdkMock.On("UpdateBootstrap", mock.Anything, mock.Anything, mock.Anything).Return(tc.sdkErr)
|
||||
sdkCall1 := sdkMock.On("UpdateBootstrapConnection", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.sdkErr)
|
||||
sdkCall2 := sdkMock.On("UpdateBootstrapCerts", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.boot, tc.sdkErr)
|
||||
out := executeCommand(t, rootCmd, append([]string{updCmd}, tc.args...)...)
|
||||
|
||||
switch tc.logType {
|
||||
case entityLog:
|
||||
err := json.Unmarshal([]byte(out), &boot)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, tc.boot, boot, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.boot, boot))
|
||||
case okLog:
|
||||
assert.True(t, strings.Contains(out, "ok"), fmt.Sprintf("%s unexpected response: expected success message, got: %v", tc.desc, out))
|
||||
case usageLog:
|
||||
assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out))
|
||||
case errLog:
|
||||
assert.Equal(t, tc.errLogMessage, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.errLogMessage, out))
|
||||
}
|
||||
sdkCall.Unset()
|
||||
sdkCall1.Unset()
|
||||
sdkCall2.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWhitelistConfigCmd(t *testing.T) {
|
||||
sdkMock := new(sdkmocks.SDK)
|
||||
cli.SetSDK(sdkMock)
|
||||
bootCmd := cli.NewBootstrapCmd()
|
||||
rootCmd := setFlags(bootCmd)
|
||||
|
||||
jsonConfig := fmt.Sprintf("{\"client_id\": \"%s\", \"state\":%d}", clientID, 1)
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
args []string
|
||||
logType outputLog
|
||||
errLogMessage string
|
||||
sdkErr errors.SDKError
|
||||
}{
|
||||
{
|
||||
desc: "whitelist config successfully",
|
||||
args: []string{
|
||||
jsonConfig,
|
||||
domainID,
|
||||
validToken,
|
||||
},
|
||||
logType: okLog,
|
||||
},
|
||||
{
|
||||
desc: "whitelist config with invalid args",
|
||||
args: []string{
|
||||
jsonConfig,
|
||||
domainID,
|
||||
validToken,
|
||||
extraArg,
|
||||
},
|
||||
logType: usageLog,
|
||||
},
|
||||
{
|
||||
desc: "whitelist config with invalid json",
|
||||
args: []string{
|
||||
fmt.Sprintf("{\"client_id\": \"%s\", \"state\":%d", clientID, 1),
|
||||
domainID,
|
||||
validToken,
|
||||
},
|
||||
sdkErr: errors.NewSDKError(errors.New("unexpected end of JSON input")),
|
||||
errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.New("unexpected end of JSON input")),
|
||||
logType: errLog,
|
||||
},
|
||||
{
|
||||
desc: "whitelist config with invalid token",
|
||||
args: []string{
|
||||
jsonConfig,
|
||||
domainID,
|
||||
invalidToken,
|
||||
},
|
||||
sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusUnauthorized),
|
||||
errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusUnauthorized)),
|
||||
logType: errLog,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
sdkCall := sdkMock.On("Whitelist", mock.Anything, mock.Anything, tc.args[1], tc.args[2]).Return(tc.sdkErr)
|
||||
out := executeCommand(t, rootCmd, append([]string{whitelistCmd}, tc.args...)...)
|
||||
switch tc.logType {
|
||||
case okLog:
|
||||
assert.True(t, strings.Contains(out, "ok"), fmt.Sprintf("%s unexpected response: expected success message, got: %v", tc.desc, out))
|
||||
case usageLog:
|
||||
assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out))
|
||||
case errLog:
|
||||
assert.Equal(t, tc.errLogMessage, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.errLogMessage, out))
|
||||
}
|
||||
sdkCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBootstrapConfigCmd(t *testing.T) {
|
||||
sdkMock := new(sdkmocks.SDK)
|
||||
cli.SetSDK(sdkMock)
|
||||
bootCmd := cli.NewBootstrapCmd()
|
||||
rootCmd := setFlags(bootCmd)
|
||||
|
||||
var boot mgsdk.BootstrapConfig
|
||||
crptoKey := "v7aT0HGxJxt2gULzr3RHwf4WIf6DusPp"
|
||||
invalidKey := "invalid key"
|
||||
cases := []struct {
|
||||
desc string
|
||||
args []string
|
||||
logType outputLog
|
||||
errLogMessage string
|
||||
sdkErr errors.SDKError
|
||||
boot mgsdk.BootstrapConfig
|
||||
}{
|
||||
{
|
||||
desc: "bootstrap secure config successfully",
|
||||
args: []string{
|
||||
"secure",
|
||||
bootConfig.ExternalID,
|
||||
bootConfig.ExternalKey,
|
||||
crptoKey,
|
||||
},
|
||||
boot: bootConfig,
|
||||
logType: entityLog,
|
||||
},
|
||||
{
|
||||
desc: "bootstrap config successfully",
|
||||
args: []string{
|
||||
bootConfig.ExternalID,
|
||||
bootConfig.ExternalKey,
|
||||
},
|
||||
boot: bootConfig,
|
||||
logType: entityLog,
|
||||
},
|
||||
{
|
||||
desc: "bootstrap secure config with invalid args",
|
||||
args: []string{
|
||||
crptoKey,
|
||||
},
|
||||
|
||||
logType: usageLog,
|
||||
},
|
||||
{
|
||||
desc: "bootstrap secure config with invalid key",
|
||||
args: []string{
|
||||
"secure",
|
||||
bootConfig.ExternalID,
|
||||
invalidKey,
|
||||
crptoKey,
|
||||
},
|
||||
sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusUnauthorized),
|
||||
errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusUnauthorized)),
|
||||
logType: errLog,
|
||||
},
|
||||
{
|
||||
desc: "bootstrap config with invalid key",
|
||||
args: []string{
|
||||
bootConfig.ExternalID,
|
||||
invalidKey,
|
||||
},
|
||||
sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusUnauthorized),
|
||||
errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusUnauthorized)),
|
||||
logType: errLog,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
sdkCall := sdkMock.On("BootstrapSecure", mock.Anything, mock.Anything, mock.Anything).Return(tc.boot, tc.sdkErr)
|
||||
sdkCall1 := sdkMock.On("Bootstrap", mock.Anything, mock.Anything).Return(tc.boot, tc.sdkErr)
|
||||
out := executeCommand(t, rootCmd, append([]string{bootStrapCmd}, tc.args...)...)
|
||||
switch tc.logType {
|
||||
case entityLog:
|
||||
err := json.Unmarshal([]byte(out), &boot)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, tc.boot, boot, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.boot, boot))
|
||||
case usageLog:
|
||||
assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out))
|
||||
case errLog:
|
||||
assert.Equal(t, tc.errLogMessage, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.errLogMessage, out))
|
||||
}
|
||||
sdkCall.Unset()
|
||||
sdkCall1.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cli_test
|
||||
|
||||
// CRUD and common commands
|
||||
const (
|
||||
createCmd = "create"
|
||||
updateCmd = "update"
|
||||
getCmd = "get"
|
||||
enableCmd = "enable"
|
||||
disableCmd = "disable"
|
||||
updCmd = "update"
|
||||
delCmd = "delete"
|
||||
rmCmd = "remove"
|
||||
whitelistCmd = "whitelist"
|
||||
bootStrapCmd = "bootstrap"
|
||||
)
|
||||
+319
@@ -0,0 +1,319 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
mgsdk "github.com/absmach/magistrala/pkg/sdk"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
"github.com/pelletier/go-toml"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const (
|
||||
defURL string = "http://localhost"
|
||||
defGroupsURL string = defURL + ":9004"
|
||||
defUsersURL string = defURL + ":9002"
|
||||
defChannelsURL string = defURL + ":9005"
|
||||
defClientsURL string = defURL + ":9006"
|
||||
defReaderURL string = defURL + ":9011"
|
||||
defBootstrapURL string = defURL + ":9013"
|
||||
defDomainsURL string = defURL + ":9003"
|
||||
defCertsURL string = defURL + ":9019"
|
||||
defInvitationsURL string = defURL + ":9020"
|
||||
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 {
|
||||
ChannelsURL string `toml:"channels_url"`
|
||||
ClientsURL string `toml:"clients_url"`
|
||||
GroupsURL string `toml:"groups_url"`
|
||||
UsersURL string `toml:"users_url"`
|
||||
ReaderURL string `toml:"reader_url"`
|
||||
DomainsURL string `toml:"domains_url"`
|
||||
HTTPAdapterURL string `toml:"http_adapter_url"`
|
||||
BootstrapURL string `toml:"bootstrap_url"`
|
||||
CertsURL string `toml:"certs_url"`
|
||||
InvitationsURL string `toml:"invitations_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 mgsdk.Config) (mgsdk.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{
|
||||
ChannelsURL: defChannelsURL,
|
||||
ClientsURL: defClientsURL,
|
||||
GroupsURL: defGroupsURL,
|
||||
UsersURL: defUsersURL,
|
||||
ReaderURL: defReaderURL,
|
||||
DomainsURL: defDomainsURL,
|
||||
HTTPAdapterURL: defHTTPURL,
|
||||
BootstrapURL: defBootstrapURL,
|
||||
CertsURL: defCertsURL,
|
||||
InvitationsURL: defInvitationsURL,
|
||||
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.ReaderURL == "" && config.Remotes.ReaderURL != "" {
|
||||
sdkConf.ReaderURL = config.Remotes.ReaderURL
|
||||
}
|
||||
|
||||
if sdkConf.DomainsURL == "" && config.Remotes.DomainsURL != "" {
|
||||
sdkConf.DomainsURL = config.Remotes.DomainsURL
|
||||
}
|
||||
|
||||
if sdkConf.HTTPAdapterURL == "" && config.Remotes.HTTPAdapterURL != "" {
|
||||
sdkConf.HTTPAdapterURL = config.Remotes.HTTPAdapterURL
|
||||
}
|
||||
|
||||
if sdkConf.BootstrapURL == "" && config.Remotes.BootstrapURL != "" {
|
||||
sdkConf.BootstrapURL = config.Remotes.BootstrapURL
|
||||
}
|
||||
|
||||
if sdkConf.CertsURL == "" && config.Remotes.CertsURL != "" {
|
||||
sdkConf.CertsURL = config.Remotes.CertsURL
|
||||
}
|
||||
|
||||
if sdkConf.InvitationsURL == "" && config.Remotes.InvitationsURL != "" {
|
||||
sdkConf.InvitationsURL = config.Remotes.InvitationsURL
|
||||
}
|
||||
|
||||
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]interface{}{
|
||||
"channels_url": &config.Remotes.ChannelsURL,
|
||||
"clients_url": &config.Remotes.ClientsURL,
|
||||
"groups_url": &config.Remotes.GroupsURL,
|
||||
"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,
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
mgsdk "github.com/absmach/magistrala/pkg/sdk"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var cmdSubscription = []cobra.Command{
|
||||
{
|
||||
Use: "create <topic> <contact> <user_auth_token>",
|
||||
Short: "Create subscription",
|
||||
Long: `Create new subscription`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 3 {
|
||||
logUsageCmd(*cmd, cmd.Use)
|
||||
return
|
||||
}
|
||||
|
||||
id, err := sdk.CreateSubscription(args[0], args[1], args[2])
|
||||
if err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
|
||||
logCreatedCmd(*cmd, id)
|
||||
},
|
||||
},
|
||||
{
|
||||
Use: "get [all | <sub_id>] <user_auth_token>",
|
||||
Short: "Get subscription",
|
||||
Long: `Get subscription.
|
||||
all - lists all subscriptions
|
||||
<sub_id> - view subscription of <sub_id>`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 2 {
|
||||
logUsageCmd(*cmd, cmd.Use)
|
||||
return
|
||||
}
|
||||
pageMetadata := mgsdk.PageMetadata{
|
||||
Offset: Offset,
|
||||
Limit: Limit,
|
||||
Topic: Topic,
|
||||
Contact: Contact,
|
||||
}
|
||||
if args[0] == "all" {
|
||||
sub, err := sdk.ListSubscriptions(pageMetadata, args[1])
|
||||
if err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
logJSONCmd(*cmd, sub)
|
||||
return
|
||||
}
|
||||
|
||||
c, err := sdk.ViewSubscription(args[0], args[1])
|
||||
if err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
|
||||
logJSONCmd(*cmd, c)
|
||||
},
|
||||
},
|
||||
{
|
||||
Use: "remove <sub_id> <user_auth_token>",
|
||||
Short: "Remove subscription",
|
||||
Long: `Removes removes a subscription with the provided id`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 2 {
|
||||
logUsageCmd(*cmd, cmd.Use)
|
||||
return
|
||||
}
|
||||
|
||||
if err := sdk.DeleteSubscription(args[0], args[1]); err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
|
||||
logOKCmd(*cmd)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// NewSubscriptionCmd returns subscription command.
|
||||
func NewSubscriptionCmd() *cobra.Command {
|
||||
cmd := cobra.Command{
|
||||
Use: "subscription [create | get | remove ]",
|
||||
Short: "Subscription management",
|
||||
Long: `Subscription management: create, get, or delete subscription`,
|
||||
}
|
||||
|
||||
for i := range cmdSubscription {
|
||||
cmd.AddCommand(&cmdSubscription[i])
|
||||
}
|
||||
|
||||
return &cmd
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cli_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/absmach/magistrala/cli"
|
||||
"github.com/absmach/magistrala/internal/testsutil"
|
||||
mgsdk "github.com/absmach/magistrala/pkg/sdk"
|
||||
sdkmocks "github.com/absmach/magistrala/pkg/sdk/mocks"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
svcerr "github.com/absmach/supermq/pkg/errors/service"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
var (
|
||||
userID = testsutil.GenerateUUID(&testing.T{})
|
||||
subscription = mgsdk.Subscription{
|
||||
ID: testsutil.GenerateUUID(&testing.T{}),
|
||||
OwnerID: userID,
|
||||
Topic: "topic",
|
||||
Contact: "identity@example.com",
|
||||
}
|
||||
)
|
||||
|
||||
func TestCreateSubscriptionCmd(t *testing.T) {
|
||||
sdkMock := new(sdkmocks.SDK)
|
||||
cli.SetSDK(sdkMock)
|
||||
subCmd := cli.NewSubscriptionCmd()
|
||||
rootCmd := setFlags(subCmd)
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
args []string
|
||||
logType outputLog
|
||||
errLogMessage string
|
||||
sdkErr errors.SDKError
|
||||
response string
|
||||
id string
|
||||
}{
|
||||
{
|
||||
desc: "create subscription successfully",
|
||||
args: []string{
|
||||
subscription.Topic,
|
||||
subscription.Contact,
|
||||
validToken,
|
||||
},
|
||||
id: userID,
|
||||
response: fmt.Sprintf("\ncreated: %s\n\n", userID),
|
||||
logType: createLog,
|
||||
},
|
||||
{
|
||||
desc: "create subscription with invalid args",
|
||||
args: []string{
|
||||
subscription.Topic,
|
||||
subscription.Contact,
|
||||
validToken,
|
||||
extraArg,
|
||||
},
|
||||
logType: usageLog,
|
||||
},
|
||||
{
|
||||
desc: "create subscription with invalid token",
|
||||
args: []string{
|
||||
subscription.Topic,
|
||||
subscription.Contact,
|
||||
invalidToken,
|
||||
},
|
||||
logType: errLog,
|
||||
sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden),
|
||||
errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
sdkCall := sdkMock.On("CreateSubscription", tc.args[0], tc.args[1], tc.args[2]).Return(tc.id, tc.sdkErr)
|
||||
out := executeCommand(t, rootCmd, append([]string{createCmd}, tc.args...)...)
|
||||
|
||||
switch tc.logType {
|
||||
case usageLog:
|
||||
assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out))
|
||||
case errLog:
|
||||
assert.Equal(t, tc.errLogMessage, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.errLogMessage, out))
|
||||
case createLog:
|
||||
assert.Equal(t, tc.response, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.response, out))
|
||||
}
|
||||
sdkCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetSubscriptionsCmd(t *testing.T) {
|
||||
sdkMock := new(sdkmocks.SDK)
|
||||
cli.SetSDK(sdkMock)
|
||||
subCmd := cli.NewSubscriptionCmd()
|
||||
rootCmd := setFlags(subCmd)
|
||||
|
||||
var sub mgsdk.Subscription
|
||||
var page mgsdk.SubscriptionPage
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
args []string
|
||||
sdkErr errors.SDKError
|
||||
page mgsdk.SubscriptionPage
|
||||
subscription mgsdk.Subscription
|
||||
logType outputLog
|
||||
errLogMessage string
|
||||
}{
|
||||
{
|
||||
desc: "get all subscriptions successfully",
|
||||
args: []string{
|
||||
all,
|
||||
validToken,
|
||||
},
|
||||
page: mgsdk.SubscriptionPage{
|
||||
Subscriptions: []mgsdk.Subscription{subscription},
|
||||
},
|
||||
logType: entityLog,
|
||||
},
|
||||
{
|
||||
desc: "get subscription with id",
|
||||
args: []string{
|
||||
subscription.ID,
|
||||
validToken,
|
||||
},
|
||||
logType: entityLog,
|
||||
subscription: subscription,
|
||||
},
|
||||
{
|
||||
desc: "get subscriptions with invalid args",
|
||||
args: []string{
|
||||
all,
|
||||
validToken,
|
||||
extraArg,
|
||||
},
|
||||
logType: usageLog,
|
||||
},
|
||||
{
|
||||
desc: "get all subscriptions with invalid token",
|
||||
args: []string{
|
||||
all,
|
||||
invalidToken,
|
||||
},
|
||||
logType: errLog,
|
||||
sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden),
|
||||
errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)),
|
||||
},
|
||||
{
|
||||
desc: "get subscription with invalid id",
|
||||
args: []string{
|
||||
invalidID,
|
||||
validToken,
|
||||
},
|
||||
sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden),
|
||||
errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)),
|
||||
logType: errLog,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
sdkCall := sdkMock.On("ViewSubscription", tc.args[0], tc.args[1]).Return(tc.subscription, tc.sdkErr)
|
||||
sdkCall1 := sdkMock.On("ListSubscriptions", mock.Anything, tc.args[1]).Return(tc.page, tc.sdkErr)
|
||||
|
||||
out := executeCommand(t, rootCmd, append([]string{getCmd}, tc.args...)...)
|
||||
|
||||
switch tc.logType {
|
||||
case entityLog:
|
||||
if tc.args[1] == all {
|
||||
err := json.Unmarshal([]byte(out), &page)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, tc.page, page, fmt.Sprintf("%v unexpected response, expected: %v, got: %v", tc.desc, tc.page, page))
|
||||
} else {
|
||||
err := json.Unmarshal([]byte(out), &sub)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, tc.subscription, sub, fmt.Sprintf("%v unexpected response, expected: %v, got: %v", tc.desc, tc.subscription, sub))
|
||||
}
|
||||
case errLog:
|
||||
assert.Equal(t, tc.errLogMessage, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.errLogMessage, out))
|
||||
case usageLog:
|
||||
assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out))
|
||||
}
|
||||
sdkCall.Unset()
|
||||
sdkCall1.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveSubscriptionCmd(t *testing.T) {
|
||||
sdkMock := new(sdkmocks.SDK)
|
||||
cli.SetSDK(sdkMock)
|
||||
subCmd := cli.NewSubscriptionCmd()
|
||||
rootCmd := setFlags(subCmd)
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
args []string
|
||||
sdkErr errors.SDKError
|
||||
logType outputLog
|
||||
errLogMessage string
|
||||
}{
|
||||
{
|
||||
desc: "remove subscription successfully",
|
||||
args: []string{
|
||||
subscription.ID,
|
||||
validToken,
|
||||
},
|
||||
logType: okLog,
|
||||
},
|
||||
{
|
||||
desc: "remove subscription with invalid args",
|
||||
args: []string{
|
||||
subscription.ID,
|
||||
validToken,
|
||||
extraArg,
|
||||
},
|
||||
logType: usageLog,
|
||||
},
|
||||
{
|
||||
desc: "remove subscription with invalid subscription id",
|
||||
args: []string{
|
||||
invalidID,
|
||||
validToken,
|
||||
},
|
||||
sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden),
|
||||
errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)),
|
||||
logType: errLog,
|
||||
},
|
||||
{
|
||||
desc: "remove subscription with invalid token",
|
||||
args: []string{
|
||||
subscription.ID,
|
||||
invalidToken,
|
||||
},
|
||||
sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden),
|
||||
errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)),
|
||||
logType: errLog,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
sdkCall := sdkMock.On("DeleteSubscription", tc.args[0], tc.args[1]).Return(tc.sdkErr)
|
||||
out := executeCommand(t, rootCmd, append([]string{rmCmd}, tc.args...)...)
|
||||
|
||||
switch tc.logType {
|
||||
case okLog:
|
||||
assert.True(t, strings.Contains(out, "ok"), fmt.Sprintf("%s unexpected response: expected success message, got: %v", tc.desc, out))
|
||||
case errLog:
|
||||
assert.Equal(t, tc.errLogMessage, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.errLogMessage, out))
|
||||
case usageLog:
|
||||
assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out))
|
||||
}
|
||||
sdkCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,410 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/0x6flab/namegenerator"
|
||||
smqsdk "github.com/absmach/supermq/pkg/sdk"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const (
|
||||
jsonExt = ".json"
|
||||
csvExt = ".csv"
|
||||
PublishType = "publish"
|
||||
SubscribeType = "subscribe"
|
||||
)
|
||||
|
||||
var (
|
||||
msgFormat = `[{"bn":"provision:", "bu":"V", "t": %d, "bver":5, "n":"voltage", "u":"V", "v":%d}]`
|
||||
namesgenerator = namegenerator.NewGenerator()
|
||||
)
|
||||
|
||||
var cmdProvision = []cobra.Command{
|
||||
{
|
||||
Use: "clients <clients_file> <domain_id> <user_token>",
|
||||
Short: "Provision clients",
|
||||
Long: `Bulk create clients`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 3 {
|
||||
logUsageCmd(*cmd, cmd.Use)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := os.Stat(args[0]); os.IsNotExist(err) {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
|
||||
clients, err := clientsFromFile(args[0])
|
||||
if err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
|
||||
clients, err = sdk.CreateClients(clients, args[1], args[2])
|
||||
if err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
|
||||
logJSONCmd(*cmd, clients)
|
||||
},
|
||||
},
|
||||
{
|
||||
Use: "channels <channels_file> <domain_id> <user_token>",
|
||||
Short: "Provision channels",
|
||||
Long: `Bulk create channels`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 3 {
|
||||
logUsageCmd(*cmd, cmd.Use)
|
||||
return
|
||||
}
|
||||
|
||||
channels, err := channelsFromFile(args[0])
|
||||
if err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
|
||||
var chs []smqsdk.Channel
|
||||
for _, c := range channels {
|
||||
c, err = sdk.CreateChannel(c, args[1], args[2])
|
||||
if err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
chs = append(chs, c)
|
||||
}
|
||||
channels = chs
|
||||
|
||||
logJSONCmd(*cmd, channels)
|
||||
},
|
||||
},
|
||||
{
|
||||
Use: "connect <connections_file> <domain_id> <user_token>",
|
||||
Short: "Provision connections",
|
||||
Long: `Bulk connect clients to channels`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 3 {
|
||||
logUsageCmd(*cmd, cmd.Use)
|
||||
return
|
||||
}
|
||||
|
||||
connIDs, err := connectionsFromFile(args[0])
|
||||
if err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
for _, conn := range connIDs {
|
||||
if err := sdk.Connect(conn, args[1], args[2]); err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
logOKCmd(*cmd)
|
||||
},
|
||||
},
|
||||
{
|
||||
Use: "test",
|
||||
Short: "test",
|
||||
Long: `Provisions test setup: one test user, two clients and two channels. \
|
||||
Connect both clients to one of the channels, \
|
||||
and only on client to other channel.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
numClients := 2
|
||||
numChan := 2
|
||||
clients := []smqsdk.Client{}
|
||||
channels := []smqsdk.Channel{}
|
||||
|
||||
if len(args) != 0 {
|
||||
logUsageCmd(*cmd, cmd.Use)
|
||||
return
|
||||
}
|
||||
|
||||
// Create test user
|
||||
name := namesgenerator.Generate()
|
||||
user := smqsdk.User{
|
||||
FirstName: name,
|
||||
Email: fmt.Sprintf("%s@email.com", name),
|
||||
Credentials: smqsdk.Credentials{
|
||||
Username: name,
|
||||
Secret: "12345678",
|
||||
},
|
||||
Status: smqsdk.EnabledStatus,
|
||||
}
|
||||
user, err := sdk.CreateUser(user, "")
|
||||
if err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
|
||||
ut, err := sdk.CreateToken(smqsdk.Login{Username: user.Credentials.Username, Password: user.Credentials.Secret})
|
||||
if err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
|
||||
// create domain
|
||||
domain := smqsdk.Domain{
|
||||
Name: fmt.Sprintf("%s-domain", name),
|
||||
Status: smqsdk.EnabledStatus,
|
||||
}
|
||||
domain, err = sdk.CreateDomain(domain, ut.AccessToken)
|
||||
if err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
|
||||
ut, err = sdk.CreateToken(smqsdk.Login{Username: user.Email, Password: user.Credentials.Secret})
|
||||
if err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Create clients
|
||||
for i := 0; i < numClients; i++ {
|
||||
t := smqsdk.Client{
|
||||
Name: fmt.Sprintf("%s-client-%d", name, i),
|
||||
Status: smqsdk.EnabledStatus,
|
||||
}
|
||||
|
||||
clients = append(clients, t)
|
||||
}
|
||||
clients, err = sdk.CreateClients(clients, domain.ID, ut.AccessToken)
|
||||
if err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Create channels
|
||||
for i := 0; i < numChan; i++ {
|
||||
c := smqsdk.Channel{
|
||||
Name: fmt.Sprintf("%s-channel-%d", name, i),
|
||||
Status: smqsdk.EnabledStatus,
|
||||
}
|
||||
c, err = sdk.CreateChannel(c, domain.ID, ut.AccessToken)
|
||||
if err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
|
||||
channels = append(channels, c)
|
||||
}
|
||||
|
||||
// Connect clients to channels - first client to both channels, second only to first
|
||||
conIDs := smqsdk.Connection{
|
||||
ChannelIDs: []string{channels[0].ID},
|
||||
ClientIDs: []string{clients[0].ID},
|
||||
Types: []string{PublishType, SubscribeType},
|
||||
}
|
||||
if err := sdk.Connect(conIDs, domain.ID, ut.AccessToken); err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
|
||||
conIDs = smqsdk.Connection{
|
||||
ChannelIDs: []string{channels[1].ID},
|
||||
ClientIDs: []string{clients[0].ID},
|
||||
Types: []string{PublishType, SubscribeType},
|
||||
}
|
||||
if err := sdk.Connect(conIDs, domain.ID, ut.AccessToken); err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
|
||||
conIDs = smqsdk.Connection{
|
||||
ChannelIDs: []string{channels[0].ID},
|
||||
ClientIDs: []string{clients[1].ID},
|
||||
Types: []string{PublishType, SubscribeType},
|
||||
}
|
||||
if err := sdk.Connect(conIDs, domain.ID, ut.AccessToken); err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
|
||||
// send message to test connectivity
|
||||
if err := sdk.SendMessage(channels[0].ID, fmt.Sprintf(msgFormat, time.Now().Unix(), rand.Int()), clients[0].Credentials.Secret); err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
if err := sdk.SendMessage(channels[0].ID, fmt.Sprintf(msgFormat, time.Now().Unix(), rand.Int()), clients[1].Credentials.Secret); err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
if err := sdk.SendMessage(channels[1].ID, fmt.Sprintf(msgFormat, time.Now().Unix(), rand.Int()), clients[0].Credentials.Secret); err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
|
||||
logJSONCmd(*cmd, user, ut, clients, channels)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// NewProvisionCmd returns provision command.
|
||||
func NewProvisionCmd() *cobra.Command {
|
||||
cmd := cobra.Command{
|
||||
Use: "provision [clients | channels | connect | test]",
|
||||
Short: "Provision clients and channels from a config file",
|
||||
Long: `Provision clients and channels: use json or csv file to bulk provision clients and channels`,
|
||||
}
|
||||
|
||||
for i := range cmdProvision {
|
||||
cmd.AddCommand(&cmdProvision[i])
|
||||
}
|
||||
|
||||
return &cmd
|
||||
}
|
||||
|
||||
func clientsFromFile(path string) ([]smqsdk.Client, error) {
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
return []smqsdk.Client{}, err
|
||||
}
|
||||
|
||||
file, err := os.OpenFile(path, os.O_RDONLY, os.ModePerm)
|
||||
if err != nil {
|
||||
return []smqsdk.Client{}, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
clients := []smqsdk.Client{}
|
||||
switch filepath.Ext(path) {
|
||||
case csvExt:
|
||||
reader := csv.NewReader(file)
|
||||
|
||||
for {
|
||||
l, err := reader.Read()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return []smqsdk.Client{}, err
|
||||
}
|
||||
|
||||
if len(l) < 1 {
|
||||
return []smqsdk.Client{}, errors.New("empty line found in file")
|
||||
}
|
||||
|
||||
client := smqsdk.Client{
|
||||
Name: l[0],
|
||||
}
|
||||
|
||||
clients = append(clients, client)
|
||||
}
|
||||
case jsonExt:
|
||||
err := json.NewDecoder(file).Decode(&clients)
|
||||
if err != nil {
|
||||
return []smqsdk.Client{}, err
|
||||
}
|
||||
default:
|
||||
return []smqsdk.Client{}, err
|
||||
}
|
||||
|
||||
return clients, nil
|
||||
}
|
||||
|
||||
func channelsFromFile(path string) ([]smqsdk.Channel, error) {
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
return []smqsdk.Channel{}, err
|
||||
}
|
||||
|
||||
file, err := os.OpenFile(path, os.O_RDONLY, os.ModePerm)
|
||||
if err != nil {
|
||||
return []smqsdk.Channel{}, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
channels := []smqsdk.Channel{}
|
||||
switch filepath.Ext(path) {
|
||||
case csvExt:
|
||||
reader := csv.NewReader(file)
|
||||
|
||||
for {
|
||||
l, err := reader.Read()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return []smqsdk.Channel{}, err
|
||||
}
|
||||
|
||||
if len(l) < 1 {
|
||||
return []smqsdk.Channel{}, errors.New("empty line found in file")
|
||||
}
|
||||
|
||||
channel := smqsdk.Channel{
|
||||
Name: l[0],
|
||||
}
|
||||
|
||||
channels = append(channels, channel)
|
||||
}
|
||||
case jsonExt:
|
||||
err := json.NewDecoder(file).Decode(&channels)
|
||||
if err != nil {
|
||||
return []smqsdk.Channel{}, err
|
||||
}
|
||||
default:
|
||||
return []smqsdk.Channel{}, err
|
||||
}
|
||||
|
||||
return channels, nil
|
||||
}
|
||||
|
||||
func connectionsFromFile(path string) ([]smqsdk.Connection, error) {
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
return []smqsdk.Connection{}, err
|
||||
}
|
||||
|
||||
file, err := os.OpenFile(path, os.O_RDONLY, os.ModePerm)
|
||||
if err != nil {
|
||||
return []smqsdk.Connection{}, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
connections := []smqsdk.Connection{}
|
||||
switch filepath.Ext(path) {
|
||||
case csvExt:
|
||||
reader := csv.NewReader(file)
|
||||
|
||||
for {
|
||||
l, err := reader.Read()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return []smqsdk.Connection{}, err
|
||||
}
|
||||
|
||||
if len(l) < 1 {
|
||||
return []smqsdk.Connection{}, errors.New("empty line found in file")
|
||||
}
|
||||
connections = append(connections, smqsdk.Connection{
|
||||
ClientIDs: []string{l[0]},
|
||||
ChannelIDs: []string{l[1]},
|
||||
Types: []string{PublishType, SubscribeType},
|
||||
})
|
||||
}
|
||||
case jsonExt:
|
||||
err := json.NewDecoder(file).Decode(&connections)
|
||||
if err != nil {
|
||||
return []smqsdk.Connection{}, err
|
||||
}
|
||||
default:
|
||||
return []smqsdk.Connection{}, err
|
||||
}
|
||||
|
||||
return connections, nil
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cli
|
||||
|
||||
import mgsdk "github.com/absmach/magistrala/pkg/sdk"
|
||||
|
||||
// Keep SDK handle in global var.
|
||||
var sdk mgsdk.SDK
|
||||
|
||||
// SetSDK sets supermq SDK instance.
|
||||
func SetSDK(s mgsdk.SDK) {
|
||||
sdk = s
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cli_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/absmach/supermq/cli"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type outputLog uint8
|
||||
|
||||
const (
|
||||
usageLog outputLog = iota
|
||||
errLog
|
||||
entityLog
|
||||
okLog
|
||||
createLog
|
||||
revokeLog
|
||||
)
|
||||
|
||||
func executeCommand(t *testing.T, root *cobra.Command, args ...string) string {
|
||||
buffer := new(bytes.Buffer)
|
||||
root.SetOut(buffer)
|
||||
root.SetErr(buffer)
|
||||
root.SetArgs(args)
|
||||
err := root.Execute()
|
||||
assert.NoError(t, err, "Error executing command")
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
func setFlags(rootCmd *cobra.Command) *cobra.Command {
|
||||
// Root Flags
|
||||
rootCmd.PersistentFlags().BoolVarP(
|
||||
&cli.RawOutput,
|
||||
"raw",
|
||||
"r",
|
||||
cli.RawOutput,
|
||||
"Enables raw output mode for easier parsing of output",
|
||||
)
|
||||
|
||||
// Client and Channels Flags
|
||||
rootCmd.PersistentFlags().Uint64VarP(
|
||||
&cli.Limit,
|
||||
"limit",
|
||||
"l",
|
||||
10,
|
||||
"Limit query parameter",
|
||||
)
|
||||
|
||||
rootCmd.PersistentFlags().Uint64VarP(
|
||||
&cli.Offset,
|
||||
"offset",
|
||||
"o",
|
||||
0,
|
||||
"Offset query parameter",
|
||||
)
|
||||
|
||||
rootCmd.PersistentFlags().StringVarP(
|
||||
&cli.Name,
|
||||
"name",
|
||||
"n",
|
||||
"",
|
||||
"Name query parameter",
|
||||
)
|
||||
|
||||
rootCmd.PersistentFlags().StringVarP(
|
||||
&cli.Identity,
|
||||
"identity",
|
||||
"I",
|
||||
"",
|
||||
"User identity query parameter",
|
||||
)
|
||||
|
||||
rootCmd.PersistentFlags().StringVarP(
|
||||
&cli.Metadata,
|
||||
"metadata",
|
||||
"m",
|
||||
"",
|
||||
"Metadata query parameter",
|
||||
)
|
||||
|
||||
rootCmd.PersistentFlags().StringVarP(
|
||||
&cli.Status,
|
||||
"status",
|
||||
"S",
|
||||
"",
|
||||
"User status query parameter",
|
||||
)
|
||||
|
||||
rootCmd.PersistentFlags().StringVarP(
|
||||
&cli.State,
|
||||
"state",
|
||||
"z",
|
||||
"",
|
||||
"Bootstrap state query parameter",
|
||||
)
|
||||
|
||||
rootCmd.PersistentFlags().StringVarP(
|
||||
&cli.Topic,
|
||||
"topic",
|
||||
"T",
|
||||
"",
|
||||
"Subscription topic query parameter",
|
||||
)
|
||||
|
||||
rootCmd.PersistentFlags().StringVarP(
|
||||
&cli.Contact,
|
||||
"contact",
|
||||
"C",
|
||||
"",
|
||||
"Subscription contact query parameter",
|
||||
)
|
||||
|
||||
return rootCmd
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/hokaccha/go-prettyjson"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
// Limit query parameter.
|
||||
Limit uint64 = 10
|
||||
// Offset query parameter.
|
||||
Offset uint64 = 0
|
||||
// Name query parameter.
|
||||
Name string = ""
|
||||
// Identity query parameter.
|
||||
Identity string = ""
|
||||
// Metadata query parameter.
|
||||
Metadata string = ""
|
||||
// Status query parameter.
|
||||
Status string = ""
|
||||
// ConfigPath config path parameter.
|
||||
ConfigPath string = ""
|
||||
// State query parameter.
|
||||
State string = ""
|
||||
// Topic query parameter.
|
||||
Topic string = ""
|
||||
// Contact query parameter.
|
||||
Contact string = ""
|
||||
// RawOutput raw output mode.
|
||||
RawOutput bool = false
|
||||
// Username query parameter.
|
||||
Username string = ""
|
||||
// FirstName query parameter.
|
||||
FirstName string = ""
|
||||
// LastName query parameter.
|
||||
LastName string = ""
|
||||
)
|
||||
|
||||
func logJSONCmd(cmd cobra.Command, iList ...interface{}) {
|
||||
for _, i := range iList {
|
||||
m, err := json.Marshal(i)
|
||||
if err != nil {
|
||||
logErrorCmd(cmd, err)
|
||||
return
|
||||
}
|
||||
|
||||
pj, err := prettyjson.Format(m)
|
||||
if err != nil {
|
||||
logErrorCmd(cmd, err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintf(cmd.OutOrStdout(), "\n%s\n\n", string(pj))
|
||||
}
|
||||
}
|
||||
|
||||
func logUsageCmd(cmd cobra.Command, u string) {
|
||||
fmt.Fprintf(cmd.OutOrStdout(), color.YellowString("\nusage: %s\n\n"), u)
|
||||
}
|
||||
|
||||
func logErrorCmd(cmd cobra.Command, err error) {
|
||||
boldRed := color.New(color.FgRed, color.Bold)
|
||||
boldRed.Fprintf(cmd.ErrOrStderr(), "\nerror: ")
|
||||
|
||||
fmt.Fprintf(cmd.ErrOrStderr(), "%s\n\n", color.RedString(err.Error()))
|
||||
}
|
||||
|
||||
func logOKCmd(cmd cobra.Command) {
|
||||
fmt.Fprintf(cmd.OutOrStdout(), "\n%s\n\n", color.BlueString("ok"))
|
||||
}
|
||||
|
||||
func logCreatedCmd(cmd cobra.Command, e string) {
|
||||
if RawOutput {
|
||||
fmt.Fprintln(cmd.OutOrStdout(), e)
|
||||
} else {
|
||||
fmt.Fprintf(cmd.OutOrStdout(), color.BlueString("\ncreated: %s\n\n"), e)
|
||||
}
|
||||
}
|
||||
+281
@@ -0,0 +1,281 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package main contains cli main function to run the cli.
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/absmach/magistrala/cli"
|
||||
mgcli "github.com/absmach/magistrala/cli"
|
||||
mgsdk "github.com/absmach/magistrala/pkg/sdk"
|
||||
smqcli "github.com/absmach/supermq/cli"
|
||||
smqsdk "github.com/absmach/supermq/pkg/sdk"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func main() {
|
||||
msgContentType := string(smqsdk.CTJSONSenML)
|
||||
smqsdkConf := smqsdk.Config{
|
||||
MsgContentType: smqsdk.ContentType(msgContentType),
|
||||
}
|
||||
mgsdkConf := mgsdk.Config{
|
||||
MsgContentType: smqsdk.ContentType(msgContentType),
|
||||
}
|
||||
|
||||
// Root
|
||||
rootCmd := &cobra.Command{
|
||||
Use: "magistrala-cli",
|
||||
PersistentPreRun: func(_ *cobra.Command, _ []string) {
|
||||
smqcliConf, err := smqcli.ParseConfig(smqsdkConf)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to parse config: %s", err)
|
||||
}
|
||||
if smqcliConf.MsgContentType == "" {
|
||||
smqcliConf.MsgContentType = smqsdk.ContentType(msgContentType)
|
||||
}
|
||||
ss := smqsdk.NewSDK(smqcliConf)
|
||||
smqcli.SetSDK(ss)
|
||||
|
||||
mgcliConf, err := mgcli.ParseConfig(mgsdkConf)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to parse config: %s", err)
|
||||
}
|
||||
if mgcliConf.MsgContentType == "" {
|
||||
mgcliConf.MsgContentType = smqsdk.ContentType(msgContentType)
|
||||
}
|
||||
ms := mgsdk.NewSDK(mgcliConf)
|
||||
mgcli.SetSDK(ms)
|
||||
},
|
||||
}
|
||||
// SuperMQ API commands
|
||||
healthCmd := smqcli.NewHealthCmd()
|
||||
usersCmd := smqcli.NewUsersCmd()
|
||||
domainsCmd := smqcli.NewDomainsCmd()
|
||||
clientsCmd := smqcli.NewClientsCmd()
|
||||
groupsCmd := smqcli.NewGroupsCmd()
|
||||
channelsCmd := smqcli.NewChannelsCmd()
|
||||
messagesCmd := smqcli.NewMessagesCmd()
|
||||
certsCmd := smqcli.NewCertsCmd()
|
||||
configCmd := smqcli.NewConfigCmd()
|
||||
invitationsCmd := smqcli.NewInvitationsCmd()
|
||||
journalCmd := smqcli.NewJournalCmd()
|
||||
|
||||
// Magistrala API commands
|
||||
provisionCmd := mgcli.NewProvisionCmd()
|
||||
bootstrapCmd := mgcli.NewBootstrapCmd()
|
||||
subscriptionsCmd := mgcli.NewSubscriptionCmd()
|
||||
|
||||
// Root Commands
|
||||
rootCmd.AddCommand(healthCmd)
|
||||
rootCmd.AddCommand(usersCmd)
|
||||
rootCmd.AddCommand(domainsCmd)
|
||||
rootCmd.AddCommand(groupsCmd)
|
||||
rootCmd.AddCommand(clientsCmd)
|
||||
rootCmd.AddCommand(channelsCmd)
|
||||
rootCmd.AddCommand(messagesCmd)
|
||||
rootCmd.AddCommand(provisionCmd)
|
||||
rootCmd.AddCommand(bootstrapCmd)
|
||||
rootCmd.AddCommand(certsCmd)
|
||||
rootCmd.AddCommand(subscriptionsCmd)
|
||||
rootCmd.AddCommand(configCmd)
|
||||
rootCmd.AddCommand(invitationsCmd)
|
||||
rootCmd.AddCommand(journalCmd)
|
||||
|
||||
// Root Flags
|
||||
rootCmd.PersistentFlags().StringVarP(
|
||||
&mgsdkConf.BootstrapURL,
|
||||
"bootstrap-url",
|
||||
"b",
|
||||
mgsdkConf.BootstrapURL,
|
||||
"Bootstrap service URL",
|
||||
)
|
||||
|
||||
rootCmd.PersistentFlags().StringVarP(
|
||||
&mgsdkConf.CertsURL,
|
||||
"certs-url",
|
||||
"s",
|
||||
mgsdkConf.CertsURL,
|
||||
"Certs service URL",
|
||||
)
|
||||
|
||||
rootCmd.PersistentFlags().StringVarP(
|
||||
&mgsdkConf.ClientsURL,
|
||||
"clients-url",
|
||||
"t",
|
||||
mgsdkConf.ClientsURL,
|
||||
"Clients service URL",
|
||||
)
|
||||
|
||||
rootCmd.PersistentFlags().StringVarP(
|
||||
&mgsdkConf.UsersURL,
|
||||
"users-url",
|
||||
"u",
|
||||
mgsdkConf.UsersURL,
|
||||
"Users service URL",
|
||||
)
|
||||
|
||||
rootCmd.PersistentFlags().StringVarP(
|
||||
&mgsdkConf.DomainsURL,
|
||||
"domains-url",
|
||||
"d",
|
||||
mgsdkConf.DomainsURL,
|
||||
"Domains service URL",
|
||||
)
|
||||
|
||||
rootCmd.PersistentFlags().StringVarP(
|
||||
&mgsdkConf.HTTPAdapterURL,
|
||||
"http-url",
|
||||
"p",
|
||||
mgsdkConf.HTTPAdapterURL,
|
||||
"HTTP adapter URL",
|
||||
)
|
||||
|
||||
rootCmd.PersistentFlags().StringVarP(
|
||||
&mgsdkConf.ReaderURL,
|
||||
"reader-url",
|
||||
"R",
|
||||
mgsdkConf.ReaderURL,
|
||||
"Reader URL",
|
||||
)
|
||||
|
||||
rootCmd.PersistentFlags().StringVarP(
|
||||
&mgsdkConf.InvitationsURL,
|
||||
"invitations-url",
|
||||
"v",
|
||||
mgsdkConf.InvitationsURL,
|
||||
"Inivitations URL",
|
||||
)
|
||||
|
||||
rootCmd.PersistentFlags().StringVarP(
|
||||
&mgsdkConf.JournalURL,
|
||||
"journal-url",
|
||||
"a",
|
||||
mgsdkConf.JournalURL,
|
||||
"Journal Log URL",
|
||||
)
|
||||
|
||||
rootCmd.PersistentFlags().StringVarP(
|
||||
&mgsdkConf.HostURL,
|
||||
"host-url",
|
||||
"H",
|
||||
mgsdkConf.HostURL,
|
||||
"Host URL",
|
||||
)
|
||||
|
||||
rootCmd.PersistentFlags().StringVarP(
|
||||
&msgContentType,
|
||||
"content-type",
|
||||
"y",
|
||||
msgContentType,
|
||||
"Message content type",
|
||||
)
|
||||
|
||||
rootCmd.PersistentFlags().BoolVarP(
|
||||
&mgsdkConf.TLSVerification,
|
||||
"insecure",
|
||||
"i",
|
||||
mgsdkConf.TLSVerification,
|
||||
"Do not check for TLS cert",
|
||||
)
|
||||
|
||||
rootCmd.PersistentFlags().StringVarP(
|
||||
&cli.ConfigPath,
|
||||
"config",
|
||||
"c",
|
||||
cli.ConfigPath,
|
||||
"Config path",
|
||||
)
|
||||
|
||||
rootCmd.PersistentFlags().BoolVarP(
|
||||
&cli.RawOutput,
|
||||
"raw",
|
||||
"r",
|
||||
cli.RawOutput,
|
||||
"Enables raw output mode for easier parsing of output",
|
||||
)
|
||||
rootCmd.PersistentFlags().BoolVarP(
|
||||
&mgsdkConf.CurlFlag,
|
||||
"curl",
|
||||
"x",
|
||||
false,
|
||||
"Convert HTTP request to cURL command",
|
||||
)
|
||||
|
||||
// Client and Channels Flags
|
||||
rootCmd.PersistentFlags().Uint64VarP(
|
||||
&cli.Limit,
|
||||
"limit",
|
||||
"l",
|
||||
10,
|
||||
"Limit query parameter",
|
||||
)
|
||||
|
||||
rootCmd.PersistentFlags().Uint64VarP(
|
||||
&cli.Offset,
|
||||
"offset",
|
||||
"o",
|
||||
0,
|
||||
"Offset query parameter",
|
||||
)
|
||||
|
||||
rootCmd.PersistentFlags().StringVarP(
|
||||
&cli.Name,
|
||||
"name",
|
||||
"n",
|
||||
"",
|
||||
"Name query parameter",
|
||||
)
|
||||
|
||||
rootCmd.PersistentFlags().StringVarP(
|
||||
&cli.Identity,
|
||||
"identity",
|
||||
"I",
|
||||
"",
|
||||
"User identity query parameter",
|
||||
)
|
||||
|
||||
rootCmd.PersistentFlags().StringVarP(
|
||||
&cli.Metadata,
|
||||
"metadata",
|
||||
"m",
|
||||
"",
|
||||
"Metadata query parameter",
|
||||
)
|
||||
|
||||
rootCmd.PersistentFlags().StringVarP(
|
||||
&cli.Status,
|
||||
"status",
|
||||
"S",
|
||||
"",
|
||||
"User status query parameter",
|
||||
)
|
||||
|
||||
rootCmd.PersistentFlags().StringVarP(
|
||||
&cli.State,
|
||||
"state",
|
||||
"z",
|
||||
"",
|
||||
"Bootstrap state query parameter",
|
||||
)
|
||||
|
||||
rootCmd.PersistentFlags().StringVarP(
|
||||
&cli.Topic,
|
||||
"topic",
|
||||
"T",
|
||||
"",
|
||||
"Subscription topic query parameter",
|
||||
)
|
||||
|
||||
rootCmd.PersistentFlags().StringVarP(
|
||||
&cli.Contact,
|
||||
"contact",
|
||||
"C",
|
||||
"",
|
||||
"Subscription contact query parameter",
|
||||
)
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
+5
-3
@@ -13,11 +13,13 @@ user_token = ""
|
||||
journal_url = "http://localhost:9021"
|
||||
bootstrap_url = "http://localhost:9013"
|
||||
certs_url = "http://localhost:9019"
|
||||
domains_url = "http://localhost:8189"
|
||||
domains_url = "http://localhost:9003"
|
||||
host_url = "http://localhost"
|
||||
http_adapter_url = "http://localhost:8008"
|
||||
invitations_url = "http://localhost:9020"
|
||||
reader_url = "http://localhost:9011"
|
||||
things_url = "http://localhost:9000"
|
||||
tls_verification = false
|
||||
clients_url = "http://localhost:9006"
|
||||
channels_url = "http://localhost:9005"
|
||||
groups_url = "http://localhost:9004"
|
||||
users_url = "http://localhost:9002"
|
||||
tls_verification = false
|
||||
|
||||
@@ -59,7 +59,7 @@ require (
|
||||
github.com/docker/go-connections v0.5.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/fatih/color v1.18.0
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.8.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
||||
@@ -74,6 +74,7 @@ require (
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jackc/pgio v1.0.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
|
||||
@@ -155,6 +155,8 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3Ar
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f h1:7LYC+Yfkj3CTRcShK0KOL/w6iTiKyqqBA9a41Wnggw8=
|
||||
github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f/go.mod h1:pFlLw2CfqZiIBOx6BuCeRLCrfxBJipTY0nIOF/VbGcI=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
|
||||
@@ -33,6 +33,8 @@ type PageMetadata struct {
|
||||
Contact string `json:"contact,omitempty"`
|
||||
DomainID string `json:"domain_id,omitempty"`
|
||||
Level uint64 `json:"level,omitempty"`
|
||||
State string `json:"state,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
}
|
||||
|
||||
type MessagePageMetadata struct {
|
||||
|
||||
Reference in New Issue
Block a user