From e903947befe6c4200cab4f889fe9fc88c7ab9981 Mon Sep 17 00:00:00 2001 From: Danielle Miu <29378233+DanielleMiu@users.noreply.github.com> Date: Mon, 8 Jan 2024 14:27:40 +0100 Subject: [PATCH] Create delete_session_recording job and associated infra --- internal/bsr/bsr.go | 9 +++- internal/daemon/controller/controller.go | 4 ++ ..._find_session_recordings_for_delete.up.sql | 48 +++++++++++++++++ .../recording/delete_session_recording_job.go | 51 +++++++++++++++++++ internal/recording/recording.go | 46 +++++++++++++++++ 5 files changed, 156 insertions(+), 2 deletions(-) create mode 100644 internal/db/schema/migrations/oss/postgres/100/04_find_session_recordings_for_delete.up.sql create mode 100644 internal/recording/delete_session_recording_job.go create mode 100644 internal/recording/recording.go diff --git a/internal/bsr/bsr.go b/internal/bsr/bsr.go index 80f5a3d10..eac7b3984 100644 --- a/internal/bsr/bsr.go +++ b/internal/bsr/bsr.go @@ -49,6 +49,11 @@ type Session struct { Summary SessionSummary } +// GetBsrFileName formats a session recording id into the BSR filename format +func GetBsrFileName(sessionRecordingId string) string { + return fmt.Sprintf(bsrFileNameTemplate, sessionRecordingId) +} + // NewSession creates a Session container for a given session id. func NewSession(ctx context.Context, meta *SessionRecordingMeta, sessionMeta *SessionMeta, f storage.FS, keys *kms.Keys, options ...Option) (*Session, error) { const op = "bsr.NewSession" @@ -82,7 +87,7 @@ func NewSession(ctx context.Context, meta *SessionRecordingMeta, sessionMeta *Se opts := getOpts(options...) - c, err := f.New(ctx, fmt.Sprintf(bsrFileNameTemplate, meta.Id)) + c, err := f.New(ctx, GetBsrFileName(meta.Id)) if err != nil { return nil, err } @@ -189,7 +194,7 @@ func OpenSession(ctx context.Context, sessionRecordingId string, f storage.FS, k return nil, fmt.Errorf("%s: missing key unwrap function: %w", op, ErrInvalidParameter) } - c, err := f.Open(ctx, fmt.Sprintf(bsrFileNameTemplate, sessionRecordingId)) + c, err := f.Open(ctx, GetBsrFileName(sessionRecordingId)) if err != nil { return nil, err } diff --git a/internal/daemon/controller/controller.go b/internal/daemon/controller/controller.go index 53a055370..97dc2191e 100644 --- a/internal/daemon/controller/controller.go +++ b/internal/daemon/controller/controller.go @@ -40,6 +40,7 @@ import ( "github.com/hashicorp/boundary/internal/plugin" "github.com/hashicorp/boundary/internal/plugin/loopback" "github.com/hashicorp/boundary/internal/ratelimit" + "github.com/hashicorp/boundary/internal/recording" "github.com/hashicorp/boundary/internal/scheduler" "github.com/hashicorp/boundary/internal/scheduler/cleaner" "github.com/hashicorp/boundary/internal/scheduler/job" @@ -615,6 +616,9 @@ func (c *Controller) registerJobs() error { if err := purge.RegisterJobs(c.baseContext, c.scheduler, rw, rw); err != nil { return err } + if err := recording.RegisterJob(c.baseContext, c.scheduler, rw, rw, c.ControllerExtension, c.kms); err != nil { + return err + } return nil } diff --git a/internal/db/schema/migrations/oss/postgres/100/04_find_session_recordings_for_delete.up.sql b/internal/db/schema/migrations/oss/postgres/100/04_find_session_recordings_for_delete.up.sql new file mode 100644 index 000000000..e1ff5300a --- /dev/null +++ b/internal/db/schema/migrations/oss/postgres/100/04_find_session_recordings_for_delete.up.sql @@ -0,0 +1,48 @@ +-- Copyright (c) HashiCorp, Inc. +-- SPDX-License-Identifier: BUSL-1.1 + +begin; + + create view find_session_recordings_for_delete as + select + -- fields for session recordings + rs.public_id, + rs.storage_bucket_id, + + -- fields for storage buckets. note this is ALL storage bucket fields + sb.scope_id as storage_bucket_scope_id, + sb.name as storage_bucket_name, + sb.description as storage_bucket_description, + sb.create_time as storage_bucket_create_time, + sb.update_time as storage_bucket_update_time, + sb.version as storage_bucket_version, + sb.plugin_id, + sb.bucket_name, + sb.bucket_prefix, + sb.worker_filter, + sb.attributes, + sb.secrets_hmac, + + -- fields for storage bucket secrets + sbs.secrets_encrypted, + sbs.key_id, + + -- fields for storage bucket plugins + plg.scope_id as plugin_scope_id, + plg.name as plugin_name, + plg.description as plugin_description + + from recording_session rs + left join storage_plugin_storage_bucket sb + on sb.public_id = rs.storage_bucket_id + left join storage_plugin_storage_bucket_secret sbs + on sbs.storage_bucket_id = sb.public_id + left join plugin plg + on plg.public_id = sb.plugin_id + where rs.delete_after < now() or rs.delete_time < now() + order by rs.delete_time desc, rs.delete_after desc; + comment on view find_session_recordings_for_delete is + 'find_session_recordings_for_delete is used by the delete_session_recording job to find all ' + 'session recordings that need to be automatically deleted along with their storage buckets.'; + +commit; diff --git a/internal/recording/delete_session_recording_job.go b/internal/recording/delete_session_recording_job.go new file mode 100644 index 000000000..f77670d02 --- /dev/null +++ b/internal/recording/delete_session_recording_job.go @@ -0,0 +1,51 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package recording + +import ( + "context" + "time" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/globals" + "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/scheduler" +) + +const deleteSessionRecordingJobName = "delete_session_recording" + +var NewDeleteSessionRecordingJobFn = newDeleteSessionRecordingJob + +type deleteSessionRecordingJob struct{} + +func newDeleteSessionRecordingJob(_ context.Context, + _ db.Reader, + _ db.Writer, + _ globals.ControllerExtension, + _ kms.GetWrapperer, +) (scheduler.Job, error) { + return &deleteSessionRecordingJob{}, nil +} + +// Status reports the job’s current status. +func (dsr *deleteSessionRecordingJob) Status() scheduler.JobStatus { return scheduler.JobStatus{} } + +// Run performs the required work depending on the implementation. +// The context is used to notify the job that it should exit early. +func (dsr *deleteSessionRecordingJob) Run(_ context.Context) error { return nil } + +// NextRunIn returns the duration until the next job run should be scheduled. +// Delete Session Recording will run every hour unless we know there are more to delete, +// then sooner +func (dsr *deleteSessionRecordingJob) NextRunIn(_ context.Context) (time.Duration, error) { + return 24 * time.Hour, nil +} + +// Name is the unique name of the job. +func (dsr *deleteSessionRecordingJob) Name() string { return deleteSessionRecordingJobName } + +// Description is the human-readable description of the job. +func (dsr *deleteSessionRecordingJob) Description() string { + return "Manages the retention of Session Recordings in accordance with org storage policies" +} diff --git a/internal/recording/recording.go b/internal/recording/recording.go new file mode 100644 index 000000000..c86185d16 --- /dev/null +++ b/internal/recording/recording.go @@ -0,0 +1,46 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package recording + +import ( + "context" + "fmt" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/globals" + "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/scheduler" +) + +// RegisterJob registers the delete session recording job with the provided scheduler. +func RegisterJob(ctx context.Context, + s *scheduler.Scheduler, + r db.Reader, + w db.Writer, + controllerExt globals.ControllerExtension, + kms kms.GetWrapperer, +) error { + const op = "storage.RegisterJob" + switch { + case s == nil: + return errors.New(ctx, errors.InvalidParameter, op, "missing scheduler") + case r == nil: + return errors.New(ctx, errors.InvalidParameter, op, "missing db.Reader") + case w == nil: + return errors.New(ctx, errors.InvalidParameter, op, "missing db.Writer") + case kms == nil: + return errors.New(ctx, errors.InvalidParameter, op, "missing kms") + } + + dsrJob, err := NewDeleteSessionRecordingJobFn(ctx, r, w, controllerExt, kms) + if err != nil { + return fmt.Errorf("error creating delete session recording job: %w", err) + } + if err := s.RegisterJob(ctx, dsrJob); err != nil { + return errors.Wrap(ctx, err, op) + } + + return nil +}