package pipeline import ( "context" "io" "git.tswf.io/infra/go-synapse-backupper/pkg/domain" "git.tswf.io/infra/go-synapse-backupper/pkg/domain/crypto" "git.tswf.io/infra/go-synapse-backupper/pkg/domain/pgdump" ) // Option configures a Runner. type Option func(*runner) // WithDumper replaces the default pg_dump dumper. func WithDumper(dumper pgdump.Dumper) Option { return func(r *runner) { r.dumper = dumper } } // WithEncryptor replaces the default encryptor. func WithEncryptor(encryptor crypto.Encryptor) Option { return func(r *runner) { r.encryptor = encryptor } } // Runner orchestrates the dump → encrypt → sink pipeline. type runner struct { dumper pgdump.Dumper encryptor crypto.Encryptor } // NewRunner creates a pipeline runner with the given functional options. func NewRunner(options ...Option) *runner { r := &runner{} for _, option := range options { option(r) } return r } // Run executes the full backup pipeline: pg_dump → encrypt → sink. func (r *runner) Run( ctx context.Context, pgDumpOpts pgdump.Options, recipients []crypto.RecipientPub, sink domain.Sink, rand io.Reader, ) (retErr error) { tx, err := sink.Begin(pgDumpOpts.Key) if err != nil { return err } defer func() { if retErr != nil { _ = tx.Abort() } }() dumpCtx, dumpCancel := context.WithCancel(ctx) defer dumpCancel() pipeR, pipeW := io.Pipe() dumpErrCh := make(chan error, 1) go func() { dumpErrCh <- r.dumper.Dump(dumpCtx, pgDumpOpts, pipeW) }() encryptErrCh := make(chan error, 1) go func() { encryptErrCh <- r.encryptor.Encrypt(pipeR, recipients, tx, rand) }() select { case derr := <-dumpErrCh: if derr != nil { _ = pipeR.CloseWithError(derr) _ = <-encryptErrCh return derr } eerr := <-encryptErrCh if eerr != nil { return eerr } return tx.Commit() case eerr := <-encryptErrCh: if eerr != nil { dumpCancel() _ = pipeR.CloseWithError(eerr) _ = <-dumpErrCh return eerr } derr := <-dumpErrCh if derr != nil { return derr } return tx.Commit() } }