Dominik Schulz 7281ca8ab4
[chore] Migrate to golangci-lint v2 (#3104)
* [chore] Migrate to golangci-lint v2

Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>

* [chore] Fix more lint issues

Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>

* [chore] Fix more lint issue

Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>

* [chore] Fix more lint issues

Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>

* [chore] Add more package comments.

Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>

* [chore] Fix golangci-lint config and the remaining checks

Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>

* [fix] Use Go 1.24

Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>

* [fix] Fix container builds

Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>

* Fix more failing tests

Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>

* Fix test failure

Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>

* Fix another len assertion

Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>

* Move location tests

Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>

* [fix] Fix most remaining lint issues

Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>

* [fix] Only run XDG specific tests on linux

Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>

* [fix] Attempt to address on source of flaky failures

Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>

---------

Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>
2025-04-17 08:05:43 +02:00

227 lines
5.9 KiB
Go

package root
import (
"context"
"fmt"
"path/filepath"
"sort"
"strings"
"github.com/gopasspw/gopass/internal/out"
"github.com/gopasspw/gopass/internal/store"
"github.com/gopasspw/gopass/internal/store/leaf"
"github.com/gopasspw/gopass/pkg/debug"
"github.com/gopasspw/gopass/pkg/fsutil"
)
// AddMount adds a new mount.
func (r *Store) AddMount(ctx context.Context, alias, path string, keys ...string) error {
if err := r.addMount(ctx, alias, path, keys...); err != nil {
return fmt.Errorf("failed to add mount: %w", err)
}
// check for duplicate mounts
return r.checkMounts()
}
func (r *Store) addMount(ctx context.Context, alias, path string, keys ...string) error {
// disallow filepath separators in alias and always disallow regular slashes
// even on Windows, since these are used internally to separate folders.
if strings.HasSuffix(alias, "/") {
return fmt.Errorf("alias must not end with '/'")
}
if strings.HasSuffix(alias, string(filepath.Separator)) {
return fmt.Errorf("alias must not end with '%s'", string(filepath.Separator))
}
alias = CleanMountAlias(alias)
if alias == "" {
return fmt.Errorf("alias must not be empty")
}
if r.mounts == nil {
r.mounts = make(map[string]*leaf.Store, 1)
}
if _, found := r.mounts[alias]; found {
return AlreadyMountedError(alias)
}
fullPath := fsutil.CleanPath(path)
debug.Log("addMount - Path: %s - Full: %s", path, fullPath)
// initialize sub store
s, err := r.initSub(ctx, alias, fullPath, keys)
if err != nil {
return fmt.Errorf("failed to init sub store %q at %q: %w", alias, fullPath, err)
}
r.mounts[alias] = s
if err := r.cfg.SetMountPath(alias, path); err != nil {
return fmt.Errorf("failed to set mount path: %w", err)
}
debug.Log("Added mount %s -> %s (%s)", alias, path, fullPath)
return nil
}
func (r *Store) initSub(ctx context.Context, alias, path string, keys []string) (*leaf.Store, error) {
alias = CleanMountAlias(alias)
// init regular sub store
s, err := leaf.New(ctx, alias, path)
if err != nil {
return nil, fmt.Errorf("failed to initialize store %q at %q: %w", alias, path, err)
}
if s.IsInitialized(ctx) {
return s, nil
}
debug.Log("[%s] Mount %s is not initialized", alias, path)
if len(keys) < 1 {
debug.Log("[%s] No keys available", alias)
return s, NotInitializedError{alias, path}
}
debug.Log("[%s] Trying to initialize at %s for %+v", alias, path, keys)
if err := s.Init(ctx, path, keys...); err != nil {
return s, fmt.Errorf("failed to initialize store %q at %q: %w", alias, path, err)
}
out.Printf(ctx, "Password store %s initialized for:", path)
for _, r := range s.Recipients(ctx) {
out.Noticef(ctx, " %s", r)
}
return s, nil
}
// RemoveMount removes and existing mount.
func (r *Store) RemoveMount(ctx context.Context, alias string) error {
if _, found := r.mounts[alias]; !found {
out.Warningf(ctx, "%s is not mounted", alias)
}
if _, found := r.mounts[alias]; !found {
out.Warningf(ctx, "%s is not initialized", alias)
}
delete(r.mounts, alias)
if err := r.cfg.Unset("", "mounts."+alias+".path"); err != nil {
return err
}
return nil
}
// Mounts returns a map of mounts with their paths.
func (r *Store) Mounts() map[string]string {
m := make(map[string]string, len(r.mounts))
for alias, sub := range r.mounts {
m[alias] = sub.Path()
}
return m
}
// MountPoints returns a sorted list of mount points. It encodes the logic that
// the longer a mount point the more specific it is. This allows to "shadow" a
// shorter mount point by a longer one.
func (r *Store) MountPoints() []string {
mps := make([]string, 0, len(r.mounts))
for k := range r.mounts {
mps = append(mps, k)
}
sort.Sort(sort.Reverse(store.ByPathLen(mps)))
return mps
}
// MountPoint returns the most-specific mount point for the given key.
func (r *Store) MountPoint(name string) string {
for _, mp := range r.MountPoints() {
if strings.HasPrefix(name+"/", mp+"/") {
return mp
}
}
return ""
}
// Lock drops all cached credentials, if any. Mostly only useful
// for the gopass REPL.
func (r *Store) Lock() error {
for _, sub := range r.mounts {
if err := sub.Lock(); err != nil {
return err
}
}
return r.store.Lock()
}
// getStore returns the Store object at the most-specific mount point for the
// given key. returns sub store reference, truncated path to secret.
func (r *Store) getStore(name string) (*leaf.Store, string) {
name = strings.TrimSuffix(name, "/")
mp := r.MountPoint(name)
if sub, found := r.mounts[mp]; found {
return sub, strings.TrimPrefix(name, sub.Alias())
}
return r.store, name
}
// GetSubStore returns an exact match for a mount point or an error if this
// mount point does not exist.
func (r *Store) GetSubStore(name string) (*leaf.Store, error) {
if name == "" {
return r.store, nil
}
if sub, found := r.mounts[name]; found {
return sub, nil
}
debug.Log("mounts available: %+v", r.mounts)
return nil, fmt.Errorf("no such mount point %q", name)
}
// checkMounts performs some sanity checks on our mounts. At the moment it
// only checks if some path is mounted twice.
func (r *Store) checkMounts() error {
paths := make(map[string]string, len(r.mounts))
for k, v := range r.mounts {
if _, found := paths[v.Path()]; found {
return fmt.Errorf("doubly mounted path at %s: %s", v.Path(), k)
}
paths[v.Path()] = k
}
return nil
}
// CleanMountAlias removes all leading and trailing slashes from a mount alias.
// Note: Slashes inside the alias are valid and will be kept.
func CleanMountAlias(alias string) string {
for strings.HasPrefix(alias, "/") || strings.HasPrefix(alias, "\\") {
alias = strings.TrimPrefix(strings.TrimSuffix(alias, "/"), "/")
alias = strings.TrimPrefix(strings.TrimSuffix(alias, "\\"), "\\")
}
for strings.HasSuffix(alias, "/") || strings.HasSuffix(alias, "\\") {
alias = strings.TrimSuffix(strings.TrimPrefix(alias, "/"), "/")
alias = strings.TrimSuffix(strings.TrimPrefix(alias, "\\"), "\\")
}
return alias
}