Files
grafana/pkg/services/supportbundles/supportbundlesimpl/service_bundle.go
Jo fc0926f8fb SupportBundles: Recover from Bundler panics gracefully (#60995)
bundler panics should not crash Grafana
2023-01-05 14:23:35 +01:00

166 lines
3.6 KiB
Go

package supportbundlesimpl
import (
"archive/tar"
"bytes"
"compress/gzip"
"context"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"runtime/debug"
"github.com/grafana/grafana/pkg/services/supportbundles"
)
var ErrCollectorPanicked = errors.New("collector panicked")
type bundleResult struct {
path string
err error
}
func (s *Service) startBundleWork(ctx context.Context, collectors []string, uid string) {
result := make(chan bundleResult)
go func() {
defer func() {
if err := recover(); err != nil {
s.log.Error("support bundle collector panic", "err", err, "stack", string(debug.Stack()))
result <- bundleResult{err: ErrCollectorPanicked}
}
}()
sbFilePath, err := s.bundle(ctx, collectors, uid)
if err != nil {
result <- bundleResult{err: err}
}
result <- bundleResult{
path: sbFilePath,
}
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 {
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 update bundle after error")
}
return
}
if err := s.store.Update(ctx, uid, supportbundles.StateComplete, r.path); 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) {
lookup := make(map[string]bool, len(collectors))
for _, c := range collectors {
lookup[c] = true
}
sbDir, err := os.MkdirTemp("", "")
if err != nil {
return "", err
}
for _, collector := range s.collectors {
if !lookup[collector.UID] && !collector.IncludedByDefault {
continue
}
item, err := collector.Fn(ctx)
if err != nil {
s.log.Warn("Failed to collect support bundle item", "error", err)
}
// 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)
}
}
}
// create tar.gz file
var buf bytes.Buffer
errCompress := compress(sbDir, &buf)
if errCompress != nil {
return "", 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
}
func compress(src string, 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
}
}
return nil
})
if err != nil {
return err
}
// produce tar
if err := tw.Close(); err != nil {
return err
}
// produce gzip
if err := zr.Close(); err != nil {
return err
}
//
return nil
}