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 }