From 935fbbe9f23c68502b29f0801bfccafa5bcd15f7 Mon Sep 17 00:00:00 2001 From: Marco Colombo Date: Thu, 16 May 2024 14:05:21 +0200 Subject: [PATCH 1/2] Introducing ota cancel command --- cli/ota/cancel.go | 64 ++++++++++++++++++++++++++++++++++++++ cli/ota/ota.go | 1 + cli/ota/status.go | 4 +-- command/ota/cancel.go | 27 ++++++++++++++++ internal/ota-api/client.go | 38 +++++++++++++++++++++- 5 files changed, 131 insertions(+), 3 deletions(-) create mode 100644 cli/ota/cancel.go create mode 100644 command/ota/cancel.go diff --git a/cli/ota/cancel.go b/cli/ota/cancel.go new file mode 100644 index 00000000..6b1a4c53 --- /dev/null +++ b/cli/ota/cancel.go @@ -0,0 +1,64 @@ +// This file is part of arduino-cloud-cli. +// +// Copyright (C) 2021 ARDUINO SA (http://www.arduino.cc/) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package ota + +import ( + "fmt" + "os" + + "github.com/arduino/arduino-cli/cli/errorcodes" + "github.com/arduino/arduino-cli/cli/feedback" + "github.com/arduino/arduino-cloud-cli/command/ota" + "github.com/arduino/arduino-cloud-cli/config" + "github.com/spf13/cobra" +) + +type cancelFlags struct { + otaID string +} + +func initOtaCancelCommand() *cobra.Command { + flags := &cancelFlags{} + uploadCommand := &cobra.Command{ + Use: "cancel", + Short: "OTA cancel", + Long: "Cancel OTA by OTA ID", + Run: func(cmd *cobra.Command, args []string) { + if err := runOtaCancelCommand(flags); err != nil { + feedback.Errorf("Error during ota cancel: %v", err) + os.Exit(errorcodes.ErrGeneric) + } + }, + } + uploadCommand.Flags().StringVarP(&flags.otaID, "ota-id", "o", "", "OTA ID") + + return uploadCommand +} + +func runOtaCancelCommand(flags *cancelFlags) error { + if flags.otaID == "" { + return fmt.Errorf("required flag \"ota-id\" not set") + } + + cred, err := config.RetrieveCredentials() + if err != nil { + return fmt.Errorf("retrieving credentials: %w", err) + } + + return ota.CancelOta(flags.otaID, cred) +} diff --git a/cli/ota/ota.go b/cli/ota/ota.go index b7a683a8..db4dbc01 100644 --- a/cli/ota/ota.go +++ b/cli/ota/ota.go @@ -33,6 +33,7 @@ func NewCommand() *cobra.Command { otaCommand.AddCommand(initOtaStatusCommand()) otaCommand.AddCommand(initEncodeBinaryCommand()) otaCommand.AddCommand(initDecodeHeaderCommand()) + otaCommand.AddCommand(initOtaCancelCommand()) return otaCommand } diff --git a/cli/ota/status.go b/cli/ota/status.go index ebcce353..89d67081 100644 --- a/cli/ota/status.go +++ b/cli/ota/status.go @@ -43,7 +43,7 @@ func initOtaStatusCommand() *cobra.Command { Short: "OTA status", Long: "Get OTA status by OTA or device ID", Run: func(cmd *cobra.Command, args []string) { - if err := runOtaStatusCommand(flags); err != nil { + if err := runPrintOtaStatusCommand(flags); err != nil { feedback.Errorf("Error during ota get status: %v", err) os.Exit(errorcodes.ErrGeneric) } @@ -58,7 +58,7 @@ func initOtaStatusCommand() *cobra.Command { return uploadCommand } -func runOtaStatusCommand(flags *statusFlags) error { +func runPrintOtaStatusCommand(flags *statusFlags) error { if flags.otaID == "" && flags.deviceId == "" && flags.otaIDs == "" { return fmt.Errorf("required flag(s) \"ota-id\" or \"device-id\" or \"ota-ids\" not set") } diff --git a/command/ota/cancel.go b/command/ota/cancel.go new file mode 100644 index 00000000..dcb02294 --- /dev/null +++ b/command/ota/cancel.go @@ -0,0 +1,27 @@ +package ota + +import ( + "fmt" + + "github.com/arduino/arduino-cli/cli/feedback" + "github.com/arduino/arduino-cloud-cli/config" + otaapi "github.com/arduino/arduino-cloud-cli/internal/ota-api" +) + +func CancelOta(otaid string, cred *config.Credentials) error { + + if feedback.GetFormat() == feedback.JSONMini { + return fmt.Errorf("jsonmini format is not supported for this command") + } + + otapi := otaapi.NewClient(cred) + + if otaid != "" { + _, err := otapi.CancelOta(otaid) + if err != nil { + return err + } + } + + return nil +} diff --git a/internal/ota-api/client.go b/internal/ota-api/client.go index dc647406..bc0f1d8a 100644 --- a/internal/ota-api/client.go +++ b/internal/ota-api/client.go @@ -58,7 +58,11 @@ func NewClient(credentials *config.Credentials) *OtaApiClient { } func (c *OtaApiClient) performGetRequest(endpoint, token string) (*http.Response, error) { - req, err := http.NewRequest("GET", endpoint, nil) + return c.performRequest(endpoint, "GET", token) +} + +func (c *OtaApiClient) performRequest(endpoint, method, token string) (*http.Response, error) { + req, err := http.NewRequest(method, endpoint, nil) if err != nil { return nil, err } @@ -205,3 +209,35 @@ func (c *OtaApiClient) GetOtaStatusByDeviceID(deviceID string, limit int, order return nil, err } + +func (c *OtaApiClient) CancelOta(otaid string) (bool, error) { + + if otaid == "" { + return false, fmt.Errorf("invalid ota-id: empty") + } + + userRequestToken, err := c.src.Token() + if err != nil { + if strings.Contains(err.Error(), "401") { + return false, errors.New("wrong credentials") + } + return false, fmt.Errorf("cannot retrieve a valid token: %w", err) + } + + endpoint := c.host + "/ota/v1/ota/" + otaid + "/cancel" + res, err := c.performRequest(endpoint, "PUT", userRequestToken.AccessToken) + if err != nil { + return false, err + } + defer res.Body.Close() + + if res.StatusCode == 200 { + return true, nil + } else if res.StatusCode == 404 || res.StatusCode == 400 { + return false, fmt.Errorf("ota-id %s not found", otaid) + } else if res.StatusCode == 409 { + return false, ErrAlreadyInProgress + } + + return false, err +} From d3bee09fdf12818be58fadf18057000f36d520b4 Mon Sep 17 00:00:00 2001 From: Marco Colombo Date: Thu, 16 May 2024 14:24:31 +0200 Subject: [PATCH 2/2] Handling conflict --- command/ota/cancel.go | 9 +++++++++ internal/ota-api/client.go | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/command/ota/cancel.go b/command/ota/cancel.go index dcb02294..1c12feda 100644 --- a/command/ota/cancel.go +++ b/command/ota/cancel.go @@ -21,6 +21,15 @@ func CancelOta(otaid string, cred *config.Credentials) error { if err != nil { return err } + // No error, get current status + res, err := otapi.GetOtaStatusByOtaID(otaid, 1, otaapi.OrderDesc) + if err != nil { + return err + } + if res != nil { + feedback.PrintResult(res.Ota) + } + return nil } return nil diff --git a/internal/ota-api/client.go b/internal/ota-api/client.go index bc0f1d8a..1c4bd52f 100644 --- a/internal/ota-api/client.go +++ b/internal/ota-api/client.go @@ -38,6 +38,7 @@ const ( ) var ErrAlreadyInProgress = fmt.Errorf("already in progress") +var ErrAlreadyCancelled = fmt.Errorf("already cancelled") type OtaApiClient struct { client *http.Client @@ -236,7 +237,7 @@ func (c *OtaApiClient) CancelOta(otaid string) (bool, error) { } else if res.StatusCode == 404 || res.StatusCode == 400 { return false, fmt.Errorf("ota-id %s not found", otaid) } else if res.StatusCode == 409 { - return false, ErrAlreadyInProgress + return false, ErrAlreadyCancelled } return false, err