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

Lines changed: 64 additions & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 2 additions & 2 deletions
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

Lines changed: 36 additions & 0 deletions
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

Lines changed: 38 additions & 1 deletion
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy