grafana/pkg/services/supportbundles/supportbundlesimpl/service_bundle.go
Ieva ed11c32c1d
Support bundles: fix user collector permissions and format collector output (#64531)
* fix user collector permissions and format collector output

* lint
2023-03-09 11:43:18 -05:00

172 lines
4.0 KiB
Go

package supportbundlesimpl
import (
"archive/tar"
"bytes"
"compress/gzip"
"context"
"errors"
"fmt"
"io"
"path/filepath"
"runtime/debug"
"time"
"filippo.io/age"
"github.com/grafana/grafana/pkg/services/supportbundles"
)
var ErrCollectorPanicked = errors.New("collector panicked")
type bundleResult struct {
tarBytes []byte
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}
}
}()
bundleBytes, err := s.bundle(ctx, collectors, uid)
if err != nil {
result <- bundleResult{err: err}
}
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, nil); err != nil {
s.log.Error("failed to update bundle after timeout")
}
return
case r := <-result:
if r.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.tarBytes); err != nil {
s.log.Error("failed to update bundle after completion")
}
return
}
}
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
}
files := map[string][]byte{}
for _, collector := range s.bundleRegistry.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, "collector", collector.UID)
}
// write item to file
if item != nil {
files[item.Filename] = item.FileBytes
}
}
// create tar.gz file
var buf bytes.Buffer
errCompress := compress(files, &buf)
if errCompress != nil {
return nil, errCompress
}
final := buf
if len(s.encryptionPublicKeys) > 0 {
var err error
final, err = encrypt(buf, s.encryptionPublicKeys...)
if err != nil {
return nil, err
}
}
return final.Bytes(), nil
}
func encrypt(buf bytes.Buffer, publicKeys ...string) (bytes.Buffer, error) {
final := bytes.Buffer{}
recipients := make([]age.Recipient, 0, len(publicKeys))
for _, key := range publicKeys {
recipient, err := age.ParseX25519Recipient(key)
if err != nil {
return final, fmt.Errorf("unable to parse support bundle recipient public key: %w", err)
}
recipients = append(recipients, recipient)
}
w, err := age.Encrypt(&final, recipients...)
if err != nil {
return final, fmt.Errorf("unable to open support bundle encryption header: %w", err)
}
if _, err = w.Write(buf.Bytes()); err != nil {
return final, fmt.Errorf("unable to write support bundle encryption: %w", err)
}
if err := w.Close(); err != nil {
return final, fmt.Errorf("unable to close support bundle encryption: %w", err)
}
return final, nil
}
func compress(files map[string][]byte, buf io.Writer) error {
// tar > gzip > buf
zr := gzip.NewWriter(buf)
tw := tar.NewWriter(zr)
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
}
}
// produce tar
if err := tw.Close(); err != nil {
return err
}
// produce gzip
if err := zr.Close(); err != nil {
return err
}
//
return nil
}