mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
SupportBundles: Build tars in memory (#61581)
* build tar in memory * match tag
This commit is contained in:
parent
be72e570cf
commit
9e097c531d
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
header.Name = filepath.ToSlash("/bundle/" + header.Name)
|
||||
|
||||
// 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
|
||||
}
|
||||
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)
|
||||
// write header
|
||||
if err := tw.WriteHeader(header); 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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user