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  }