diff --git a/pkg/services/supportbundles/interface.go b/pkg/services/supportbundles/interface.go index 3a46ab3e1ea..66ed4a1e867 100644 --- a/pkg/services/supportbundles/interface.go +++ b/pkg/services/supportbundles/interface.go @@ -23,10 +23,10 @@ func (s State) String() string { type Bundle struct { UID string `json:"uid"` State State `json:"state"` - FilePath string `json:"filePath"` Creator string `json:"creator"` CreatedAt int64 `json:"createdAt"` ExpiresAt int64 `json:"expiresAt"` + TarBytes []byte `json:"tarBytes,omitempty"` } type CollectorFunc func(context.Context) (*SupportItem, error) diff --git a/pkg/services/supportbundles/supportbundlesimpl/api.go b/pkg/services/supportbundles/supportbundlesimpl/api.go index 9c48dde29d0..aaeab726e3a 100644 --- a/pkg/services/supportbundles/supportbundlesimpl/api.go +++ b/pkg/services/supportbundles/supportbundlesimpl/api.go @@ -5,7 +5,6 @@ import ( "encoding/json" "fmt" "net/http" - "os" "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/routing" @@ -72,32 +71,20 @@ func (s *Service) handleCreate(ctx *models.ReqContext) response.Response { return response.JSON(http.StatusCreated, data) } -func (s *Service) handleDownload(ctx *models.ReqContext) { +func (s *Service) handleDownload(ctx *models.ReqContext) response.Response { uid := web.Params(ctx.Req)[":uid"] bundle, err := s.get(ctx.Req.Context(), uid) if err != nil { - ctx.Redirect("/admin/support-bundles") - return + return response.Redirect("/admin/support-bundles") } if bundle.State != supportbundles.StateComplete { - ctx.Redirect("/admin/support-bundles") - return - } - - if bundle.FilePath == "" { - ctx.Redirect("/admin/support-bundles") - return - } - - if _, err := os.Stat(bundle.FilePath); err != nil { - ctx.Redirect("/admin/support-bundles") - return + return response.Redirect("/admin/support-bundles") } ctx.Resp.Header().Set("Content-Type", "application/tar+gzip") - ctx.Resp.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%d.tar.gz", bundle.CreatedAt)) - http.ServeFile(ctx.Resp, ctx.Req, bundle.FilePath) + ctx.Resp.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s.tar.gz", uid)) + return response.CreateNormalResponse(ctx.Resp.Header(), bundle.TarBytes, http.StatusOK) } func (s *Service) handleRemove(ctx *models.ReqContext) response.Response { diff --git a/pkg/services/supportbundles/supportbundlesimpl/service.go b/pkg/services/supportbundles/supportbundlesimpl/service.go index b9264fdda33..599e82b492c 100644 --- a/pkg/services/supportbundles/supportbundlesimpl/service.go +++ b/pkg/services/supportbundles/supportbundlesimpl/service.go @@ -3,8 +3,6 @@ package supportbundlesimpl import ( "context" "fmt" - "os" - "path/filepath" "time" "github.com/grafana/grafana/pkg/api/routing" @@ -151,12 +149,6 @@ func (s *Service) remove(ctx context.Context, uid string) error { return fmt.Errorf("could not remove a support bundle with uid %s as it is still being created", uid) } - if bundle.FilePath != "" { - if err := os.RemoveAll(filepath.Dir(bundle.FilePath)); err != nil { - return fmt.Errorf("could not remove directory for support bundle %s: %w", uid, err) - } - } - // Remove the KV store entry return s.store.Remove(ctx, uid) } diff --git a/pkg/services/supportbundles/supportbundlesimpl/service_bundle.go b/pkg/services/supportbundles/supportbundlesimpl/service_bundle.go index 2af15e44fec..6a798a0c051 100644 --- a/pkg/services/supportbundles/supportbundlesimpl/service_bundle.go +++ b/pkg/services/supportbundles/supportbundlesimpl/service_bundle.go @@ -6,11 +6,10 @@ import ( "compress/gzip" "context" "errors" - "fmt" "io" - "os" "path/filepath" "runtime/debug" + "time" "github.com/grafana/grafana/pkg/services/supportbundles" ) @@ -18,8 +17,8 @@ import ( var ErrCollectorPanicked = errors.New("collector panicked") type bundleResult struct { - path string - err error + tarBytes []byte + err error } func (s *Service) startBundleWork(ctx context.Context, collectors []string, uid string) { @@ -33,47 +32,43 @@ func (s *Service) startBundleWork(ctx context.Context, collectors []string, uid } }() - sbFilePath, err := s.bundle(ctx, collectors, uid) + bundleBytes, err := s.bundle(ctx, collectors, uid) if err != nil { result <- bundleResult{err: err} } - result <- bundleResult{ - path: sbFilePath, - } + result <- bundleResult{tarBytes: bundleBytes} close(result) }() select { case <-ctx.Done(): s.log.Warn("Context cancelled while collecting support bundle") - if err := s.store.Update(ctx, uid, supportbundles.StateTimeout, ""); err != nil { + if err := s.store.Update(ctx, uid, supportbundles.StateTimeout, nil); err != nil { s.log.Error("failed to update bundle after timeout") } return case r := <-result: if r.err != nil { - if err := s.store.Update(ctx, uid, supportbundles.StateError, ""); err != nil { + s.log.Error("failed to make bundle", "error", r.err, "uid", uid) + if err := s.store.Update(ctx, uid, supportbundles.StateError, nil); err != nil { s.log.Error("failed to update bundle after error") } return } - if err := s.store.Update(ctx, uid, supportbundles.StateComplete, r.path); err != nil { + if err := s.store.Update(ctx, uid, supportbundles.StateComplete, r.tarBytes); err != nil { s.log.Error("failed to update bundle after completion") } return } } -func (s *Service) bundle(ctx context.Context, collectors []string, uid string) (string, error) { +func (s *Service) bundle(ctx context.Context, collectors []string, uid string) ([]byte, error) { lookup := make(map[string]bool, len(collectors)) for _, c := range collectors { lookup[c] = true } - sbDir, err := os.MkdirTemp("", "") - if err != nil { - return "", err - } + files := map[string][]byte{} for _, collector := range s.collectors { if !lookup[collector.UID] && !collector.IncludedByDefault { @@ -86,70 +81,42 @@ func (s *Service) bundle(ctx context.Context, collectors []string, uid string) ( // write item to file if item != nil { - if err := os.WriteFile(filepath.Join(sbDir, item.Filename), item.FileBytes, 0600); err != nil { - s.log.Warn("Failed to collect support bundle item", "error", err) - } + files[item.Filename] = item.FileBytes } } // create tar.gz file var buf bytes.Buffer - errCompress := compress(sbDir, &buf) + errCompress := compress(files, &buf) if errCompress != nil { - return "", errCompress + return nil, errCompress } - finalFilePath := filepath.Join(sbDir, fmt.Sprintf("%s.tar.gz", uid)) - - // Ignore gosec G304 as this function is only used internally. - //nolint:gosec - fileToWrite, err := os.OpenFile(finalFilePath, os.O_CREATE|os.O_RDWR, 0600) - if err != nil { - return "", err - } - if _, err := io.Copy(fileToWrite, &buf); err != nil { - return "", err - } - - return finalFilePath, nil + return buf.Bytes(), nil } -func compress(src string, buf io.Writer) error { +func compress(files map[string][]byte, buf io.Writer) error { // tar > gzip > buf zr := gzip.NewWriter(buf) tw := tar.NewWriter(zr) - // walk through every file in the folder - err := filepath.Walk(src, func(file string, fi os.FileInfo, err error) error { - // if not a dir, write file content - if !fi.IsDir() { - // generate tar header - header, err := tar.FileInfoHeader(fi, file) - if err != nil { - return err - } + for name, data := range files { + header := &tar.Header{ + Name: name, + ModTime: time.Now(), + Mode: int64(0o644), + Size: int64(len(data)), + } - header.Name = filepath.ToSlash("/bundle/" + header.Name) + header.Name = filepath.ToSlash("/bundle/" + header.Name) + // write header + if err := tw.WriteHeader(header); err != nil { + return err + } - // write header - if err := tw.WriteHeader(header); err != nil { - return err - } - - // Ignore gosec G304 as this function is only used internally. - //nolint:gosec - data, err := os.Open(file) - if err != nil { - return err - } - if _, err := io.Copy(tw, data); err != nil { - return err - } + if _, err := io.Copy(tw, bytes.NewReader(data)); err != nil { + return err } - return nil - }) - if err != nil { - return err } // produce tar diff --git a/pkg/services/supportbundles/supportbundlesimpl/store.go b/pkg/services/supportbundles/supportbundlesimpl/store.go index 01a3fe1becc..d0f8df74094 100644 --- a/pkg/services/supportbundles/supportbundlesimpl/store.go +++ b/pkg/services/supportbundles/supportbundlesimpl/store.go @@ -31,7 +31,7 @@ type bundleStore interface { Get(ctx context.Context, uid string) (*supportbundles.Bundle, error) List() ([]supportbundles.Bundle, error) Remove(ctx context.Context, uid string) error - Update(ctx context.Context, uid string, state supportbundles.State, filePath string) error + Update(ctx context.Context, uid string, state supportbundles.State, tarBytes []byte) error } func (s *store) Create(ctx context.Context, usr *user.SignedInUser) (*supportbundles.Bundle, error) { @@ -54,14 +54,14 @@ func (s *store) Create(ctx context.Context, usr *user.SignedInUser) (*supportbun return &bundle, nil } -func (s *store) Update(ctx context.Context, uid string, state supportbundles.State, filePath string) error { +func (s *store) Update(ctx context.Context, uid string, state supportbundles.State, tarBytes []byte) error { bundle, err := s.Get(ctx, uid) if err != nil { return err } bundle.State = state - bundle.FilePath = filePath + bundle.TarBytes = tarBytes return s.set(ctx, bundle) } @@ -108,6 +108,8 @@ func (s *store) List() ([]supportbundles.Bundle, error) { if err := json.NewDecoder(strings.NewReader(s)).Decode(&b); err != nil { return nil, err } + + b.TarBytes = nil res = append(res, b) } }