Skip to content

Commit d4cd9aa

Browse files
Update interface for rwx logs (#267)
Co-authored-by: Dan Manges <[email protected]>
1 parent ff55b94 commit d4cd9aa

File tree

4 files changed

+447
-31
lines changed

4 files changed

+447
-31
lines changed

cmd/rwx/logs.go

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@ import (
1212
)
1313

1414
var (
15-
LogsOutput string
15+
LogsOutputDir string
16+
LogsOutputFile string
17+
LogsJson bool
18+
LogsAutoExtract bool
19+
LogsOpen bool
1620

1721
logsCmd = &cobra.Command{
1822
PreRunE: func(cmd *cobra.Command, args []string) error {
@@ -25,27 +29,42 @@ var (
2529

2630
taskId := args[0]
2731

28-
outputDir := LogsOutput
29-
if outputDir == "" {
30-
var err error
31-
outputDir, err = getDefaultLogsDir()
32-
if err != nil {
33-
return errors.Wrap(err, "unable to determine default logs directory")
34-
}
32+
outputDirSet := cmd.Flags().Changed("output-dir")
33+
outputFileSet := cmd.Flags().Changed("output-file")
34+
if outputDirSet && outputFileSet {
35+
return errors.New("output-dir and output-file cannot be used together")
3536
}
3637

37-
if err := os.MkdirAll(outputDir, 0755); err != nil {
38-
return errors.Wrapf(err, "unable to create output directory %s", outputDir)
39-
}
38+
var absOutputDir string
39+
var absOutputFile string
40+
var err error
4041

41-
absOutputDir, err := filepath.Abs(outputDir)
42-
if err != nil {
43-
return errors.Wrapf(err, "unable to resolve absolute path for %s", outputDir)
42+
if LogsOutputFile != "" {
43+
absOutputFile, err = filepath.Abs(LogsOutputFile)
44+
if err != nil {
45+
return errors.Wrapf(err, "unable to resolve absolute path for %s", LogsOutputFile)
46+
}
47+
} else {
48+
outputDir := LogsOutputDir
49+
if !outputDirSet {
50+
outputDir, err = getDefaultLogsDir()
51+
if err != nil {
52+
return errors.Wrap(err, "unable to determine default logs directory")
53+
}
54+
}
55+
absOutputDir, err = filepath.Abs(outputDir)
56+
if err != nil {
57+
return errors.Wrapf(err, "unable to resolve absolute path for %s", outputDir)
58+
}
4459
}
4560

4661
err = service.DownloadLogs(cli.DownloadLogsConfig{
47-
TaskID: taskId,
48-
OutputDir: absOutputDir,
62+
TaskID: taskId,
63+
OutputDir: absOutputDir,
64+
OutputFile: absOutputFile,
65+
Json: LogsJson,
66+
AutoExtract: LogsAutoExtract,
67+
Open: LogsOpen,
4968
})
5069
if err != nil {
5170
return err
@@ -59,7 +78,12 @@ var (
5978
)
6079

6180
func init() {
62-
logsCmd.Flags().StringVarP(&LogsOutput, "output", "o", "", "output directory for the downloaded log file (defaults to Downloads folder)")
81+
logsCmd.Flags().StringVar(&LogsOutputDir, "output-dir", "", "output directory for the downloaded log file (defaults to Downloads folder)")
82+
logsCmd.Flags().StringVar(&LogsOutputFile, "output-file", "", "output file path for the downloaded log file")
83+
logsCmd.MarkFlagsMutuallyExclusive("output-dir", "output-file")
84+
logsCmd.Flags().BoolVar(&LogsJson, "json", false, "output file locations as JSON")
85+
logsCmd.Flags().BoolVar(&LogsAutoExtract, "auto-extract", false, "automatically extract zip archives")
86+
logsCmd.Flags().BoolVar(&LogsOpen, "open", false, "automatically open the downloaded file(s)")
6387
}
6488

6589
func getDefaultLogsDir() (string, error) {

internal/cli/config.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -329,13 +329,20 @@ func (c PullImageConfig) Validate() error {
329329
}
330330

331331
type DownloadLogsConfig struct {
332-
TaskID string
333-
OutputDir string
332+
TaskID string
333+
OutputDir string
334+
OutputFile string
335+
Json bool
336+
AutoExtract bool
337+
Open bool
334338
}
335339

336340
func (c DownloadLogsConfig) Validate() error {
337341
if c.TaskID == "" {
338342
return errors.New("task ID must be provided")
339343
}
344+
if c.OutputDir != "" && c.OutputFile != "" {
345+
return errors.New("output-dir and output-file cannot be used together")
346+
}
340347
return nil
341348
}

internal/cli/service.go

Lines changed: 125 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cli
22

33
import (
4+
"archive/zip"
45
"encoding/json"
56
"fmt"
67
"io"
@@ -12,6 +13,8 @@ import (
1213
"sync/atomic"
1314
"time"
1415

16+
"github.com/skratchdot/open-golang/open"
17+
1518
"github.com/rwx-cloud/cli/internal/accesstoken"
1619
"github.com/rwx-cloud/cli/internal/api"
1720
"github.com/rwx-cloud/cli/internal/dotenv"
@@ -587,20 +590,139 @@ func (s Service) DownloadLogs(cfg DownloadLogsConfig) error {
587590
return errors.Wrap(err, "unable to download logs")
588591
}
589592

590-
outputPath := filepath.Join(cfg.OutputDir, LogDownloadRequest.Filename)
593+
var outputPath string
594+
if cfg.OutputFile != "" {
595+
outputPath = cfg.OutputFile
596+
} else {
597+
outputPath = filepath.Join(cfg.OutputDir, LogDownloadRequest.Filename)
598+
}
599+
600+
outputDir := filepath.Dir(outputPath)
601+
if err := os.MkdirAll(outputDir, 0755); err != nil {
602+
return errors.Wrapf(err, "unable to create output directory %s", outputDir)
603+
}
591604

592605
if _, err := os.Stat(outputPath); err == nil {
593-
fmt.Fprintf(s.Stdout, "Overwriting existing file at %s\n", outputPath)
606+
if !cfg.Json {
607+
fmt.Fprintf(s.Stdout, "Overwriting existing file at %s\n", outputPath)
608+
}
594609
}
595610

596611
if err := os.WriteFile(outputPath, logBytes, 0644); err != nil {
597612
return errors.Wrapf(err, "unable to write log file to %s", outputPath)
598613
}
599614

600-
fmt.Fprintf(s.Stdout, "Logs downloaded to %s\n", outputPath)
615+
var outputFiles []string
616+
outputFiles = append(outputFiles, outputPath)
617+
618+
if cfg.AutoExtract && strings.HasSuffix(strings.ToLower(outputPath), ".zip") {
619+
// Create a directory named after the zip file (without .zip extension)
620+
zipName := filepath.Base(outputPath)
621+
extractDirName := strings.TrimSuffix(zipName, filepath.Ext(zipName))
622+
extractDir := filepath.Join(filepath.Dir(outputPath), extractDirName)
623+
624+
if err := os.MkdirAll(extractDir, 0755); err != nil {
625+
return errors.Wrapf(err, "unable to create extraction directory %s", extractDir)
626+
}
627+
628+
extractedFiles, err := extractZip(outputPath, extractDir)
629+
if err != nil {
630+
return errors.Wrapf(err, "unable to extract zip archive %s", outputPath)
631+
}
632+
outputFiles = extractedFiles
633+
634+
if !cfg.Json {
635+
fmt.Fprintf(s.Stdout, "Extracted %d file(s) from %s to %s\n", len(extractedFiles), outputPath, extractDir)
636+
}
637+
}
638+
639+
if cfg.Open {
640+
for _, file := range outputFiles {
641+
if err := open.Run(file); err != nil {
642+
if !cfg.Json {
643+
fmt.Fprintf(s.Stderr, "Failed to open %s: %v\n", file, err)
644+
}
645+
}
646+
}
647+
}
648+
649+
if cfg.Json {
650+
output := struct {
651+
OutputFiles []string `json:"outputFiles"`
652+
}{
653+
OutputFiles: outputFiles,
654+
}
655+
if err := json.NewEncoder(s.Stdout).Encode(output); err != nil {
656+
return errors.Wrap(err, "unable to encode JSON output")
657+
}
658+
} else {
659+
if len(outputFiles) == 1 {
660+
fmt.Fprintf(s.Stdout, "Logs downloaded to %s\n", outputFiles[0])
661+
} else {
662+
fmt.Fprintf(s.Stdout, "Logs downloaded and extracted:\n")
663+
for _, file := range outputFiles {
664+
fmt.Fprintf(s.Stdout, " %s\n", file)
665+
}
666+
}
667+
}
601668
return nil
602669
}
603670

671+
func extractZip(zipPath, destDir string) ([]string, error) {
672+
reader, err := zip.OpenReader(zipPath)
673+
if err != nil {
674+
return nil, errors.Wrap(err, "unable to open zip file")
675+
}
676+
defer reader.Close()
677+
678+
var extractedFiles []string
679+
680+
for _, file := range reader.File {
681+
filePath := filepath.Join(destDir, file.Name)
682+
if !strings.HasPrefix(filePath, filepath.Clean(destDir)+string(os.PathSeparator)) {
683+
return nil, fmt.Errorf("invalid file path in zip: %s", file.Name)
684+
}
685+
686+
if file.FileInfo().IsDir() {
687+
if err := os.MkdirAll(filePath, 0755); err != nil {
688+
return nil, errors.Wrapf(err, "unable to create directory %s", filePath)
689+
}
690+
continue
691+
}
692+
693+
if err := os.MkdirAll(filepath.Dir(filePath), 0755); err != nil {
694+
return nil, errors.Wrapf(err, "unable to create directory for %s", filePath)
695+
}
696+
697+
rc, err := file.Open()
698+
if err != nil {
699+
return nil, errors.Wrapf(err, "unable to open file %s in zip", file.Name)
700+
}
701+
702+
outFile, err := os.Create(filePath)
703+
if err != nil {
704+
rc.Close()
705+
return nil, errors.Wrapf(err, "unable to create file %s", filePath)
706+
}
707+
708+
_, err = io.Copy(outFile, rc)
709+
outFile.Close()
710+
rc.Close()
711+
712+
if err != nil {
713+
return nil, errors.Wrapf(err, "unable to extract file %s", filePath)
714+
}
715+
716+
if err := os.Chmod(filePath, file.FileInfo().Mode()); err != nil {
717+
return nil, errors.Wrapf(err, "unable to set permissions for %s", filePath)
718+
}
719+
720+
extractedFiles = append(extractedFiles, filePath)
721+
}
722+
723+
return extractedFiles, nil
724+
}
725+
604726
func (s Service) SetSecretsInVault(cfg SetSecretsInVaultConfig) error {
605727
defer s.outputLatestVersionMessage()
606728
err := cfg.Validate()

0 commit comments

Comments
 (0)