github.com/grafana/pyroscope@v1.18.0/pkg/settings/settings.go (about)

     1  package settings
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"flag"
     7  	"fmt"
     8  	"time"
     9  
    10  	"connectrpc.com/connect"
    11  	"github.com/go-kit/log"
    12  	"github.com/go-kit/log/level"
    13  	"github.com/grafana/dskit/services"
    14  	"github.com/grafana/dskit/tenant"
    15  	"github.com/thanos-io/objstore"
    16  
    17  	settingsv1 "github.com/grafana/pyroscope/api/gen/proto/go/settings/v1"
    18  	"github.com/grafana/pyroscope/api/gen/proto/go/settings/v1/settingsv1connect"
    19  	"github.com/grafana/pyroscope/pkg/settings/recording"
    20  	"github.com/grafana/pyroscope/pkg/validation"
    21  )
    22  
    23  type Config struct {
    24  	Recording recording.Config `yaml:"recording_rules"`
    25  }
    26  
    27  func (cfg *Config) RegisterFlags(fs *flag.FlagSet) {
    28  	cfg.Recording.RegisterFlags(fs)
    29  }
    30  
    31  func (cfg *Config) Validate() error {
    32  	return errors.Join(
    33  		cfg.Recording.Validate(),
    34  	)
    35  }
    36  
    37  var _ settingsv1connect.SettingsServiceHandler = (*TenantSettings)(nil)
    38  
    39  func New(cfg Config, bucket objstore.Bucket, logger log.Logger, overrides *validation.Overrides) (*TenantSettings, error) {
    40  	if bucket == nil {
    41  		bucket = objstore.NewInMemBucket()
    42  		level.Warn(logger).Log("msg", "using in-memory settings store, changes will be lost after shutdown")
    43  	}
    44  
    45  	ts := &TenantSettings{
    46  		RecordingRulesServiceHandler: &settingsv1connect.UnimplementedRecordingRulesServiceHandler{},
    47  		store:                        newBucketStore(bucket),
    48  		logger:                       logger,
    49  	}
    50  
    51  	if cfg.Recording.Enabled {
    52  		ts.RecordingRulesServiceHandler = recording.New(bucket, logger, overrides)
    53  	}
    54  
    55  	ts.Service = services.NewBasicService(ts.starting, ts.running, ts.stopping)
    56  
    57  	return ts, nil
    58  }
    59  
    60  type TenantSettings struct {
    61  	services.Service
    62  	settingsv1connect.RecordingRulesServiceHandler
    63  
    64  	store  store
    65  	logger log.Logger
    66  }
    67  
    68  func (ts *TenantSettings) starting(ctx context.Context) error {
    69  	return nil
    70  }
    71  
    72  func (ts *TenantSettings) running(ctx context.Context) error {
    73  	<-ctx.Done()
    74  	return nil
    75  }
    76  
    77  func (ts *TenantSettings) stopping(_ error) error {
    78  	err := ts.store.Close()
    79  	if err != nil {
    80  		return err
    81  	}
    82  	return nil
    83  }
    84  
    85  func (ts *TenantSettings) Get(
    86  	ctx context.Context,
    87  	req *connect.Request[settingsv1.GetSettingsRequest],
    88  ) (*connect.Response[settingsv1.GetSettingsResponse], error) {
    89  	tenantID, err := tenant.TenantID(ctx)
    90  	if err != nil {
    91  		return nil, connect.NewError(connect.CodeInvalidArgument, err)
    92  	}
    93  
    94  	settings, err := ts.store.Get(ctx, tenantID)
    95  	if err != nil {
    96  		return nil, connect.NewError(connect.CodeInternal, err)
    97  	}
    98  
    99  	return connect.NewResponse(&settingsv1.GetSettingsResponse{
   100  		Settings: settings,
   101  	}), nil
   102  }
   103  
   104  func (ts *TenantSettings) Set(
   105  	ctx context.Context,
   106  	req *connect.Request[settingsv1.SetSettingsRequest],
   107  ) (*connect.Response[settingsv1.SetSettingsResponse], error) {
   108  	tenantID, err := tenant.TenantID(ctx)
   109  	if err != nil {
   110  		return nil, connect.NewError(connect.CodeInvalidArgument, err)
   111  	}
   112  
   113  	if req.Msg == nil || req.Msg.Setting == nil {
   114  		return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("no setting values provided"))
   115  	}
   116  
   117  	if req.Msg.Setting.ModifiedAt <= 0 {
   118  		req.Msg.Setting.ModifiedAt = time.Now().UnixMilli()
   119  	}
   120  
   121  	setting, err := ts.store.Set(ctx, tenantID, req.Msg.Setting)
   122  	if err != nil {
   123  		if errors.Is(err, oldSettingErr) {
   124  			return nil, connect.NewError(connect.CodeAlreadyExists, err)
   125  		}
   126  		return nil, connect.NewError(connect.CodeInternal, err)
   127  	}
   128  
   129  	return connect.NewResponse(&settingsv1.SetSettingsResponse{
   130  		Setting: setting,
   131  	}), nil
   132  }
   133  
   134  func (ts *TenantSettings) Delete(ctx context.Context, req *connect.Request[settingsv1.DeleteSettingsRequest]) (*connect.Response[settingsv1.DeleteSettingsResponse], error) {
   135  	tenantID, err := tenant.TenantID(ctx)
   136  	if err != nil {
   137  		return nil, connect.NewError(connect.CodeInvalidArgument, err)
   138  	}
   139  
   140  	if req.Msg == nil || req.Msg.Name == "" {
   141  		return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("no setting name provided"))
   142  	}
   143  
   144  	modifiedAt := time.Now().UnixMilli()
   145  	err = ts.store.Delete(ctx, tenantID, req.Msg.Name, modifiedAt)
   146  	if err != nil {
   147  		if errors.Is(err, oldSettingErr) {
   148  			return nil, connect.NewError(connect.CodeAlreadyExists, err)
   149  		}
   150  		return nil, connect.NewError(connect.CodeInternal, err)
   151  	}
   152  
   153  	return connect.NewResponse(&settingsv1.DeleteSettingsResponse{}), nil
   154  }