github.com/arduino/arduino-cloud-cli@v0.0.0-20240517070944-e7a449561083/cli/ota/massupload.go (about) 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 "context" 22 "fmt" 23 "os" 24 "sort" 25 26 "github.com/arduino/arduino-cli/cli/errorcodes" 27 "github.com/arduino/arduino-cli/cli/feedback" 28 "github.com/arduino/arduino-cli/table" 29 "github.com/arduino/arduino-cloud-cli/command/ota" 30 "github.com/arduino/arduino-cloud-cli/config" 31 "github.com/sirupsen/logrus" 32 "github.com/spf13/cobra" 33 ) 34 35 type massUploadFlags struct { 36 deviceIDs []string 37 tags map[string]string 38 file string 39 deferred bool 40 fqbn string 41 doNotApplyHeader bool 42 } 43 44 func initMassUploadCommand() *cobra.Command { 45 flags := &massUploadFlags{} 46 massUploadCommand := &cobra.Command{ 47 Use: "mass-upload", 48 Short: "Mass OTA upload", 49 Long: "Mass OTA upload on devices of Arduino IoT Cloud", 50 Run: func(cmd *cobra.Command, args []string) { 51 if err := runMassUploadCommand(flags); err != nil { 52 feedback.Errorf("Error during ota mass-upload: %v", err) 53 os.Exit(errorcodes.ErrGeneric) 54 } 55 }, 56 } 57 massUploadCommand.Flags().StringSliceVarP(&flags.deviceIDs, "device-ids", "d", nil, 58 "Comma-separated list of device IDs to update") 59 massUploadCommand.Flags().StringToStringVar(&flags.tags, "device-tags", nil, 60 "Comma-separated list of tags with format <key>=<value>.\n"+ 61 "Perform an OTA upload on all devices that match the provided tags.\n"+ 62 "Mutually exclusive with '--device-ids'.", 63 ) 64 massUploadCommand.Flags().StringVarP(&flags.file, "file", "", "", "Binary file (.bin) to be uploaded") 65 massUploadCommand.Flags().BoolVar(&flags.deferred, "deferred", false, "Perform a deferred OTA. It can take up to 1 week.") 66 massUploadCommand.Flags().StringVarP(&flags.fqbn, "fqbn", "b", "", "FQBN of the devices to update") 67 massUploadCommand.Flags().BoolVar(&flags.doNotApplyHeader, "no-header", false, "Do not apply header and compression to binary file before upload") 68 massUploadCommand.MarkFlagRequired("file") 69 massUploadCommand.MarkFlagRequired("fqbn") 70 return massUploadCommand 71 } 72 73 func runMassUploadCommand(flags *massUploadFlags) error { 74 logrus.Infof("Uploading binary %s", flags.file) 75 76 params := &ota.MassUploadParams{ 77 DeviceIDs: flags.deviceIDs, 78 Tags: flags.tags, 79 File: flags.file, 80 Deferred: flags.deferred, 81 FQBN: flags.fqbn, 82 DoNotApplyHeader: flags.doNotApplyHeader, 83 } 84 85 cred, err := config.RetrieveCredentials() 86 if err != nil { 87 return fmt.Errorf("retrieving credentials: %w", err) 88 } 89 90 resp, err := ota.MassUpload(context.TODO(), params, cred) 91 if err != nil { 92 return err 93 } 94 95 // Put successful devices ahead 96 sort.SliceStable(resp, func(i, j int) bool { 97 return resp[i].Err == nil 98 }) 99 100 feedback.PrintResult(massUploadResult{resp}) 101 return nil 102 } 103 104 type massUploadResult struct { 105 res []ota.Result 106 } 107 108 func (r massUploadResult) Data() interface{} { 109 return r.res 110 } 111 112 func (r massUploadResult) String() string { 113 if len(r.res) == 0 { 114 return "No OTA done." 115 } 116 t := table.New() 117 hasErrorReason := false 118 for _, r := range r.res { 119 if r.OtaStatus.ErrorReason != "" { 120 hasErrorReason = true 121 break 122 } 123 } 124 125 if hasErrorReason { 126 t.SetHeader("Device ID", "Ota ID", "Result", "Error Reason") 127 } else { 128 t.SetHeader("Device ID", "Ota ID", "Result") 129 } 130 131 // Now print the table 132 for _, r := range r.res { 133 outcome := "Success" 134 if r.Err != nil { 135 outcome = fmt.Sprintf("Fail: %s", r.Err.Error()) 136 } 137 if r.OtaStatus.Status != "" { 138 outcome = r.OtaStatus.MapStatus() 139 } 140 141 line := []interface{}{r.ID, r.OtaStatus.ID, outcome} 142 if hasErrorReason { 143 line = append(line, r.OtaStatus.ErrorReason) 144 } 145 t.AddRow(line...) 146 } 147 return t.Render() 148 }