mirror of
https://github.com/tailscale/tailscale.git
synced 2025-08-13 06:07:34 +00:00
ipn/store: add ability to store data as k8s secrets.
Signed-off-by: Maisem Ali <maisem@tailscale.com>
This commit is contained in:
@@ -613,9 +613,19 @@ func Run(ctx context.Context, logf logger.Logf, logid string, getEngine func() (
|
||||
|
||||
var store ipn.StateStore
|
||||
if opts.StatePath != "" {
|
||||
store, err = ipn.NewFileStore(opts.StatePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ipn.NewFileStore(%q): %v", opts.StatePath, err)
|
||||
const kubePrefix = "kube:"
|
||||
switch {
|
||||
case strings.HasPrefix(opts.StatePath, kubePrefix):
|
||||
secretName := strings.TrimPrefix(opts.StatePath, kubePrefix)
|
||||
store, err = ipn.NewKubeStore(secretName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ipn.NewKubeStore(%q): %v", secretName, err)
|
||||
}
|
||||
default:
|
||||
store, err = ipn.NewFileStore(opts.StatePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ipn.NewFileStore(%q): %v", opts.StatePath, err)
|
||||
}
|
||||
}
|
||||
if opts.AutostartStateKey == "" {
|
||||
autoStartKey, err := store.ReadState(ipn.ServerModeStartKey)
|
||||
|
72
ipn/store.go
72
ipn/store.go
@@ -6,6 +6,7 @@ package ipn
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -14,8 +15,10 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"tailscale.com/atomicfile"
|
||||
"tailscale.com/kube"
|
||||
)
|
||||
|
||||
// ErrStateNotExist is returned by StateStore.ReadState when the
|
||||
@@ -55,6 +58,75 @@ type StateStore interface {
|
||||
WriteState(id StateKey, bs []byte) error
|
||||
}
|
||||
|
||||
// KubeStore is a StateStore that uses a Kubernetes Secret for persistence.
|
||||
type KubeStore struct {
|
||||
client *kube.Client
|
||||
secretName string
|
||||
}
|
||||
|
||||
// NewKubeStore returns a new KubeStore that persists to the named secret.
|
||||
func NewKubeStore(secretName string) (*KubeStore, error) {
|
||||
c, err := kube.New()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &KubeStore{
|
||||
client: c,
|
||||
secretName: secretName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *KubeStore) String() string { return "KubeStore" }
|
||||
|
||||
// ReadState implements the StateStore interface.
|
||||
func (s *KubeStore) ReadState(id StateKey) ([]byte, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
secret, err := s.client.GetSecret(ctx, s.secretName)
|
||||
if err != nil {
|
||||
if st, ok := err.(*kube.Status); ok && st.Code == 404 {
|
||||
return nil, ErrStateNotExist
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
b, ok := secret.Data[string(id)]
|
||||
if !ok {
|
||||
return nil, ErrStateNotExist
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// WriteState implements the StateStore interface.
|
||||
func (s *KubeStore) WriteState(id StateKey, bs []byte) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
secret, err := s.client.GetSecret(ctx, s.secretName)
|
||||
if err != nil {
|
||||
if st, ok := err.(*kube.Status); ok && st.Code == 404 {
|
||||
return s.client.CreateSecret(ctx, &kube.Secret{
|
||||
TypeMeta: kube.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "Secret",
|
||||
},
|
||||
ObjectMeta: kube.ObjectMeta{
|
||||
Name: s.secretName,
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
string(id): bs,
|
||||
},
|
||||
})
|
||||
}
|
||||
return err
|
||||
}
|
||||
secret.Data[string(id)] = bs
|
||||
if err := s.client.UpdateSecret(ctx, secret); err != nil {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// MemoryStore is a store that keeps state in memory only.
|
||||
type MemoryStore struct {
|
||||
mu sync.Mutex
|
||||
|
Reference in New Issue
Block a user