github.com/saucelabs/saucectl@v0.175.1/internal/cmd/artifacts/download.go (about) 1 package artifacts 2 3 import ( 4 "errors" 5 "fmt" 6 "os" 7 "path" 8 9 cmds "github.com/saucelabs/saucectl/internal/cmd" 10 "github.com/saucelabs/saucectl/internal/fpath" 11 "github.com/saucelabs/saucectl/internal/http" 12 "github.com/saucelabs/saucectl/internal/segment" 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 DownloadCommand() *cobra.Command { 21 var targetDir string 22 var out string 23 24 cmd := &cobra.Command{ 25 Use: "download <jobID> <file-pattern>", 26 Short: "Downloads the specified artifacts from the given job. Supports glob pattern.", 27 SilenceUsage: true, 28 Args: func(cmd *cobra.Command, args []string) error { 29 if len(args) == 0 || args[0] == "" { 30 return errors.New("no job ID specified") 31 } 32 if len(args) == 1 || args[1] == "" { 33 return errors.New("no file pattern specified") 34 } 35 36 return nil 37 }, 38 PreRunE: func(cmd *cobra.Command, args []string) error { 39 err := http.CheckProxy() 40 if err != nil { 41 return fmt.Errorf("invalid HTTP_PROXY value") 42 } 43 44 tracker := segment.DefaultTracker 45 46 go func() { 47 tracker.Collect( 48 cases.Title(language.English).String(cmds.FullName(cmd)), 49 usage.Properties{}.SetFlags(cmd.Flags()), 50 ) 51 _ = tracker.Close() 52 }() 53 return nil 54 }, 55 RunE: func(cmd *cobra.Command, args []string) error { 56 jobID := args[0] 57 filePattern := args[1] 58 return download(jobID, filePattern, targetDir, out) 59 }, 60 } 61 62 flags := cmd.Flags() 63 flags.StringVar(&targetDir, "target-dir", "", "Save files to target directory. Defaults to current working directory.") 64 flags.StringVarP(&out, "out", "o", "text", "Output format to the console. Options: text, json.") 65 66 return cmd 67 } 68 69 func download(jobID, filePattern, targetDir, outputFormat string) error { 70 lst, err := artifactSvc.List(jobID) 71 if err != nil { 72 return err 73 } 74 75 files := fpath.MatchFiles(lst.Items, []string{filePattern}) 76 lst.Items = files 77 78 bar := newDownloadProgressBar(outputFormat, len(files)) 79 for _, f := range files { 80 _ = bar.Add(1) 81 body, err := artifactSvc.Download(jobID, f) 82 if err != nil { 83 return fmt.Errorf("failed to get file: %w", err) 84 } 85 86 filePath := f 87 if targetDir != "" { 88 if err := os.MkdirAll(targetDir, os.ModePerm); err != nil { 89 return fmt.Errorf("failed to create target dir: %w", err) 90 } 91 filePath = path.Join(targetDir, filePath) 92 } 93 94 file, err := os.Create(filePath) 95 if err != nil { 96 return fmt.Errorf("failed to create file: %w", err) 97 } 98 99 _, err = file.Write(body) 100 if err != nil { 101 return fmt.Errorf("failed to write to the file: %w", err) 102 } 103 _ = file.Close() 104 } 105 bar.Close() 106 107 switch outputFormat { 108 case "json": 109 if err := renderJSON(lst); err != nil { 110 return fmt.Errorf("failed to render output: %w", err) 111 } 112 case "text": 113 renderTable(lst) 114 default: 115 return errors.New("unknown output format") 116 } 117 118 return nil 119 } 120 121 func newDownloadProgressBar(output string, count int) *progressbar.ProgressBar { 122 switch output { 123 case "json": 124 return progressbar.DefaultSilent(int64(count)) 125 default: 126 return progressbar.Default(int64(count)) 127 } 128 }