ipn/store: make StateStore.All optional (#16409)

This method is only needed to migrate between store.FileStore and
tpm.tpmStore. We can make a runtime type assertion instead of
implementing an unused method for every platform.

Updates #15830

Signed-off-by: Andrew Lytvynov <awly@tailscale.com>
This commit is contained in:
Andrew Lytvynov
2025-06-27 15:14:18 -07:00
committed by GitHub
parent 0a64e86a0d
commit 76b9afb54d
10 changed files with 45 additions and 70 deletions

View File

@@ -10,7 +10,6 @@ import (
"context"
"errors"
"fmt"
"iter"
"net/url"
"regexp"
"strings"
@@ -254,7 +253,3 @@ func (s *awsStore) persistState() error {
_, err = s.ssmClient.PutParameter(context.TODO(), in)
return err
}
func (s *awsStore) All() iter.Seq2[ipn.StateKey, []byte] {
return s.memory.All()
}

View File

@@ -7,7 +7,6 @@ package kubestore
import (
"context"
"fmt"
"iter"
"log"
"net"
"os"
@@ -429,7 +428,3 @@ func sanitizeKey[T ~string](k T) string {
return '_'
}, string(k))
}
func (s *Store) All() iter.Seq2[ipn.StateKey, []byte] {
return s.memory.All()
}

View File

@@ -7,7 +7,6 @@ package mem
import (
"bytes"
"encoding/json"
"iter"
"sync"
xmaps "golang.org/x/exp/maps"
@@ -86,16 +85,3 @@ func (s *Store) ExportToJSON() ([]byte, error) {
}
return json.MarshalIndent(s.cache, "", " ")
}
func (s *Store) All() iter.Seq2[ipn.StateKey, []byte] {
return func(yield func(ipn.StateKey, []byte) bool) {
s.mu.Lock()
defer s.mu.Unlock()
for k, v := range s.cache {
if !yield(k, v) {
break
}
}
}
}

View File

@@ -235,6 +235,23 @@ func (s *FileStore) All() iter.Seq2[ipn.StateKey, []byte] {
}
}
// Ensure FileStore implements ExportableStore for migration to/from
// tpm.tpmStore.
var _ ExportableStore = (*FileStore)(nil)
// ExportableStore is an ipn.StateStore that can export all of its contents.
// This interface is optional to implement, and used for migrating the state
// between different store implementations.
type ExportableStore interface {
ipn.StateStore
// All returns an iterator over all store keys. Using ReadState or
// WriteState is not safe while iterating and can lead to a deadlock. The
// order of keys in the iterator is not specified and may change between
// runs.
All() iter.Seq2[ipn.StateKey, []byte]
}
func maybeMigrateLocalStateFile(logf logger.Logf, path string) error {
path, toTPM := strings.CutPrefix(path, TPMPrefix)
@@ -297,10 +314,15 @@ func maybeMigrateLocalStateFile(logf logger.Logf, path string) error {
}
defer os.Remove(tmpPath)
fromExp, ok := from.(ExportableStore)
if !ok {
return fmt.Errorf("%T does not implement the exportableStore interface", from)
}
// Copy all the items. This is pretty inefficient, because both stores
// write the file to disk for each WriteState, but that's ok for a one-time
// migration.
for k, v := range from.All() {
for k, v := range fromExp.All() {
if err := to.WriteState(k, v); err != nil {
return err
}