mirror of
https://github.com/absmach/magistrala.git
synced 2026-08-07 07:14:46 +00:00
SMQ-3399 - Unify Magistrala and SuperMQ (#3400)
Signed-off-by: nyagamunene <stevenyaga2014@gmail.com> Signed-off-by: dusan <borovcanindusan1@gmail.com> Co-authored-by: Steve Munene <stevenyaga2014@gmail.com>
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
mgsdk "github.com/absmach/supermq/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(cmd.Context(), 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(cmd.Context(), pageMetadata, args[1], args[2])
|
||||
if err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
logJSONCmd(*cmd, l)
|
||||
return
|
||||
}
|
||||
|
||||
c, err := sdk.ViewBootstrap(cmd.Context(), 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(cmd.Context(), 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(cmd.Context(), args[1], ids, args[3], args[4]); err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
|
||||
logOKCmd(*cmd)
|
||||
return
|
||||
}
|
||||
if args[0] == "certs" {
|
||||
cfg, err := sdk.UpdateBootstrapCerts(cmd.Context(), 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(cmd.Context(), 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(cmd.Context(), args[1], args[2], args[3])
|
||||
if err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
|
||||
logJSONCmd(*cmd, c)
|
||||
return
|
||||
}
|
||||
c, err := sdk.Bootstrap(cmd.Context(), 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(cmd.Context(), 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/supermq/cli"
|
||||
"github.com/absmach/supermq/internal/testsutil"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
svcerr "github.com/absmach/supermq/pkg/errors/service"
|
||||
mgsdk "github.com/absmach/supermq/pkg/sdk"
|
||||
sdkmocks "github.com/absmach/supermq/pkg/sdk/mocks"
|
||||
"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 Bootstrap", 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, 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", mock.Anything, tc.args[0], tc.args[1], tc.args[2]).Return(tc.boot, tc.sdkErr)
|
||||
sdkCall1 := sdkMock.On("Bootstraps", mock.Anything, 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", mock.Anything, 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, mock.Anything).Return(tc.sdkErr)
|
||||
sdkCall1 := sdkMock.On("UpdateBootstrapConnection", mock.Anything, 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, 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, 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
|
||||
cryptoKey := "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,
|
||||
cryptoKey,
|
||||
},
|
||||
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{
|
||||
cryptoKey,
|
||||
},
|
||||
|
||||
logType: usageLog,
|
||||
},
|
||||
{
|
||||
desc: "bootstrap secure config with invalid key",
|
||||
args: []string{
|
||||
"secure",
|
||||
bootConfig.ExternalID,
|
||||
invalidKey,
|
||||
cryptoKey,
|
||||
},
|
||||
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, mock.Anything).Return(tc.boot, tc.sdkErr)
|
||||
sdkCall1 := sdkMock.On("Bootstrap", mock.Anything, 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()
|
||||
})
|
||||
}
|
||||
}
|
||||
+342
@@ -0,0 +1,342 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
|
||||
"github.com/absmach/supermq/certs"
|
||||
smqsdk "github.com/absmach/supermq/pkg/sdk"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var cmdCerts = []cobra.Command{
|
||||
{
|
||||
Use: "get [all | <entity_id>] <domain_id> <token>",
|
||||
Short: "Get certificate",
|
||||
Long: `Gets a certificate for a given entity ID or all certificates.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 3 {
|
||||
logUsageCmd(*cmd, cmd.Use)
|
||||
return
|
||||
}
|
||||
if args[0] == all {
|
||||
pm := smqsdk.PageMetadata{
|
||||
Limit: Limit,
|
||||
Offset: Offset,
|
||||
}
|
||||
page, err := sdk.ListCerts(cmd.Context(), pm, args[1], args[2])
|
||||
if err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
logJSONCmd(*cmd, page)
|
||||
return
|
||||
}
|
||||
pm := smqsdk.PageMetadata{
|
||||
EntityID: args[0],
|
||||
Limit: Limit,
|
||||
Offset: Offset,
|
||||
}
|
||||
page, err := sdk.ListCerts(cmd.Context(), pm, args[1], args[2])
|
||||
if err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
logJSONCmd(*cmd, page)
|
||||
},
|
||||
},
|
||||
{
|
||||
Use: "revoke <serial_number> <domain_id> <token>",
|
||||
Short: "Revoke certificate",
|
||||
Long: `Revokes a certificate for a given serial number.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 3 {
|
||||
logUsageCmd(*cmd, cmd.Use)
|
||||
return
|
||||
}
|
||||
err := sdk.RevokeCert(cmd.Context(), args[0], args[1], args[2])
|
||||
if err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
logOKCmd(*cmd)
|
||||
},
|
||||
},
|
||||
{
|
||||
Use: "delete <entity_id> <domain_id> <token>",
|
||||
Short: "Delete certificate",
|
||||
Long: `Deletes certificates for a given entity id.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 3 {
|
||||
logUsageCmd(*cmd, cmd.Use)
|
||||
return
|
||||
}
|
||||
err := sdk.DeleteCert(cmd.Context(), args[0], args[1], args[2])
|
||||
if err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
logOKCmd(*cmd)
|
||||
},
|
||||
},
|
||||
{
|
||||
Use: "renew <serial_number> <domain_id> <token>",
|
||||
Short: "Renew certificate",
|
||||
Long: `Renews a certificate for a given serial number.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 3 {
|
||||
logUsageCmd(*cmd, cmd.Use)
|
||||
return
|
||||
}
|
||||
_, err := sdk.RenewCert(cmd.Context(), args[0], args[1], args[2])
|
||||
if err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
logOKCmd(*cmd)
|
||||
},
|
||||
},
|
||||
{
|
||||
Use: "ocsp <serial_number_or_certificate_path>",
|
||||
Short: "OCSP",
|
||||
Long: `OCSP for a given serial number or certificate.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 1 {
|
||||
logUsageCmd(*cmd, cmd.Use)
|
||||
return
|
||||
}
|
||||
var serialNumber, certContent string
|
||||
if _, statErr := os.Stat(args[0]); statErr == nil {
|
||||
certBytes, err := os.ReadFile(args[0])
|
||||
if err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
certContent = string(certBytes)
|
||||
} else {
|
||||
serialNumber = args[0]
|
||||
}
|
||||
response, err := sdk.OCSP(cmd.Context(), serialNumber, certContent)
|
||||
if err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
logJSONCmd(*cmd, response)
|
||||
},
|
||||
},
|
||||
{
|
||||
Use: "view <serial_number> <domain_id> <token>",
|
||||
Short: "View certificate",
|
||||
Long: `Views a certificate for a given serial number.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 3 {
|
||||
logUsageCmd(*cmd, cmd.Use)
|
||||
return
|
||||
}
|
||||
cert, err := sdk.ViewCert(cmd.Context(), args[0], args[1], args[2])
|
||||
if err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
logJSONCmd(*cmd, cert)
|
||||
},
|
||||
},
|
||||
{
|
||||
Use: "view-ca",
|
||||
Short: "View-ca certificate",
|
||||
Long: `Views ca certificate.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 0 {
|
||||
logUsageCmd(*cmd, cmd.Use)
|
||||
return
|
||||
}
|
||||
cert, err := sdk.ViewCA(cmd.Context())
|
||||
if err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
logJSONCmd(*cmd, cert)
|
||||
},
|
||||
},
|
||||
{
|
||||
Use: "download-ca",
|
||||
Short: "Download signing CA",
|
||||
Long: `Download intermediate cert and ca.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 0 {
|
||||
logUsageCmd(*cmd, cmd.Use)
|
||||
return
|
||||
}
|
||||
bundle, err := sdk.DownloadCA(cmd.Context())
|
||||
if err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
logSaveCAFiles(*cmd, bundle)
|
||||
},
|
||||
},
|
||||
{
|
||||
Use: "csr <metadata> <private_key_path>",
|
||||
Short: "Create CSR",
|
||||
Long: `Creates a CSR.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 2 {
|
||||
logUsageCmd(*cmd, cmd.Use)
|
||||
return
|
||||
}
|
||||
var pm certs.CSRMetadata
|
||||
if err := json.Unmarshal([]byte(args[0]), &pm); err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
data, err := os.ReadFile(args[1])
|
||||
if err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
csr, err := sdk.CreateCSR(cmd.Context(), pm, data)
|
||||
if err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
logSaveCSRFiles(*cmd, csr)
|
||||
},
|
||||
},
|
||||
{
|
||||
Use: "issue-csr <entity_id> <ttl> <path_to_csr> <domain_id> <token>",
|
||||
Short: "Issue from CSR",
|
||||
Long: `issues a certificate for a given csr.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 5 {
|
||||
logUsageCmd(*cmd, cmd.Use)
|
||||
return
|
||||
}
|
||||
csrData, err := os.ReadFile(args[2])
|
||||
if err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
cert, err := sdk.IssueFromCSR(cmd.Context(), args[0], args[1], string(csrData), args[3], args[4])
|
||||
if err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
logJSONCmd(*cmd, cert)
|
||||
logSaveCertFiles(*cmd, cert)
|
||||
},
|
||||
},
|
||||
{
|
||||
Use: "issue-csr-internal <entity_id> <ttl> <path_to_csr> <agent_token>",
|
||||
Short: "Issue from CSR Internal (Agent)",
|
||||
Long: `Issues a certificate for a given CSR using agent authentication.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 4 {
|
||||
logUsageCmd(*cmd, cmd.Use)
|
||||
return
|
||||
}
|
||||
csrData, err := os.ReadFile(args[2])
|
||||
if err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
cert, err := sdk.IssueFromCSRInternal(cmd.Context(), args[0], args[1], string(csrData), args[3])
|
||||
if err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
logJSONCmd(*cmd, cert)
|
||||
logSaveCertFiles(*cmd, cert)
|
||||
},
|
||||
},
|
||||
{
|
||||
Use: "crl",
|
||||
Short: "Generate CRL",
|
||||
Long: `Generates a Certificate Revocation List (CRL).`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 0 {
|
||||
logUsageCmd(*cmd, cmd.Use)
|
||||
return
|
||||
}
|
||||
crlBytes, err := sdk.GenerateCRL(cmd.Context())
|
||||
if err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
logSaveCRLFile(*cmd, crlBytes)
|
||||
},
|
||||
},
|
||||
{
|
||||
Use: "entity-id <serial_number> <domain_id> <token>",
|
||||
Short: "Get entity ID by serial number",
|
||||
Long: `Gets the entity ID for a certificate by its serial number.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 3 {
|
||||
logUsageCmd(*cmd, cmd.Use)
|
||||
return
|
||||
}
|
||||
entityID, err := sdk.EntityID(cmd.Context(), args[0], args[1], args[2])
|
||||
if err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
logJSONCmd(*cmd, map[string]string{"entity_id": entityID})
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// NewCertsCmd returns certificate command.
|
||||
func NewCertsCmd() *cobra.Command {
|
||||
var ttl string
|
||||
issueCmd := cobra.Command{
|
||||
Use: "issue <entity_id> <common_name> <ip_addrs_json> [<options_json>] <domain_id> <token> [--ttl=8760h]",
|
||||
Short: "Issue certificate",
|
||||
Long: `Issues a certificate for a given entity ID.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) < 5 || len(args) > 6 {
|
||||
logUsageCmd(*cmd, cmd.Use)
|
||||
return
|
||||
}
|
||||
var ipAddrs []string
|
||||
if err := json.Unmarshal([]byte(args[2]), &ipAddrs); err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
var option smqsdk.Options
|
||||
option.CommonName = args[1]
|
||||
var domainID, token string
|
||||
if len(args) == 5 {
|
||||
domainID = args[3]
|
||||
token = args[4]
|
||||
} else {
|
||||
if err := json.Unmarshal([]byte(args[3]), &option); err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
domainID = args[4]
|
||||
token = args[5]
|
||||
}
|
||||
cert, err := sdk.IssueCert(cmd.Context(), args[0], ttl, ipAddrs, option, domainID, token)
|
||||
if err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
logJSONCmd(*cmd, cert)
|
||||
logSaveCertFiles(*cmd, cert)
|
||||
},
|
||||
}
|
||||
issueCmd.Flags().StringVar(&ttl, "ttl", "8760h", "certificate time to live in duration")
|
||||
|
||||
cmd := cobra.Command{
|
||||
Use: "certs [issue | get | revoke | renew | ocsp | view | download-ca | view-ca | csr | issue-csr | issue-csr-internal | crl | entity-id]",
|
||||
Short: "Certificates management",
|
||||
Long: `Certificates management: issue, get all, get by entity ID, revoke, renew, OCSP, view, CRL generation, entity ID lookup, agent CSR issuing, and CA operations.`,
|
||||
}
|
||||
cmd.AddCommand(&issueCmd)
|
||||
for i := range cmdCerts {
|
||||
cmd.AddCommand(&cmdCerts[i])
|
||||
}
|
||||
return &cmd
|
||||
}
|
||||
@@ -0,0 +1,905 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cli_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/absmach/supermq/certs"
|
||||
"github.com/absmach/supermq/cli"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
"github.com/absmach/supermq/pkg/sdk"
|
||||
sdkmocks "github.com/absmach/supermq/pkg/sdk/mocks"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
const (
|
||||
revokeCmd = "revoke"
|
||||
deleteCmd = "delete"
|
||||
issueCmd = "issue"
|
||||
renewCmd = "renew"
|
||||
certsListCmd = "get"
|
||||
downloadCACmd = "download-ca"
|
||||
CATokenCmd = "certsToken-ca"
|
||||
viewCACmd = "view-ca"
|
||||
filePermission = 0o644
|
||||
)
|
||||
|
||||
var (
|
||||
serialNumber = "39054620502613157373429341617471746606"
|
||||
id = "5b4c9ee3-e719-4a0a-9ee5-354932c5e6a4"
|
||||
commonName = "test-name"
|
||||
certsToken = "certsToken"
|
||||
certsDomainID = "domain-id"
|
||||
)
|
||||
|
||||
func TestIssueCertCmd(t *testing.T) {
|
||||
sdkMock := new(sdkmocks.SDK)
|
||||
cli.SetSDK(sdkMock)
|
||||
certCmd := cli.NewCertsCmd()
|
||||
rootCmd := setFlags(certCmd)
|
||||
|
||||
ipAddrs := "[\"192.168.100.22\"]"
|
||||
|
||||
var cert sdk.Certificate
|
||||
cases := []struct {
|
||||
desc string
|
||||
args []string
|
||||
sdkErr errors.SDKError
|
||||
errLogMessage string
|
||||
logType outputLog
|
||||
cert sdk.Certificate
|
||||
}{
|
||||
{
|
||||
desc: "issue cert successfully",
|
||||
args: []string{
|
||||
id,
|
||||
commonName,
|
||||
ipAddrs,
|
||||
certsDomainID,
|
||||
certsToken,
|
||||
},
|
||||
logType: entityLog,
|
||||
cert: sdk.Certificate{SerialNumber: serialNumber},
|
||||
},
|
||||
{
|
||||
desc: "issue cert with invalid args",
|
||||
args: []string{
|
||||
id,
|
||||
ipAddrs,
|
||||
},
|
||||
logType: usageLog,
|
||||
},
|
||||
{
|
||||
desc: "issue cert failed",
|
||||
args: []string{
|
||||
id,
|
||||
commonName,
|
||||
ipAddrs,
|
||||
certsDomainID,
|
||||
certsToken,
|
||||
},
|
||||
sdkErr: errors.NewSDKErrorWithStatus(certs.ErrCreateEntity, http.StatusUnprocessableEntity),
|
||||
errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(certs.ErrCreateEntity, http.StatusUnprocessableEntity)),
|
||||
logType: errLog,
|
||||
},
|
||||
{
|
||||
desc: "issue cert with 6 args",
|
||||
args: []string{
|
||||
id,
|
||||
commonName,
|
||||
ipAddrs,
|
||||
"{\"organization\":[\"organization_name\"]}",
|
||||
certsDomainID,
|
||||
certsToken,
|
||||
},
|
||||
logType: entityLog,
|
||||
cert: sdk.Certificate{SerialNumber: serialNumber},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
defer func() {
|
||||
cleanupFiles(t, []string{"cert.pem", "key.pem"})
|
||||
}()
|
||||
sdkCall := sdkMock.On("IssueCert", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.cert, tc.sdkErr)
|
||||
out := executeCommand(t, rootCmd, append([]string{issueCmd}, tc.args...)...)
|
||||
switch tc.logType {
|
||||
case entityLog:
|
||||
lines := strings.Split(out, "\n")
|
||||
var jsonLines []string
|
||||
var inJSON bool
|
||||
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if strings.HasPrefix(line, "{") {
|
||||
inJSON = true
|
||||
jsonLines = append(jsonLines, line)
|
||||
} else if inJSON && strings.HasSuffix(line, "}") {
|
||||
jsonLines = append(jsonLines, line)
|
||||
break
|
||||
} else if inJSON {
|
||||
jsonLines = append(jsonLines, line)
|
||||
}
|
||||
}
|
||||
|
||||
if len(jsonLines) == 0 {
|
||||
t.Fatalf("No JSON found in output: %s", out)
|
||||
}
|
||||
|
||||
jsonPart := strings.Join(jsonLines, "")
|
||||
|
||||
err := json.Unmarshal([]byte(jsonPart), &cert)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, tc.cert, cert, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.cert, cert))
|
||||
assert.True(t, strings.Contains(out, "All certificate files have been saved successfully"), fmt.Sprintf("%s should save files", tc.desc))
|
||||
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 TestRevokeCertCmd(t *testing.T) {
|
||||
sdkMock := new(sdkmocks.SDK)
|
||||
cli.SetSDK(sdkMock)
|
||||
certCmd := cli.NewCertsCmd()
|
||||
rootCmd := setFlags(certCmd)
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
args []string
|
||||
sdkErr errors.SDKError
|
||||
errLogMessage string
|
||||
logType outputLog
|
||||
}{
|
||||
{
|
||||
desc: "revoke cert successfully",
|
||||
args: []string{
|
||||
serialNumber,
|
||||
certsDomainID,
|
||||
certsToken,
|
||||
},
|
||||
logType: okLog,
|
||||
},
|
||||
{
|
||||
desc: "revoke cert with invalid args",
|
||||
args: []string{
|
||||
serialNumber,
|
||||
extraArg,
|
||||
},
|
||||
logType: usageLog,
|
||||
},
|
||||
{
|
||||
desc: "revoke cert failed",
|
||||
args: []string{
|
||||
serialNumber,
|
||||
certsDomainID,
|
||||
certsToken,
|
||||
},
|
||||
sdkErr: errors.NewSDKErrorWithStatus(certs.ErrUpdateEntity, http.StatusUnprocessableEntity),
|
||||
errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(certs.ErrUpdateEntity, http.StatusUnprocessableEntity)),
|
||||
logType: errLog,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
sdkCall := sdkMock.On("RevokeCert", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.sdkErr)
|
||||
out := executeCommand(t, rootCmd, append([]string{revokeCmd}, 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 TestDeleteCertCmd(t *testing.T) {
|
||||
sdkMock := new(sdkmocks.SDK)
|
||||
cli.SetSDK(sdkMock)
|
||||
certCmd := cli.NewCertsCmd()
|
||||
rootCmd := setFlags(certCmd)
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
args []string
|
||||
sdkErr errors.SDKError
|
||||
errLogMessage string
|
||||
logType outputLog
|
||||
}{
|
||||
{
|
||||
desc: "delete certs successfully",
|
||||
args: []string{
|
||||
id,
|
||||
certsDomainID,
|
||||
certsToken,
|
||||
},
|
||||
logType: okLog,
|
||||
},
|
||||
{
|
||||
desc: "delete certs with invalid args",
|
||||
args: []string{
|
||||
id,
|
||||
extraArg,
|
||||
},
|
||||
logType: usageLog,
|
||||
},
|
||||
{
|
||||
desc: "delete certs failed",
|
||||
args: []string{
|
||||
id,
|
||||
certsDomainID,
|
||||
certsToken,
|
||||
},
|
||||
sdkErr: errors.NewSDKErrorWithStatus(certs.ErrUpdateEntity, http.StatusUnprocessableEntity),
|
||||
errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(certs.ErrUpdateEntity, http.StatusUnprocessableEntity)),
|
||||
logType: errLog,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
sdkCall := sdkMock.On("DeleteCert", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.sdkErr)
|
||||
out := executeCommand(t, rootCmd, append([]string{deleteCmd}, 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 TestRenewCertCmd(t *testing.T) {
|
||||
sdkMock := new(sdkmocks.SDK)
|
||||
cli.SetSDK(sdkMock)
|
||||
certCmd := cli.NewCertsCmd()
|
||||
rootCmd := setFlags(certCmd)
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
args []string
|
||||
sdkErr errors.SDKError
|
||||
errLogMessage string
|
||||
logType outputLog
|
||||
}{
|
||||
{
|
||||
desc: "renew cert successfully",
|
||||
args: []string{
|
||||
serialNumber,
|
||||
certsDomainID,
|
||||
certsToken,
|
||||
},
|
||||
logType: okLog,
|
||||
},
|
||||
{
|
||||
desc: "renew cert with invalid args",
|
||||
args: []string{
|
||||
serialNumber,
|
||||
extraArg,
|
||||
},
|
||||
logType: usageLog,
|
||||
},
|
||||
{
|
||||
desc: "renew cert failed",
|
||||
args: []string{
|
||||
serialNumber,
|
||||
certsDomainID,
|
||||
certsToken,
|
||||
},
|
||||
sdkErr: errors.NewSDKErrorWithStatus(certs.ErrUpdateEntity, http.StatusUnprocessableEntity),
|
||||
errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(certs.ErrUpdateEntity, http.StatusUnprocessableEntity)),
|
||||
logType: errLog,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
sdkCall := sdkMock.On("RenewCert", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(sdk.Certificate{}, tc.sdkErr)
|
||||
out := executeCommand(t, rootCmd, append([]string{renewCmd}, 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 TestListCertsCmd(t *testing.T) {
|
||||
sdkMock := new(sdkmocks.SDK)
|
||||
cli.SetSDK(sdkMock)
|
||||
certCmd := cli.NewCertsCmd()
|
||||
rootCmd := setFlags(certCmd)
|
||||
|
||||
var page sdk.CertificatePage
|
||||
cases := []struct {
|
||||
desc string
|
||||
args []string
|
||||
sdkErr errors.SDKError
|
||||
errLogMessage string
|
||||
logType outputLog
|
||||
page sdk.CertificatePage
|
||||
}{
|
||||
{
|
||||
desc: "list certs successfully",
|
||||
args: []string{
|
||||
all,
|
||||
certsDomainID,
|
||||
certsToken,
|
||||
},
|
||||
logType: entityLog,
|
||||
page: sdk.CertificatePage{
|
||||
Total: 1,
|
||||
Offset: 0,
|
||||
Limit: 10,
|
||||
Certificates: []sdk.Certificate{
|
||||
{SerialNumber: serialNumber},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "list certs successfully with entity ID",
|
||||
args: []string{
|
||||
id,
|
||||
certsDomainID,
|
||||
certsToken,
|
||||
},
|
||||
logType: entityLog,
|
||||
page: sdk.CertificatePage{
|
||||
Total: 1,
|
||||
Offset: 0,
|
||||
Limit: 10,
|
||||
Certificates: []sdk.Certificate{
|
||||
{SerialNumber: serialNumber},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "list certs with invalid args",
|
||||
args: []string{
|
||||
all,
|
||||
extraArg,
|
||||
},
|
||||
logType: usageLog,
|
||||
},
|
||||
{
|
||||
desc: "failed list certs with all",
|
||||
args: []string{
|
||||
all,
|
||||
certsDomainID,
|
||||
certsToken,
|
||||
},
|
||||
sdkErr: errors.NewSDKErrorWithStatus(certs.ErrViewEntity, http.StatusUnprocessableEntity),
|
||||
errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(certs.ErrViewEntity, http.StatusUnprocessableEntity)),
|
||||
logType: errLog,
|
||||
},
|
||||
{
|
||||
desc: "failed list certs with entity ID",
|
||||
args: []string{
|
||||
id,
|
||||
certsDomainID,
|
||||
certsToken,
|
||||
},
|
||||
sdkErr: errors.NewSDKErrorWithStatus(certs.ErrViewEntity, http.StatusUnprocessableEntity),
|
||||
errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(certs.ErrViewEntity, http.StatusUnprocessableEntity)),
|
||||
logType: errLog,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
sdkCall := sdkMock.On("ListCerts", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.page, tc.sdkErr)
|
||||
out := executeCommand(t, rootCmd, append([]string{certsListCmd}, tc.args...)...)
|
||||
|
||||
switch tc.logType {
|
||||
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))
|
||||
case entityLog:
|
||||
err := json.Unmarshal([]byte(out), &page)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to unmarshal JSON: %v", err)
|
||||
}
|
||||
assert.Equal(t, tc.page, page, fmt.Sprintf("%v unexpected response, expected: %v, got: %v", tc.desc, tc.page, page))
|
||||
}
|
||||
|
||||
sdkCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDownloadCACmd(t *testing.T) {
|
||||
sdkMock := new(sdkmocks.SDK)
|
||||
cli.SetSDK(sdkMock)
|
||||
certCmd := cli.NewCertsCmd()
|
||||
rootCmd := setFlags(certCmd)
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
args []string
|
||||
sdkErr errors.SDKError
|
||||
errLogMessage string
|
||||
logMessage string
|
||||
logType outputLog
|
||||
certBundle sdk.CertificateBundle
|
||||
}{
|
||||
{
|
||||
desc: "download CA successfully",
|
||||
args: []string{},
|
||||
logType: entityLog,
|
||||
certBundle: sdk.CertificateBundle{
|
||||
Certificate: []byte("certificate"),
|
||||
},
|
||||
logMessage: "Saved ca.crt\n\nAll certificate files have been saved successfully.\n",
|
||||
},
|
||||
{
|
||||
desc: "download CA with invalid args",
|
||||
args: []string{
|
||||
extraArg,
|
||||
},
|
||||
logType: usageLog,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
defer func() {
|
||||
cleanupFiles(t, []string{"ca.crt"})
|
||||
}()
|
||||
sdkCall := sdkMock.On("DownloadCA", mock.Anything).Return(tc.certBundle, tc.sdkErr)
|
||||
out := executeCommand(t, rootCmd, append([]string{downloadCACmd}, tc.args...)...)
|
||||
switch tc.logType {
|
||||
case entityLog:
|
||||
assert.True(t, strings.Contains(out, "Saved ca.crt"), fmt.Sprintf("%s invalid output: %s", 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 TestViewCACmd(t *testing.T) {
|
||||
sdkMock := new(sdkmocks.SDK)
|
||||
cli.SetSDK(sdkMock)
|
||||
certCmd := cli.NewCertsCmd()
|
||||
rootCmd := setFlags(certCmd)
|
||||
|
||||
var cert sdk.Certificate
|
||||
cases := []struct {
|
||||
desc string
|
||||
args []string
|
||||
sdkErr errors.SDKError
|
||||
errLogMessage string
|
||||
logType outputLog
|
||||
cert sdk.Certificate
|
||||
}{
|
||||
{
|
||||
desc: "view cert successfully",
|
||||
args: []string{},
|
||||
logType: entityLog,
|
||||
cert: sdk.Certificate{
|
||||
Certificate: "certificate",
|
||||
Key: "privatekey",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "view cert failed",
|
||||
args: []string{},
|
||||
sdkErr: errors.NewSDKErrorWithStatus(certs.ErrUpdateEntity, http.StatusUnprocessableEntity),
|
||||
errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(certs.ErrUpdateEntity, http.StatusUnprocessableEntity)),
|
||||
logType: errLog,
|
||||
cert: sdk.Certificate{},
|
||||
},
|
||||
{
|
||||
desc: "view cert with invalid args",
|
||||
args: []string{extraArg},
|
||||
logType: usageLog,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
sdkCall := sdkMock.On("ViewCA", mock.Anything).Return(tc.cert, tc.sdkErr)
|
||||
out := executeCommand(t, rootCmd, append([]string{viewCACmd}, tc.args...)...)
|
||||
switch tc.logType {
|
||||
case entityLog:
|
||||
err := json.Unmarshal([]byte(out), &cert)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, tc.cert, cert, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.cert, cert))
|
||||
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 TestGenerateCRLCmd(t *testing.T) {
|
||||
sdkMock := new(sdkmocks.SDK)
|
||||
cli.SetSDK(sdkMock)
|
||||
certCmd := cli.NewCertsCmd()
|
||||
rootCmd := setFlags(certCmd)
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
args []string
|
||||
sdkErr errors.SDKError
|
||||
errLogMessage string
|
||||
logType outputLog
|
||||
crlBytes []byte
|
||||
}{
|
||||
{
|
||||
desc: "generate CRL successfully",
|
||||
args: []string{},
|
||||
logType: entityLog,
|
||||
crlBytes: []byte("crl-data"),
|
||||
},
|
||||
{
|
||||
desc: "generate CRL failed",
|
||||
args: []string{},
|
||||
sdkErr: errors.NewSDKErrorWithStatus(certs.ErrFailedCertCreation, http.StatusUnprocessableEntity),
|
||||
errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(certs.ErrFailedCertCreation, http.StatusUnprocessableEntity)),
|
||||
logType: errLog,
|
||||
},
|
||||
{
|
||||
desc: "generate CRL with invalid args",
|
||||
args: []string{"invalid"},
|
||||
logType: usageLog,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
defer func() {
|
||||
cleanupFiles(t, []string{"ca.crl"})
|
||||
}()
|
||||
|
||||
sdkCall := sdkMock.On("GenerateCRL", mock.Anything).Return(tc.crlBytes, tc.sdkErr)
|
||||
defer sdkCall.Unset()
|
||||
|
||||
out := executeCommand(t, rootCmd, append([]string{"crl"}, tc.args...)...)
|
||||
|
||||
switch tc.logType {
|
||||
case entityLog:
|
||||
assert.True(t, strings.Contains(out, "CRL file has been saved successfully"), fmt.Sprintf("%s invalid output: %s", 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))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetEntityIDCmd(t *testing.T) {
|
||||
sdkMock := new(sdkmocks.SDK)
|
||||
cli.SetSDK(sdkMock)
|
||||
certCmd := cli.NewCertsCmd()
|
||||
rootCmd := setFlags(certCmd)
|
||||
|
||||
entityID := "test-entity-id"
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
args []string
|
||||
sdkErr errors.SDKError
|
||||
errLogMessage string
|
||||
logType outputLog
|
||||
entityID string
|
||||
}{
|
||||
{
|
||||
desc: "get entity ID successfully",
|
||||
args: []string{serialNumber, certsDomainID, certsToken},
|
||||
logType: entityLog,
|
||||
entityID: entityID,
|
||||
},
|
||||
{
|
||||
desc: "get entity ID with invalid args",
|
||||
args: []string{serialNumber, extraArg},
|
||||
logType: usageLog,
|
||||
},
|
||||
{
|
||||
desc: "get entity ID failed",
|
||||
args: []string{serialNumber, certsDomainID, certsToken},
|
||||
sdkErr: errors.NewSDKErrorWithStatus(certs.ErrViewEntity, http.StatusUnprocessableEntity),
|
||||
errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(certs.ErrViewEntity, http.StatusUnprocessableEntity)),
|
||||
logType: errLog,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
sdkCall := sdkMock.On("EntityID", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.entityID, tc.sdkErr)
|
||||
out := executeCommand(t, rootCmd, append([]string{"entity-id"}, tc.args...)...)
|
||||
|
||||
switch tc.logType {
|
||||
case entityLog:
|
||||
assert.True(t, strings.Contains(out, tc.entityID), fmt.Sprintf("%s invalid output: %s", 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 cleanupFiles(t *testing.T, filenames []string) {
|
||||
for _, filename := range filenames {
|
||||
err := os.Remove(filename)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
t.Logf("Failed to remove file %s: %v", filename, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssueFromCSRInternalCmd(t *testing.T) {
|
||||
sdkMock := new(sdkmocks.SDK)
|
||||
cli.SetSDK(sdkMock)
|
||||
certCmd := cli.NewCertsCmd()
|
||||
rootCmd := setFlags(certCmd)
|
||||
|
||||
agentToken := "agent-certsToken-123"
|
||||
csrPath := "test.csr"
|
||||
bytes := []byte("-----BEGIN CERTIFICATE REQUEST-----\n-csr-content\n-----END CERTIFICATE REQUEST-----")
|
||||
|
||||
err := os.WriteFile(csrPath, bytes, filePermission)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create test CSR file: %v", err)
|
||||
}
|
||||
defer os.Remove(csrPath)
|
||||
|
||||
var cert sdk.Certificate
|
||||
cases := []struct {
|
||||
desc string
|
||||
args []string
|
||||
sdkErr errors.SDKError
|
||||
errLogMessage string
|
||||
logType outputLog
|
||||
cert sdk.Certificate
|
||||
}{
|
||||
{
|
||||
desc: "issue cert from CSR internal successfully",
|
||||
args: []string{
|
||||
id,
|
||||
"10h",
|
||||
csrPath,
|
||||
agentToken,
|
||||
},
|
||||
logType: entityLog,
|
||||
cert: sdk.Certificate{SerialNumber: serialNumber},
|
||||
},
|
||||
{
|
||||
desc: "issue cert from CSR internal with invalid args",
|
||||
args: []string{
|
||||
id,
|
||||
extraArg,
|
||||
},
|
||||
logType: usageLog,
|
||||
},
|
||||
{
|
||||
desc: "issue cert from CSR internal failed",
|
||||
args: []string{
|
||||
id,
|
||||
"10h",
|
||||
csrPath,
|
||||
agentToken,
|
||||
},
|
||||
sdkErr: errors.NewSDKErrorWithStatus(certs.ErrFailedCertCreation, http.StatusUnprocessableEntity),
|
||||
errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(certs.ErrFailedCertCreation, http.StatusUnprocessableEntity)),
|
||||
logType: errLog,
|
||||
},
|
||||
{
|
||||
desc: "issue cert from CSR internal with non-existent file",
|
||||
args: []string{
|
||||
id,
|
||||
"10h",
|
||||
"non-existent.csr",
|
||||
agentToken,
|
||||
},
|
||||
logType: errLog,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
defer func() {
|
||||
cleanupFiles(t, []string{"cert.pem", "key.pem"})
|
||||
}()
|
||||
sdkCall := sdkMock.On("IssueFromCSRInternal", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.cert, tc.sdkErr)
|
||||
out := executeCommand(t, rootCmd, append([]string{"issue-csr-internal"}, tc.args...)...)
|
||||
switch tc.logType {
|
||||
case entityLog:
|
||||
lines := strings.Split(out, "\n")
|
||||
var jsonLines []string
|
||||
var inJSON bool
|
||||
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if strings.HasPrefix(line, "{") {
|
||||
inJSON = true
|
||||
jsonLines = append(jsonLines, line)
|
||||
} else if inJSON && strings.HasSuffix(line, "}") {
|
||||
jsonLines = append(jsonLines, line)
|
||||
break
|
||||
} else if inJSON {
|
||||
jsonLines = append(jsonLines, line)
|
||||
}
|
||||
}
|
||||
|
||||
if len(jsonLines) == 0 {
|
||||
t.Fatalf("No JSON found in output: %s", out)
|
||||
}
|
||||
|
||||
jsonPart := strings.Join(jsonLines, "")
|
||||
|
||||
err := json.Unmarshal([]byte(jsonPart), &cert)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, tc.cert, cert, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.cert, cert))
|
||||
assert.True(t, strings.Contains(out, "All certificate files have been saved successfully"), fmt.Sprintf("%s should save files", tc.desc))
|
||||
case errLog:
|
||||
if tc.errLogMessage != "" {
|
||||
assert.Equal(t, tc.errLogMessage, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.errLogMessage, out))
|
||||
} else {
|
||||
assert.True(t, strings.Contains(out, "error"), fmt.Sprintf("%s should contain error message: %s", tc.desc, out))
|
||||
}
|
||||
case usageLog:
|
||||
assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out))
|
||||
}
|
||||
sdkCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssueFromCSRCmd(t *testing.T) {
|
||||
sdkMock := new(sdkmocks.SDK)
|
||||
cli.SetSDK(sdkMock)
|
||||
certCmd := cli.NewCertsCmd()
|
||||
rootCmd := setFlags(certCmd)
|
||||
|
||||
csrPath := "test.csr"
|
||||
bytes := []byte("-----BEGIN CERTIFICATE REQUEST-----\n-csr-content\n-----END CERTIFICATE REQUEST-----")
|
||||
|
||||
err := os.WriteFile(csrPath, bytes, filePermission)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create test CSR file: %v", err)
|
||||
}
|
||||
defer os.Remove(csrPath)
|
||||
|
||||
var cert sdk.Certificate
|
||||
cases := []struct {
|
||||
desc string
|
||||
args []string
|
||||
sdkErr errors.SDKError
|
||||
errLogMessage string
|
||||
logType outputLog
|
||||
cert sdk.Certificate
|
||||
}{
|
||||
{
|
||||
desc: "issue cert from CSR successfully",
|
||||
args: []string{
|
||||
id,
|
||||
"10h",
|
||||
csrPath,
|
||||
certsDomainID,
|
||||
certsToken,
|
||||
},
|
||||
logType: entityLog,
|
||||
cert: sdk.Certificate{SerialNumber: serialNumber},
|
||||
},
|
||||
{
|
||||
desc: "issue cert from CSR with invalid args",
|
||||
args: []string{
|
||||
id,
|
||||
extraArg,
|
||||
},
|
||||
logType: usageLog,
|
||||
},
|
||||
{
|
||||
desc: "issue cert from CSR failed",
|
||||
args: []string{
|
||||
id,
|
||||
"10h",
|
||||
csrPath,
|
||||
certsDomainID,
|
||||
certsToken,
|
||||
},
|
||||
sdkErr: errors.NewSDKErrorWithStatus(certs.ErrFailedCertCreation, http.StatusUnprocessableEntity),
|
||||
errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(certs.ErrFailedCertCreation, http.StatusUnprocessableEntity)),
|
||||
logType: errLog,
|
||||
},
|
||||
{
|
||||
desc: "issue cert from CSR with non-existent file",
|
||||
args: []string{
|
||||
id,
|
||||
"10h",
|
||||
"non-existent.csr",
|
||||
certsDomainID,
|
||||
certsToken,
|
||||
},
|
||||
logType: errLog,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
defer func() {
|
||||
cleanupFiles(t, []string{"cert.pem", "key.pem"})
|
||||
}()
|
||||
sdkCall := sdkMock.On("IssueFromCSR", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.cert, tc.sdkErr)
|
||||
out := executeCommand(t, rootCmd, append([]string{"issue-csr"}, tc.args...)...)
|
||||
switch tc.logType {
|
||||
case entityLog:
|
||||
lines := strings.Split(out, "\n")
|
||||
var jsonLines []string
|
||||
var inJSON bool
|
||||
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if strings.HasPrefix(line, "{") {
|
||||
inJSON = true
|
||||
jsonLines = append(jsonLines, line)
|
||||
} else if inJSON && strings.HasSuffix(line, "}") {
|
||||
jsonLines = append(jsonLines, line)
|
||||
break
|
||||
} else if inJSON {
|
||||
jsonLines = append(jsonLines, line)
|
||||
}
|
||||
}
|
||||
|
||||
if len(jsonLines) == 0 {
|
||||
t.Fatalf("No JSON found in output: %s", out)
|
||||
}
|
||||
|
||||
jsonPart := strings.Join(jsonLines, "")
|
||||
|
||||
err := json.Unmarshal([]byte(jsonPart), &cert)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, tc.cert, cert, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.cert, cert))
|
||||
assert.True(t, strings.Contains(out, "All certificate files have been saved successfully"), fmt.Sprintf("%s should save files", tc.desc))
|
||||
case errLog:
|
||||
if tc.errLogMessage != "" {
|
||||
assert.Equal(t, tc.errLogMessage, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.errLogMessage, out))
|
||||
} else {
|
||||
assert.True(t, strings.Contains(out, "error"), fmt.Sprintf("%s should contain error message: %s", tc.desc, out))
|
||||
}
|
||||
case usageLog:
|
||||
assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out))
|
||||
}
|
||||
sdkCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -24,9 +24,7 @@ import (
|
||||
|
||||
var (
|
||||
token = "valid" + "domaintoken"
|
||||
domainID = "domain-id"
|
||||
relation = "administrator"
|
||||
all = "all"
|
||||
conntype = `["publish","subscribe"]`
|
||||
|
||||
errEndJSONInput = errors.New("unexpected end of JSON input")
|
||||
|
||||
@@ -51,3 +51,11 @@ const (
|
||||
listCmd = "list"
|
||||
membersCmd = "members"
|
||||
)
|
||||
|
||||
// Bootstrap commands
|
||||
const (
|
||||
updCmd = "update"
|
||||
rmCmd = "remove"
|
||||
whitelistCmd = "whitelist"
|
||||
bootStrapCmd = "bootstrap"
|
||||
)
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
mgsdk "github.com/absmach/supermq/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(cmd.Context(), 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(cmd.Context(), pageMetadata, args[1])
|
||||
if err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
logJSONCmd(*cmd, sub)
|
||||
return
|
||||
}
|
||||
|
||||
c, err := sdk.ViewSubscription(cmd.Context(), 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(cmd.Context(), 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/supermq/cli"
|
||||
"github.com/absmach/supermq/internal/testsutil"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
svcerr "github.com/absmach/supermq/pkg/errors/service"
|
||||
mgsdk "github.com/absmach/supermq/pkg/sdk"
|
||||
sdkmocks "github.com/absmach/supermq/pkg/sdk/mocks"
|
||||
"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", mock.Anything, 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", mock.Anything, tc.args[0], tc.args[1]).Return(tc.subscription, tc.sdkErr)
|
||||
sdkCall1 := sdkMock.On("ListSubscriptions", mock.Anything, 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", mock.Anything, 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()
|
||||
})
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -31,7 +31,7 @@ func NewMessagesCmd() *cobra.Command {
|
||||
cmd := cobra.Command{
|
||||
Use: "messages [send]",
|
||||
Short: "Send messages",
|
||||
Long: `Send messages using the http-adapter`,
|
||||
Long: `Send messages using the HTTP API`,
|
||||
}
|
||||
|
||||
for i := range cmdMessages {
|
||||
|
||||
@@ -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(cmd.Context(), 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(cmd.Context(), 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(cmd.Context(), 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(cmd.Context(), user, "")
|
||||
if err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
|
||||
ut, err := sdk.CreateToken(cmd.Context(), 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(cmd.Context(), domain, ut.AccessToken)
|
||||
if err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
|
||||
ut, err = sdk.CreateToken(cmd.Context(), 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(cmd.Context(), 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(cmd.Context(), 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(cmd.Context(), 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(cmd.Context(), 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(cmd.Context(), conIDs, domain.ID, ut.AccessToken); err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
|
||||
// send message to test connectivity
|
||||
if err := sdk.SendMessage(cmd.Context(), domain.ID, channels[0].ID, clients[0].Credentials.Secret, fmt.Sprintf(msgFormat, time.Now().Unix(), rand.Int())); err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
if err := sdk.SendMessage(cmd.Context(), domain.ID, channels[0].ID, clients[1].Credentials.Secret, fmt.Sprintf(msgFormat, time.Now().Unix(), rand.Int())); err != nil {
|
||||
logErrorCmd(*cmd, err)
|
||||
return
|
||||
}
|
||||
if err := sdk.SendMessage(cmd.Context(), domain.ID, channels[1].ID, clients[0].Credentials.Secret, fmt.Sprintf(msgFormat, time.Now().Unix(), rand.Int())); 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
|
||||
}
|
||||
@@ -33,13 +33,6 @@ var user = mgsdk.User{
|
||||
Status: users.EnabledStatus.String(),
|
||||
}
|
||||
|
||||
var (
|
||||
validToken = "valid"
|
||||
invalidToken = ""
|
||||
invalidID = "invalidID"
|
||||
extraArg = "extra-arg"
|
||||
)
|
||||
|
||||
func TestCreateUsersCmd(t *testing.T) {
|
||||
sdkMock := new(sdkmocks.SDK)
|
||||
cli.SetSDK(sdkMock)
|
||||
|
||||
@@ -6,7 +6,11 @@ package cli
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/absmach/supermq/certs"
|
||||
smqsdk "github.com/absmach/supermq/pkg/sdk"
|
||||
"github.com/fatih/color"
|
||||
"github.com/hokaccha/go-prettyjson"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -76,6 +80,14 @@ 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)
|
||||
}
|
||||
}
|
||||
|
||||
func convertMetadata(m string) (map[string]any, error) {
|
||||
var metadata map[string]any
|
||||
if m == "" {
|
||||
@@ -86,3 +98,72 @@ func convertMetadata(m string) (map[string]any, error) {
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
const certFileMode = 0o644
|
||||
|
||||
func logSaveCertFiles(cmd cobra.Command, cert smqsdk.Certificate) {
|
||||
files := map[string][]byte{
|
||||
"cert.pem": []byte(cert.Certificate),
|
||||
}
|
||||
if cert.Key != "" {
|
||||
files["key.pem"] = []byte(cert.Key)
|
||||
}
|
||||
for filename, content := range files {
|
||||
if err := saveToFile(filename, content); err != nil {
|
||||
logErrorCmd(cmd, err)
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(cmd.OutOrStdout(), "Saved %s\n", filename)
|
||||
}
|
||||
fmt.Fprintf(cmd.OutOrStdout(), "\nAll certificate files have been saved successfully.\n")
|
||||
}
|
||||
|
||||
func logSaveCAFiles(cmd cobra.Command, certBundle smqsdk.CertificateBundle) {
|
||||
files := map[string][]byte{
|
||||
"ca.crt": certBundle.Certificate,
|
||||
}
|
||||
for filename, content := range files {
|
||||
if err := saveToFile(filename, content); err != nil {
|
||||
logErrorCmd(cmd, err)
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(cmd.OutOrStdout(), "Saved %s\n", filename)
|
||||
}
|
||||
fmt.Fprintf(cmd.OutOrStdout(), "\nAll certificate files have been saved successfully.\n")
|
||||
}
|
||||
|
||||
func logSaveCSRFiles(cmd cobra.Command, csr certs.CSR) {
|
||||
files := map[string][]byte{
|
||||
"file.csr": csr.CSR,
|
||||
}
|
||||
for filename, content := range files {
|
||||
if err := saveToFile(filename, content); err != nil {
|
||||
logErrorCmd(cmd, err)
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(cmd.OutOrStdout(), "Saved %s\n", filename)
|
||||
}
|
||||
fmt.Fprintf(cmd.OutOrStdout(), "\nCSR file have been saved successfully.\n")
|
||||
}
|
||||
|
||||
func logSaveCRLFile(cmd cobra.Command, crlBytes []byte) {
|
||||
filename := "ca.crl"
|
||||
if err := saveToFile(filename, crlBytes); err != nil {
|
||||
logErrorCmd(cmd, err)
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(cmd.OutOrStdout(), "Saved %s\n", filename)
|
||||
fmt.Fprintf(cmd.OutOrStdout(), "\nCRL file has been saved successfully.\n")
|
||||
}
|
||||
|
||||
func saveToFile(filename string, content []byte) error {
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get current working directory: %w", err)
|
||||
}
|
||||
filePath := filepath.Join(cwd, filename)
|
||||
if err := os.WriteFile(filePath, content, certFileMode); err != nil {
|
||||
return fmt.Errorf("failed to write file %s: %w", filename, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user