gopass/internal/store/leaf/reencrypt.go
Ludovic Fernandez b8f0ff8ff2
[chore]: linting (#2840)
Signed-off-by: Fernandez Ludovic <ldez@users.noreply.github.com>
2024-03-25 19:32:57 +01:00

132 lines
3.3 KiB
Go

package leaf
import (
"context"
"errors"
"fmt"
"log"
"os"
"strings"
"sync"
"github.com/gopasspw/gopass/internal/config"
"github.com/gopasspw/gopass/internal/out"
"github.com/gopasspw/gopass/internal/store"
"github.com/gopasspw/gopass/pkg/ctxutil"
"github.com/gopasspw/gopass/pkg/debug"
"github.com/gopasspw/gopass/pkg/termio"
)
// nolint:ifshort
// reencrypt will re-encrypt all entries for the current recipients.
func (s *Store) reencrypt(ctx context.Context) error {
entries, err := s.List(ctx, "")
if err != nil {
return fmt.Errorf("failed to list store: %w", err)
}
// Most gnupg setups don't work well with concurrency > 1, but
// for other backends - e.g. age - this could very well be > 1.
conc := s.crypto.Concurrency()
// save original value of auto push
{
// shadow ctx in this block only
ctx := ctxutil.WithGitCommit(ctx, false)
// progress bar
bar := termio.NewProgressBar(int64(len(entries)))
bar.Hidden = !ctxutil.IsTerminal(ctx) || ctxutil.IsHidden(ctx)
var wg sync.WaitGroup
jobs := make(chan string)
// We use a logger to write without race condition on stdout
logger := log.New(os.Stdout, "", 0)
out.Print(ctx, "Starting reencrypt")
for i := range conc {
wg.Add(1) // we start a new job
go func(workerId int) {
// the workers are fed through an unbuffered channel
for e := range jobs {
content, err := s.Get(ctx, e)
if err != nil {
logger.Printf("Worker %d: Failed to get current value for %s: %s\n", workerId, e, err)
continue
}
if err := s.Set(WithNoGitOps(ctx, conc > 1), e, content); err != nil {
if !errors.Is(err, store.ErrMeaninglessWrite) {
logger.Printf("Worker %d: Failed to write %s: %s\n", workerId, e, err)
continue
}
logger.Printf("Worker %d: Writing secret %s is not needed\n", workerId, e)
}
}
wg.Done() // report the job as finished
}(i)
}
for _, e := range entries {
// check for context cancellation
select {
case <-ctx.Done():
// We close the channel, so the worker will terminate
close(jobs)
// we wait for all workers to have finished
wg.Wait()
return fmt.Errorf("context canceled")
default:
}
if bar != nil {
bar.Inc()
}
e = strings.TrimPrefix(e, s.alias)
jobs <- e
}
// We close the channel, so the workers will terminate
close(jobs)
// we wait for all workers to have finished
wg.Wait()
bar.Done()
}
// if we are working concurrently, we cannot git add during the process
// to avoid a race condition on git .index.lock file, so we do it now.
if conc > 1 {
for _, name := range entries {
p := s.Passfile(name)
if err := s.storage.TryAdd(ctx, p); err != nil {
return fmt.Errorf("failed to add %q to git: %w", p, err)
}
debug.Log("added %s to git", p)
}
}
if err := s.storage.TryCommit(ctx, ctxutil.GetCommitMessage(ctx)); err != nil {
return fmt.Errorf("failed to commit changes to git: %w", err)
}
return s.reencryptGitPush(ctx)
}
func (s *Store) reencryptGitPush(ctx context.Context) error {
ctx = config.WithMount(ctx, s.alias)
if !config.Bool(ctx, "core.autopush") {
debug.Log("not pushing to git remote, core.autopush is false")
return nil
}
if err := s.storage.TryPush(ctx, "", ""); err != nil {
return fmt.Errorf("failed to push change to git remote: %w", err)
}
return nil
}