NOISSUE - Update bootstrap and provision service (#3476)
Property Based Tests / api-test (push) Has been cancelled
Continuous Delivery / lint-and-build (push) Has been cancelled
Deploy GitHub Pages / swagger-ui (push) Has been cancelled
CI Pipeline / Lint Proto (push) Has been cancelled
CI Pipeline / Detect Changes (push) Has been cancelled
Continuous Delivery / Build and Push Docker Images (push) Has been cancelled
CI Pipeline / lint-and-build (push) Has been cancelled
CI Pipeline / Test ${{ matrix.module }} (push) Has been cancelled
CI Pipeline / Upload Coverage (push) Has been cancelled

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
Signed-off-by: JeffMboya <jangina.mboya@gmail.com>
Co-authored-by: JeffMboya <jangina.mboya@gmail.com>
This commit is contained in:
Steve Munene
2026-05-08 11:35:00 +03:00
committed by GitHub
parent f736bca7c1
commit 7f03134d8e
76 changed files with 9930 additions and 6249 deletions
+186 -14
View File
@@ -14,7 +14,7 @@ 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`,
Long: `Create a new bootstrap enrollment in the given domain`,
Run: func(cmd *cobra.Command, args []string) {
if len(args) != 3 {
logUsageCmd(*cmd, cmd.Use)
@@ -37,11 +37,11 @@ var cmdBootstrap = []cobra.Command{
},
},
{
Use: "get [all | <client_id>] <domain_id> <user_auth_token>",
Use: "get [all | <config_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.
Long: `Get bootstrap enrollment with given ID belonging to the user identified by the given key.
all - lists all config
<client_id> - view config of <client_id>`,
<config_id> - view config of <config_id>`,
Run: func(cmd *cobra.Command, args []string) {
if len(args) != 3 {
logUsageCmd(*cmd, cmd.Use)
@@ -50,7 +50,7 @@ var cmdBootstrap = []cobra.Command{
pageMetadata := mgsdk.PageMetadata{
Offset: Offset,
Limit: Limit,
State: State,
Status: Status,
Name: Name,
}
if args[0] == all {
@@ -77,8 +77,7 @@ var cmdBootstrap = []cobra.Command{
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", ...]'
connection <id> <channel_ids> - Unsupported legacy operation kept for compatibility.
certs <id> <client_cert> <client_key> <ca> - Update bootstrap config certificates.`,
Run: func(cmd *cobra.Command, args []string) {
if len(args) < 4 {
@@ -92,7 +91,7 @@ var cmdBootstrap = []cobra.Command{
return
}
if err := sdk.UpdateBootstrap(cmd.Context(), cfg, args[1], args[2]); err != nil {
if err := sdk.UpdateBootstrap(cmd.Context(), cfg, args[2], args[3]); err != nil {
logErrorCmd(*cmd, err)
return
}
@@ -115,7 +114,7 @@ var cmdBootstrap = []cobra.Command{
return
}
if args[0] == "certs" {
cfg, err := sdk.UpdateBootstrapCerts(cmd.Context(), args[0], args[1], args[2], args[3], args[4], args[5])
cfg, err := sdk.UpdateBootstrapCerts(cmd.Context(), args[1], args[2], args[3], args[4], args[5], args[6])
if err != nil {
logErrorCmd(*cmd, err)
return
@@ -128,7 +127,7 @@ var cmdBootstrap = []cobra.Command{
},
},
{
Use: "remove <client_id> <domain_id> <user_auth_token>",
Use: "remove <config_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) {
@@ -177,7 +176,7 @@ var cmdBootstrap = []cobra.Command{
{
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`,
Long: `Whitelist updates bootstrap status for the given enrollment`,
Run: func(cmd *cobra.Command, args []string) {
if len(args) != 3 {
logUsageCmd(*cmd, cmd.Use)
@@ -190,7 +189,7 @@ var cmdBootstrap = []cobra.Command{
return
}
if err := sdk.Whitelist(cmd.Context(), cfg.ClientID, cfg.State, args[1], args[2]); err != nil {
if err := sdk.Whitelist(cmd.Context(), cfg.ID, cfg.Status, args[1], args[2]); err != nil {
logErrorCmd(*cmd, err)
return
}
@@ -198,14 +197,187 @@ var cmdBootstrap = []cobra.Command{
logOKCmd(*cmd)
},
},
{
Use: "profiles [create <JSON_profile> <domain_id> <user_auth_token> | get [all | <profile_id>] <domain_id> <user_auth_token> | update <JSON_profile> <domain_id> <user_auth_token> | remove <profile_id> <domain_id> <user_auth_token>]",
Short: "Manage bootstrap profiles",
Long: `Manage bootstrap profiles.
create <JSON_profile> - Create a bootstrap profile.
get all - List bootstrap profiles.
get <profile_id> - View bootstrap profile.
update <JSON_profile> - Update bootstrap profile.
remove <profile_id> - Remove bootstrap profile.`,
Run: func(cmd *cobra.Command, args []string) {
if len(args) == 0 {
logUsageCmd(*cmd, cmd.Use)
return
}
switch args[0] {
case create:
if len(args) != 4 {
logUsageCmd(*cmd, cmd.Use)
return
}
var profile mgsdk.BootstrapProfile
if err := json.Unmarshal([]byte(args[1]), &profile); err != nil {
logErrorCmd(*cmd, err)
return
}
profile, err := sdk.CreateBootstrapProfile(cmd.Context(), profile, args[2], args[3])
if err != nil {
logErrorCmd(*cmd, err)
return
}
logJSONCmd(*cmd, profile)
case get:
if len(args) != 4 {
logUsageCmd(*cmd, cmd.Use)
return
}
if args[1] == all {
pageMetadata := mgsdk.PageMetadata{
Offset: Offset,
Limit: Limit,
}
profiles, err := sdk.BootstrapProfiles(cmd.Context(), pageMetadata, args[2], args[3])
if err != nil {
logErrorCmd(*cmd, err)
return
}
logJSONCmd(*cmd, profiles)
return
}
profile, err := sdk.ViewBootstrapProfile(cmd.Context(), args[1], args[2], args[3])
if err != nil {
logErrorCmd(*cmd, err)
return
}
logJSONCmd(*cmd, profile)
case update:
if len(args) != 4 {
logUsageCmd(*cmd, cmd.Use)
return
}
var profile mgsdk.BootstrapProfile
if err := json.Unmarshal([]byte(args[1]), &profile); err != nil {
logErrorCmd(*cmd, err)
return
}
if err := sdk.UpdateBootstrapProfile(cmd.Context(), profile, args[2], args[3]); err != nil {
logErrorCmd(*cmd, err)
return
}
logOKCmd(*cmd)
case "remove":
if len(args) != 4 {
logUsageCmd(*cmd, cmd.Use)
return
}
if err := sdk.RemoveBootstrapProfile(cmd.Context(), args[1], args[2], args[3]); err != nil {
logErrorCmd(*cmd, err)
return
}
logOKCmd(*cmd)
default:
logUsageCmd(*cmd, cmd.Use)
}
},
},
{
Use: "enrollments [assign-profile <config_id> <profile_id> <domain_id> <user_auth_token> | bind <config_id> <JSON_bindings> <domain_id> <user_auth_token> | get-bindings <config_id> <domain_id> <user_auth_token> | refresh-bindings <config_id> <domain_id> <user_auth_token>]",
Short: "Manage bootstrap enrollment bindings",
Long: `Manage bootstrap enrollment profile assignments and bindings.
assign-profile <config_id> <profile_id> - Assign a profile to an enrollment.
bind <config_id> <JSON_bindings> - Bind concrete resources to an enrollment.
get-bindings <config_id> - List stored binding snapshots for an enrollment.
refresh-bindings <config_id> - Refresh stored binding snapshots for an enrollment.`,
Run: func(cmd *cobra.Command, args []string) {
if len(args) == 0 {
logUsageCmd(*cmd, cmd.Use)
return
}
switch args[0] {
case "assign-profile":
if len(args) != 5 {
logUsageCmd(*cmd, cmd.Use)
return
}
if err := sdk.AssignBootstrapProfile(cmd.Context(), args[1], args[2], args[3], args[4]); err != nil {
logErrorCmd(*cmd, err)
return
}
logOKCmd(*cmd)
case "bind":
if len(args) != 5 {
logUsageCmd(*cmd, cmd.Use)
return
}
var bindings []mgsdk.BootstrapBindingRequest
if err := json.Unmarshal([]byte(args[2]), &bindings); err != nil {
logErrorCmd(*cmd, err)
return
}
if err := sdk.BindBootstrapResources(cmd.Context(), args[1], bindings, args[3], args[4]); err != nil {
logErrorCmd(*cmd, err)
return
}
logOKCmd(*cmd)
case "get-bindings":
if len(args) != 4 {
logUsageCmd(*cmd, cmd.Use)
return
}
bindings, err := sdk.BootstrapBindings(cmd.Context(), args[1], args[2], args[3])
if err != nil {
logErrorCmd(*cmd, err)
return
}
logJSONCmd(*cmd, bindings)
case "refresh-bindings":
if len(args) != 4 {
logUsageCmd(*cmd, cmd.Use)
return
}
if err := sdk.RefreshBootstrapBindings(cmd.Context(), args[1], args[2], args[3]); err != nil {
logErrorCmd(*cmd, err)
return
}
logOKCmd(*cmd)
default:
logUsageCmd(*cmd, cmd.Use)
}
},
},
}
// NewBootstrapCmd returns bootstrap command.
func NewBootstrapCmd() *cobra.Command {
cmd := cobra.Command{
Use: "bootstrap [create | get | update | remove | bootstrap | whitelist]",
Use: "bootstrap [create | get | update | remove | bootstrap | whitelist | profiles | enrollments]",
Short: "Bootstrap management",
Long: `Bootstrap management: create, get, update, delete or whitelist Bootstrap config`,
Long: `Bootstrap management: create, get, update, delete, whitelist, profiles, and enrollment bindings`,
}
for i := range cmdBootstrap {
+244 -6
View File
@@ -24,13 +24,21 @@ var (
clientID = testsutil.GenerateUUID(&testing.T{})
channelID = testsutil.GenerateUUID(&testing.T{})
domainID = testsutil.GenerateUUID(&testing.T{})
profileID = testsutil.GenerateUUID(&testing.T{})
bootConfig = mgsdk.BootstrapConfig{
ClientID: clientID,
Channels: []string{channelID},
ID: clientID,
Name: "Test Bootstrap",
ExternalID: "09:6:0:sb:sa",
ExternalKey: "key",
}
bootProfile = mgsdk.BootstrapProfile{
ID: profileID,
Name: "Test Profile",
Description: "Test profile",
TemplateFormat: "go-template",
ContentTemplate: "{\"device_id\":\"{{ .Device.ID }}\"}",
Version: 1,
}
validToken = "validToken"
invalidToken = "invalidToken"
extraArg = "extra-arg"
@@ -44,8 +52,8 @@ func TestCreateBootstrapConfigCmd(t *testing.T) {
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)
jsonConfig := fmt.Sprintf("{\"external_id\":\"09:6:0:sb:sa\", \"external_key\":\"key\", \"name\": \"%s\"}", "Test Bootstrap")
invalidJson := fmt.Sprintf("{\"external_id\":\"09:6:0:sb:sa\", \"external_key\":\"key\", \"name\": \"%s\"", "Test Bootstrap")
cases := []struct {
desc string
args []string
@@ -473,7 +481,7 @@ func TestWhitelistConfigCmd(t *testing.T) {
bootCmd := cli.NewBootstrapCmd()
rootCmd := setFlags(bootCmd)
jsonConfig := fmt.Sprintf("{\"client_id\": \"%s\", \"state\":%d}", clientID, 1)
jsonConfig := fmt.Sprintf("{\"client_id\": \"%s\", \"status\":%d}", clientID, 1)
cases := []struct {
desc string
@@ -504,7 +512,7 @@ func TestWhitelistConfigCmd(t *testing.T) {
{
desc: "whitelist config with invalid json",
args: []string{
fmt.Sprintf("{\"client_id\": \"%s\", \"state\":%d", clientID, 1),
fmt.Sprintf("{\"client_id\": \"%s\", \"status\":%d", clientID, 1),
domainID,
validToken,
},
@@ -631,3 +639,233 @@ func TestBootstrapConfigCmd(t *testing.T) {
})
}
}
func TestBootstrapProfilesCmd(t *testing.T) {
sdkMock := new(sdkmocks.SDK)
cli.SetSDK(sdkMock)
bootCmd := cli.NewBootstrapCmd()
rootCmd := setFlags(bootCmd)
profilePayload, err := json.Marshal(bootProfile)
assert.Nil(t, err)
jsonProfile := string(profilePayload)
cases := []struct {
desc string
args []string
profile mgsdk.BootstrapProfile
page mgsdk.BootstrapProfilesPage
sdkErr errors.SDKError
logType outputLog
errLogMessage string
}{
{
desc: "create bootstrap profile successfully",
args: []string{
"create",
jsonProfile,
domainID,
validToken,
},
profile: bootProfile,
logType: entityLog,
},
{
desc: "get all bootstrap profiles successfully",
args: []string{
"get",
all,
domainID,
validToken,
},
page: mgsdk.BootstrapProfilesPage{
PageRes: mgsdk.PageRes{
Total: 1,
Offset: 0,
Limit: 10,
},
Profiles: []mgsdk.BootstrapProfile{bootProfile},
},
logType: entityLog,
},
{
desc: "view bootstrap profile successfully",
args: []string{
"get",
profileID,
domainID,
validToken,
},
profile: bootProfile,
logType: entityLog,
},
{
desc: "update bootstrap profile successfully",
args: []string{
"update",
jsonProfile,
domainID,
validToken,
},
logType: okLog,
},
{
desc: "remove bootstrap profile successfully",
args: []string{
"remove",
profileID,
domainID,
validToken,
},
logType: okLog,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
var gotProfile mgsdk.BootstrapProfile
var gotPage mgsdk.BootstrapProfilesPage
createCall := sdkMock.On("CreateBootstrapProfile", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.profile, tc.sdkErr)
listCall := sdkMock.On("BootstrapProfiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.page, tc.sdkErr)
viewCall := sdkMock.On("ViewBootstrapProfile", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.profile, tc.sdkErr)
updateCall := sdkMock.On("UpdateBootstrapProfile", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.sdkErr)
removeCall := sdkMock.On("RemoveBootstrapProfile", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.sdkErr)
out := executeCommand(t, rootCmd, append([]string{"profiles"}, tc.args...)...)
switch tc.logType {
case entityLog:
if tc.args[0] == "get" && tc.args[1] == all {
err := json.Unmarshal([]byte(out), &gotPage)
assert.Nil(t, err)
assert.Equal(t, tc.page, gotPage, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.page, gotPage))
} else {
err := json.Unmarshal([]byte(out), &gotProfile)
assert.Nil(t, err)
assert.Equal(t, tc.profile, gotProfile, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.profile, gotProfile))
}
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))
}
createCall.Unset()
listCall.Unset()
viewCall.Unset()
updateCall.Unset()
removeCall.Unset()
})
}
}
func TestBootstrapEnrollmentsCmd(t *testing.T) {
sdkMock := new(sdkmocks.SDK)
cli.SetSDK(sdkMock)
bootCmd := cli.NewBootstrapCmd()
rootCmd := setFlags(bootCmd)
bindings := []mgsdk.BootstrapBindingRequest{
{
Slot: "mqtt_client",
Type: "client",
ResourceID: clientID,
},
}
snapshots := []mgsdk.BootstrapBindingSnapshot{
{
ConfigID: clientID,
Slot: "mqtt_client",
Type: "client",
ResourceID: clientID,
},
}
jsonBindings := fmt.Sprintf("[{\"slot\":\"%s\",\"type\":\"%s\",\"resource_id\":\"%s\"}]", bindings[0].Slot, bindings[0].Type, bindings[0].ResourceID)
cases := []struct {
desc string
args []string
snapshots []mgsdk.BootstrapBindingSnapshot
sdkErr errors.SDKError
logType outputLog
errLogMessage string
}{
{
desc: "assign bootstrap profile successfully",
args: []string{
"assign-profile",
clientID,
profileID,
domainID,
validToken,
},
logType: okLog,
},
{
desc: "bind bootstrap resources successfully",
args: []string{
"bind",
clientID,
jsonBindings,
domainID,
validToken,
},
logType: okLog,
},
{
desc: "get bootstrap bindings successfully",
args: []string{
"get-bindings",
clientID,
domainID,
validToken,
},
snapshots: snapshots,
logType: entityLog,
},
{
desc: "refresh bootstrap bindings successfully",
args: []string{
"refresh-bindings",
clientID,
domainID,
validToken,
},
logType: okLog,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
var gotSnapshots []mgsdk.BootstrapBindingSnapshot
assignCall := sdkMock.On("AssignBootstrapProfile", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.sdkErr)
bindCall := sdkMock.On("BindBootstrapResources", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.sdkErr)
listCall := sdkMock.On("BootstrapBindings", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.snapshots, tc.sdkErr)
refreshCall := sdkMock.On("RefreshBootstrapBindings", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.sdkErr)
out := executeCommand(t, rootCmd, append([]string{"enrollments"}, tc.args...)...)
switch tc.logType {
case entityLog:
err := json.Unmarshal([]byte(out), &gotSnapshots)
assert.Nil(t, err)
assert.Equal(t, tc.snapshots, gotSnapshots, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.snapshots, gotSnapshots))
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))
}
assignCall.Unset()
bindCall.Unset()
listCall.Unset()
refreshCall.Unset()
})
}
}
-2
View File
@@ -30,8 +30,6 @@ var (
Status string = ""
// ConfigPath config path parameter.
ConfigPath string = ""
// State query parameter.
State string = ""
// Topic query parameter.
Topic string = ""
// Contact query parameter.