Skip to content

Commit e7a4495

Browse files
authored
OTA v2: support for cancel of an in-progess/pending ota (#153)
* Introducing ota cancel command * Handling conflict
1 parent 4022ef6 commit e7a4495

File tree

5 files changed

+141
-3
lines changed

5 files changed

+141
-3
lines changed

cli/ota/cancel.go

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// This file is part of arduino-cloud-cli.
2+
//
3+
// Copyright (C) 2021 ARDUINO SA (http://www.arduino.cc/)
4+
//
5+
// This program is free software: you can redistribute it and/or modify
6+
// it under the terms of the GNU Affero General Public License as published
7+
// by the Free Software Foundation, either version 3 of the License, or
8+
// (at your option) any later version.
9+
//
10+
// This program is distributed in the hope that it will be useful,
11+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
// GNU Affero General Public License for more details.
14+
//
15+
// You should have received a copy of the GNU Affero General Public License
16+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
17+
18+
package ota
19+
20+
import (
21+
"fmt"
22+
"os"
23+
24+
"github.com/arduino/arduino-cli/cli/errorcodes"
25+
"github.com/arduino/arduino-cli/cli/feedback"
26+
"github.com/arduino/arduino-cloud-cli/command/ota"
27+
"github.com/arduino/arduino-cloud-cli/config"
28+
"github.com/spf13/cobra"
29+
)
30+
31+
type cancelFlags struct {
32+
otaID string
33+
}
34+
35+
func initOtaCancelCommand() *cobra.Command {
36+
flags := &cancelFlags{}
37+
uploadCommand := &cobra.Command{
38+
Use: "cancel",
39+
Short: "OTA cancel",
40+
Long: "Cancel OTA by OTA ID",
41+
Run: func(cmd *cobra.Command, args []string) {
42+
if err := runOtaCancelCommand(flags); err != nil {
43+
feedback.Errorf("Error during ota cancel: %v", err)
44+
os.Exit(errorcodes.ErrGeneric)
45+
}
46+
},
47+
}
48+
uploadCommand.Flags().StringVarP(&flags.otaID, "ota-id", "o", "", "OTA ID")
49+
50+
return uploadCommand
51+
}
52+
53+
func runOtaCancelCommand(flags *cancelFlags) error {
54+
if flags.otaID == "" {
55+
return fmt.Errorf("required flag \"ota-id\" not set")
56+
}
57+
58+
cred, err := config.RetrieveCredentials()
59+
if err != nil {
60+
return fmt.Errorf("retrieving credentials: %w", err)
61+
}
62+
63+
return ota.CancelOta(flags.otaID, cred)
64+
}

cli/ota/ota.go

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ func NewCommand() *cobra.Command {
3333
otaCommand.AddCommand(initOtaStatusCommand())
3434
otaCommand.AddCommand(initEncodeBinaryCommand())
3535
otaCommand.AddCommand(initDecodeHeaderCommand())
36+
otaCommand.AddCommand(initOtaCancelCommand())
3637

3738
return otaCommand
3839
}

cli/ota/status.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ func initOtaStatusCommand() *cobra.Command {
4343
Short: "OTA status",
4444
Long: "Get OTA status by OTA or device ID",
4545
Run: func(cmd *cobra.Command, args []string) {
46-
if err := runOtaStatusCommand(flags); err != nil {
46+
if err := runPrintOtaStatusCommand(flags); err != nil {
4747
feedback.Errorf("Error during ota get status: %v", err)
4848
os.Exit(errorcodes.ErrGeneric)
4949
}
@@ -58,7 +58,7 @@ func initOtaStatusCommand() *cobra.Command {
5858
return uploadCommand
5959
}
6060

61-
func runOtaStatusCommand(flags *statusFlags) error {
61+
func runPrintOtaStatusCommand(flags *statusFlags) error {
6262
if flags.otaID == "" && flags.deviceId == "" && flags.otaIDs == "" {
6363
return fmt.Errorf("required flag(s) \"ota-id\" or \"device-id\" or \"ota-ids\" not set")
6464
}

command/ota/cancel.go

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package ota
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/arduino/arduino-cli/cli/feedback"
7+
"github.com/arduino/arduino-cloud-cli/config"
8+
otaapi "github.com/arduino/arduino-cloud-cli/internal/ota-api"
9+
)
10+
11+
func CancelOta(otaid string, cred *config.Credentials) error {
12+
13+
if feedback.GetFormat() == feedback.JSONMini {
14+
return fmt.Errorf("jsonmini format is not supported for this command")
15+
}
16+
17+
otapi := otaapi.NewClient(cred)
18+
19+
if otaid != "" {
20+
_, err := otapi.CancelOta(otaid)
21+
if err != nil {
22+
return err
23+
}
24+
// No error, get current status
25+
res, err := otapi.GetOtaStatusByOtaID(otaid, 1, otaapi.OrderDesc)
26+
if err != nil {
27+
return err
28+
}
29+
if res != nil {
30+
feedback.PrintResult(res.Ota)
31+
}
32+
return nil
33+
}
34+
35+
return nil
36+
}

internal/ota-api/client.go

+38-1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ const (
3838
)
3939

4040
var ErrAlreadyInProgress = fmt.Errorf("already in progress")
41+
var ErrAlreadyCancelled = fmt.Errorf("already cancelled")
4142

4243
type OtaApiClient struct {
4344
client *http.Client
@@ -58,7 +59,11 @@ func NewClient(credentials *config.Credentials) *OtaApiClient {
5859
}
5960

6061
func (c *OtaApiClient) performGetRequest(endpoint, token string) (*http.Response, error) {
61-
req, err := http.NewRequest("GET", endpoint, nil)
62+
return c.performRequest(endpoint, "GET", token)
63+
}
64+
65+
func (c *OtaApiClient) performRequest(endpoint, method, token string) (*http.Response, error) {
66+
req, err := http.NewRequest(method, endpoint, nil)
6267
if err != nil {
6368
return nil, err
6469
}
@@ -205,3 +210,35 @@ func (c *OtaApiClient) GetOtaStatusByDeviceID(deviceID string, limit int, order
205210

206211
return nil, err
207212
}
213+
214+
func (c *OtaApiClient) CancelOta(otaid string) (bool, error) {
215+
216+
if otaid == "" {
217+
return false, fmt.Errorf("invalid ota-id: empty")
218+
}
219+
220+
userRequestToken, err := c.src.Token()
221+
if err != nil {
222+
if strings.Contains(err.Error(), "401") {
223+
return false, errors.New("wrong credentials")
224+
}
225+
return false, fmt.Errorf("cannot retrieve a valid token: %w", err)
226+
}
227+
228+
endpoint := c.host + "/ota/v1/ota/" + otaid + "/cancel"
229+
res, err := c.performRequest(endpoint, "PUT", userRequestToken.AccessToken)
230+
if err != nil {
231+
return false, err
232+
}
233+
defer res.Body.Close()
234+
235+
if res.StatusCode == 200 {
236+
return true, nil
237+
} else if res.StatusCode == 404 || res.StatusCode == 400 {
238+
return false, fmt.Errorf("ota-id %s not found", otaid)
239+
} else if res.StatusCode == 409 {
240+
return false, ErrAlreadyCancelled
241+
}
242+
243+
return false, err
244+
}

0 commit comments

Comments
 (0)