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  }