github.com/saucelabs/saucectl@v0.175.1/internal/cmd/storage/upload.go (about) 1 package storage 2 3 import ( 4 "errors" 5 "fmt" 6 "os" 7 8 cmds "github.com/saucelabs/saucectl/internal/cmd" 9 "github.com/saucelabs/saucectl/internal/hashio" 10 "github.com/saucelabs/saucectl/internal/progress" 11 "github.com/saucelabs/saucectl/internal/segment" 12 "github.com/saucelabs/saucectl/internal/storage" 13 "github.com/saucelabs/saucectl/internal/usage" 14 "github.com/schollz/progressbar/v3" 15 "github.com/spf13/cobra" 16 "golang.org/x/text/cases" 17 "golang.org/x/text/language" 18 ) 19 20 func UploadCommand() *cobra.Command { 21 var out string 22 var force bool 23 var description string 24 25 cmd := &cobra.Command{ 26 Use: "upload filename", 27 Short: "Uploads an app file to Sauce Storage and returns a unique file ID assigned to the app. " + 28 "Sauce Storage supports app files in *.apk, *.aab, *.ipa, or *.zip format.", 29 SilenceUsage: true, 30 Args: func(cmd *cobra.Command, args []string) error { 31 if len(args) == 0 || args[0] == "" { 32 return errors.New("no filename specified") 33 } 34 35 return nil 36 }, 37 PreRun: func(cmd *cobra.Command, args []string) { 38 tracker := segment.DefaultTracker 39 40 go func() { 41 tracker.Collect( 42 cases.Title(language.English).String(cmds.FullName(cmd)), 43 usage.Properties{}.SetFlags(cmd.Flags()), 44 ) 45 _ = tracker.Close() 46 }() 47 }, 48 RunE: func(cmd *cobra.Command, args []string) error { 49 file, err := os.Open(args[0]) 50 if err != nil { 51 return fmt.Errorf("failed to open file: %w", err) 52 } 53 finfo, err := file.Stat() 54 if err != nil { 55 return fmt.Errorf("failed to inspect file: %w", err) 56 } 57 58 hash, err := hashio.SHA256(args[0]) 59 if err != nil { 60 return fmt.Errorf("failed to compute checksum: %w", err) 61 } 62 63 var item storage.Item 64 skipUpload := false 65 66 // Look up the file first. 67 if !force { 68 list, err := appsClient.List(storage.ListOptions{ 69 SHA256: hash, 70 MaxResults: 1, 71 }) 72 if err != nil { 73 return fmt.Errorf("storage lookup failed: %w", err) 74 } 75 if len(list.Items) > 0 { 76 item = list.Items[0] 77 skipUpload = true 78 } 79 } 80 81 // Upload the file if necessary. 82 if !skipUpload { 83 bar := newProgressBar(out, finfo.Size(), "Uploading") 84 reader := progress.NewReadSeeker(file, bar) 85 86 item, err = appsClient.UploadStream(finfo.Name(), description, &reader) 87 if err != nil { 88 return fmt.Errorf("failed to upload file: %w", err) 89 } 90 } 91 92 switch out { 93 case "text": 94 if skipUpload { 95 println("File already stored! The ID of your file is " + item.ID) 96 return nil 97 } 98 println("Success! The ID of your file is " + item.ID) 99 case "json": 100 if err := renderJSON(item); err != nil { 101 return fmt.Errorf("failed to render output: %w", err) 102 } 103 default: 104 return errors.New("unknown output format") 105 } 106 107 return nil 108 }, 109 } 110 111 flags := cmd.Flags() 112 flags.StringVarP(&out, "out", "o", "text", 113 "Output format to the console. Options: text, json.", 114 ) 115 flags.BoolVar(&force, "force", false, 116 "Forces the upload to happen, even if there's already a file in storage with a matching checksum.", 117 ) 118 flags.StringVarP(&description, "description", "d", "", "A description to distinguish your app.") 119 120 return cmd 121 } 122 123 func newProgressBar(outputFormat string, size int64, description ...string) *progressbar.ProgressBar { 124 switch outputFormat { 125 case "text": 126 return progressbar.DefaultBytes(size, description...) 127 default: 128 return progressbar.DefaultBytesSilent(size, description...) 129 } 130 }