Compare commits

...

2 Commits

Author SHA1 Message Date
brynmailer-mdc 4781f30966 refactor: merge the kvstore test exports into one file 2026-11-05 17:02:01 +10:00
brynmailer-mdc c07e0be4e1 refactor: fold kvutil into kvstore and rename Claim to Take
kvutil was the layer under kvstore, so its bucket, replica, version, key
and CAS helpers now live in kvstore and every importer changes only its
import path and qualifier. kvutil.Claim becomes kvstore.Take so that it
does not read as a lock claim, and the kvutil package is deleted.

The bucket-creation invariant now exempts kvstore/buckets.go, the one
file holding the chokepoint, rather than the whole package.
2026-11-05 17:02:01 +10:00
153 changed files with 767 additions and 798 deletions
+1 -2
View File
@@ -14,7 +14,6 @@ import (
iamdomain "github.com/mulgadc/spinifex/spinifex/domains/iam"
"github.com/mulgadc/spinifex/spinifex/foundation/aws/arn"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/nats-io/nats.go"
"github.com/nats-io/nats.go/jetstream"
)
@@ -184,7 +183,7 @@ func NewStore(ctx context.Context, nc *nats.Conn, masterKey []byte) (*Store, err
return nil, fmt.Errorf("failed to get JetStream context: %w", err)
}
kv, err := kvutil.GetOrCreateBucket(ctx, js, KVBucketACM, KVBucketACMVersion)
kv, err := kvstore.GetOrCreateBucket(ctx, js, KVBucketACM, KVBucketACMVersion)
if err != nil {
return nil, err
}
+2 -2
View File
@@ -10,7 +10,7 @@ import (
"time"
"github.com/mulgadc/spinifex/spinifex/foundation/aws/errors"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/nats-io/nats.go/jetstream"
"golang.org/x/time/rate"
)
@@ -171,7 +171,7 @@ func (s *Service) RunBedrockRPMSync(ctx context.Context, js jetstream.JetStream)
if s == nil || !s.limits.RequestsPerMinuteEnabled {
return
}
kv, err := kvutil.GetOrCreateBucket(ctx, js, KVBucketBedrockRPMUsage, 1)
kv, err := kvstore.GetOrCreateBucket(ctx, js, KVBucketBedrockRPMUsage, 1)
if err != nil {
slog.Warn("bedrock rpm sync: open usage bucket failed, sync disabled", "err", err)
return
+3 -3
View File
@@ -9,7 +9,7 @@ import (
"time"
awserrors "github.com/mulgadc/spinifex/spinifex/foundation/aws/errors"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/nats-io/nats.go/jetstream"
)
@@ -187,7 +187,7 @@ func (s *Service) PutAccountQuota(ctx context.Context, accountID string, over Ov
over.UpdatedAt = time.Now().UTC().Format(time.RFC3339)
over.UpdatedBy = updatedBy
_, err := kvutil.Update(ctx, s.overrides, accountID, kvutil.CASConfig{
_, err := kvstore.Update(ctx, s.overrides, accountID, kvstore.CASConfig{
CreateIfAbsent: true,
Attempts: overrideMaxAttempts,
Exhausted: func(string, int) error { return errOverridesContended },
@@ -230,7 +230,7 @@ func (s *Service) clearAccountQuota(ctx context.Context, accountID string) error
return nil
case errors.Is(err, jetstream.ErrKeyNotFound):
return nil
case kvutil.IsRevisionConflict(err):
case kvstore.IsRevisionConflict(err):
// A concurrent write landed between the read and the guarded
// delete; retry against whatever revision now holds.
continue
+3 -3
View File
@@ -8,7 +8,7 @@ import (
awserrors "github.com/mulgadc/spinifex/spinifex/foundation/aws/errors"
awsidentifiers "github.com/mulgadc/spinifex/spinifex/foundation/aws/identifiers"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/nats-io/nats.go/jetstream"
)
@@ -73,7 +73,7 @@ func (s *Service) sealBucket(ctx context.Context) (jetstream.KeyValue, error) {
slog.ErrorContext(ctx, "Quota override seal has no JetStream client wired to open the override bucket")
return nil, errors.New(awserrors.ErrorInternalError)
}
kv, err := kvutil.GetOrCreateBucket(ctx, s.js, KVBucketAccountQuota, 1)
kv, err := kvstore.GetOrCreateBucket(ctx, s.js, KVBucketAccountQuota, 1)
if err != nil {
slog.ErrorContext(ctx, "Failed to open quota override bucket for seal", "err", err)
return nil, errors.New(awserrors.ErrorInternalError)
@@ -136,7 +136,7 @@ func (s *Service) SealAccountOverrides(ctx context.Context, accountID string) (*
switch {
case writeErr == nil:
return &OverrideSeal{Revision: newRev}, nil
case kvutil.IsRevisionConflict(writeErr):
case kvstore.IsRevisionConflict(writeErr):
continue
default:
slog.ErrorContext(ctx, "Failed to write sealed quota override record", "accountID", accountID, "err", writeErr)
+2 -2
View File
@@ -8,7 +8,7 @@ import (
awserrors "github.com/mulgadc/spinifex/spinifex/foundation/aws/errors"
awsidentifiers "github.com/mulgadc/spinifex/spinifex/foundation/aws/identifiers"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/nats-io/nats.go/jetstream"
)
@@ -109,7 +109,7 @@ func (s *AccountSettingsServiceImpl) SealAccount(ctx context.Context, accountID
switch {
case writeErr == nil:
return &AccountSettingsSeal{Revision: newRev}, nil
case kvutil.IsRevisionConflict(writeErr):
case kvstore.IsRevisionConflict(writeErr):
continue
default:
slog.ErrorContext(ctx, "Failed to write sealed account settings record", "accountID", accountID, "err", writeErr)
+7 -7
View File
@@ -13,7 +13,7 @@ import (
ebsencryption "github.com/mulgadc/spinifex/spinifex/domains/ec2/ebs/encryption"
awserrors "github.com/mulgadc/spinifex/spinifex/foundation/aws/errors"
awsidentifiers "github.com/mulgadc/spinifex/spinifex/foundation/aws/identifiers"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/mulgadc/spinifex/spinifex/foundation/state/migrate"
"github.com/nats-io/nats.go"
"github.com/nats-io/nats.go/jetstream"
@@ -57,7 +57,7 @@ func NewAccountSettingsServiceImplWithNATS(ctx context.Context, cfg *config.Conf
return nil, fmt.Errorf("failed to get JetStream context: %w", err)
}
settingsKV, err := kvutil.GetOrCreateBucket(ctx, js, KVBucketAccountSettings, 10)
settingsKV, err := kvstore.GetOrCreateBucket(ctx, js, KVBucketAccountSettings, 10)
if err != nil {
return nil, fmt.Errorf("failed to create account settings KV bucket: %w", err)
}
@@ -115,13 +115,13 @@ func mapSettingsError(ctx context.Context, accountID string, err error) error {
return errors.New(awserrors.ErrorBlocked)
case errors.Is(err, errSettingsContended):
slog.ErrorContext(ctx, "Account settings CAS retries exhausted under contention", "accountID", accountID)
case errors.Is(err, kvutil.ErrRead):
case errors.Is(err, kvstore.ErrRead):
slog.ErrorContext(ctx, "Failed to read account settings from KV", "accountID", accountID, "err", err)
case errors.Is(err, kvutil.ErrDecode):
case errors.Is(err, kvstore.ErrDecode):
slog.ErrorContext(ctx, "Corrupt account settings record in KV", "accountID", accountID, "err", err)
case errors.Is(err, kvutil.ErrEncode):
case errors.Is(err, kvstore.ErrEncode):
slog.ErrorContext(ctx, "Failed to marshal account settings record", "accountID", accountID, "err", err)
case errors.Is(err, kvutil.ErrWrite):
case errors.Is(err, kvstore.ErrWrite):
slog.ErrorContext(ctx, "Failed to write account settings to KV", "accountID", accountID, "err", err)
default:
slog.ErrorContext(ctx, "Account settings operation failed", "accountID", accountID, "err", err)
@@ -133,7 +133,7 @@ func mapSettingsError(ctx context.Context, accountID string, err error) error {
// changed is always true, so every success is a leader-accepted write over an
// active value; a seal landing between the read and the write fails the CAS.
func (s *AccountSettingsServiceImpl) mutateSettings(ctx context.Context, accountID string, set func(*AccountSettingsRecord)) error {
_, err := kvutil.Update(ctx, s.settingsKV, settingsKey(accountID), kvutil.CASConfig{
_, err := kvstore.Update(ctx, s.settingsKV, settingsKey(accountID), kvstore.CASConfig{
CreateIfAbsent: true,
Exhausted: func(string, int) error { return errSettingsContended },
}, func(rec *AccountSettingsRecord) (bool, error) {
+7 -7
View File
@@ -19,7 +19,7 @@ import (
awsfilters "github.com/mulgadc/spinifex/spinifex/foundation/aws/filters"
awsidentifiers "github.com/mulgadc/spinifex/spinifex/foundation/aws/identifiers"
awstags "github.com/mulgadc/spinifex/spinifex/foundation/aws/tags"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/mulgadc/spinifex/spinifex/foundation/state/migrate"
"github.com/nats-io/nats.go"
"github.com/nats-io/nats.go/jetstream"
@@ -56,7 +56,7 @@ func NewEgressOnlyIGWServiceImplWithNATS(ctx context.Context, cfg *config.Config
return nil, fmt.Errorf("failed to get JetStream context: %w", err)
}
eigwKV, err := kvutil.GetOrCreateBucket(ctx, js, KVBucketEgressOnlyIGW, 10)
eigwKV, err := kvstore.GetOrCreateBucket(ctx, js, KVBucketEgressOnlyIGW, 10)
if err != nil {
return nil, fmt.Errorf("failed to create KV bucket %s: %w", KVBucketEgressOnlyIGW, err)
}
@@ -65,7 +65,7 @@ func NewEgressOnlyIGWServiceImplWithNATS(ctx context.Context, cfg *config.Config
}
// Get or create VPC KV bucket for cross-resource ownership validation
vpcKV, err := kvutil.GetOrCreateBucket(ctx, js, ec2vpc.KVBucketVPCs, 10)
vpcKV, err := kvstore.GetOrCreateBucket(ctx, js, ec2vpc.KVBucketVPCs, 10)
if err != nil {
return nil, fmt.Errorf("failed to get VPC KV bucket: %w", err)
}
@@ -90,7 +90,7 @@ func (s *EgressOnlyIGWServiceImpl) CreateEgressOnlyInternetGateway(ctx context.C
slog.ErrorContext(ctx, "VPC KV unavailable, cannot verify VPC ownership")
return nil, errors.New(awserrors.ErrorServerInternal)
}
if _, err := s.vpcKV.Get(ctx, kvutil.AccountKey(accountID, *input.VpcId)); err != nil {
if _, err := s.vpcKV.Get(ctx, kvstore.AccountKey(accountID, *input.VpcId)); err != nil {
slog.WarnContext(ctx, "CreateEgressOnlyInternetGateway: VPC not found for account", "vpcId", *input.VpcId, "accountID", accountID)
return nil, awserrors.IDNotFound(awserrors.ErrorInvalidVpcIDNotFound, "vpc", *input.VpcId)
}
@@ -109,7 +109,7 @@ func (s *EgressOnlyIGWServiceImpl) CreateEgressOnlyInternetGateway(ctx context.C
if err != nil {
return nil, fmt.Errorf("failed to marshal Egress-only IGW record: %w", err)
}
if _, err := s.eigwKV.Put(ctx, kvutil.AccountKey(accountID, eigwID), data); err != nil {
if _, err := s.eigwKV.Put(ctx, kvstore.AccountKey(accountID, eigwID), data); err != nil {
return nil, errors.New(awserrors.ErrorServerInternal)
}
@@ -127,7 +127,7 @@ func (s *EgressOnlyIGWServiceImpl) DeleteEgressOnlyInternetGateway(ctx context.C
}
eigwID := *input.EgressOnlyInternetGatewayId
key := kvutil.AccountKey(accountID, eigwID)
key := kvstore.AccountKey(accountID, eigwID)
// Verify the EIGW exists before deleting
if _, err := s.eigwKV.Get(ctx, key); err != nil {
@@ -178,7 +178,7 @@ func (s *EgressOnlyIGWServiceImpl) DescribeEgressOnlyInternetGateways(ctx contex
}
for _, key := range keys {
if key == kvutil.VersionKey {
if key == kvstore.VersionKey {
continue
}
if !strings.HasPrefix(key, prefix) {
@@ -9,7 +9,7 @@ import (
"github.com/mulgadc/spinifex/internal/testkit"
ec2vpc "github.com/mulgadc/spinifex/spinifex/domains/ec2/vpc"
"github.com/mulgadc/spinifex/spinifex/foundation/aws/errors"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -23,7 +23,7 @@ func setupTestEIGWService(t *testing.T) *EgressOnlyIGWServiceImpl {
// Create VPC KV bucket and register test VPCs so fail-closed ownership checks pass
vpcEntries := map[string][]byte{}
for _, vpcID := range []string{"vpc-test123", "vpc-tagged", "vpc-tagged2"} {
vpcEntries[kvutil.AccountKey(testAccountID, vpcID)] = []byte(`{"vpc_id":"` + vpcID + `","state":"available"}`)
vpcEntries[kvstore.AccountKey(testAccountID, vpcID)] = []byte(`{"vpc_id":"` + vpcID + `","state":"available"}`)
}
testutil.SeedKV(t, js, ec2vpc.KVBucketVPCs, vpcEntries)
@@ -312,7 +312,7 @@ func TestCreateEgressOnlyInternetGateway_CrossAccountVPCRejected(t *testing.T) {
// Create VPC KV bucket with a VPC owned by testAccountID
vpcID := "vpc-alpha456"
testutil.SeedKV(t, js, ec2vpc.KVBucketVPCs, map[string][]byte{
kvutil.AccountKey(testAccountID, vpcID): []byte(`{"vpc_id":"vpc-alpha456","state":"available"}`),
kvstore.AccountKey(testAccountID, vpcID): []byte(`{"vpc_id":"vpc-alpha456","state":"available"}`),
})
var err error
@@ -12,7 +12,7 @@ import (
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/mulgadc/spinifex/spinifex/foundation/lifecycle/idempotency"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/nats-io/nats.go/jetstream"
)
@@ -36,7 +36,7 @@ func (s *EIPServiceImpl) allocateOnce(ctx context.Context, accountID string, all
if key == "" || s.idemKV == nil {
return alloc()
}
kvKey := kvutil.AccountKey(accountID, idempotencyKeyHash(key))
kvKey := kvstore.AccountKey(accountID, idempotencyKeyHash(key))
if out := s.cachedAllocation(ctx, kvKey); out != nil {
slog.WarnContext(ctx, "AllocateAddress: returning first result for a retried request",
+3 -3
View File
@@ -10,7 +10,7 @@ import (
"github.com/mulgadc/spinifex/spinifex/domains/network/projection"
"github.com/mulgadc/spinifex/spinifex/domains/network/topology"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/nats-io/nats.go/jetstream"
)
@@ -80,7 +80,7 @@ func (s *EIPServiceImpl) AllocationExists(ctx context.Context, allocationID stri
}
suffix := "." + allocationID
for _, k := range keys {
if k != kvutil.VersionKey && strings.HasSuffix(k, suffix) {
if k != kvstore.VersionKey && strings.HasSuffix(k, suffix) {
return true, nil
}
}
@@ -98,7 +98,7 @@ func (s *EIPServiceImpl) findByAllocationIDAnyAccount(ctx context.Context, alloc
suffix := "." + allocationID
for _, k := range keys {
if k == kvutil.VersionKey || !strings.HasSuffix(k, suffix) {
if k == kvstore.VersionKey || !strings.HasSuffix(k, suffix) {
continue
}
entry, err := s.eipKV.Get(ctx, k)
+12 -12
View File
@@ -20,7 +20,7 @@ import (
awsfilters "github.com/mulgadc/spinifex/spinifex/foundation/aws/filters"
awsidentifiers "github.com/mulgadc/spinifex/spinifex/foundation/aws/identifiers"
awstags "github.com/mulgadc/spinifex/spinifex/foundation/aws/tags"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/mulgadc/spinifex/spinifex/foundation/state/migrate"
"github.com/nats-io/nats.go"
"github.com/nats-io/nats.go/jetstream"
@@ -66,7 +66,7 @@ func NewEIPServiceImpl(ctx context.Context, natsConn *nats.Conn, externalIPAM *e
return nil, fmt.Errorf("failed to get JetStream context: %w", err)
}
eipKV, err := kvutil.GetOrCreateBucket(ctx, js, KVBucketEIPs, 10)
eipKV, err := kvstore.GetOrCreateBucket(ctx, js, KVBucketEIPs, 10)
if err != nil {
return nil, fmt.Errorf("failed to create KV bucket %s: %w", KVBucketEIPs, err)
}
@@ -76,7 +76,7 @@ func NewEIPServiceImpl(ctx context.Context, natsConn *nats.Conn, externalIPAM *e
// TTL'd, so a failure to create it is not worth refusing to serve EIPs over:
// a nil bucket only costs post-completion dedupe.
idemKV, err := kvutil.GetOrCreateBucketWithTTL(ctx, js, KVBucketEIPIdempotency, 1, eipIdempotencyTTL)
idemKV, err := kvstore.GetOrCreateBucketWithTTL(ctx, js, KVBucketEIPIdempotency, 1, eipIdempotencyTTL)
if err != nil {
slog.Warn("EIP service: idempotency bucket unavailable; a retried AllocateAddress may allocate twice",
"bucket", KVBucketEIPIdempotency, "err", err)
@@ -146,7 +146,7 @@ func (s *EIPServiceImpl) allocateAddress(ctx context.Context, input *ec2.Allocat
if err != nil {
return nil, fmt.Errorf("failed to marshal EIP record: %w", err)
}
if _, err := s.eipKV.Put(ctx, kvutil.AccountKey(accountID, allocID), data); err != nil {
if _, err := s.eipKV.Put(ctx, kvstore.AccountKey(accountID, allocID), data); err != nil {
return nil, errors.New(awserrors.ErrorServerInternal)
}
@@ -167,7 +167,7 @@ func (s *EIPServiceImpl) ReleaseAddress(ctx context.Context, input *ec2.ReleaseA
}
allocID := *input.AllocationId
key := kvutil.AccountKey(accountID, allocID)
key := kvstore.AccountKey(accountID, allocID)
entry, err := s.eipKV.Get(ctx, key)
if err != nil {
@@ -206,7 +206,7 @@ func (s *EIPServiceImpl) AssociateAddress(ctx context.Context, input *ec2.Associ
}
allocID := *input.AllocationId
key := kvutil.AccountKey(accountID, allocID)
key := kvstore.AccountKey(accountID, allocID)
entry, err := s.eipKV.Get(ctx, key)
if err != nil {
@@ -536,7 +536,7 @@ func (s *EIPServiceImpl) findByENI(ctx context.Context, accountID, eniID string)
if err := ctx.Err(); err != nil {
return nil, err
}
if k == kvutil.VersionKey || !strings.HasPrefix(k, prefix) {
if k == kvstore.VersionKey || !strings.HasPrefix(k, prefix) {
continue
}
entry, err := s.eipKV.Get(ctx, k)
@@ -590,7 +590,7 @@ func (s *EIPServiceImpl) DescribeAddresses(ctx context.Context, input *ec2.Descr
var addresses []*ec2.Address
for _, k := range keys {
if k == kvutil.VersionKey {
if k == kvstore.VersionKey {
continue
}
if !strings.HasPrefix(k, prefix) {
@@ -699,7 +699,7 @@ func (s *EIPServiceImpl) DescribeAddressesAttribute(ctx context.Context, input *
if id == nil {
continue
}
key := kvutil.AccountKey(accountID, *id)
key := kvstore.AccountKey(accountID, *id)
entry, err := s.eipKV.Get(ctx, key)
if err != nil {
continue // not found, skip
@@ -722,7 +722,7 @@ func (s *EIPServiceImpl) DescribeAddressesAttribute(ctx context.Context, input *
return nil, errors.New(awserrors.ErrorServerInternal)
}
for _, k := range keys {
if k == kvutil.VersionKey {
if k == kvstore.VersionKey {
continue
}
if !strings.HasPrefix(k, prefix) {
@@ -819,7 +819,7 @@ func (s *EIPServiceImpl) findByAssociationID(ctx context.Context, accountID, ass
}
for _, k := range keys {
if k == kvutil.VersionKey {
if k == kvstore.VersionKey {
continue
}
if !strings.HasPrefix(k, prefix) {
@@ -857,7 +857,7 @@ func (s *EIPServiceImpl) AssociatedPublicIPForInstance(ctx context.Context, acco
return "", false
}
for _, k := range keys {
if k == kvutil.VersionKey || !strings.HasPrefix(k, prefix) {
if k == kvstore.VersionKey || !strings.HasPrefix(k, prefix) {
continue
}
entry, err := s.eipKV.Get(ctx, k)
+15 -15
View File
@@ -22,7 +22,7 @@ import (
awsidentifiers "github.com/mulgadc/spinifex/spinifex/foundation/aws/identifiers"
"github.com/mulgadc/spinifex/spinifex/foundation/aws/paging"
awstags "github.com/mulgadc/spinifex/spinifex/foundation/aws/tags"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/mulgadc/spinifex/spinifex/foundation/state/migrate"
"github.com/nats-io/nats.go"
"github.com/nats-io/nats.go/jetstream"
@@ -93,7 +93,7 @@ func NewIGWServiceImplWithNATS(ctx context.Context, cfg *config.Config, natsConn
return nil, fmt.Errorf("failed to get JetStream context: %w", err)
}
igwKV, err := kvutil.GetOrCreateBucket(ctx, js, KVBucketIGW, 10)
igwKV, err := kvstore.GetOrCreateBucket(ctx, js, KVBucketIGW, 10)
if err != nil {
return nil, fmt.Errorf("failed to create KV bucket %s: %w", KVBucketIGW, err)
}
@@ -102,13 +102,13 @@ func NewIGWServiceImplWithNATS(ctx context.Context, cfg *config.Config, natsConn
}
// Get or create VPC KV bucket for cross-resource ownership validation
vpcKV, err := kvutil.GetOrCreateBucket(ctx, js, ec2vpc.KVBucketVPCs, 10)
vpcKV, err := kvstore.GetOrCreateBucket(ctx, js, ec2vpc.KVBucketVPCs, 10)
if err != nil {
return nil, fmt.Errorf("failed to get VPC KV bucket: %w", err)
}
// Read EIP associations so detach can refuse while addresses are mapped.
eipKV, err := kvutil.GetOrCreateBucket(ctx, js, ec2eip.KVBucketEIPs, 10)
eipKV, err := kvstore.GetOrCreateBucket(ctx, js, ec2eip.KVBucketEIPs, 10)
if err != nil {
return nil, fmt.Errorf("failed to get EIP KV bucket: %w", err)
}
@@ -142,7 +142,7 @@ func (s *IGWServiceImpl) createIGW(ctx context.Context, input *ec2.CreateInterne
if err != nil {
return nil, fmt.Errorf("failed to marshal IGW record: %w", err)
}
if _, err := s.igwKV.Put(ctx, kvutil.AccountKey(accountID, igwID), data); err != nil {
if _, err := s.igwKV.Put(ctx, kvstore.AccountKey(accountID, igwID), data); err != nil {
return nil, errors.New(awserrors.ErrorServerInternal)
}
@@ -160,7 +160,7 @@ func (s *IGWServiceImpl) DeleteInternetGateway(ctx context.Context, input *ec2.D
}
igwID := *input.InternetGatewayId
key := kvutil.AccountKey(accountID, igwID)
key := kvstore.AccountKey(accountID, igwID)
entry, err := s.igwKV.Get(ctx, key)
if err != nil {
@@ -236,7 +236,7 @@ func (s *IGWServiceImpl) DescribeInternetGateways(ctx context.Context, input *ec
foundIDs := make(map[string]bool)
for _, key := range keys {
if key == kvutil.VersionKey {
if key == kvstore.VersionKey {
continue
}
if !strings.HasPrefix(key, prefix) {
@@ -326,7 +326,7 @@ func (s *IGWServiceImpl) AttachInternetGateway(ctx context.Context, input *ec2.A
igwID := *input.InternetGatewayId
vpcID := *input.VpcId
key := kvutil.AccountKey(accountID, igwID)
key := kvstore.AccountKey(accountID, igwID)
entry, err := s.igwKV.Get(ctx, key)
if err != nil {
@@ -347,7 +347,7 @@ func (s *IGWServiceImpl) AttachInternetGateway(ctx context.Context, input *ec2.A
slog.ErrorContext(ctx, "VPC KV unavailable, cannot verify VPC ownership")
return nil, errors.New(awserrors.ErrorServerInternal)
}
if _, err := s.vpcKV.Get(ctx, kvutil.AccountKey(accountID, vpcID)); err != nil {
if _, err := s.vpcKV.Get(ctx, kvstore.AccountKey(accountID, vpcID)); err != nil {
slog.WarnContext(ctx, "AttachInternetGateway: VPC not found for account", "vpcId", vpcID, "accountID", accountID)
return nil, awserrors.IDNotFound(awserrors.ErrorInvalidVpcIDNotFound, "vpc", vpcID)
}
@@ -388,7 +388,7 @@ func (s *IGWServiceImpl) DetachInternetGateway(ctx context.Context, input *ec2.D
igwID := *input.InternetGatewayId
vpcID := *input.VpcId
key := kvutil.AccountKey(accountID, igwID)
key := kvstore.AccountKey(accountID, igwID)
entry, err := s.igwKV.Get(ctx, key)
if err != nil {
@@ -449,7 +449,7 @@ func (s *IGWServiceImpl) DetachInternetGateway(ctx context.Context, input *ec2.D
// associated into vpcID, whether to an interface or a NAT gateway. The listing
// is filtered server-side to the account's keys.
func (s *IGWServiceImpl) vpcHasMappedAddress(ctx context.Context, accountID, vpcID string) (bool, error) {
lister, err := s.eipKV.ListKeysFiltered(ctx, kvutil.AccountKey(accountID, "*"))
lister, err := s.eipKV.ListKeysFiltered(ctx, kvstore.AccountKey(accountID, "*"))
if err != nil {
return false, err
}
@@ -493,11 +493,11 @@ func (s *IGWServiceImpl) CreateAttachedInternetGateway(ctx context.Context, acco
if s.vpcKV == nil {
return false, errors.New(awserrors.ErrorServerInternal)
}
if _, err := s.vpcKV.Get(ctx, kvutil.AccountKey(accountID, vpcID)); err != nil {
if _, err := s.vpcKV.Get(ctx, kvstore.AccountKey(accountID, vpcID)); err != nil {
return false, awserrors.IDNotFound(awserrors.ErrorInvalidVpcIDNotFound, "vpc", vpcID)
}
key := kvutil.AccountKey(accountID, igwID)
key := kvstore.AccountKey(accountID, igwID)
data, err := json.Marshal(IGWRecord{
InternetGatewayId: igwID,
VpcId: vpcID,
@@ -558,7 +558,7 @@ func (s *IGWServiceImpl) AttachmentIntent(ctx context.Context, accountID, vpcID
}
for _, key := range keys {
if key == kvutil.VersionKey || !strings.HasPrefix(key, prefix) {
if key == kvstore.VersionKey || !strings.HasPrefix(key, prefix) {
continue
}
entry, err := s.igwKV.Get(ctx, key)
@@ -599,7 +599,7 @@ func (s *IGWServiceImpl) AttachmentIntent(ctx context.Context, accountID, vpcID
// Writing only on the pending transition keeps a pass from waking the drift loop.
func MarkAttached(ctx context.Context, kv jetstream.KeyValue, recordKey, vpcID string) error {
confirmed := false
record, err := kvutil.Update(ctx, kv, recordKey, kvutil.CASConfig{}, func(r *IGWRecord) (bool, error) {
record, err := kvstore.Update(ctx, kv, recordKey, kvstore.CASConfig{}, func(r *IGWRecord) (bool, error) {
// Reset per attempt: a retried CAS may see a record another writer has
// already moved out of pending.
confirmed = r.VpcId == vpcID && r.AttachState == AttachStatePending
@@ -13,7 +13,7 @@ import (
ec2eip "github.com/mulgadc/spinifex/spinifex/domains/ec2/eip"
ec2vpc "github.com/mulgadc/spinifex/spinifex/domains/ec2/vpc"
"github.com/mulgadc/spinifex/spinifex/foundation/aws/errors"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/nats-io/nats.go"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -55,7 +55,7 @@ func setupTestIGWService(t *testing.T) (*IGWServiceImpl, *nats.Conn) {
// Create VPC KV bucket and register test VPCs so fail-closed ownership checks pass
vpcEntries := map[string][]byte{}
for _, vpcID := range []string{"vpc-test123", "vpc-other", "vpc-lifecycle", "vpc-event-test"} {
vpcEntries[kvutil.AccountKey(testAccountID, vpcID)] = []byte(`{"vpc_id":"` + vpcID + `","state":"available"}`)
vpcEntries[kvstore.AccountKey(testAccountID, vpcID)] = []byte(`{"vpc_id":"` + vpcID + `","state":"available"}`)
}
testutil.SeedKV(t, js, ec2vpc.KVBucketVPCs, vpcEntries)
@@ -68,7 +68,7 @@ func setupTestIGWService(t *testing.T) (*IGWServiceImpl, *nats.Conn) {
// gateway come up. Without it an attach stays pending and reports no attachment.
func confirmAttach(t *testing.T, svc *IGWServiceImpl, igwID, vpcID string) {
t.Helper()
require.NoError(t, MarkAttached(context.Background(), svc.igwKV, kvutil.AccountKey(testAccountID, igwID), vpcID))
require.NoError(t, MarkAttached(context.Background(), svc.igwKV, kvstore.AccountKey(testAccountID, igwID), vpcID))
}
func createTestIGW(t *testing.T, svc *IGWServiceImpl) string {
@@ -331,7 +331,7 @@ func TestDescribeInternetGateways_LegacyRecordReportsAttachment(t *testing.T) {
svc, _ := setupTestIGWService(t)
igwID := createTestIGW(t, svc)
key := kvutil.AccountKey(testAccountID, igwID)
key := kvstore.AccountKey(testAccountID, igwID)
entry, err := svc.igwKV.Get(context.Background(), key)
require.NoError(t, err)
var record IGWRecord
@@ -398,7 +398,7 @@ func TestMarkAttached_NoRewriteWhenAlreadyAttached(t *testing.T) {
}, testAccountID)
require.NoError(t, err)
key := kvutil.AccountKey(testAccountID, igwID)
key := kvstore.AccountKey(testAccountID, igwID)
confirmAttach(t, svc, igwID, "vpc-test123")
first, err := svc.igwKV.Get(context.Background(), key)
require.NoError(t, err)
@@ -605,7 +605,7 @@ func TestDetachInternetGateway_MappedPublicAddress(t *testing.T) {
putEIP := func(rec ec2eip.EIPRecord) {
data, err := json.Marshal(rec)
require.NoError(t, err)
_, err = svc.eipKV.Put(context.Background(), kvutil.AccountKey(testAccountID, rec.AllocationId), data)
_, err = svc.eipKV.Put(context.Background(), kvstore.AccountKey(testAccountID, rec.AllocationId), data)
require.NoError(t, err)
}
putEIP(ec2eip.EIPRecord{AllocationId: "eipalloc-other", AssociationId: "eipassoc-other", VpcId: "vpc-other", State: "associated"})
@@ -614,7 +614,7 @@ func TestDetachInternetGateway_MappedPublicAddress(t *testing.T) {
// Another account's address naming the same VPC ID is not this account's dependency.
foreign, err := json.Marshal(ec2eip.EIPRecord{AllocationId: "eipalloc-foreign", AssociationId: "eipassoc-foreign", VpcId: "vpc-test123", State: "associated"})
require.NoError(t, err)
_, err = svc.eipKV.Put(context.Background(), kvutil.AccountKey("999999999999", "eipalloc-foreign"), foreign)
_, err = svc.eipKV.Put(context.Background(), kvstore.AccountKey("999999999999", "eipalloc-foreign"), foreign)
require.NoError(t, err)
detach := func() error {
@@ -828,7 +828,7 @@ func TestAttachInternetGateway_CrossAccountVPCRejected(t *testing.T) {
require.NoError(t, err)
vpcID := "vpc-alpha123"
_, err = vpcKV.Put(t.Context(), kvutil.AccountKey(testAccountID, vpcID), []byte(`{"vpc_id":"vpc-alpha123","state":"available"}`))
_, err = vpcKV.Put(t.Context(), kvstore.AccountKey(testAccountID, vpcID), []byte(`{"vpc_id":"vpc-alpha123","state":"available"}`))
require.NoError(t, err)
// Refresh service to pick up the VPC KV bucket
@@ -10,7 +10,6 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/providers/objectstore"
"github.com/nats-io/nats.go/jetstream"
)
@@ -72,7 +71,7 @@ func (s *Store) InstanceIDs(ctx context.Context) ([]string, error) {
var ids []string
for key := range lister.Keys() {
if key != kvutil.VersionKey {
if key != kvstore.VersionKey {
ids = append(ids, key)
}
}
@@ -11,7 +11,6 @@ import (
testutil "github.com/mulgadc/spinifex/internal/testkit"
"github.com/mulgadc/spinifex/spinifex/domains/ec2/instance/uefistore"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/nats-io/nats.go/jetstream"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -60,7 +59,7 @@ func TestOpen_CreatesBucketWithNoTTLAndStampsVersion(t *testing.T) {
assert.Equal(t, int64(1), status.History())
assert.Zero(t, status.TTL(), "a TTL would expire deletion obligations before they are settled")
v, err := kvutil.ReadVersion(t.Context(), kv)
v, err := kvstore.ReadVersion(t.Context(), kv)
require.NoError(t, err)
assert.Equal(t, uefistore.BucketVersion, v)
}
@@ -9,7 +9,7 @@ import (
"strings"
"time"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/nats-io/nats.go/jetstream"
)
@@ -33,7 +33,7 @@ type AccountCleanup struct {
// Unreadable headers, their bodies and unrecognised entries are left for the
// owner scan to report. A listing failure is returned and nothing is removed.
func (s *LaunchTemplateServiceImpl) CleanupAccountResidue(ctx context.Context, accountID string, settledBefore time.Time) (*AccountCleanup, error) {
keys, err := kvutil.Keys(ctx, s.kv)
keys, err := kvstore.Keys(ctx, s.kv)
if err != nil && !errors.Is(err, jetstream.ErrNoKeysFound) {
return nil, fmt.Errorf("list launch-template keys: %w", err)
}
@@ -145,7 +145,7 @@ type AccountTemplateDeletes struct {
// it cannot read or decode is left for the owner scan to report. A listing
// failure is returned and nothing is deleted.
func (s *LaunchTemplateServiceImpl) DeleteLiveTemplates(ctx context.Context, accountID string, referenced func(id, name string) string) (*AccountTemplateDeletes, error) {
keys, err := kvutil.Keys(ctx, s.kv)
keys, err := kvstore.Keys(ctx, s.kv)
if err != nil && !errors.Is(err, jetstream.ErrNoKeysFound) {
return nil, fmt.Errorf("list launch-template keys: %w", err)
}
@@ -9,7 +9,7 @@ import (
"strconv"
"strings"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/nats-io/nats.go/jetstream"
)
@@ -37,7 +37,7 @@ type AccountScan struct {
// Headers and name-index entries are read; version bodies are classified from
// their keys alone, so template data and user data are never loaded.
func (s *LaunchTemplateServiceImpl) ScanAccount(ctx context.Context, accountID string) (*AccountScan, error) {
keys, err := kvutil.Keys(ctx, s.kv)
keys, err := kvstore.Keys(ctx, s.kv)
if err != nil && !errors.Is(err, jetstream.ErrNoKeysFound) {
return nil, fmt.Errorf("list launch-template keys: %w", err)
}
@@ -11,7 +11,7 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/mulgadc/spinifex/internal/testkit/readonlystore"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/nats-io/nats.go/jetstream"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -45,7 +45,7 @@ func TestScanAccountClassifiesLaunchTemplateRecords(t *testing.T) {
// A header removed alone, as a delete interrupted after it leaves the rest.
deleted := createWithUserData(t, svc, "deleted")
inner := svc.kv
require.NoError(t, inner.Delete(t.Context(), kvutil.AccountKey(testAccountID, deleted)))
require.NoError(t, inner.Delete(t.Context(), kvstore.AccountKey(testAccountID, deleted)))
// A create whose header write and clean-up both fail.
svc.kv = &faultyKV{KeyValue: inner,
@@ -59,16 +59,16 @@ func TestScanAccountClassifiesLaunchTemplateRecords(t *testing.T) {
require.Error(t, err)
svc.kv = inner
putRaw(t, inner, kvutil.AccountKey(testAccountID, "lt-0badbadbadbadbad0"), "{not json")
putRaw(t, inner, kvutil.AccountKey(testAccountID, "lt-0badbadbadbadbad0.v1"), `{"version_number":1}`)
putRaw(t, inner, kvutil.AccountKey(testAccountID, "lt-0mismatch00000000"), `{"launch_template_id":"lt-0other"}`)
putRaw(t, inner, kvutil.AccountKey(testAccountID, "lt-0unreadable000000"), `{"launch_template_id":"lt-0unreadable000000"}`)
putRaw(t, inner, kvstore.AccountKey(testAccountID, "lt-0badbadbadbadbad0"), "{not json")
putRaw(t, inner, kvstore.AccountKey(testAccountID, "lt-0badbadbadbadbad0.v1"), `{"version_number":1}`)
putRaw(t, inner, kvstore.AccountKey(testAccountID, "lt-0mismatch00000000"), `{"launch_template_id":"lt-0other"}`)
putRaw(t, inner, kvstore.AccountKey(testAccountID, "lt-0unreadable000000"), `{"launch_template_id":"lt-0unreadable000000"}`)
putRaw(t, inner, nameKey(testAccountID, "unreadable"), "lt-0unreadable000000")
putRaw(t, inner, kvutil.AccountKey(testAccountID, "lt-0bad.vx"), "{}")
putRaw(t, inner, kvstore.AccountKey(testAccountID, "lt-0bad.vx"), "{}")
recorder := &readRecordingKV{KeyValue: readonlystore.NewKV(t, inner)}
svc.kv = &faultyKV{KeyValue: recorder, failGet: func(key string) bool {
return key == kvutil.AccountKey(testAccountID, "lt-0unreadable000000")
return key == kvstore.AccountKey(testAccountID, "lt-0unreadable000000")
}}
scan, err := svc.ScanAccount(context.Background(), testAccountID)
require.NoError(t, err)
@@ -16,7 +16,7 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/mulgadc/spinifex/spinifex/foundation/aws/errors"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/nats-io/nats.go/jetstream"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -159,7 +159,7 @@ func TestCurrentBehaviour_AccountTermination_LaunchTemplateStorageLayout(t *test
header := testAccountID + "." + id
name := testAccountID + ".name.776562283129" // hex of "web(1)"
assert.Equal(t, sortedKeys(header, header+".v1", header+".v2", name, kvutil.VersionKey), storedKeys(t, svc.kv))
assert.Equal(t, sortedKeys(header, header+".v1", header+".v2", name, kvstore.VersionKey), storedKeys(t, svc.kv))
entry, err := svc.kv.Get(t.Context(), name)
require.NoError(t, err)
@@ -223,13 +223,13 @@ func TestAccountTermination_DeleteLaunchTemplateKeepsTheTemplateWhenAStepFails(t
assert.Equal(t, awserrors.ErrorServerInternal, err.Error())
svc.kv = inner
assert.Equal(t, sortedKeys(append(tc.left(header, nameIdx), kvutil.VersionKey)...), storedKeys(t, inner))
assert.Equal(t, sortedKeys(append(tc.left(header, nameIdx), kvstore.VersionKey)...), storedKeys(t, inner))
assert.Equal(t, []string{id}, describedTemplateIDs(t, svc, testAccountID))
out, err := svc.DeleteLaunchTemplate(context.Background(), &ec2.DeleteLaunchTemplateInput{LaunchTemplateId: aws.String(id)}, testAccountID)
require.NoError(t, err)
assert.Equal(t, id, aws.StringValue(out.LaunchTemplate.LaunchTemplateId))
assert.Equal(t, []string{kvutil.VersionKey}, storedKeys(t, inner))
assert.Equal(t, []string{kvstore.VersionKey}, storedKeys(t, inner))
assert.Empty(t, describedTemplateIDs(t, svc, testAccountID))
})
}
@@ -260,7 +260,7 @@ func TestAccountTermination_DeleteLaunchTemplateRestartsWhenTheHeaderChanges(t *
_, err = svc.DeleteLaunchTemplate(context.Background(), &ec2.DeleteLaunchTemplateInput{LaunchTemplateId: aws.String(id)}, testAccountID)
require.NoError(t, err)
assert.True(t, raced)
assert.Equal(t, []string{kvutil.VersionKey}, storedKeys(t, inner))
assert.Equal(t, []string{kvstore.VersionKey}, storedKeys(t, inner))
}
// A create that fails after claiming the name removes what it wrote, so no
@@ -287,7 +287,7 @@ func TestAccountTermination_FailedCreateRemovesWhatItWrote(t *testing.T) {
assert.Equal(t, awserrors.ErrorServerInternal, err.Error())
svc.kv = inner
assert.Equal(t, []string{kvutil.VersionKey}, storedKeys(t, inner))
assert.Equal(t, []string{kvstore.VersionKey}, storedKeys(t, inner))
retried := createTemplate(t, svc, "web", "t3.micro")
assert.Equal(t, []string{aws.StringValue(retried.LaunchTemplateId)}, describedTemplateIDs(t, svc, testAccountID))
})
@@ -299,7 +299,7 @@ func TestAccountTermination_FailedCreateRemovesWhatItWrote(t *testing.T) {
// failure is an internal error, never a missing template.
func TestCurrentBehaviour_AccountTermination_DescribeLaunchTemplatesHidesUnreadableHeaders(t *testing.T) {
const brokenID = "lt-0badbadbadbadbad0"
brokenHeader := kvutil.AccountKey(testAccountID, brokenID)
brokenHeader := kvstore.AccountKey(testAccountID, brokenID)
tests := []struct {
name string
seed func(t *testing.T, svc *LaunchTemplateServiceImpl)
@@ -350,7 +350,7 @@ func TestAccountCleanup_KeepsTemplatesAndRemovesSettledResidue(t *testing.T) {
require.NoError(t, err)
orphanBody := versionKey(testAccountID, "lt-0orphan000000000", 1)
orphanName := nameKey(testAccountID, "gone")
brokenHeader := kvutil.AccountKey(testAccountID, "lt-0broken000000000")
brokenHeader := kvstore.AccountKey(testAccountID, "lt-0broken000000000")
brokenBody := versionKey(testAccountID, "lt-0broken000000000", 1)
brokenName := nameKey(testAccountID, "broken")
for key, value := range map[string]string{
@@ -384,7 +384,7 @@ func TestAccountCleanup_KeepsTemplatesAndRemovesSettledResidue(t *testing.T) {
func TestAccountCleanup_InterruptedTemplateDeleteIsLeftToThePublicRetry(t *testing.T) {
svc := setupTestService(t)
id := createWithUserData(t, svc, "web")
header := kvutil.AccountKey(testAccountID, id)
header := kvstore.AccountKey(testAccountID, id)
kv := svc.kv
svc.kv = &faultyKV{KeyValue: kv, failDelete: func(key string) bool { return key == header }}
_, err := svc.DeleteLaunchTemplate(context.Background(), &ec2.DeleteLaunchTemplateInput{LaunchTemplateId: aws.String(id)}, testAccountID)
@@ -19,7 +19,7 @@ import (
awsfilters "github.com/mulgadc/spinifex/spinifex/foundation/aws/filters"
awsidentifiers "github.com/mulgadc/spinifex/spinifex/foundation/aws/identifiers"
awstags "github.com/mulgadc/spinifex/spinifex/foundation/aws/tags"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/mulgadc/spinifex/spinifex/foundation/state/migrate"
"github.com/nats-io/nats.go"
"github.com/nats-io/nats.go/jetstream"
@@ -64,7 +64,7 @@ func NewLaunchTemplateServiceImplWithNATS(ctx context.Context, cfg *config.Confi
return nil, fmt.Errorf("failed to get JetStream context: %w", err)
}
kv, err := kvutil.GetOrCreateBucket(ctx, js, KVBucketLaunchTemplates, 10)
kv, err := kvstore.GetOrCreateBucket(ctx, js, KVBucketLaunchTemplates, 10)
if err != nil {
return nil, fmt.Errorf("failed to create KV bucket %s: %w", KVBucketLaunchTemplates, err)
}
@@ -87,24 +87,24 @@ func NewLaunchTemplateServiceImplWithNATS(ctx context.Context, cfg *config.Confi
// Launch template names allow characters such as parentheses that are not valid
// in NATS KV keys, so the name must not be used in its raw form here.
func nameKey(accountID, name string) string {
return kvutil.AccountKey(accountID, "name."+hex.EncodeToString([]byte(name)))
return kvstore.AccountKey(accountID, "name."+hex.EncodeToString([]byte(name)))
}
// versionKey returns a version-body key: account.lt-<id>.v<n>.
func versionKey(accountID, ltID string, n int64) string {
return kvutil.AccountKey(accountID, ltID+".v"+strconv.FormatInt(n, 10))
return kvstore.AccountKey(accountID, ltID+".v"+strconv.FormatInt(n, 10))
}
// versionPrefix returns the scan prefix for a template's version bodies.
func versionPrefix(accountID, ltID string) string {
return kvutil.AccountKey(accountID, ltID+".v")
return kvstore.AccountKey(accountID, ltID+".v")
}
// --- record load helpers ---
// getHeaderByID reads a header and its KV entry (for CAS) by launch template id.
func (s *LaunchTemplateServiceImpl) getHeaderByID(ctx context.Context, accountID, ltID string) (*LaunchTemplateHeader, jetstream.KeyValueEntry, error) {
entry, err := s.kv.Get(ctx, kvutil.AccountKey(accountID, ltID))
entry, err := s.kv.Get(ctx, kvstore.AccountKey(accountID, ltID))
if err != nil {
if errors.Is(err, jetstream.ErrKeyNotFound) {
return nil, nil, errors.New(awserrors.ErrorInvalidLaunchTemplateIdNotFound)
@@ -386,7 +386,7 @@ func (s *LaunchTemplateServiceImpl) claimName(ctx context.Context, accountID, na
// header means the name is taken; any other read error (transient fault, or a
// concurrent in-flight create whose header is not yet written) fails closed so
// the name is never stolen from a possibly-live template.
_, herr := s.kv.Get(ctx, kvutil.AccountKey(accountID, string(entry.Value())))
_, herr := s.kv.Get(ctx, kvstore.AccountKey(accountID, string(entry.Value())))
switch {
case herr == nil:
return errNameInUse()
@@ -404,7 +404,7 @@ func (s *LaunchTemplateServiceImpl) putHeader(ctx context.Context, accountID str
if err != nil {
return errors.New(awserrors.ErrorServerInternal)
}
if _, err := s.kv.Put(ctx, kvutil.AccountKey(accountID, h.LaunchTemplateId), data); err != nil {
if _, err := s.kv.Put(ctx, kvstore.AccountKey(accountID, h.LaunchTemplateId), data); err != nil {
return errors.New(awserrors.ErrorServerInternal)
}
return nil
@@ -510,13 +510,13 @@ func (s *LaunchTemplateServiceImpl) commitVersion(ctx context.Context, accountID
// re-reading on a conflict. A non-nil check vets each re-read header and
// stops the commit with its error.
func (s *LaunchTemplateServiceImpl) commitHeader(ctx context.Context, op, accountID, ltID string, entry jetstream.KeyValueEntry, check func(*LaunchTemplateHeader) error) error {
key := kvutil.AccountKey(accountID, ltID)
key := kvstore.AccountKey(accountID, ltID)
for range maxVersionRetries {
_, err := s.kv.Update(ctx, key, entry.Value(), entry.Revision())
if err == nil {
return nil
}
if !kvutil.IsRevisionConflict(err) {
if !kvstore.IsRevisionConflict(err) {
slog.ErrorContext(ctx, op+": header commit failed", "launchTemplateId", ltID, "err", err)
return err
}
@@ -590,7 +590,7 @@ func (s *LaunchTemplateServiceImpl) ModifyLaunchTemplate(ctx context.Context, in
// header deleted meanwhile is NotFound for the identifier the request used.
func (s *LaunchTemplateServiceImpl) setDefaultVersion(ctx context.Context, accountID string, input *ec2.ModifyLaunchTemplateInput, header *LaunchTemplateHeader, entry jetstream.KeyValueEntry, sel string) (*LaunchTemplateHeader, error) {
notFound := fmt.Sprintf("The launch template version %s is not found for the specified launch template.", sel)
key := kvutil.AccountKey(accountID, header.LaunchTemplateId)
key := kvstore.AccountKey(accountID, header.LaunchTemplateId)
for range maxVersionRetries {
n, err := s.resolveVersionNumber(ctx, accountID, header, sel)
if err != nil {
@@ -611,7 +611,7 @@ func (s *LaunchTemplateServiceImpl) setDefaultVersion(ctx context.Context, accou
if err == nil {
return header, nil
}
if !kvutil.IsRevisionConflict(err) {
if !kvstore.IsRevisionConflict(err) {
slog.ErrorContext(ctx, "ModifyLaunchTemplate: header update failed", "launchTemplateId", header.LaunchTemplateId, "err", err)
return nil, errors.New(awserrors.ErrorServerInternal)
}
@@ -662,7 +662,7 @@ const maxHeaderDeleteAttempts = 3
// through the public delete; a header changed in between restarts the sweep.
func (s *LaunchTemplateServiceImpl) deleteTemplate(ctx context.Context, accountID string, header *LaunchTemplateHeader, entry jetstream.KeyValueEntry) error {
ltID := header.LaunchTemplateId
headerKey := kvutil.AccountKey(accountID, ltID)
headerKey := kvstore.AccountKey(accountID, ltID)
for attempt := 1; ; attempt++ {
if err := s.deleteVersionBodies(ctx, accountID, ltID); err != nil {
return err
@@ -824,7 +824,7 @@ func (s *LaunchTemplateServiceImpl) commitVersionDeletes(ctx context.Context, ac
}
// A template delete that removed the header before the restore has already
// swept its bodies, so the restored ones would be left headerless.
if _, gerr := s.kv.Get(ctx, kvutil.AccountKey(accountID, ltID)); errors.Is(gerr, jetstream.ErrKeyNotFound) {
if _, gerr := s.kv.Get(ctx, kvstore.AccountKey(accountID, ltID)); errors.Is(gerr, jetstream.ErrKeyNotFound) {
for n := range deleted {
if derr := s.kv.Delete(ctx, versionKey(accountID, ltID, n)); derr != nil {
slog.ErrorContext(ctx, "DeleteLaunchTemplateVersions: failed to remove a restored version", "launchTemplateId", ltID, "version", n, "err", derr)
@@ -9,7 +9,7 @@ import (
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/mulgadc/spinifex/internal/testkit"
"github.com/mulgadc/spinifex/spinifex/foundation/aws/errors"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -116,7 +116,7 @@ func TestCreateLaunchTemplate_OrphanNameReclaim(t *testing.T) {
lt := createTemplate(t, svc, "orphan", "t3.micro")
// Simulate a crash after the name claim but with the header lost.
require.NoError(t, svc.kv.Delete(t.Context(), kvutil.AccountKey(testAccountID, aws.StringValue(lt.LaunchTemplateId))))
require.NoError(t, svc.kv.Delete(t.Context(), kvstore.AccountKey(testAccountID, aws.StringValue(lt.LaunchTemplateId))))
// The name index still points at the now-orphaned id; create must reclaim it.
out, err := svc.CreateLaunchTemplate(context.Background(), &ec2.CreateLaunchTemplateInput{
@@ -10,7 +10,7 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/mulgadc/spinifex/spinifex/foundation/aws/errors"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -38,7 +38,7 @@ func TestAccountTermination_VersionCreateRacingADeleteLeavesNoBody(t *testing.T)
t.Run(pause, func(t *testing.T) {
svc := setupTestService(t)
id := createWithUserData(t, svc, "web")
header := kvutil.AccountKey(testAccountID, id)
header := kvstore.AccountKey(testAccountID, id)
raced := false
race := func(key string, at string) {
if raced || pause != at {
@@ -71,7 +71,7 @@ func TestAccountTermination_DeleteRacingACommittedVersionRemovesIt(t *testing.T)
svc := setupTestService(t)
id := createWithUserData(t, svc, "web")
inner := svc.kv
header := kvutil.AccountKey(testAccountID, id)
header := kvstore.AccountKey(testAccountID, id)
raced := false
svc.kv = &faultyKV{KeyValue: inner, beforeDelete: func(key string) {
if key != header || raced {
@@ -95,7 +95,7 @@ func TestAccountTermination_FailedVersionCommitRemovesTheBody(t *testing.T) {
svc := setupTestService(t)
id := createWithUserData(t, svc, "web")
before := accountKeys(t, svc.kv, testAccountID)
header := kvutil.AccountKey(testAccountID, id)
header := kvstore.AccountKey(testAccountID, id)
svc.kv = &faultyKV{KeyValue: svc.kv, failUpdate: func(key string) bool { return key == header }}
_, err := svc.CreateLaunchTemplateVersion(context.Background(), newVersionInput(id), testAccountID)
@@ -110,7 +110,7 @@ func TestAccountTermination_VersionCommitKeepsAConcurrentHeaderChange(t *testing
svc := setupTestService(t)
id := createWithUserData(t, svc, "web")
inner := svc.kv
header := kvutil.AccountKey(testAccountID, id)
header := kvstore.AccountKey(testAccountID, id)
raced := false
svc.kv = &faultyKV{KeyValue: inner, beforeUpdate: func(key string) {
if key != header || raced {
@@ -215,10 +215,10 @@ func TestAccountTemplateReferences_FailsClosed(t *testing.T) {
require.NoError(t, err)
},
"header unreadable": func(t *testing.T, svc *LaunchTemplateServiceImpl, id string) {
svc.kv = &faultyKV{KeyValue: svc.kv, failGet: func(key string) bool { return key == kvutil.AccountKey(testAccountID, id) }}
svc.kv = &faultyKV{KeyValue: svc.kv, failGet: func(key string) bool { return key == kvstore.AccountKey(testAccountID, id) }}
},
"header undecodable": func(t *testing.T, svc *LaunchTemplateServiceImpl, _ string) {
_, err := svc.kv.Put(t.Context(), kvutil.AccountKey(testAccountID, "lt-0broken000000000"), []byte("{not json"))
_, err := svc.kv.Put(t.Context(), kvstore.AccountKey(testAccountID, "lt-0broken000000000"), []byte("{not json"))
require.NoError(t, err)
},
}
@@ -241,7 +241,7 @@ func TestModifyLaunchTemplate_RacingADeleteIsNotFound(t *testing.T) {
t.Run(map[bool]string{false: "by-id", true: "by-name"}[byName], func(t *testing.T) {
svc := setupTestService(t)
id := createWithUserData(t, svc, "web")
header := kvutil.AccountKey(testAccountID, id)
header := kvstore.AccountKey(testAccountID, id)
raced := false
svc.kv = &faultyKV{KeyValue: svc.kv, beforeUpdate: func(key string) {
if key == header && !raced {
@@ -275,7 +275,7 @@ func TestModifyLaunchTemplate_RetriesAConcurrentHeaderChange(t *testing.T) {
_, err := svc.CreateLaunchTemplateVersion(context.Background(), newVersionInput(id), testAccountID) // v3
require.NoError(t, err)
inner := svc.kv
header := kvutil.AccountKey(testAccountID, id)
header := kvstore.AccountKey(testAccountID, id)
raced := false
svc.kv = &faultyKV{KeyValue: inner, beforeUpdate: func(key string) {
if key != header || raced {
@@ -306,7 +306,7 @@ func TestModifyLaunchTemplate_ConflictRetriesAreBounded(t *testing.T) {
svc := setupTestService(t)
id := createWithUserData(t, svc, "web")
inner := svc.kv
header := kvutil.AccountKey(testAccountID, id)
header := kvstore.AccountKey(testAccountID, id)
attempts := 0
svc.kv = &faultyKV{KeyValue: inner, beforeUpdate: func(key string) {
if key != header {
@@ -333,7 +333,7 @@ func TestModifyLaunchTemplate_ConflictReReadFailureIsInternal(t *testing.T) {
svc := setupTestService(t)
id := createWithUserData(t, svc, "web")
inner := svc.kv
header := kvutil.AccountKey(testAccountID, id)
header := kvstore.AccountKey(testAccountID, id)
conflicted := false
svc.kv = &faultyKV{
KeyValue: inner,
@@ -410,7 +410,7 @@ func TestDefaultChangeRacingAVersionDeleteFailsNotFound(t *testing.T) {
svc := setupTestService(t)
id := createWithUserData(t, svc, "web")
inner := svc.kv
header := kvutil.AccountKey(testAccountID, id)
header := kvstore.AccountKey(testAccountID, id)
raced := false
svc.kv = &faultyKV{KeyValue: inner, beforeUpdate: func(key string) {
if key != header || raced {
@@ -460,7 +460,7 @@ func TestFailedVersionDeleteCommitRestoresTheBodies(t *testing.T) {
svc := setupTestService(t)
id := createWithUserData(t, svc, "web")
before := accountKeys(t, svc.kv, testAccountID)
header := kvutil.AccountKey(testAccountID, id)
header := kvstore.AccountKey(testAccountID, id)
svc.kv = &faultyKV{KeyValue: svc.kv, failUpdate: func(key string) bool { return key == header }}
_, err := deleteVersions(svc, id, "2")
@@ -474,7 +474,7 @@ func TestFailedVersionDeleteCommitRestoresTheBodies(t *testing.T) {
func TestVersionDeleteRacingATemplateDeleteLeavesNothing(t *testing.T) {
svc := setupTestService(t)
id := createWithUserData(t, svc, "web")
header := kvutil.AccountKey(testAccountID, id)
header := kvstore.AccountKey(testAccountID, id)
raced := false
svc.kv = &faultyKV{KeyValue: svc.kv, beforeUpdate: func(key string) {
if key != header || raced {
@@ -531,7 +531,7 @@ func TestDefaultChangeRetriesAfterAConcurrentVersionCreate(t *testing.T) {
svc := setupTestService(t)
id := createWithUserData(t, svc, "web")
inner := svc.kv
header := kvutil.AccountKey(testAccountID, id)
header := kvstore.AccountKey(testAccountID, id)
raced := false
svc.kv = &faultyKV{KeyValue: inner, beforeUpdate: func(key string) {
if key != header || raced {
@@ -8,7 +8,7 @@ import (
"strings"
"github.com/aws/aws-sdk-go/aws"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/nats-io/nats.go/jetstream"
)
@@ -27,7 +27,7 @@ type TemplateReferences struct {
// header or body it cannot read or decode, is an error, because the unread
// record may name the resource. Headerless bodies resolve from no launch.
func (s *LaunchTemplateServiceImpl) AccountTemplateReferences(ctx context.Context, accountID string) (*TemplateReferences, error) {
keys, err := kvutil.Keys(ctx, s.kv)
keys, err := kvstore.Keys(ctx, s.kv)
if err != nil && !errors.Is(err, jetstream.ErrNoKeysFound) {
return nil, fmt.Errorf("list launch-template keys: %w", err)
}
@@ -10,7 +10,7 @@ import (
"github.com/aws/aws-sdk-go/service/ec2"
ec2eip "github.com/mulgadc/spinifex/spinifex/domains/ec2/eip"
"github.com/mulgadc/spinifex/spinifex/foundation/aws/errors"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -42,7 +42,7 @@ func TestRLC3_NatGatewayDeletesWhileRouted(t *testing.T) {
natgwID := createTestNatGateway(t, svc)
testutil.SeedKV(t, js, kvBucketRouteTables, map[string][]byte{
kvutil.AccountKey(testAccountID, "rtb-routed"): fmt.Appendf(nil,
kvstore.AccountKey(testAccountID, "rtb-routed"): fmt.Appendf(nil,
`{"route_table_id":"rtb-routed","vpc_id":"vpc-test1","routes":[{"destination_cidr_block":"0.0.0.0/0","nat_gateway_id":%q}]}`, natgwID),
})
@@ -93,7 +93,7 @@ func createTestNatGateway(t *testing.T, svc *NatGatewayServiceImpl) string {
func readEIP(t *testing.T, svc *NatGatewayServiceImpl, allocID string) ec2eip.EIPRecord {
t.Helper()
entry, err := svc.eipKV.Get(t.Context(), kvutil.AccountKey(testAccountID, allocID))
entry, err := svc.eipKV.Get(t.Context(), kvstore.AccountKey(testAccountID, allocID))
require.NoError(t, err)
var eip ec2eip.EIPRecord
require.NoError(t, json.Unmarshal(entry.Value(), &eip))
+17 -17
View File
@@ -20,7 +20,7 @@ import (
awsfilters "github.com/mulgadc/spinifex/spinifex/foundation/aws/filters"
awsidentifiers "github.com/mulgadc/spinifex/spinifex/foundation/aws/identifiers"
awstags "github.com/mulgadc/spinifex/spinifex/foundation/aws/tags"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/mulgadc/spinifex/spinifex/foundation/state/migrate"
"github.com/nats-io/nats.go"
"github.com/nats-io/nats.go/jetstream"
@@ -45,7 +45,7 @@ func NewNatGatewayServiceImplWithNATS(ctx context.Context, natsConn *nats.Conn)
return nil, fmt.Errorf("failed to get JetStream context: %w", err)
}
natgwKV, err := kvutil.GetOrCreateBucket(ctx, js, KVBucketNatGateways, 10)
natgwKV, err := kvstore.GetOrCreateBucket(ctx, js, KVBucketNatGateways, 10)
if err != nil {
return nil, fmt.Errorf("failed to create KV bucket %s: %w", KVBucketNatGateways, err)
}
@@ -61,17 +61,17 @@ func NewNatGatewayServiceImplWithNATS(ctx context.Context, natsConn *nats.Conn)
return nil, fmt.Errorf("migrate %s: %w", KVBucketDeletedNatGateways, err)
}
eipKV, err := kvutil.GetOrCreateBucket(ctx, js, ec2eip.KVBucketEIPs, 10)
eipKV, err := kvstore.GetOrCreateBucket(ctx, js, ec2eip.KVBucketEIPs, 10)
if err != nil {
return nil, fmt.Errorf("failed to get EIP KV bucket: %w", err)
}
subnetKV, err := kvutil.GetOrCreateBucket(ctx, js, ec2vpc.KVBucketSubnets, 10)
subnetKV, err := kvstore.GetOrCreateBucket(ctx, js, ec2vpc.KVBucketSubnets, 10)
if err != nil {
return nil, fmt.Errorf("failed to get subnet KV bucket: %w", err)
}
vpcKV, err := kvutil.GetOrCreateBucket(ctx, js, ec2vpc.KVBucketVPCs, 10)
vpcKV, err := kvstore.GetOrCreateBucket(ctx, js, ec2vpc.KVBucketVPCs, 10)
if err != nil {
return nil, fmt.Errorf("failed to get VPC KV bucket: %w", err)
}
@@ -92,7 +92,7 @@ func NewNatGatewayServiceImplWithNATS(ctx context.Context, natsConn *nats.Conn)
// for as long as a client may still ask about it.
func getOrCreateDeletedBucket(ctx context.Context, js jetstream.KeyValueManager) (jetstream.KeyValue, error) {
// Terraform polls DescribeNatGateways after delete and expects state=deleted.
return kvutil.GetOrCreateBucketWithOptions(ctx, js, kvutil.BucketOptions{
return kvstore.GetOrCreateBucketWithOptions(ctx, js, kvstore.BucketOptions{
Name: KVBucketDeletedNatGateways,
Description: "Deleted NAT Gateways (auto-expire after 1 hour)",
History: 1,
@@ -113,7 +113,7 @@ func (s *NatGatewayServiceImpl) CreateNatGateway(ctx context.Context, input *ec2
allocID := *input.AllocationId
// Validate subnet exists and get its VPC
subnetEntry, err := s.subnetKV.Get(ctx, kvutil.AccountKey(accountID, subnetID))
subnetEntry, err := s.subnetKV.Get(ctx, kvstore.AccountKey(accountID, subnetID))
if err != nil {
return nil, errors.New(awserrors.ErrorInvalidSubnetIDNotFound)
}
@@ -123,7 +123,7 @@ func (s *NatGatewayServiceImpl) CreateNatGateway(ctx context.Context, input *ec2
}
// Validate EIP exists and is not already associated
eipEntry, err := s.eipKV.Get(ctx, kvutil.AccountKey(accountID, allocID))
eipEntry, err := s.eipKV.Get(ctx, kvstore.AccountKey(accountID, allocID))
if err != nil {
return nil, errors.New(awserrors.ErrorInvalidAllocationIDNotFound)
}
@@ -152,7 +152,7 @@ func (s *NatGatewayServiceImpl) CreateNatGateway(ctx context.Context, input *ec2
if err != nil {
return nil, errors.New(awserrors.ErrorServerInternal)
}
if _, err := s.natgwKV.Put(ctx, kvutil.AccountKey(accountID, natgwID), data); err != nil {
if _, err := s.natgwKV.Put(ctx, kvstore.AccountKey(accountID, natgwID), data); err != nil {
slog.ErrorContext(ctx, "Failed to store NAT Gateway", "natGatewayId", natgwID, "err", err)
return nil, errors.New(awserrors.ErrorServerInternal)
}
@@ -165,7 +165,7 @@ func (s *NatGatewayServiceImpl) CreateNatGateway(ctx context.Context, input *ec2
eipRecord.VpcId = subnetRecord.VpcId
eipRecord.State = "associated"
if eipData, err := json.Marshal(eipRecord); err == nil {
if _, err := s.eipKV.Update(ctx, kvutil.AccountKey(accountID, allocID), eipData, eipEntry.Revision()); err != nil {
if _, err := s.eipKV.Update(ctx, kvstore.AccountKey(accountID, allocID), eipData, eipEntry.Revision()); err != nil {
slog.WarnContext(ctx, "CreateNatGateway: failed to mark EIP associated", "natGatewayId", natgwID, "allocationId", allocID, "err", err)
}
}
@@ -185,7 +185,7 @@ func (s *NatGatewayServiceImpl) DeleteNatGateway(ctx context.Context, input *ec2
}
natgwID := *input.NatGatewayId
key := kvutil.AccountKey(accountID, natgwID)
key := kvstore.AccountKey(accountID, natgwID)
entry, err := s.natgwKV.Get(ctx, key)
if err != nil {
@@ -255,7 +255,7 @@ func (s *NatGatewayServiceImpl) publishDeleteEventsForNatGateway(ctx context.Con
slog.WarnContext(ctx, "DeleteNatGateway: JetStream setup failed, SNAT teardown deferred to route delete", "natGatewayId", record.NatGatewayId, "err", err)
return
}
rtbKV, err := kvutil.GetOrCreateBucket(ctx, js, kvBucketRouteTables, 10)
rtbKV, err := kvstore.GetOrCreateBucket(ctx, js, kvBucketRouteTables, 10)
if err != nil {
slog.WarnContext(ctx, "DeleteNatGateway: route-table scan failed, SNAT teardown deferred to route delete", "natGatewayId", record.NatGatewayId, "err", err)
return
@@ -266,7 +266,7 @@ func (s *NatGatewayServiceImpl) publishDeleteEventsForNatGateway(ctx context.Con
}
prefix := accountID + "."
for _, key := range keys {
if key == kvutil.VersionKey || !strings.HasPrefix(key, prefix) {
if key == kvstore.VersionKey || !strings.HasPrefix(key, prefix) {
continue
}
entry, err := rtbKV.Get(ctx, key)
@@ -302,7 +302,7 @@ func (s *NatGatewayServiceImpl) publishDeleteEventsForNatGateway(ctx context.Con
if assoc.SubnetId == "" {
continue
}
subnetEntry, err := s.subnetKV.Get(ctx, kvutil.AccountKey(accountID, assoc.SubnetId))
subnetEntry, err := s.subnetKV.Get(ctx, kvstore.AccountKey(accountID, assoc.SubnetId))
if err != nil {
continue
}
@@ -322,7 +322,7 @@ func (s *NatGatewayServiceImpl) disassociateEIP(ctx context.Context, record *Nat
if record.AllocationId == "" {
return
}
key := kvutil.AccountKey(accountID, record.AllocationId)
key := kvstore.AccountKey(accountID, record.AllocationId)
entry, err := s.eipKV.Get(ctx, key)
if err != nil {
if !errors.Is(err, jetstream.ErrKeyNotFound) {
@@ -380,7 +380,7 @@ func (s *NatGatewayServiceImpl) DescribeNatGateways(ctx context.Context, input *
foundIDs := make(map[string]bool)
for _, key := range keys {
if key == kvutil.VersionKey || !strings.HasPrefix(key, prefix) {
if key == kvstore.VersionKey || !strings.HasPrefix(key, prefix) {
continue
}
@@ -415,7 +415,7 @@ func (s *NatGatewayServiceImpl) DescribeNatGateways(ctx context.Context, input *
if foundIDs[id] {
continue
}
key := kvutil.AccountKey(accountID, id)
key := kvstore.AccountKey(accountID, id)
entry, err := s.deletedNatgwKV.Get(ctx, key)
if err != nil {
if errors.Is(err, jetstream.ErrKeyNotFound) {
@@ -11,7 +11,7 @@ import (
ec2vpc "github.com/mulgadc/spinifex/spinifex/domains/ec2/vpc"
"github.com/mulgadc/spinifex/spinifex/foundation/aws/errors"
awsfilters "github.com/mulgadc/spinifex/spinifex/foundation/aws/filters"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/nats-io/nats.go/jetstream"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -31,18 +31,18 @@ func setupTestServiceJS(t *testing.T) (*NatGatewayServiceImpl, jetstream.JetStre
// Seed VPC KV
testutil.SeedKV(t, js, ec2vpc.KVBucketVPCs, map[string][]byte{
kvutil.AccountKey(testAccountID, "vpc-test1"): []byte(`{"vpc_id":"vpc-test1","cidr_block":"10.0.0.0/16","state":"available"}`),
kvstore.AccountKey(testAccountID, "vpc-test1"): []byte(`{"vpc_id":"vpc-test1","cidr_block":"10.0.0.0/16","state":"available"}`),
})
// Seed subnet KV (public subnet)
testutil.SeedKV(t, js, ec2vpc.KVBucketSubnets, map[string][]byte{
kvutil.AccountKey(testAccountID, "subnet-pub1"): []byte(`{"subnet_id":"subnet-pub1","vpc_id":"vpc-test1","cidr_block":"10.0.1.0/24","state":"available","map_public_ip_on_launch":true}`),
kvstore.AccountKey(testAccountID, "subnet-pub1"): []byte(`{"subnet_id":"subnet-pub1","vpc_id":"vpc-test1","cidr_block":"10.0.1.0/24","state":"available","map_public_ip_on_launch":true}`),
})
// Seed EIP KV
testutil.SeedKV(t, js, ec2eip.KVBucketEIPs, map[string][]byte{
kvutil.AccountKey(testAccountID, "eipalloc-test1"): []byte(`{"allocation_id":"eipalloc-test1","public_ip":"203.0.113.50","state":"allocated"}`),
kvutil.AccountKey(testAccountID, "eipalloc-used"): []byte(`{"allocation_id":"eipalloc-used","public_ip":"203.0.113.51","state":"associated","association_id":"eipassoc-xxx"}`),
kvstore.AccountKey(testAccountID, "eipalloc-test1"): []byte(`{"allocation_id":"eipalloc-test1","public_ip":"203.0.113.50","state":"allocated"}`),
kvstore.AccountKey(testAccountID, "eipalloc-used"): []byte(`{"allocation_id":"eipalloc-used","public_ip":"203.0.113.51","state":"associated","association_id":"eipassoc-xxx"}`),
})
svc, err := NewNatGatewayServiceImplWithNATS(t.Context(), nc)
@@ -170,7 +170,7 @@ func TestDeleteNatGateway(t *testing.T) {
assert.Equal(t, natgwID, *deleteOut.NatGatewayId)
// Should be gone
_, err = svc.natgwKV.Get(t.Context(), kvutil.AccountKey(testAccountID, natgwID))
_, err = svc.natgwKV.Get(t.Context(), kvstore.AccountKey(testAccountID, natgwID))
assert.ErrorIs(t, err, jetstream.ErrKeyNotFound)
}
@@ -8,7 +8,7 @@ import (
"log/slog"
"strings"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/nats-io/nats.go/jetstream"
)
@@ -39,7 +39,7 @@ type AccountCleanup struct {
// referenced returns a reason is kept; unreadable records are left for the owner scan to report. A
// listing failure is returned and nothing is deleted.
func (s *PlacementGroupServiceImpl) CleanupAccount(ctx context.Context, accountID string, referenced func(groupID, groupName string) string) (*AccountCleanup, error) {
keys, err := kvutil.Keys(ctx, s.kv)
keys, err := kvstore.Keys(ctx, s.kv)
if err != nil && !errors.Is(err, jetstream.ErrNoKeysFound) {
return nil, fmt.Errorf("list placement-group keys: %w", err)
}
@@ -47,7 +47,7 @@ func (s *PlacementGroupServiceImpl) CleanupAccount(ctx context.Context, accountI
prefix := accountID + "."
out := &AccountCleanup{Referenced: map[string]string{}}
for _, key := range keys {
if key == kvutil.VersionKey || !strings.HasPrefix(key, prefix) {
if key == kvstore.VersionKey || !strings.HasPrefix(key, prefix) {
continue
}
entry, err := s.kv.Get(ctx, key)
@@ -8,7 +8,7 @@ import (
"log/slog"
"strings"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/nats-io/nats.go/jetstream"
)
@@ -34,7 +34,7 @@ type AccountScan struct {
// A listing failure is returned as an error; per-record read failures are
// counted. The bucket's schema-version key belongs to no account.
func (s *PlacementGroupServiceImpl) ScanAccount(ctx context.Context, accountID string) (*AccountScan, error) {
keys, err := kvutil.Keys(ctx, s.kv)
keys, err := kvstore.Keys(ctx, s.kv)
if err != nil && !errors.Is(err, jetstream.ErrNoKeysFound) {
return nil, fmt.Errorf("list placement-group keys: %w", err)
}
@@ -42,7 +42,7 @@ func (s *PlacementGroupServiceImpl) ScanAccount(ctx context.Context, accountID s
prefix := accountID + "."
scan := &AccountScan{}
for _, key := range keys {
if key == kvutil.VersionKey || !strings.HasPrefix(key, prefix) {
if key == kvstore.VersionKey || !strings.HasPrefix(key, prefix) {
continue
}
entry, err := s.kv.Get(ctx, key)
@@ -11,7 +11,7 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/mulgadc/spinifex/internal/testkit/readonlystore"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/nats-io/nats.go/jetstream"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -42,14 +42,14 @@ func TestScanAccountClassifiesPlacementGroupRecords(t *testing.T) {
GroupName: "held", NodeInstances: map[string][]string{"node-2": {"i-0bbb", "i-0aaa"}, "node-3": {"i-0aaa"}},
}, testAccountID)
require.NoError(t, err)
_, err = svc.kv.Put(t.Context(), kvutil.AccountKey(testAccountID, "undecodable"), []byte("{not json"))
_, err = svc.kv.Put(t.Context(), kvstore.AccountKey(testAccountID, "undecodable"), []byte("{not json"))
require.NoError(t, err)
_, err = svc.kv.Put(t.Context(), kvutil.AccountKey(testAccountID, "anonymous"), []byte(`{"group_name":"anonymous"}`))
_, err = svc.kv.Put(t.Context(), kvstore.AccountKey(testAccountID, "anonymous"), []byte(`{"group_name":"anonymous"}`))
require.NoError(t, err)
createTestGroup(t, svc, "unreadable", ec2.PlacementStrategySpread)
require.Contains(t, bucketKeys(t, svc.kv), kvutil.VersionKey)
require.Contains(t, bucketKeys(t, svc.kv), kvstore.VersionKey)
svc.kv = &hookedKV{KeyValue: readonlystore.NewKV(t, svc.kv), failGet: kvutil.AccountKey(testAccountID, "unreadable")}
svc.kv = &hookedKV{KeyValue: readonlystore.NewKV(t, svc.kv), failGet: kvstore.AccountKey(testAccountID, "unreadable")}
scan, err := svc.ScanAccount(context.Background(), testAccountID)
require.NoError(t, err)
@@ -14,7 +14,7 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/mulgadc/spinifex/spinifex/foundation/aws/errors"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/nats-io/nats.go/jetstream"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -84,7 +84,7 @@ func TestCurrentBehaviour_AccountTermination_PlacementGroupStorageLayout(t *test
}, testAccountID)
require.NoError(t, err)
assert.Equal(t, []string{testAccountID + ".spread-a", kvutil.VersionKey}, bucketKeys(t, svc.kv))
assert.Equal(t, []string{testAccountID + ".spread-a", kvstore.VersionKey}, bucketKeys(t, svc.kv))
entry, err := svc.kv.Get(t.Context(), testAccountID+".spread-a")
require.NoError(t, err)
@@ -100,7 +100,7 @@ func TestCurrentBehaviour_AccountTermination_PlacementGroupStorageLayout(t *test
// a record cannot be removed through DeletePlacementGroup either, and a read
// failure is an internal error, never an unknown group.
func TestCurrentBehaviour_AccountTermination_DescribePlacementGroupsHidesUnreadableRecords(t *testing.T) {
brokenKey := kvutil.AccountKey(testAccountID, "broken")
brokenKey := kvstore.AccountKey(testAccountID, "broken")
tests := []struct {
name string
seed func(t *testing.T, svc *PlacementGroupServiceImpl)
@@ -139,7 +139,7 @@ func TestCurrentBehaviour_AccountTermination_DescribePlacementGroupsHidesUnreada
func TestAccountTermination_DeletePlacementGroupLosesToAConcurrentMember(t *testing.T) {
svc := setupTestService(t)
createTestGroup(t, svc, "cluster-a", ec2.PlacementStrategyCluster)
key := kvutil.AccountKey(testAccountID, "cluster-a")
key := kvstore.AccountKey(testAccountID, "cluster-a")
launcher := &PlacementGroupServiceImpl{kv: svc.kv, region: svc.region}
var reserveErr, finalizeErr error
@@ -222,7 +222,7 @@ func TestAccountCleanup_DeletesEmptyGroupsAndBlocksOnMembers(t *testing.T) {
empty := createTestGroup(t, svc, "empty", ec2.PlacementStrategySpread)
held := createTestGroup(t, svc, "held", ec2.PlacementStrategyCluster)
require.NoError(t, addMember(t, svc, "held", "i-0123456789abcdef0"))
_, err := svc.kv.Put(t.Context(), kvutil.AccountKey(testAccountID, "broken"), []byte("{not json"))
_, err := svc.kv.Put(t.Context(), kvstore.AccountKey(testAccountID, "broken"), []byte("{not json"))
require.NoError(t, err)
other := "999988887777"
_, err = svc.CreatePlacementGroup(context.Background(), &ec2.CreatePlacementGroupInput{
@@ -238,7 +238,7 @@ func TestAccountCleanup_DeletesEmptyGroupsAndBlocksOnMembers(t *testing.T) {
assert.Equal(t, []string{"held"}, describedGroupNames(t, svc, testAccountID))
assert.Equal(t, []string{"theirs"}, describedGroupNames(t, svc, other))
_, err = svc.kv.Get(t.Context(), kvutil.AccountKey(testAccountID, "broken"))
_, err = svc.kv.Get(t.Context(), kvstore.AccountKey(testAccountID, "broken"))
require.NoError(t, err, "an unreadable record is never deleted")
}
@@ -247,7 +247,7 @@ func TestAccountCleanup_DeletesEmptyGroupsAndBlocksOnMembers(t *testing.T) {
func TestAccountCleanup_ConcurrentMemberBlocksTheDelete(t *testing.T) {
svc := setupTestService(t)
group := createTestGroup(t, svc, "cluster-a", ec2.PlacementStrategyCluster)
key := kvutil.AccountKey(testAccountID, "cluster-a")
key := kvstore.AccountKey(testAccountID, "cluster-a")
launcher := &PlacementGroupServiceImpl{kv: svc.kv, region: svc.region}
var memberErr error
// Read 1 is the cleanup's listing read; read 2 is the delete's check.
@@ -17,7 +17,7 @@ import (
awsfilters "github.com/mulgadc/spinifex/spinifex/foundation/aws/filters"
awsidentifiers "github.com/mulgadc/spinifex/spinifex/foundation/aws/identifiers"
awstags "github.com/mulgadc/spinifex/spinifex/foundation/aws/tags"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/mulgadc/spinifex/spinifex/foundation/state/migrate"
"github.com/nats-io/nats.go"
"github.com/nats-io/nats.go/jetstream"
@@ -87,7 +87,7 @@ func NewPlacementGroupServiceImplWithNATS(ctx context.Context, cfg *config.Confi
return nil, fmt.Errorf("failed to get JetStream context: %w", err)
}
kv, err := kvutil.GetOrCreateBucket(ctx, js, KVBucketPlacementGroups, 10)
kv, err := kvstore.GetOrCreateBucket(ctx, js, KVBucketPlacementGroups, 10)
if err != nil {
return nil, fmt.Errorf("failed to create KV bucket %s: %w", KVBucketPlacementGroups, err)
}
@@ -137,7 +137,7 @@ func (s *PlacementGroupServiceImpl) CreatePlacementGroup(ctx context.Context, in
}
groupName := *input.GroupName
key := kvutil.AccountKey(accountID, groupName)
key := kvstore.AccountKey(accountID, groupName)
groupID := awsidentifiers.GenerateResourceID("pg")
record := PlacementGroupRecord{
@@ -175,7 +175,7 @@ func (s *PlacementGroupServiceImpl) DeletePlacementGroup(ctx context.Context, in
}
groupName := *input.GroupName
if _, err := s.deleteGroup(ctx, accountID, kvutil.AccountKey(accountID, groupName)); err != nil {
if _, err := s.deleteGroup(ctx, accountID, kvstore.AccountKey(accountID, groupName)); err != nil {
if errors.Is(err, errPGAbsent) {
// Lower case, unlike DescribePlacementGroups' message for the same code.
return nil, awserrors.Errorf(awserrors.ErrorInvalidPlacementGroupUnknown, "The placement group '%s' is unknown.", groupName)
@@ -194,7 +194,7 @@ func (s *PlacementGroupServiceImpl) DeletePlacementGroup(ctx context.Context, in
// The returned count is the members seen by the last check.
func (s *PlacementGroupServiceImpl) deleteGroup(ctx context.Context, accountID, key string) (int, error) {
members := 0
record, err := kvutil.DeleteIf(ctx, s.kv, key, casConfig(), func(record *PlacementGroupRecord) error {
record, err := kvstore.DeleteIf(ctx, s.kv, key, casConfig(), func(record *PlacementGroupRecord) error {
members = memberCount(record.NodeInstances)
if members > 0 {
return errPGInUse
@@ -238,7 +238,7 @@ func (s *PlacementGroupServiceImpl) DescribePlacementGroups(ctx context.Context,
var groups []*ec2.PlacementGroup
for _, k := range keys {
if k == kvutil.VersionKey {
if k == kvstore.VersionKey {
continue
}
if !strings.HasPrefix(k, prefix) {
@@ -341,7 +341,7 @@ func pgMatchesFilters(record *PlacementGroupRecord, filters map[string][]string)
const maxCASRetries = 5
// errPGAbsent and errPGContended mark the two kvutil outcomes that map to
// errPGAbsent and errPGContended mark the two kvstore outcomes that map to
// specific AWS errors rather than a generic internal failure, and neither
// escapes this file. errPGInUse is the AWS error itself.
var (
@@ -350,8 +350,8 @@ var (
errPGInUse = errors.New(awserrors.ErrorInvalidPlacementGroupInUse)
)
// mapCASError turns a kvutil failure into the AWS error the caller returns.
// A non-kvutil error came from mutate and is already an AWS code.
// mapCASError turns a kvstore failure into the AWS error the caller returns.
// A non-kvstore error came from mutate and is already an AWS code.
func mapCASError(ctx context.Context, op, groupName, accountID string, err error) error {
switch {
case errors.Is(err, errPGAbsent):
@@ -359,16 +359,16 @@ func mapCASError(ctx context.Context, op, groupName, accountID string, err error
case errors.Is(err, errPGContended):
slog.ErrorContext(ctx, "Placement group CAS retries exhausted under contention",
"op", op, "groupName", groupName, "accountID", accountID)
case errors.Is(err, kvutil.ErrRead):
case errors.Is(err, kvstore.ErrRead):
slog.ErrorContext(ctx, "Failed to read placement group from KV",
"op", op, "groupName", groupName, "accountID", accountID, "err", err)
case errors.Is(err, kvutil.ErrDecode):
case errors.Is(err, kvstore.ErrDecode):
slog.ErrorContext(ctx, "Corrupt placement group record in KV",
"op", op, "groupName", groupName, "accountID", accountID, "err", err)
case errors.Is(err, kvutil.ErrEncode):
case errors.Is(err, kvstore.ErrEncode):
slog.ErrorContext(ctx, "Failed to marshal placement group record",
"op", op, "groupName", groupName, "accountID", accountID, "err", err)
case errors.Is(err, kvutil.ErrWrite):
case errors.Is(err, kvstore.ErrWrite):
slog.ErrorContext(ctx, "Failed to write placement group to KV",
"op", op, "groupName", groupName, "accountID", accountID, "err", err)
default:
@@ -378,8 +378,8 @@ func mapCASError(ctx context.Context, op, groupName, accountID string, err error
}
// casConfig is the shared CAS policy for every placement group mutation.
func casConfig() kvutil.CASConfig {
return kvutil.CASConfig{
func casConfig() kvstore.CASConfig {
return kvstore.CASConfig{
Attempts: maxCASRetries,
NotFound: errPGAbsent,
Exhausted: func(string, int) error { return errPGContended },
@@ -394,7 +394,7 @@ func (s *PlacementGroupServiceImpl) ReserveSpreadNodes(ctx context.Context, inpu
}
var selected []string
_, err := kvutil.Update(ctx, s.kv, kvutil.AccountKey(accountID, input.GroupName), casConfig(),
_, err := kvstore.Update(ctx, s.kv, kvstore.AccountKey(accountID, input.GroupName), casConfig(),
func(record *PlacementGroupRecord) (bool, error) {
if record.State != ec2.PlacementGroupStateAvailable {
return false, errors.New(awserrors.ErrorInvalidPlacementGroupUnknown)
@@ -437,7 +437,7 @@ func (s *PlacementGroupServiceImpl) FinalizeSpreadInstances(ctx context.Context,
return nil, errors.New(awserrors.ErrorMissingParameter)
}
_, err := kvutil.Update(ctx, s.kv, kvutil.AccountKey(accountID, input.GroupName), casConfig(),
_, err := kvstore.Update(ctx, s.kv, kvstore.AccountKey(accountID, input.GroupName), casConfig(),
func(record *PlacementGroupRecord) (bool, error) {
maps.Copy(record.NodeInstances, input.NodeInstances)
return true, nil
@@ -462,7 +462,7 @@ func (s *PlacementGroupServiceImpl) ReleaseSpreadNodes(ctx context.Context, inpu
releaseSet[n] = true
}
_, err := kvutil.Update(ctx, s.kv, kvutil.AccountKey(accountID, input.GroupName), casConfig(),
_, err := kvstore.Update(ctx, s.kv, kvstore.AccountKey(accountID, input.GroupName), casConfig(),
func(record *PlacementGroupRecord) (bool, error) {
changed := false
for node := range releaseSet {
@@ -490,7 +490,7 @@ func (s *PlacementGroupServiceImpl) RemoveInstance(ctx context.Context, input *R
return nil, errors.New(awserrors.ErrorMissingParameter)
}
_, err := kvutil.Update(ctx, s.kv, kvutil.AccountKey(accountID, input.GroupName), casConfig(),
_, err := kvstore.Update(ctx, s.kv, kvstore.AccountKey(accountID, input.GroupName), casConfig(),
func(record *PlacementGroupRecord) (bool, error) {
instances, exists := record.NodeInstances[input.NodeName]
if !exists {
@@ -533,7 +533,7 @@ func (s *PlacementGroupServiceImpl) ReserveClusterNode(ctx context.Context, inpu
}
var targetNode string
_, err := kvutil.Update(ctx, s.kv, kvutil.AccountKey(accountID, input.GroupName), casConfig(),
_, err := kvstore.Update(ctx, s.kv, kvstore.AccountKey(accountID, input.GroupName), casConfig(),
func(record *PlacementGroupRecord) (bool, error) {
if record.State != ec2.PlacementGroupStateAvailable {
return false, errors.New(awserrors.ErrorInvalidPlacementGroupUnknown)
@@ -570,7 +570,7 @@ func (s *PlacementGroupServiceImpl) FinalizeClusterInstances(ctx context.Context
return nil, errors.New(awserrors.ErrorMissingParameter)
}
_, err := kvutil.Update(ctx, s.kv, kvutil.AccountKey(accountID, input.GroupName), casConfig(),
_, err := kvstore.Update(ctx, s.kv, kvstore.AccountKey(accountID, input.GroupName), casConfig(),
func(record *PlacementGroupRecord) (bool, error) {
// Append rather than replace: a cluster group can have several launches in flight at once.
for node, ids := range input.NodeInstances {
@@ -10,7 +10,7 @@ import (
"github.com/mulgadc/spinifex/internal/testkit"
"github.com/mulgadc/spinifex/spinifex/bootstrap/config"
"github.com/mulgadc/spinifex/spinifex/foundation/aws/errors"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -39,7 +39,7 @@ func createTestGroup(t *testing.T, svc *PlacementGroupServiceImpl, name, strateg
// readRecord returns the stored record for assertions.
func readRecord(t *testing.T, svc *PlacementGroupServiceImpl, groupName string) *PlacementGroupRecord {
t.Helper()
entry, err := svc.kv.Get(t.Context(), kvutil.AccountKey(testAccountID, groupName))
entry, err := svc.kv.Get(t.Context(), kvstore.AccountKey(testAccountID, groupName))
require.NoError(t, err)
var record PlacementGroupRecord
require.NoError(t, json.Unmarshal(entry.Value(), &record))
@@ -50,7 +50,7 @@ func readRecord(t *testing.T, svc *PlacementGroupServiceImpl, groupName string)
// starting state the public API cannot produce.
func seedRecord(t *testing.T, svc *PlacementGroupServiceImpl, groupName string, mutate func(*PlacementGroupRecord)) {
t.Helper()
_, err := kvutil.Update(t.Context(), svc.kv, kvutil.AccountKey(testAccountID, groupName), kvutil.CASConfig{},
_, err := kvstore.Update(t.Context(), svc.kv, kvstore.AccountKey(testAccountID, groupName), kvstore.CASConfig{},
func(record *PlacementGroupRecord) (bool, error) {
mutate(record)
return true, nil
+29 -29
View File
@@ -25,7 +25,7 @@ import (
awsidentifiers "github.com/mulgadc/spinifex/spinifex/foundation/aws/identifiers"
"github.com/mulgadc/spinifex/spinifex/foundation/aws/paging"
awstags "github.com/mulgadc/spinifex/spinifex/foundation/aws/tags"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/mulgadc/spinifex/spinifex/foundation/state/migrate"
"github.com/nats-io/nats.go"
"github.com/nats-io/nats.go/jetstream"
@@ -52,7 +52,7 @@ func NewRouteTableServiceImplWithNATS(ctx context.Context, cfg *config.Config, n
return nil, fmt.Errorf("failed to get JetStream context: %w", err)
}
rtbKV, err := kvutil.GetOrCreateBucket(ctx, js, KVBucketRouteTables, 10)
rtbKV, err := kvstore.GetOrCreateBucket(ctx, js, KVBucketRouteTables, 10)
if err != nil {
return nil, fmt.Errorf("failed to create KV bucket %s: %w", KVBucketRouteTables, err)
}
@@ -60,22 +60,22 @@ func NewRouteTableServiceImplWithNATS(ctx context.Context, cfg *config.Config, n
return nil, fmt.Errorf("migrate %s: %w", KVBucketRouteTables, err)
}
vpcKV, err := kvutil.GetOrCreateBucket(ctx, js, ec2vpc.KVBucketVPCs, 10)
vpcKV, err := kvstore.GetOrCreateBucket(ctx, js, ec2vpc.KVBucketVPCs, 10)
if err != nil {
return nil, fmt.Errorf("failed to get VPC KV bucket: %w", err)
}
igwKV, err := kvutil.GetOrCreateBucket(ctx, js, ec2igw.KVBucketIGW, 10)
igwKV, err := kvstore.GetOrCreateBucket(ctx, js, ec2igw.KVBucketIGW, 10)
if err != nil {
return nil, fmt.Errorf("failed to get IGW KV bucket: %w", err)
}
subnetKV, err := kvutil.GetOrCreateBucket(ctx, js, ec2vpc.KVBucketSubnets, 10)
subnetKV, err := kvstore.GetOrCreateBucket(ctx, js, ec2vpc.KVBucketSubnets, 10)
if err != nil {
return nil, fmt.Errorf("failed to get subnet KV bucket: %w", err)
}
natgwKV, err := kvutil.GetOrCreateBucket(ctx, js, ec2natgw.KVBucketNatGateways, 10)
natgwKV, err := kvstore.GetOrCreateBucket(ctx, js, ec2natgw.KVBucketNatGateways, 10)
if err != nil {
return nil, fmt.Errorf("failed to get NAT Gateway KV bucket: %w", err)
}
@@ -95,7 +95,7 @@ func NewRouteTableServiceImplWithNATS(ctx context.Context, cfg *config.Config, n
// getRouteTable retrieves a route table record from KV.
func (s *RouteTableServiceImpl) getRouteTable(ctx context.Context, accountID, rtbID string) (*RouteTableRecord, error) {
entry, err := s.rtbKV.Get(ctx, kvutil.AccountKey(accountID, rtbID))
entry, err := s.rtbKV.Get(ctx, kvstore.AccountKey(accountID, rtbID))
if err != nil {
if errors.Is(err, jetstream.ErrKeyNotFound) {
return nil, rtbNotFoundError(rtbID)
@@ -118,7 +118,7 @@ func (s *RouteTableServiceImpl) putRouteTable(ctx context.Context, accountID str
slog.ErrorContext(ctx, "Failed to marshal route table record", "routeTableId", record.RouteTableId, "err", err)
return errors.New(awserrors.ErrorServerInternal)
}
if _, err := s.rtbKV.Put(ctx, kvutil.AccountKey(accountID, record.RouteTableId), data); err != nil {
if _, err := s.rtbKV.Put(ctx, kvstore.AccountKey(accountID, record.RouteTableId), data); err != nil {
slog.ErrorContext(ctx, "Failed to write route table to KV", "routeTableId", record.RouteTableId, "err", err)
return errors.New(awserrors.ErrorServerInternal)
}
@@ -130,7 +130,7 @@ func (s *RouteTableServiceImpl) putRouteTable(ctx context.Context, accountID str
// parallel AssociateRouteTable calls Terraform fans out per route table.
const rtbCASMaxRetries = 16
// errRTBAbsent and errRTBContended mark the two kvutil outcomes that map to
// errRTBAbsent and errRTBContended mark the two kvstore outcomes that map to
// specific AWS errors rather than a generic internal failure. Neither
// escapes this file.
var (
@@ -143,7 +143,7 @@ var (
// several subnets with one route table at once. mutate reports whether
// it changed the record; a false return commits nothing.
func (s *RouteTableServiceImpl) mutateRouteTableCAS(ctx context.Context, accountID, rtbID string, mutate func(*RouteTableRecord) (bool, error)) error {
_, err := kvutil.Update(ctx, s.rtbKV, kvutil.AccountKey(accountID, rtbID), kvutil.CASConfig{
_, err := kvstore.Update(ctx, s.rtbKV, kvstore.AccountKey(accountID, rtbID), kvstore.CASConfig{
Attempts: rtbCASMaxRetries,
NotFound: errRTBAbsent,
Exhausted: func(string, int) error { return errRTBContended },
@@ -157,13 +157,13 @@ func (s *RouteTableServiceImpl) mutateRouteTableCAS(ctx context.Context, account
// Contended rather than broken.
slog.ErrorContext(ctx, "Route table CAS retries exhausted under contention",
"routeTableId", rtbID, "attempts", rtbCASMaxRetries)
case errors.Is(err, kvutil.ErrRead):
case errors.Is(err, kvstore.ErrRead):
slog.ErrorContext(ctx, "Failed to read route table from KV", "routeTableId", rtbID, "err", err)
case errors.Is(err, kvutil.ErrDecode):
case errors.Is(err, kvstore.ErrDecode):
slog.ErrorContext(ctx, "Corrupt route table record in KV", "routeTableId", rtbID, "err", err)
case errors.Is(err, kvutil.ErrEncode):
case errors.Is(err, kvstore.ErrEncode):
slog.ErrorContext(ctx, "Failed to marshal route table record", "routeTableId", rtbID, "err", err)
case errors.Is(err, kvutil.ErrWrite):
case errors.Is(err, kvstore.ErrWrite):
slog.ErrorContext(ctx, "Failed to write route table to KV", "routeTableId", rtbID, "err", err)
default:
return err
@@ -173,7 +173,7 @@ func (s *RouteTableServiceImpl) mutateRouteTableCAS(ctx context.Context, account
// getVPCCidr looks up a VPC's CIDR block from the VPC KV bucket.
func (s *RouteTableServiceImpl) getVPCCidr(ctx context.Context, accountID, vpcID string) (string, error) {
entry, err := s.vpcKV.Get(ctx, kvutil.AccountKey(accountID, vpcID))
entry, err := s.vpcKV.Get(ctx, kvstore.AccountKey(accountID, vpcID))
if err != nil {
return "", awserrors.IDNotFound(awserrors.ErrorInvalidVpcIDNotFound, "vpc", vpcID)
}
@@ -229,7 +229,7 @@ func (s *RouteTableServiceImpl) subnetsImplicitlyOnMainRT(ctx context.Context, a
}
var implicit []string
for _, key := range keys {
if key == kvutil.VersionKey || !strings.HasPrefix(key, prefix) {
if key == kvstore.VersionKey || !strings.HasPrefix(key, prefix) {
continue
}
entry, err := s.subnetKV.Get(ctx, key)
@@ -387,7 +387,7 @@ func (s *RouteTableServiceImpl) allSubnetsForVPC(ctx context.Context, accountID,
}
var subnets []string
for _, key := range keys {
if key == kvutil.VersionKey || !strings.HasPrefix(key, prefix) {
if key == kvstore.VersionKey || !strings.HasPrefix(key, prefix) {
continue
}
entry, err := s.subnetKV.Get(ctx, key)
@@ -455,7 +455,7 @@ func (s *RouteTableServiceImpl) allRouteTablesForVPC(ctx context.Context, accoun
var results []RouteTableRecord
for _, key := range keys {
if key == kvutil.VersionKey || !strings.HasPrefix(key, prefix) {
if key == kvstore.VersionKey || !strings.HasPrefix(key, prefix) {
continue
}
entry, err := s.rtbKV.Get(ctx, key)
@@ -577,7 +577,7 @@ func (s *RouteTableServiceImpl) DeleteRouteTable(ctx context.Context, input *ec2
}
}
if err := s.rtbKV.Delete(ctx, kvutil.AccountKey(accountID, rtbID)); err != nil {
if err := s.rtbKV.Delete(ctx, kvstore.AccountKey(accountID, rtbID)); err != nil {
return nil, errors.New(awserrors.ErrorServerInternal)
}
@@ -633,7 +633,7 @@ func (s *RouteTableServiceImpl) DescribeRouteTables(ctx context.Context, input *
foundIDs := make(map[string]bool)
for _, key := range keys {
if key == kvutil.VersionKey || !strings.HasPrefix(key, prefix) {
if key == kvstore.VersionKey || !strings.HasPrefix(key, prefix) {
continue
}
@@ -740,7 +740,7 @@ func (s *RouteTableServiceImpl) CreateRoute(ctx context.Context, input *ec2.Crea
case input.GatewayId != nil && *input.GatewayId != "":
igwID := *input.GatewayId
// Verify IGW exists and is attached to the same VPC
igwEntry, err := s.igwKV.Get(ctx, kvutil.AccountKey(accountID, igwID))
igwEntry, err := s.igwKV.Get(ctx, kvstore.AccountKey(accountID, igwID))
if err != nil {
return nil, gatewayNotFoundError(igwID)
}
@@ -770,7 +770,7 @@ func (s *RouteTableServiceImpl) CreateRoute(ctx context.Context, input *ec2.Crea
case input.NatGatewayId != nil && *input.NatGatewayId != "":
natgwID := *input.NatGatewayId
// Verify NAT GW exists and belongs to the same VPC
natgwEntry, err := s.natgwKV.Get(ctx, kvutil.AccountKey(accountID, natgwID))
natgwEntry, err := s.natgwKV.Get(ctx, kvstore.AccountKey(accountID, natgwID))
if err != nil {
return nil, errors.New(awserrors.ErrorInvalidNatGatewayIDNotFound)
}
@@ -924,7 +924,7 @@ func (s *RouteTableServiceImpl) ReplaceRoute(ctx context.Context, input *ec2.Rep
igwID := *input.GatewayId
// AWS reports a missing gateway ahead of a missing route.
igwEntry, err := s.igwKV.Get(ctx, kvutil.AccountKey(accountID, igwID))
igwEntry, err := s.igwKV.Get(ctx, kvstore.AccountKey(accountID, igwID))
if err != nil {
return nil, gatewayNotFoundError(igwID)
}
@@ -983,7 +983,7 @@ func (s *RouteTableServiceImpl) AssociateRouteTable(ctx context.Context, input *
}
// Verify subnet exists and belongs to the same VPC
subnetEntry, err := s.subnetKV.Get(ctx, kvutil.AccountKey(accountID, subnetID))
subnetEntry, err := s.subnetKV.Get(ctx, kvstore.AccountKey(accountID, subnetID))
if err != nil {
return nil, errors.New(awserrors.ErrorInvalidSubnetIDNotFound)
}
@@ -1077,7 +1077,7 @@ func (s *RouteTableServiceImpl) DisassociateRouteTable(ctx context.Context, inpu
}
for _, key := range keys {
if key == kvutil.VersionKey || !strings.HasPrefix(key, prefix) {
if key == kvstore.VersionKey || !strings.HasPrefix(key, prefix) {
continue
}
@@ -1168,7 +1168,7 @@ func (s *RouteTableServiceImpl) ReplaceRouteTableAssociation(ctx context.Context
}
for _, key := range keys {
if key == kvutil.VersionKey || !strings.HasPrefix(key, prefix) {
if key == kvstore.VersionKey || !strings.HasPrefix(key, prefix) {
continue
}
@@ -1369,7 +1369,7 @@ func (s *RouteTableServiceImpl) publishNatGatewayDeleteEvents(ctx context.Contex
if s.natsConn == nil {
return
}
natgwEntry, err := s.natgwKV.Get(ctx, kvutil.AccountKey(accountID, natgwID))
natgwEntry, err := s.natgwKV.Get(ctx, kvstore.AccountKey(accountID, natgwID))
if err != nil {
slog.WarnContext(ctx, "NAT GW event: natgw lookup failed", "topic", networkv1.NATGatewayDeleteSubject, "natGatewayId", natgwID, "err", err)
return
@@ -1417,7 +1417,7 @@ func (s *RouteTableServiceImpl) publishNatGatewayEventsForAssociation(ctx contex
if r.NatGatewayId == "" {
continue
}
natgwEntry, err := s.natgwKV.Get(ctx, kvutil.AccountKey(accountID, r.NatGatewayId))
natgwEntry, err := s.natgwKV.Get(ctx, kvstore.AccountKey(accountID, r.NatGatewayId))
if err != nil {
slog.WarnContext(ctx, "NAT GW event: natgw lookup failed", "topic", topic, "natGatewayId", r.NatGatewayId, "err", err)
continue
@@ -1442,7 +1442,7 @@ func (s *RouteTableServiceImpl) publishNatGatewayEventForSubnet(ctx context.Cont
if s.natsConn == nil {
return
}
subnetEntry, err := s.subnetKV.Get(ctx, kvutil.AccountKey(accountID, subnetID))
subnetEntry, err := s.subnetKV.Get(ctx, kvstore.AccountKey(accountID, subnetID))
if err != nil {
slog.WarnContext(ctx, "NAT GW event: subnet lookup failed", "topic", topic, "subnetId", subnetID, "err", err)
return
@@ -13,7 +13,7 @@ import (
ec2natgw "github.com/mulgadc/spinifex/spinifex/domains/ec2/natgw"
ec2vpc "github.com/mulgadc/spinifex/spinifex/domains/ec2/vpc"
"github.com/mulgadc/spinifex/spinifex/foundation/aws/errors"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/nats-io/nats.go"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -28,23 +28,23 @@ func setupTestService(t *testing.T) *RouteTableServiceImpl {
// Seed VPC KV
testutil.SeedKV(t, js, ec2vpc.KVBucketVPCs, map[string][]byte{
kvutil.AccountKey(testAccountID, "vpc-test1"): []byte(`{"vpc_id":"vpc-test1","cidr_block":"10.0.0.0/16","state":"available"}`),
kvstore.AccountKey(testAccountID, "vpc-test1"): []byte(`{"vpc_id":"vpc-test1","cidr_block":"10.0.0.0/16","state":"available"}`),
})
// Seed IGW KV (attached to vpc-test1)
testutil.SeedKV(t, js, ec2igw.KVBucketIGW, map[string][]byte{
kvutil.AccountKey(testAccountID, "igw-test1"): []byte(`{"internet_gateway_id":"igw-test1","vpc_id":"vpc-test1","state":"available"}`),
kvstore.AccountKey(testAccountID, "igw-test1"): []byte(`{"internet_gateway_id":"igw-test1","vpc_id":"vpc-test1","state":"available"}`),
})
// Seed subnet KV
testutil.SeedKV(t, js, ec2vpc.KVBucketSubnets, map[string][]byte{
kvutil.AccountKey(testAccountID, "subnet-test1"): []byte(`{"subnet_id":"subnet-test1","vpc_id":"vpc-test1","cidr_block":"10.0.1.0/24","state":"available"}`),
kvutil.AccountKey(testAccountID, "subnet-priv1"): []byte(`{"subnet_id":"subnet-priv1","vpc_id":"vpc-test1","cidr_block":"10.0.11.0/24","state":"available"}`),
kvstore.AccountKey(testAccountID, "subnet-test1"): []byte(`{"subnet_id":"subnet-test1","vpc_id":"vpc-test1","cidr_block":"10.0.1.0/24","state":"available"}`),
kvstore.AccountKey(testAccountID, "subnet-priv1"): []byte(`{"subnet_id":"subnet-priv1","vpc_id":"vpc-test1","cidr_block":"10.0.11.0/24","state":"available"}`),
})
// Seed NAT Gateway KV
testutil.SeedKV(t, js, ec2natgw.KVBucketNatGateways, map[string][]byte{
kvutil.AccountKey(testAccountID, "nat-test1"): []byte(`{"nat_gateway_id":"nat-test1","vpc_id":"vpc-test1","subnet_id":"subnet-test1","public_ip":"192.168.1.100","state":"available"}`),
kvstore.AccountKey(testAccountID, "nat-test1"): []byte(`{"nat_gateway_id":"nat-test1","vpc_id":"vpc-test1","subnet_id":"subnet-test1","public_ip":"192.168.1.100","state":"available"}`),
})
svc, err := NewRouteTableServiceImplWithNATS(t.Context(), nil, nc)
@@ -10,7 +10,7 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/nats-io/nats.go/jetstream"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
)
// AccountSpotRequest is one persisted request in an account scan.
@@ -54,7 +54,7 @@ func scanBucket(ctx context.Context, kv jetstream.KeyValue, accountID string) ([
prefix := accountID + "."
var out []AccountSpotRequest
for _, key := range keys {
if key == kvutil.VersionKey || !strings.HasPrefix(key, prefix) {
if key == kvstore.VersionKey || !strings.HasPrefix(key, prefix) {
continue
}
entry, err := kv.Get(ctx, key)
@@ -11,7 +11,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
)
func TestScanAccountListsActiveAndTerminalForOneAccount(t *testing.T) {
@@ -35,7 +35,7 @@ func TestScanAccountListsActiveAndTerminalForOneAccount(t *testing.T) {
func TestScanAccountFailsOnAnUndecodableRecord(t *testing.T) {
svc := setupTestService(t)
putSIRs(t, svc, makeSIR("sir-aaa", "i-aaa"))
_, err := svc.activeKV.Put(t.Context(), kvutil.AccountKey(testAccountID, "sir-bad"), []byte("{not json"))
_, err := svc.activeKV.Put(t.Context(), kvstore.AccountKey(testAccountID, "sir-bad"), []byte("{not json"))
require.NoError(t, err)
scan, err := svc.ScanAccount(t.Context(), testAccountID)
@@ -45,7 +45,7 @@ func TestScanAccountFailsOnAnUndecodableRecord(t *testing.T) {
func TestScanAccountFailsOnARecordWithoutARequest(t *testing.T) {
svc := setupTestService(t)
_, err := svc.terminalKV.Put(t.Context(), kvutil.AccountKey(testAccountID, "sir-empty"), []byte(`{"account_id":"x"}`))
_, err := svc.terminalKV.Put(t.Context(), kvstore.AccountKey(testAccountID, "sir-empty"), []byte(`{"account_id":"x"}`))
require.NoError(t, err)
_, err = svc.ScanAccount(t.Context(), testAccountID)
@@ -76,7 +76,7 @@ func (k failingGetKV) Get(ctx context.Context, key string) (jetstream.KeyValueEn
func TestScanAccountFailsWhenARecordCannotBeRead(t *testing.T) {
svc := setupTestService(t)
putSIRs(t, svc, makeSIR("sir-aaa", "i-aaa"), makeSIR("sir-bbb", "i-bbb"))
svc.activeKV = failingGetKV{KeyValue: svc.activeKV, key: kvutil.AccountKey(testAccountID, "sir-bbb")}
svc.activeKV = failingGetKV{KeyValue: svc.activeKV, key: kvstore.AccountKey(testAccountID, "sir-bbb")}
scan, err := svc.ScanAccount(t.Context(), testAccountID)
require.Error(t, err)
@@ -8,7 +8,7 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/nats-io/nats.go/jetstream"
)
@@ -24,14 +24,14 @@ type LaunchReferences struct {
// account. It is complete or an error: an unlistable bucket or an unreadable
// request is an error, because the unread request may name the resource.
func (s *SpotInstanceServiceImpl) AccountLaunchReferences(ctx context.Context, accountID string) (*LaunchReferences, error) {
keys, err := kvutil.Keys(ctx, s.activeKV)
keys, err := kvstore.Keys(ctx, s.activeKV)
if err != nil && !errors.Is(err, jetstream.ErrNoKeysFound) {
return nil, fmt.Errorf("list spot requests: %w", err)
}
prefix := accountID + "."
out := &LaunchReferences{}
for _, key := range keys {
if key == kvutil.VersionKey || !strings.HasPrefix(key, prefix) {
if key == kvstore.VersionKey || !strings.HasPrefix(key, prefix) {
continue
}
record, err := s.readRecord(ctx, s.activeKV, key)
@@ -5,7 +5,7 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -41,7 +41,7 @@ func TestAccountLaunchReferences_ReadsOpenAndActiveRequests(t *testing.T) {
func TestAccountLaunchReferences_UnreadableRequestFailsTheCheck(t *testing.T) {
svc := setupTestService(t)
putSIRs(t, svc, makeSIR("sir-active01", "i-0000000000000001"))
_, err := svc.activeKV.Put(t.Context(), kvutil.AccountKey(testAccountID, "sir-broken01"), []byte("{not json"))
_, err := svc.activeKV.Put(t.Context(), kvstore.AccountKey(testAccountID, "sir-broken01"), []byte("{not json"))
require.NoError(t, err)
_, err = svc.AccountLaunchReferences(t.Context(), testAccountID)
@@ -15,7 +15,7 @@ import (
"github.com/mulgadc/spinifex/spinifex/bootstrap/config"
"github.com/mulgadc/spinifex/spinifex/foundation/aws/errors"
awsfilters "github.com/mulgadc/spinifex/spinifex/foundation/aws/filters"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/mulgadc/spinifex/spinifex/foundation/state/migrate"
"github.com/nats-io/nats.go"
"github.com/nats-io/nats.go/jetstream"
@@ -41,7 +41,7 @@ func NewSpotInstanceServiceImplWithNATS(ctx context.Context, cfg *config.Config,
return nil, fmt.Errorf("failed to get JetStream context: %w", err)
}
activeKV, err := kvutil.GetOrCreateBucket(ctx, js, KVBucketSpotRequests, 10)
activeKV, err := kvstore.GetOrCreateBucket(ctx, js, KVBucketSpotRequests, 10)
if err != nil {
return nil, fmt.Errorf("failed to create KV bucket %s: %w", KVBucketSpotRequests, err)
}
@@ -71,7 +71,7 @@ func NewSpotInstanceServiceImplWithNATS(ctx context.Context, cfg *config.Config,
// getOrCreateTerminalBucket opens the terminal bucket without changing its
// existing replica or placement configuration, creating it only when absent.
func getOrCreateTerminalBucket(ctx context.Context, js jetstream.KeyValueManager) (jetstream.KeyValue, error) {
return kvutil.GetOrCreateBucketWithOptions(ctx, js, kvutil.BucketOptions{
return kvstore.GetOrCreateBucketWithOptions(ctx, js, kvstore.BucketOptions{
Name: KVBucketSpotRequestsTerminal,
Description: "Terminal Spot Instance Requests (auto-expire after 1 hour)",
History: 1,
@@ -91,7 +91,7 @@ func (s *SpotInstanceServiceImpl) PutSpotInstanceRequests(ctx context.Context, i
if err != nil {
return nil, errors.New(awserrors.ErrorServerInternal)
}
if _, err := s.activeKV.Put(ctx, kvutil.AccountKey(accountID, sirID), data); err != nil {
if _, err := s.activeKV.Put(ctx, kvstore.AccountKey(accountID, sirID), data); err != nil {
slog.ErrorContext(ctx, "PutSpotInstanceRequests: KV put failed", "sirId", sirID, "err", err)
return nil, errors.New(awserrors.ErrorServerInternal)
}
@@ -171,7 +171,7 @@ func (s *SpotInstanceServiceImpl) CancelSpotInstanceRequests(ctx context.Context
cancelled := make([]*ec2.CancelledSpotInstanceRequest, 0, len(input.SpotInstanceRequestIds))
for _, idPtr := range input.SpotInstanceRequestIds {
sirID := aws.StringValue(idPtr)
key := kvutil.AccountKey(accountID, sirID)
key := kvstore.AccountKey(accountID, sirID)
record, err := s.readRecord(ctx, s.activeKV, key)
if err == nil {
@@ -213,7 +213,7 @@ func (s *SpotInstanceServiceImpl) CloseForInstance(ctx context.Context, instance
prefix := accountID + "."
for _, key := range keys {
if key == kvutil.VersionKey || !strings.HasPrefix(key, prefix) {
if key == kvstore.VersionKey || !strings.HasPrefix(key, prefix) {
continue
}
record, err := s.readRecord(ctx, s.activeKV, key)
@@ -251,7 +251,7 @@ func (s *SpotInstanceServiceImpl) listRequests(ctx context.Context, kv jetstream
prefix := accountID + "."
var requests []*ec2.SpotInstanceRequest
for _, key := range keys {
if key == kvutil.VersionKey || !strings.HasPrefix(key, prefix) {
if key == kvstore.VersionKey || !strings.HasPrefix(key, prefix) {
continue
}
record, err := s.readRecord(ctx, kv, key)
@@ -6,7 +6,7 @@ import (
"sync"
"testing"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/nats-io/nats.go/jetstream"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -39,7 +39,7 @@ func (k *interleavingKV) Get(ctx context.Context, key string) (jetstream.KeyValu
// its write both survive alongside its own tag.
func TestCurrentBehaviour_Tagging_RecordMirrorKeepsInterleavedWrites(t *testing.T) {
const acct, id = "123456789012", "sg-0123456789abcdef0"
key := kvutil.AccountKey(acct, id)
key := kvstore.AccountKey(acct, id)
seed, err := json.Marshal(tagMirrorRecord{Name: "original", Tags: map[string]string{}})
require.NoError(t, err)
kv := seedKV(t, "tagmirror-cb", map[string][]byte{key: seed})
+3 -3
View File
@@ -10,7 +10,7 @@ import (
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/mulgadc/spinifex/spinifex/foundation/aws/errors"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/nats-io/nats.go/jetstream"
)
@@ -83,8 +83,8 @@ var errRecordAbsent = errors.New("tagmirror: record absent")
// applying mut and committing at the read revision, retrying on conflict so a
// concurrent write is never reverted. An absent resource is skipped.
func UpdateKVRecordTags[R any](ctx context.Context, kv jetstream.KeyValue, accountID, resourceID string, mut func(*R)) error {
key := kvutil.AccountKey(accountID, resourceID)
_, err := kvutil.Update(ctx, kv, key, kvutil.CASConfig{NotFound: errRecordAbsent}, func(rec *R) (bool, error) {
key := kvstore.AccountKey(accountID, resourceID)
_, err := kvstore.Update(ctx, kv, key, kvstore.CASConfig{NotFound: errRecordAbsent}, func(rec *R) (bool, error) {
mut(rec)
return true, nil
})
@@ -8,7 +8,7 @@ import (
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/mulgadc/spinifex/internal/testkit"
"github.com/mulgadc/spinifex/spinifex/foundation/aws/errors"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/nats-io/nats.go/jetstream"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -75,15 +75,15 @@ func TestUpdateKVRecordTags(t *testing.T) {
rec, err := json.Marshal(&tagMirrorRecord{Name: "r1", Tags: map[string]string{"a": "1"}})
require.NoError(t, err)
kv := seedKV(t, "tagmirror-test", map[string][]byte{
kvutil.AccountKey("acct", "res-1"): rec,
kvutil.AccountKey("acct", "res-bad"): []byte("not-json"),
kvstore.AccountKey("acct", "res-1"): rec,
kvstore.AccountKey("acct", "res-bad"): []byte("not-json"),
})
t.Run("mutates and persists", func(t *testing.T) {
require.NoError(t, UpdateKVRecordTags(t.Context(), kv, "acct", "res-1", func(r *tagMirrorRecord) {
r.Tags["b"] = "2"
}))
entry, err := kv.Get(t.Context(), kvutil.AccountKey("acct", "res-1"))
entry, err := kv.Get(t.Context(), kvstore.AccountKey("acct", "res-1"))
require.NoError(t, err)
var got tagMirrorRecord
require.NoError(t, json.Unmarshal(entry.Value(), &got))
@@ -110,14 +110,14 @@ func TestMirrorKVRecordTags(t *testing.T) {
nilTags, err := json.Marshal(&tagMirrorRecord{})
require.NoError(t, err)
kv := seedKV(t, "tagmirror-mirror-test", map[string][]byte{
kvutil.AccountKey("acct", "vol-1"): withTags,
kvutil.AccountKey("acct", "vol-2"): nilTags,
kvstore.AccountKey("acct", "vol-1"): withTags,
kvstore.AccountKey("acct", "vol-2"): nilTags,
})
tagsOf := func(r *tagMirrorRecord) *map[string]string { return &r.Tags }
readTags := func(t *testing.T, id string) map[string]string {
entry, err := kv.Get(t.Context(), kvutil.AccountKey("acct", id))
entry, err := kv.Get(t.Context(), kvstore.AccountKey("acct", id))
require.NoError(t, err)
var got tagMirrorRecord
require.NoError(t, json.Unmarshal(entry.Value(), &got))
@@ -150,7 +150,7 @@ func TestMirrorKVRecordTags(t *testing.T) {
// must never be touched even though it exists in this bucket.
otherRec, err := json.Marshal(&tagMirrorRecord{Tags: map[string]string{"x": "y"}})
require.NoError(t, err)
_, err = kv.Put(t.Context(), kvutil.AccountKey("acct", "snap-other"), otherRec)
_, err = kv.Put(t.Context(), kvstore.AccountKey("acct", "snap-other"), otherRec)
require.NoError(t, err)
require.NoError(t, MirrorKVRecordTags(t.Context(), kv, "acct", "vol-", resources, tagsOf,
MergeTagsMut(&ec2.CreateTagsInput{Tags: []*ec2.Tag{
+2 -2
View File
@@ -23,7 +23,7 @@ import (
"github.com/mulgadc/spinifex/spinifex/foundation/aws/arn"
"github.com/mulgadc/spinifex/spinifex/foundation/aws/errors"
awsfilters "github.com/mulgadc/spinifex/spinifex/foundation/aws/filters"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/mulgadc/spinifex/spinifex/providers/objectstore"
"github.com/nats-io/nats.go/jetstream"
)
@@ -274,7 +274,7 @@ func (s *TagsServiceImpl) CreateTags(ctx context.Context, input *ec2.CreateTagsI
func (s *TagsServiceImpl) taggedResourceIDs(ctx context.Context, accountID string) ([]string, error) {
seen := make(map[string]struct{})
keys, err := kvutil.Keys(ctx, s.kv)
keys, err := kvstore.Keys(ctx, s.kv)
if err != nil && !errors.Is(err, jetstream.ErrNoKeysFound) {
return nil, fmt.Errorf("list tag keys: %w", err)
}
+4 -4
View File
@@ -6,7 +6,7 @@ import (
"fmt"
"strings"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/nats-io/nats.go/jetstream"
)
@@ -46,7 +46,7 @@ func resourceIDFromKVKey(key, accountID string) (string, bool) {
// GetOrCreateTagsBucket opens the tag bucket, creating it at the cluster's
// replica count if this is the first node to reach it.
func GetOrCreateTagsBucket(ctx context.Context, js jetstream.KeyValueManager) (jetstream.KeyValue, error) {
return kvutil.GetOrCreateBucket(ctx, js, KVBucketTags, kvTagsHistory)
return kvstore.GetOrCreateBucket(ctx, js, KVBucketTags, kvTagsHistory)
}
// readKVTags returns a resource's tags from the bucket, and whether the key
@@ -76,7 +76,7 @@ func (s *TagsServiceImpl) readKVTags(ctx context.Context, accountID, resourceID
// before the store moved.
func (s *TagsServiceImpl) mutateTags(ctx context.Context, accountID, resourceID string, mutate func(map[string]string)) error {
key := tagsKVKey(accountID, resourceID)
_, err := kvutil.Update(ctx, s.kv, key, kvutil.CASConfig{
_, err := kvstore.Update(ctx, s.kv, key, kvstore.CASConfig{
Attempts: tagCASAttempts,
CreateIfAbsent: true,
Exhausted: func(key string, attempts int) error {
@@ -104,7 +104,7 @@ func (s *TagsServiceImpl) PutResourceTags(ctx context.Context, accountID, resour
if err != nil {
return err
}
return kvutil.Put(ctx, s.kv, tagsKVKey(accountID, resourceID), kvutil.CASConfig{
return kvstore.Put(ctx, s.kv, tagsKVKey(accountID, resourceID), kvstore.CASConfig{
Attempts: tagCASAttempts,
CreateIfAbsent: true,
}, data)
+6 -6
View File
@@ -12,7 +12,7 @@ import (
networkv1 "github.com/mulgadc/spinifex/contracts/network/v1"
awsidentifiers "github.com/mulgadc/spinifex/spinifex/foundation/aws/identifiers"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/nats-io/nats.go/jetstream"
)
@@ -105,7 +105,7 @@ func (s *VPCServiceImpl) ensureDefaultVPC(ctx context.Context, accountID string,
return claim.info(), nil
}
if _, err := s.vpcKV.Get(ctx, kvutil.AccountKey(accountID, claim.VpcId)); err == nil {
if _, err := s.vpcKV.Get(ctx, kvstore.AccountKey(accountID, claim.VpcId)); err == nil {
return claim.info(), nil
} else if !errors.Is(err, jetstream.ErrKeyNotFound) {
return nil, fmt.Errorf("read default VPC %s: %w", claim.VpcId, err)
@@ -233,7 +233,7 @@ func oldestAccountRecord[T any](ctx context.Context, kv jetstream.KeyValue, acco
var bestID string
var bestCreated time.Time
for _, key := range keys {
if key == kvutil.VersionKey || !strings.HasPrefix(key, prefix) {
if key == kvstore.VersionKey || !strings.HasPrefix(key, prefix) {
continue
}
entry, err := kv.Get(ctx, key)
@@ -262,7 +262,7 @@ func oldestAccountRecord[T any](ctx context.Context, kv jetstream.KeyValue, acco
// marks the claim complete. Records are written with Create, so only the caller
// that actually wrote one announces it to vpcd.
func (s *VPCServiceImpl) buildDefaultVPC(ctx context.Context, accountID string, claim *defaultVPCClaim) error {
created, err := createRecord(ctx, s.vpcKV, kvutil.AccountKey(accountID, claim.VpcId), VPCRecord{
created, err := createRecord(ctx, s.vpcKV, kvstore.AccountKey(accountID, claim.VpcId), VPCRecord{
VpcId: claim.VpcId,
CidrBlock: claim.Cidr,
State: "available",
@@ -284,7 +284,7 @@ func (s *VPCServiceImpl) buildDefaultVPC(ctx context.Context, accountID string,
if s.config != nil && s.config.AZ != "" {
az = s.config.AZ
}
created, err = createRecord(ctx, s.subnetKV, kvutil.AccountKey(accountID, claim.SubnetId), SubnetRecord{
created, err = createRecord(ctx, s.subnetKV, kvstore.AccountKey(accountID, claim.SubnetId), SubnetRecord{
SubnetId: claim.SubnetId,
VpcId: claim.VpcId,
CidrBlock: claim.SubnetCidr,
@@ -320,7 +320,7 @@ func (s *VPCServiceImpl) buildDefaultVPC(ctx context.Context, accountID string,
}
}
if _, err := kvutil.Update(ctx, s.defaultsKV, accountID, kvutil.CASConfig{}, func(c *defaultVPCClaim) (bool, error) {
if _, err := kvstore.Update(ctx, s.defaultsKV, accountID, kvstore.CASConfig{}, func(c *defaultVPCClaim) (bool, error) {
if c.Complete || c.VpcId != claim.VpcId {
return false, nil
}
+4 -4
View File
@@ -13,7 +13,7 @@ import (
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/mulgadc/spinifex/internal/testkit"
ec2vpc "github.com/mulgadc/spinifex/spinifex/domains/ec2/vpc"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/nats-io/nats.go"
"github.com/nats-io/nats.go/jetstream"
"github.com/stretchr/testify/assert"
@@ -149,12 +149,12 @@ func TestEnsureDefaultVPC_AdoptsOldestExistingDefault(t *testing.T) {
const accountID = "000000000051"
created := time.Date(2026, 9, 11, 14, 13, 40, 0, time.UTC)
for i, id := range []string{"vpc-0000000000000newer", "vpc-0000000000000older"} {
putRecord(t, js, ec2vpc.KVBucketVPCs, kvutil.AccountKey(accountID, id), ec2vpc.VPCRecord{
putRecord(t, js, ec2vpc.KVBucketVPCs, kvstore.AccountKey(accountID, id), ec2vpc.VPCRecord{
VpcId: id, CidrBlock: ec2vpc.DefaultVPCCidr, IsDefault: true, VNI: int64(200 + i),
CreatedAt: created.Add(time.Duration(1-i) * time.Minute),
})
}
putRecord(t, js, ec2vpc.KVBucketSubnets, kvutil.AccountKey(accountID, "subnet-00000000000older"), ec2vpc.SubnetRecord{
putRecord(t, js, ec2vpc.KVBucketSubnets, kvstore.AccountKey(accountID, "subnet-00000000000older"), ec2vpc.SubnetRecord{
SubnetId: "subnet-00000000000older", VpcId: "vpc-0000000000000older", CidrBlock: ec2vpc.DefaultSubnetCidr,
IsDefault: true, CreatedAt: created,
})
@@ -183,7 +183,7 @@ func TestEnsureDefaultVPC_FinishesHalfBuiltClaim(t *testing.T) {
"route_table_id": "rtb-00000000000half", "internet_gateway_id": "igw-00000000000half",
"cidr": ec2vpc.DefaultVPCCidr, "subnet_cidr": ec2vpc.DefaultSubnetCidr, "vni": 4242,
})
putRecord(t, js, ec2vpc.KVBucketVPCs, kvutil.AccountKey(accountID, "vpc-00000000000000half"), ec2vpc.VPCRecord{
putRecord(t, js, ec2vpc.KVBucketVPCs, kvstore.AccountKey(accountID, "vpc-00000000000000half"), ec2vpc.VPCRecord{
VpcId: "vpc-00000000000000half", CidrBlock: ec2vpc.DefaultVPCCidr, IsDefault: true, VNI: 4242,
})
+18 -18
View File
@@ -23,7 +23,7 @@ import (
awsidentifiers "github.com/mulgadc/spinifex/spinifex/foundation/aws/identifiers"
"github.com/mulgadc/spinifex/spinifex/foundation/aws/paging"
"github.com/mulgadc/spinifex/spinifex/foundation/aws/tags"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/nats-io/nats.go/jetstream"
)
@@ -118,7 +118,7 @@ func (s *VPCServiceImpl) CreateNetworkInterface(ctx context.Context, input *ec2.
subnetId := *input.SubnetId
// Verify subnet exists and belongs to this account
subnetEntry, err := s.subnetKV.Get(ctx, kvutil.AccountKey(accountID, subnetId))
subnetEntry, err := s.subnetKV.Get(ctx, kvstore.AccountKey(accountID, subnetId))
if err != nil {
return nil, errors.New(awserrors.ErrorInvalidSubnetIDNotFound)
}
@@ -205,7 +205,7 @@ func (s *VPCServiceImpl) CreateNetworkInterface(ctx context.Context, input *ec2.
if err != nil {
return nil, fmt.Errorf("failed to marshal ENI record: %w", err)
}
if _, err := s.eniKV.Put(ctx, kvutil.AccountKey(accountID, eniId), data); err != nil {
if _, err := s.eniKV.Put(ctx, kvstore.AccountKey(accountID, eniId), data); err != nil {
return nil, errors.New(awserrors.ErrorServerInternal)
}
@@ -251,7 +251,7 @@ func (s *VPCServiceImpl) ForceDeleteInstanceENI(ctx context.Context, accountID,
}
func (s *VPCServiceImpl) deleteNetworkInterface(ctx context.Context, eniId, accountID string, force bool) (*ec2.DeleteNetworkInterfaceOutput, error) {
key := kvutil.AccountKey(accountID, eniId)
key := kvstore.AccountKey(accountID, eniId)
// Get the ENI record
entry, err := s.eniKV.Get(ctx, key)
@@ -346,7 +346,7 @@ func (s *VPCServiceImpl) releaseENISideEffects(ctx context.Context, eniId, accou
// deletion that did not happen.
func (s *VPCServiceImpl) DetachAndDeleteENI(ctx context.Context, accountID, eniID string, force bool) (deleted bool, err error) {
const maxAttempts = 3
key := kvutil.AccountKey(accountID, eniID)
key := kvstore.AccountKey(accountID, eniID)
var lastErr error
for range maxAttempts {
@@ -458,7 +458,7 @@ func (s *VPCServiceImpl) ModifyNetworkInterfaceAttribute(ctx context.Context, in
}
eniId := *input.NetworkInterfaceId
key := kvutil.AccountKey(accountID, eniId)
key := kvstore.AccountKey(accountID, eniId)
entry, err := s.eniKV.Get(ctx, key)
if err != nil {
@@ -569,7 +569,7 @@ func (s *VPCServiceImpl) DescribeNetworkInterfaces(ctx context.Context, input *e
}
for _, key := range keys {
if key == kvutil.VersionKey {
if key == kvstore.VersionKey {
continue
}
if !strings.HasPrefix(key, prefix) {
@@ -707,7 +707,7 @@ func (s *VPCServiceImpl) AttachENIWithOwner(accountID, eniId, instanceId string,
func (s *VPCServiceImpl) attachENI(ctx context.Context, accountID, eniId, instanceId string, deviceIndex int64, instanceOwnerID string) (string, error) {
attachmentId := awsidentifiers.GenerateResourceID("eni-attach")
err := s.casENI(ctx, kvutil.AccountKey(accountID, eniId), eniId, func(record *ENIRecord) error {
err := s.casENI(ctx, kvstore.AccountKey(accountID, eniId), eniId, func(record *ENIRecord) error {
if record.Status == "in-use" {
return errors.New(awserrors.ErrorInvalidNetworkInterfaceInUse)
}
@@ -737,7 +737,7 @@ func (s *VPCServiceImpl) attachENI(ctx context.Context, accountID, eniId, instan
// DetachENI marks an ENI as detached from an instance (internal use by TerminateInstances).
// accountID scopes the lookup to the correct KV key.
func (s *VPCServiceImpl) DetachENI(ctx context.Context, accountID, eniId string) error {
key := kvutil.AccountKey(accountID, eniId)
key := kvstore.AccountKey(accountID, eniId)
entry, err := s.eniKV.Get(ctx, key)
if err != nil {
return eniNotFoundError(eniId)
@@ -773,7 +773,7 @@ func (s *VPCServiceImpl) GetENIRecord(accountID, eniId string) (ENIRecord, error
}
func (s *VPCServiceImpl) getENIRecord(ctx context.Context, accountID, eniId string) (ENIRecord, error) {
key := kvutil.AccountKey(accountID, eniId)
key := kvstore.AccountKey(accountID, eniId)
entry, err := s.eniKV.Get(ctx, key)
if err != nil {
return ENIRecord{}, eniNotFoundError(eniId)
@@ -793,7 +793,7 @@ func (s *VPCServiceImpl) UpdateENI(accountID, eniId string, fn func(*ENIRecord))
}
func (s *VPCServiceImpl) updateENI(ctx context.Context, accountID, eniId string, fn func(*ENIRecord)) error {
return s.casENI(ctx, kvutil.AccountKey(accountID, eniId), eniId, func(r *ENIRecord) error {
return s.casENI(ctx, kvstore.AccountKey(accountID, eniId), eniId, func(r *ENIRecord) error {
fn(r)
return nil
})
@@ -876,7 +876,7 @@ func (s *VPCServiceImpl) listInstanceENIs(ctx context.Context, accountID, instan
prefix := accountID + "."
var out []ENIRecord
for _, key := range keys {
if key == kvutil.VersionKey || !strings.HasPrefix(key, prefix) {
if key == kvstore.VersionKey || !strings.HasPrefix(key, prefix) {
continue
}
entry, err := s.eniKV.Get(ctx, key)
@@ -938,7 +938,7 @@ func (s *VPCServiceImpl) scanENIs(ctx context.Context, match func(*ENIRecord) bo
var out []AccountENI
for _, key := range keys {
if key == kvutil.VersionKey {
if key == kvstore.VersionKey {
continue
}
accountID, _, found := strings.Cut(key, ".")
@@ -1004,7 +1004,7 @@ func (s *VPCServiceImpl) findENIByAttachment(ctx context.Context, accountID, att
}
prefix := accountID + "."
for _, key := range keys {
if key == kvutil.VersionKey || !strings.HasPrefix(key, prefix) {
if key == kvstore.VersionKey || !strings.HasPrefix(key, prefix) {
continue
}
entry, err := s.eniKV.Get(ctx, key)
@@ -1028,7 +1028,7 @@ func (s *VPCServiceImpl) UpdateENIPublicIP(accountID, eniId, publicIP, poolName
}
func (s *VPCServiceImpl) updateENIPublicIP(ctx context.Context, accountID, eniId, publicIP, poolName string) error {
key := kvutil.AccountKey(accountID, eniId)
key := kvstore.AccountKey(accountID, eniId)
entry, err := s.eniKV.Get(ctx, key)
if err != nil {
return fmt.Errorf("ENI %s not found: %w", eniId, err)
@@ -1063,7 +1063,7 @@ func (s *VPCServiceImpl) fillSGNames(ctx context.Context, accountID string, ids
continue
}
names[id] = ""
entry, err := s.sgKV.Get(ctx, kvutil.AccountKey(accountID, id))
entry, err := s.sgKV.Get(ctx, kvstore.AccountKey(accountID, id))
if err != nil {
slog.WarnContext(ctx, "fillSGNames: SG read failed", "groupId", id, "err", err)
continue
@@ -1080,7 +1080,7 @@ func (s *VPCServiceImpl) fillSGNames(ctx context.Context, accountID string, ids
// vpcDNSHostnames reports whether the VPC has enableDnsHostnames on. An
// unreadable VPC reads as off, so its interfaces go unnamed.
func (s *VPCServiceImpl) vpcDNSHostnames(ctx context.Context, accountID, vpcID string) bool {
entry, err := s.vpcKV.Get(ctx, kvutil.AccountKey(accountID, vpcID))
entry, err := s.vpcKV.Get(ctx, kvstore.AccountKey(accountID, vpcID))
if err != nil {
slog.WarnContext(ctx, "vpcDNSHostnames: VPC read failed", "vpcId", vpcID, "err", err)
return false
@@ -1238,7 +1238,7 @@ func (s *VPCServiceImpl) validateSGAttachment(ctx context.Context, accountID str
// Each SG must exist in the caller's account and belong to the same VPC.
for _, sgId := range sgIds {
sgEntry, err := s.sgKV.Get(ctx, kvutil.AccountKey(accountID, sgId))
sgEntry, err := s.sgKV.Get(ctx, kvstore.AccountKey(accountID, sgId))
if err != nil {
return sgNotFoundError(sgId)
}
+2 -2
View File
@@ -10,7 +10,7 @@ import (
"github.com/mulgadc/spinifex/spinifex/domains/network/projection"
"github.com/mulgadc/spinifex/spinifex/domains/network/topology"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/nats-io/nats.go/jetstream"
)
@@ -69,7 +69,7 @@ func (s *VPCServiceImpl) findENIAnyAccount(ctx context.Context, eniID string) (*
suffix := "." + eniID
for _, k := range keys {
if k == kvutil.VersionKey || !strings.HasSuffix(k, suffix) {
if k == kvstore.VersionKey || !strings.HasSuffix(k, suffix) {
continue
}
entry, err := s.eniKV.Get(ctx, k)
@@ -7,7 +7,7 @@ import (
"testing"
testutil "github.com/mulgadc/spinifex/internal/testkit"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/nats-io/nats.go/jetstream"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -30,7 +30,7 @@ func newStaleReadENI(t *testing.T) (*VPCServiceImpl, string, func() ENIRecord) {
// Reads the stored record directly: the wrapped bucket would serve the
// check itself from a lagging replica.
stored := func() ENIRecord {
entry, err := inner.Get(context.Background(), kvutil.AccountKey(testAccountID, eniID))
entry, err := inner.Get(context.Background(), kvstore.AccountKey(testAccountID, eniID))
require.NoError(t, err)
var rec ENIRecord
require.NoError(t, json.Unmarshal(entry.Value(), &rec))
@@ -114,7 +114,7 @@ func TestCASENIGivesUpAfterBoundedConflicts(t *testing.T) {
t.Parallel()
svc, eniID, _ := newStaleReadENI(t)
conflicts := 0
err := svc.casENI(t.Context(), kvutil.AccountKey(testAccountID, eniID), eniID, func(*ENIRecord) error {
err := svc.casENI(t.Context(), kvstore.AccountKey(testAccountID, eniID), eniID, func(*ENIRecord) error {
conflicts++
// Move the record under the next compare-and-swap, as a competing writer does.
return svc.UpdateENI(testAccountID, eniID, func(r *ENIRecord) { r.Description = "other writer" })
+2 -2
View File
@@ -6,7 +6,7 @@ import (
"fmt"
"strings"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/nats-io/nats.go/jetstream"
)
@@ -42,7 +42,7 @@ func keyWithSuffixExists(ctx context.Context, kv jetstream.KeyValue, resourceID
}
suffix := "." + resourceID
for _, k := range keys {
if k != kvutil.VersionKey && strings.HasSuffix(k, suffix) {
if k != kvstore.VersionKey && strings.HasSuffix(k, suffix) {
return true, nil
}
}
+20 -20
View File
@@ -23,7 +23,7 @@ import (
awsidentifiers "github.com/mulgadc/spinifex/spinifex/foundation/aws/identifiers"
"github.com/mulgadc/spinifex/spinifex/foundation/aws/paging"
awstags "github.com/mulgadc/spinifex/spinifex/foundation/aws/tags"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/nats-io/nats.go/jetstream"
)
@@ -245,7 +245,7 @@ func (s *VPCServiceImpl) CreateSecurityGroup(ctx context.Context, input *ec2.Cre
}
sgsInVPC := 0
for _, k := range sgKeys {
if k == kvutil.VersionKey {
if k == kvstore.VersionKey {
continue
}
if !strings.HasPrefix(k, prefix) {
@@ -305,7 +305,7 @@ func (s *VPCServiceImpl) CreateSecurityGroup(ctx context.Context, input *ec2.Cre
if err != nil {
return nil, fmt.Errorf("failed to marshal security group record: %w", err)
}
if _, err := s.sgKV.Put(ctx, kvutil.AccountKey(accountID, groupId), data); err != nil {
if _, err := s.sgKV.Put(ctx, kvstore.AccountKey(accountID, groupId), data); err != nil {
return nil, errors.New(awserrors.ErrorServerInternal)
}
@@ -331,7 +331,7 @@ func (s *VPCServiceImpl) DeleteSecurityGroup(ctx context.Context, input *ec2.Del
}
groupId := *input.GroupId
key := kvutil.AccountKey(accountID, groupId)
key := kvstore.AccountKey(accountID, groupId)
entry, err := s.sgKV.Get(ctx, key)
if err != nil {
@@ -380,7 +380,7 @@ func (s *VPCServiceImpl) validateSGRuleReferences(ctx context.Context, accountID
if r.SourceSG == "" {
continue
}
entry, err := s.sgKV.Get(ctx, kvutil.AccountKey(accountID, r.SourceSG))
entry, err := s.sgKV.Get(ctx, kvstore.AccountKey(accountID, r.SourceSG))
if err != nil {
return sgNotFoundError(r.SourceSG)
}
@@ -406,7 +406,7 @@ func (s *VPCServiceImpl) checkSGDependencies(ctx context.Context, accountID, gro
return errors.New(awserrors.ErrorServerInternal)
}
for _, k := range eniKeys {
if k == kvutil.VersionKey || !strings.HasPrefix(k, prefix) {
if k == kvstore.VersionKey || !strings.HasPrefix(k, prefix) {
continue
}
entry, err := s.eniKV.Get(ctx, k)
@@ -435,7 +435,7 @@ func (s *VPCServiceImpl) checkSGDependencies(ctx context.Context, accountID, gro
return errors.New(awserrors.ErrorServerInternal)
}
for _, k := range sgKeys {
if k == kvutil.VersionKey || !strings.HasPrefix(k, prefix) {
if k == kvstore.VersionKey || !strings.HasPrefix(k, prefix) {
continue
}
entry, err := s.sgKV.Get(ctx, k)
@@ -561,7 +561,7 @@ func (s *VPCServiceImpl) DescribeSecurityGroups(ctx context.Context, input *ec2.
}
for _, key := range keys {
if key == kvutil.VersionKey {
if key == kvstore.VersionKey {
continue
}
if !strings.HasPrefix(key, prefix) {
@@ -713,7 +713,7 @@ func (s *VPCServiceImpl) GetSecurityGroupsForVpc(ctx context.Context, input *ec2
groups := []*ec2.SecurityGroupForVpc{}
for _, key := range keys {
if key == kvutil.VersionKey || !strings.HasPrefix(key, prefix) {
if key == kvstore.VersionKey || !strings.HasPrefix(key, prefix) {
continue
}
entry, err := s.sgKV.Get(ctx, key)
@@ -844,7 +844,7 @@ func (s *VPCServiceImpl) DescribeSecurityGroupRules(ctx context.Context, input *
emitted := make(map[string]bool)
for _, key := range keys {
if key == kvutil.VersionKey || !strings.HasPrefix(key, prefix) {
if key == kvstore.VersionKey || !strings.HasPrefix(key, prefix) {
continue
}
entry, err := s.sgKV.Get(ctx, key)
@@ -945,7 +945,7 @@ func (s *VPCServiceImpl) updateSGRuleTags(ctx context.Context, accountID, ruleID
}
for _, key := range keys {
if key == kvutil.VersionKey || !strings.HasPrefix(key, prefix) {
if key == kvstore.VersionKey || !strings.HasPrefix(key, prefix) {
continue
}
entry, err := s.sgKV.Get(ctx, key)
@@ -1056,7 +1056,7 @@ func (s *VPCServiceImpl) AuthorizeSecurityGroupIngress(ctx context.Context, inpu
}
groupId := *input.GroupId
key := kvutil.AccountKey(accountID, groupId)
key := kvstore.AccountKey(accountID, groupId)
entry, err := s.sgKV.Get(ctx, key)
if err != nil {
@@ -1134,7 +1134,7 @@ func (s *VPCServiceImpl) AuthorizeSecurityGroupEgress(ctx context.Context, input
}
groupId := *input.GroupId
key := kvutil.AccountKey(accountID, groupId)
key := kvstore.AccountKey(accountID, groupId)
entry, err := s.sgKV.Get(ctx, key)
if err != nil {
@@ -1212,7 +1212,7 @@ func (s *VPCServiceImpl) RevokeSecurityGroupIngress(ctx context.Context, input *
}
groupId := *input.GroupId
key := kvutil.AccountKey(accountID, groupId)
key := kvstore.AccountKey(accountID, groupId)
entry, err := s.sgKV.Get(ctx, key)
if err != nil {
@@ -1277,7 +1277,7 @@ func (s *VPCServiceImpl) RevokeSecurityGroupEgress(ctx context.Context, input *e
}
groupId := *input.GroupId
key := kvutil.AccountKey(accountID, groupId)
key := kvstore.AccountKey(accountID, groupId)
entry, err := s.sgKV.Get(ctx, key)
if err != nil {
@@ -1397,7 +1397,7 @@ func (s *VPCServiceImpl) updateSGRuleDescriptions(ctx context.Context, accountID
return errors.New(awserrors.ErrorInvalidParameterCombination)
}
key := kvutil.AccountKey(accountID, req.groupId)
key := kvstore.AccountKey(accountID, req.groupId)
entry, err := s.sgKV.Get(ctx, key)
if err != nil {
return sgNotFoundError(req.groupId)
@@ -1581,7 +1581,7 @@ func (s *VPCServiceImpl) ModifySecurityGroupRules(ctx context.Context, input *ec
}
groupId := *input.GroupId
key := kvutil.AccountKey(accountID, groupId)
key := kvstore.AccountKey(accountID, groupId)
entry, err := s.sgKV.Get(ctx, key)
if err != nil {
@@ -2158,7 +2158,7 @@ func (s *VPCServiceImpl) storeDefaultSecurityGroup(ctx context.Context, accountI
if err != nil {
return nil, false, fmt.Errorf("marshal default security group: %w", err)
}
if _, err := s.sgKV.Create(ctx, kvutil.AccountKey(accountID, groupId), data); err != nil {
if _, err := s.sgKV.Create(ctx, kvstore.AccountKey(accountID, groupId), data); err != nil {
if errors.Is(err, jetstream.ErrKeyExists) {
return record, false, nil
}
@@ -2182,7 +2182,7 @@ func (s *VPCServiceImpl) announceDefaultSecurityGroup(record *SecurityGroupRecor
// CannotDelete guard for default SGs. Used by DeleteVpc to cascade-delete the
// per-VPC default SG. Surfaces vpcd tear-down failures to the caller.
func (s *VPCServiceImpl) deleteSecurityGroupInternal(ctx context.Context, accountID, groupId string) error {
key := kvutil.AccountKey(accountID, groupId)
key := kvstore.AccountKey(accountID, groupId)
entry, err := s.sgKV.Get(ctx, key)
if err != nil {
return fmt.Errorf("read default security group: %w", err)
@@ -2219,7 +2219,7 @@ func (s *VPCServiceImpl) findDefaultSGForVPC(ctx context.Context, accountID, vpc
return "", err
}
for _, k := range keys {
if k == kvutil.VersionKey || !strings.HasPrefix(k, prefix) {
if k == kvstore.VersionKey || !strings.HasPrefix(k, prefix) {
continue
}
entry, err := s.sgKV.Get(ctx, k)
@@ -15,7 +15,7 @@ import (
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/mulgadc/spinifex/spinifex/foundation/aws/errors"
awsfilters "github.com/mulgadc/spinifex/spinifex/foundation/aws/filters"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -62,7 +62,7 @@ func tcpPeerRequest(port int64, peer string) *ec2.SecurityGroupRuleRequest {
// modify wrote nothing.
func storedSGRecord(t *testing.T, svc *VPCServiceImpl, sgID string) []byte {
t.Helper()
entry, err := svc.sgKV.Get(t.Context(), kvutil.AccountKey(testAccountID, sgID))
entry, err := svc.sgKV.Get(t.Context(), kvstore.AccountKey(testAccountID, sgID))
require.NoError(t, err)
return entry.Value()
}
@@ -689,7 +689,7 @@ func TestRevokeSecurityGroupIngress_InvertedPortRangeStillRevocable(t *testing.T
vpcID := createTestVPC(t, svc, "10.0.0.0/16")
sgID := createTestSG(t, svc, vpcID, "stale-inverted-ports")
key := kvutil.AccountKey(testAccountID, sgID)
key := kvstore.AccountKey(testAccountID, sgID)
var rec SecurityGroupRecord
require.NoError(t, json.Unmarshal(storedSGRecord(t, svc, sgID), &rec))
rec.IngressRules = append(rec.IngressRules, SGRule{
@@ -13,7 +13,7 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/mulgadc/spinifex/spinifex/foundation/aws/errors"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -35,7 +35,7 @@ func plantIngressRules(t *testing.T, svc *VPCServiceImpl, sgID string, rules ...
rec.IngressRules = append(rec.IngressRules, rules...)
data, err := json.Marshal(rec)
require.NoError(t, err)
_, err = svc.sgKV.Put(t.Context(), kvutil.AccountKey(testAccountID, sgID), data)
_, err = svc.sgKV.Put(t.Context(), kvstore.AccountKey(testAccountID, sgID), data)
require.NoError(t, err)
}
@@ -13,7 +13,7 @@ import (
"github.com/mulgadc/spinifex/internal/testkit"
"github.com/mulgadc/spinifex/spinifex/foundation/aws/errors"
awsfilters "github.com/mulgadc/spinifex/spinifex/foundation/aws/filters"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/nats-io/nats.go"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -1524,7 +1524,7 @@ func findDefaultSGInVPC(t *testing.T, svc *VPCServiceImpl, vpcID string) string
require.NoError(t, err)
var matches []string
for _, k := range keys {
if k == kvutil.VersionKey {
if k == kvstore.VersionKey {
continue
}
entry, err := svc.sgKV.Get(t.Context(), k)
@@ -2122,7 +2122,7 @@ func seedAvailableVPC(t *testing.T, svc *VPCServiceImpl, vpcID string) {
rec := VPCRecord{VpcId: vpcID, CidrBlock: "10.0.0.0/16", State: "available"}
data, err := json.Marshal(rec)
require.NoError(t, err)
_, err = svc.vpcKV.Put(t.Context(), kvutil.AccountKey(testAccountID, vpcID), data)
_, err = svc.vpcKV.Put(t.Context(), kvstore.AccountKey(testAccountID, vpcID), data)
require.NoError(t, err)
}
@@ -2359,7 +2359,7 @@ func TestAuthorizeSecurityGroupIngress_AssignsRuleIDs(t *testing.T) {
}, testAccountID)
require.NoError(t, err)
entry, err := svc.sgKV.Get(t.Context(), kvutil.AccountKey(testAccountID, sgID))
entry, err := svc.sgKV.Get(t.Context(), kvstore.AccountKey(testAccountID, sgID))
require.NoError(t, err)
var record SecurityGroupRecord
require.NoError(t, json.Unmarshal(entry.Value(), &record))
+35 -35
View File
@@ -26,7 +26,7 @@ import (
"github.com/mulgadc/spinifex/spinifex/foundation/aws/paging"
awstags "github.com/mulgadc/spinifex/spinifex/foundation/aws/tags"
"github.com/mulgadc/spinifex/spinifex/foundation/aws/zoneid"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/mulgadc/spinifex/spinifex/foundation/state/migrate"
"github.com/nats-io/nats.go"
"github.com/nats-io/nats.go/jetstream"
@@ -173,7 +173,7 @@ func NewVPCServiceImplWithNATS(ctx context.Context, cfg *config.Config, natsConn
return nil, fmt.Errorf("create JetStream client: %w", err)
}
vpcKV, err := kvutil.GetOrCreateBucket(ctx, js, KVBucketVPCs, 10)
vpcKV, err := kvstore.GetOrCreateBucket(ctx, js, KVBucketVPCs, 10)
if err != nil {
return nil, fmt.Errorf("failed to create KV bucket %s: %w", KVBucketVPCs, err)
}
@@ -181,7 +181,7 @@ func NewVPCServiceImplWithNATS(ctx context.Context, cfg *config.Config, natsConn
return nil, fmt.Errorf("migrate %s: %w", KVBucketVPCs, err)
}
subnetKV, err := kvutil.GetOrCreateBucket(ctx, js, KVBucketSubnets, 10)
subnetKV, err := kvstore.GetOrCreateBucket(ctx, js, KVBucketSubnets, 10)
if err != nil {
return nil, fmt.Errorf("failed to create KV bucket %s: %w", KVBucketSubnets, err)
}
@@ -189,7 +189,7 @@ func NewVPCServiceImplWithNATS(ctx context.Context, cfg *config.Config, natsConn
return nil, fmt.Errorf("migrate %s: %w", KVBucketSubnets, err)
}
vniKV, err := kvutil.GetOrCreateBucket(ctx, js, KVBucketVNICounter, 10)
vniKV, err := kvstore.GetOrCreateBucket(ctx, js, KVBucketVNICounter, 10)
if err != nil {
return nil, fmt.Errorf("failed to create KV bucket %s: %w", KVBucketVNICounter, err)
}
@@ -197,7 +197,7 @@ func NewVPCServiceImplWithNATS(ctx context.Context, cfg *config.Config, natsConn
return nil, fmt.Errorf("migrate %s: %w", KVBucketVNICounter, err)
}
eniKV, err := kvutil.GetOrCreateBucket(ctx, js, KVBucketENIs, 10)
eniKV, err := kvstore.GetOrCreateBucket(ctx, js, KVBucketENIs, 10)
if err != nil {
return nil, fmt.Errorf("failed to create KV bucket %s: %w", KVBucketENIs, err)
}
@@ -205,7 +205,7 @@ func NewVPCServiceImplWithNATS(ctx context.Context, cfg *config.Config, natsConn
return nil, fmt.Errorf("migrate %s: %w", KVBucketENIs, err)
}
sgKV, err := kvutil.GetOrCreateBucket(ctx, js, KVBucketSecurityGroups, 10)
sgKV, err := kvstore.GetOrCreateBucket(ctx, js, KVBucketSecurityGroups, 10)
if err != nil {
return nil, fmt.Errorf("failed to create KV bucket %s: %w", KVBucketSecurityGroups, err)
}
@@ -213,7 +213,7 @@ func NewVPCServiceImplWithNATS(ctx context.Context, cfg *config.Config, natsConn
return nil, fmt.Errorf("migrate %s: %w", KVBucketSecurityGroups, err)
}
defaultsKV, err := kvutil.GetOrCreateBucket(ctx, js, KVBucketDefaultVPCs, 10)
defaultsKV, err := kvstore.GetOrCreateBucket(ctx, js, KVBucketDefaultVPCs, 10)
if err != nil {
return nil, fmt.Errorf("failed to create KV bucket %s: %w", KVBucketDefaultVPCs, err)
}
@@ -221,18 +221,18 @@ func NewVPCServiceImplWithNATS(ctx context.Context, cfg *config.Config, natsConn
return nil, fmt.Errorf("migrate %s: %w", KVBucketDefaultVPCs, err)
}
rtbKV, err := kvutil.GetOrCreateBucket(ctx, js, "spinifex-vpc-route-tables", 10)
rtbKV, err := kvstore.GetOrCreateBucket(ctx, js, "spinifex-vpc-route-tables", 10)
if err != nil {
return nil, fmt.Errorf("failed to create KV bucket spinifex-vpc-route-tables: %w", err)
}
// Named literally, like the route table bucket above: the IGW package
// imports this one, so its constant cannot be referenced here.
igwKV, err := kvutil.GetOrCreateBucket(ctx, js, "spinifex-igw", 10)
igwKV, err := kvstore.GetOrCreateBucket(ctx, js, "spinifex-igw", 10)
if err != nil {
return nil, fmt.Errorf("failed to create KV bucket spinifex-igw: %w", err)
}
eigwKV, err := kvutil.GetOrCreateBucket(ctx, js, "spinifex-eigw", 10)
eigwKV, err := kvstore.GetOrCreateBucket(ctx, js, "spinifex-eigw", 10)
if err != nil {
return nil, fmt.Errorf("failed to create KV bucket spinifex-eigw: %w", err)
}
@@ -268,7 +268,7 @@ func NewVPCServiceImplWithNATS(ctx context.Context, cfg *config.Config, natsConn
// nextVNI allocates the next VNI using atomic increment on the NATS KV counter.
func (s *VPCServiceImpl) nextVNI(ctx context.Context) (int64, error) {
var allocated int64
_, err := kvutil.Update(ctx, s.vniKV, vniCounterKey, kvutil.CASConfig{CreateIfAbsent: true, Attempts: vniCASAttempts},
_, err := kvstore.Update(ctx, s.vniKV, vniCounterKey, kvstore.CASConfig{CreateIfAbsent: true, Attempts: vniCASAttempts},
func(next *int64) (bool, error) {
if *next == 0 {
*next = vniStart
@@ -331,7 +331,7 @@ func (s *VPCServiceImpl) CreateVpc(ctx context.Context, input *ec2.CreateVpcInpu
if err != nil {
return nil, fmt.Errorf("failed to marshal VPC record: %w", err)
}
if _, err := s.vpcKV.Put(ctx, kvutil.AccountKey(accountID, vpcID), data); err != nil {
if _, err := s.vpcKV.Put(ctx, kvstore.AccountKey(accountID, vpcID), data); err != nil {
return nil, errors.New(awserrors.ErrorServerInternal)
}
@@ -366,7 +366,7 @@ func (s *VPCServiceImpl) CreateVpc(ctx context.Context, input *ec2.CreateVpcInpu
// requireVPCExists returns InvalidVpcID.NotFound if the VPC doesn't exist for
// this account.
func (s *VPCServiceImpl) requireVPCExists(ctx context.Context, accountID, vpcId string) error {
if _, err := s.vpcKV.Get(ctx, kvutil.AccountKey(accountID, vpcId)); err != nil {
if _, err := s.vpcKV.Get(ctx, kvstore.AccountKey(accountID, vpcId)); err != nil {
return vpcNotFoundError(vpcId)
}
return nil
@@ -379,7 +379,7 @@ func (s *VPCServiceImpl) DeleteVpc(ctx context.Context, input *ec2.DeleteVpcInpu
}
vpcID := *input.VpcId
key := kvutil.AccountKey(accountID, vpcID)
key := kvstore.AccountKey(accountID, vpcID)
if _, err := s.vpcKV.Get(ctx, key); err != nil {
// AWS-faithful: an absent VPC is NotFound (the tofu/SDK provider
@@ -398,7 +398,7 @@ func (s *VPCServiceImpl) DeleteVpc(ctx context.Context, input *ec2.DeleteVpcInpu
return nil, errors.New(awserrors.ErrorServerInternal)
}
for _, k := range subnetKeys {
if k == kvutil.VersionKey {
if k == kvstore.VersionKey {
continue
}
if !strings.HasPrefix(k, prefix) {
@@ -435,7 +435,7 @@ func (s *VPCServiceImpl) DeleteVpc(ctx context.Context, input *ec2.DeleteVpcInpu
return nil, errors.New(awserrors.ErrorServerInternal)
}
for _, k := range sgKeys {
if k == kvutil.VersionKey || !strings.HasPrefix(k, prefix) {
if k == kvstore.VersionKey || !strings.HasPrefix(k, prefix) {
continue
}
entry, err := s.sgKV.Get(ctx, k)
@@ -472,7 +472,7 @@ func (s *VPCServiceImpl) DeleteVpc(ctx context.Context, input *ec2.DeleteVpcInpu
return nil, errors.New(awserrors.ErrorServerInternal)
}
for _, k := range rtbKeys {
if k == kvutil.VersionKey || !strings.HasPrefix(k, prefix) {
if k == kvstore.VersionKey || !strings.HasPrefix(k, prefix) {
continue
}
entry, err := s.rtbKV.Get(ctx, k)
@@ -533,7 +533,7 @@ func (s *VPCServiceImpl) DeleteVpc(ctx context.Context, input *ec2.DeleteVpcInpu
return nil, errors.New(awserrors.ErrorServerInternal)
}
if rtbID != "" {
if err := s.rtbKV.Delete(ctx, kvutil.AccountKey(accountID, rtbID)); err != nil && !errors.Is(err, jetstream.ErrKeyNotFound) {
if err := s.rtbKV.Delete(ctx, kvstore.AccountKey(accountID, rtbID)); err != nil && !errors.Is(err, jetstream.ErrKeyNotFound) {
slog.ErrorContext(ctx, "DeleteVpc: main route table reap failed", "vpcId", vpcID, "routeTableId", rtbID, "err", err)
return nil, errors.New(awserrors.ErrorServerInternal)
}
@@ -574,7 +574,7 @@ func rejectAttachedGateway(ctx context.Context, kv jetstream.KeyValue, accountID
}
for _, k := range keys {
if k == kvutil.VersionKey || !strings.HasPrefix(k, prefix) {
if k == kvstore.VersionKey || !strings.HasPrefix(k, prefix) {
continue
}
entry, err := kv.Get(ctx, k)
@@ -653,7 +653,7 @@ func (s *VPCServiceImpl) DescribeVpcs(ctx context.Context, input *ec2.DescribeVp
}
for _, key := range keys {
if key == kvutil.VersionKey {
if key == kvstore.VersionKey {
continue
}
if !strings.HasPrefix(key, prefix) {
@@ -723,7 +723,7 @@ func (s *VPCServiceImpl) CreateSubnet(ctx context.Context, input *ec2.CreateSubn
}
// Verify VPC exists and belongs to this account
vpcEntry, err := s.vpcKV.Get(ctx, kvutil.AccountKey(accountID, vpcID))
vpcEntry, err := s.vpcKV.Get(ctx, kvstore.AccountKey(accountID, vpcID))
if err != nil {
return nil, vpcNotFoundError(vpcID)
}
@@ -757,7 +757,7 @@ func (s *VPCServiceImpl) CreateSubnet(ctx context.Context, input *ec2.CreateSubn
}
for _, k := range subnetKeys {
if k == kvutil.VersionKey {
if k == kvstore.VersionKey {
continue
}
if !strings.HasPrefix(k, prefix) {
@@ -822,7 +822,7 @@ func (s *VPCServiceImpl) CreateSubnet(ctx context.Context, input *ec2.CreateSubn
if err != nil {
return nil, fmt.Errorf("failed to marshal subnet record: %w", err)
}
if _, err := s.subnetKV.Put(ctx, kvutil.AccountKey(accountID, subnetID), data); err != nil {
if _, err := s.subnetKV.Put(ctx, kvstore.AccountKey(accountID, subnetID), data); err != nil {
return nil, errors.New(awserrors.ErrorServerInternal)
}
@@ -843,7 +843,7 @@ func (s *VPCServiceImpl) DeleteSubnet(ctx context.Context, input *ec2.DeleteSubn
}
subnetID := *input.SubnetId
key := kvutil.AccountKey(accountID, subnetID)
key := kvstore.AccountKey(accountID, subnetID)
// Read subnet record before deletion (needed for vpcd event)
subnetEntry, err := s.subnetKV.Get(ctx, key)
@@ -902,7 +902,7 @@ func (s *VPCServiceImpl) checkSubnetResidents(ctx context.Context, accountID, su
}
prefix := accountID + "."
for _, key := range keys {
if key == kvutil.VersionKey || !strings.HasPrefix(key, prefix) {
if key == kvstore.VersionKey || !strings.HasPrefix(key, prefix) {
continue
}
entry, err := s.eniKV.Get(ctx, key)
@@ -950,7 +950,7 @@ func (s *VPCServiceImpl) clearRouteTableAssociationsForSubnet(ctx context.Contex
}
prefix := accountID + "."
for _, key := range keys {
if key == kvutil.VersionKey || !strings.HasPrefix(key, prefix) {
if key == kvstore.VersionKey || !strings.HasPrefix(key, prefix) {
continue
}
entry, err := s.rtbKV.Get(ctx, key)
@@ -1035,7 +1035,7 @@ func (s *VPCServiceImpl) DescribeSubnets(ctx context.Context, input *ec2.Describ
}
for _, key := range keys {
if key == kvutil.VersionKey {
if key == kvstore.VersionKey {
continue
}
if !strings.HasPrefix(key, prefix) {
@@ -1386,7 +1386,7 @@ func (s *VPCServiceImpl) ModifySubnetAttribute(ctx context.Context, input *ec2.M
}
subnetID := *input.SubnetId
key := kvutil.AccountKey(accountID, subnetID)
key := kvstore.AccountKey(accountID, subnetID)
entry, err := s.subnetKV.Get(ctx, key)
if err != nil {
@@ -1438,7 +1438,7 @@ func (s *VPCServiceImpl) ModifyVpcAttribute(ctx context.Context, input *ec2.Modi
}
vpcID := *input.VpcId
key := kvutil.AccountKey(accountID, vpcID)
key := kvstore.AccountKey(accountID, vpcID)
entry, err := s.vpcKV.Get(ctx, key)
if err != nil {
@@ -1486,7 +1486,7 @@ func (s *VPCServiceImpl) DescribeVpcAttribute(ctx context.Context, input *ec2.De
}
vpcID := *input.VpcId
key := kvutil.AccountKey(accountID, vpcID)
key := kvstore.AccountKey(accountID, vpcID)
entry, err := s.vpcKV.Get(ctx, key)
if err != nil {
@@ -1587,7 +1587,7 @@ func (s *VPCServiceImpl) writeMainRouteTable(ctx context.Context, accountID, vpc
if err != nil {
return false, fmt.Errorf("marshal main route table: %w", err)
}
if _, err := s.rtbKV.Create(ctx, kvutil.AccountKey(accountID, rtbID), data); err != nil {
if _, err := s.rtbKV.Create(ctx, kvstore.AccountKey(accountID, rtbID), data); err != nil {
if errors.Is(err, jetstream.ErrKeyExists) {
return false, nil
}
@@ -1611,7 +1611,7 @@ func (s *VPCServiceImpl) findMainRouteTableID(ctx context.Context, accountID, vp
return "", fmt.Errorf("list rtb keys: %w", err)
}
for _, key := range keys {
if key == kvutil.VersionKey || !strings.HasPrefix(key, prefix) {
if key == kvstore.VersionKey || !strings.HasPrefix(key, prefix) {
continue
}
entry, err := s.rtbKV.Get(ctx, key)
@@ -1652,7 +1652,7 @@ func (s *VPCServiceImpl) getDefaultSubnet(ctx context.Context, accountID string)
}
for _, key := range keys {
if key == kvutil.VersionKey {
if key == kvstore.VersionKey {
continue
}
if !strings.HasPrefix(key, prefix) {
@@ -1680,7 +1680,7 @@ func (s *VPCServiceImpl) GetSubnet(accountID, subnetId string) (*SubnetRecord, e
}
func (s *VPCServiceImpl) getSubnet(ctx context.Context, accountID, subnetId string) (*SubnetRecord, error) {
key := kvutil.AccountKey(accountID, subnetId)
key := kvstore.AccountKey(accountID, subnetId)
entry, err := s.subnetKV.Get(ctx, key)
if err != nil {
return nil, fmt.Errorf("subnet %s not found: %w", subnetId, err)
@@ -1726,7 +1726,7 @@ func (s *VPCServiceImpl) SubnetAvailabilityZones(ctx context.Context) ([]string,
}
seen := map[string]struct{}{}
for _, k := range keys {
if k == kvutil.VersionKey {
if k == kvstore.VersionKey {
continue
}
entry, err := s.subnetKV.Get(ctx, k)
@@ -16,7 +16,7 @@ import (
"github.com/mulgadc/spinifex/spinifex/bootstrap/config"
"github.com/mulgadc/spinifex/spinifex/foundation/aws/errors"
"github.com/mulgadc/spinifex/spinifex/foundation/aws/zoneid"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/nats-io/nats.go"
"github.com/nats-io/nats.go/jetstream"
"github.com/stretchr/testify/assert"
@@ -2689,7 +2689,7 @@ func TestCreateSubnet_ZoneStoreFailureWritesNothingAndPublishesNothing(t *testin
require.ErrorIs(t, err, jetstream.ErrNoKeysFound)
}
for _, k := range keys {
assert.Equal(t, kvutil.VersionKey, k, "no subnet record may exist")
assert.Equal(t, kvstore.VersionKey, k, "no subnet record may exist")
}
assert.Zero(t, events.Load(), "no topology event may be published")
}
+2 -2
View File
@@ -9,7 +9,7 @@ import (
"github.com/golang-jwt/jwt/v5"
"github.com/mulgadc/spinifex/internal/testkit"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -111,7 +111,7 @@ func TestLoadOrCreateSigningKey_FailsWhenNoKeyDecrypts(t *testing.T) {
require.Error(t, err)
assert.Contains(t, err.Error(), "master key does not match")
entries, err := kvutil.Keys(t.Context(), kv)
entries, err := kvstore.Keys(t.Context(), kv)
require.NoError(t, err)
assert.Len(t, entries, 1, "a failed load must not mint a key alongside the unreadable one")
}
+3 -3
View File
@@ -21,7 +21,7 @@ import (
"time"
iamdomain "github.com/mulgadc/spinifex/spinifex/domains/iam"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/nats-io/nats.go/jetstream"
)
@@ -111,7 +111,7 @@ func openSigningBucket(ctx context.Context, js jetstream.JetStream, masterKey []
if len(masterKey) == 0 {
return nil, errors.New("ecrauth: empty master key")
}
return kvutil.GetOrCreateBucket(ctx, js, SigningBucket, signingKeyHistory)
return kvstore.GetOrCreateBucket(ctx, js, SigningBucket, signingKeyHistory)
}
// reloadKeys reads every stored signing key, returning the verify set, each
@@ -125,7 +125,7 @@ func openSigningBucket(ctx context.Context, js jetstream.JetStream, masterKey []
// load instead takes the gateway down on every node, permanently, over a key no
// live request needs.
func reloadKeys(ctx context.Context, kv jetstream.KeyValue, masterKey []byte) (active *SigningKey, verify map[string]*ecdsa.PublicKey, metas []keyMeta, undecodable int, err error) {
names, err := kvutil.Keys(ctx, kv)
names, err := kvstore.Keys(ctx, kv)
if err != nil && !errors.Is(err, jetstream.ErrNoKeysFound) {
return nil, nil, nil, 0, fmt.Errorf("list signing keys: %w", err)
}
+2 -2
View File
@@ -10,7 +10,7 @@ import (
"sync"
"time"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/nats-io/nats.go/jetstream"
)
@@ -179,7 +179,7 @@ func (s *KVMetaStore) bucket(ctx context.Context, accountID string) (jetstream.K
return kv, nil
}
name := KVAccountBucket(accountID)
kv, err := kvutil.GetOrCreateBucket(ctx, s.js, name, KVBucketAccountHistory)
kv, err := kvstore.GetOrCreateBucket(ctx, s.js, name, KVBucketAccountHistory)
if err != nil {
return nil, fmt.Errorf("ecr: open account bucket %s: %w", name, err)
}
+3 -3
View File
@@ -7,7 +7,7 @@ import (
"time"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvlease"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/mulgadc/spinifex/spinifex/foundation/state/migrate"
"github.com/nats-io/nats.go"
"github.com/nats-io/nats.go/jetstream"
@@ -158,7 +158,7 @@ func accountBuckets(ctx context.Context, nc *nats.Conn) ([]accountBucket, error)
if err != nil {
return nil, fmt.Errorf("jetstream: %w", err)
}
names, err := kvutil.BucketNames(ctx, js)
names, err := kvstore.BucketNames(ctx, js)
if err != nil {
return nil, err
}
@@ -176,7 +176,7 @@ func accountBuckets(ctx context.Context, nc *nats.Conn) ([]accountBucket, error)
// return the existing handle.
func GetOrCreateAccountBucket(ctx context.Context, js jetstream.JetStream, accountID string) (jetstream.KeyValue, error) {
bucket := AccountBucketName(accountID)
kv, err := kvutil.GetOrCreateBucket(ctx, js, bucket, KVBucketECSAccountHistory)
kv, err := kvstore.GetOrCreateBucket(ctx, js, bucket, KVBucketECSAccountHistory)
if err != nil {
return nil, fmt.Errorf("failed to create ECS per-account KV bucket %s: %w", bucket, err)
}
+2 -2
View File
@@ -10,7 +10,7 @@ import (
"slices"
"strings"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/nats-io/nats.go/jetstream"
)
@@ -128,7 +128,7 @@ func deleteRecord(ctx context.Context, kv jetstream.KeyValue, cluster, principal
// casUpdate does a revision-checked read-modify-write. mutate returns true
// when a field changed. Returns ErrNotFound if absent.
func casUpdate(ctx context.Context, kv jetstream.KeyValue, cluster, principalARN string, mutate func(*Record) bool) (*Record, error) {
return kvutil.Update(ctx, kv, Key(cluster, principalARN), kvutil.CASConfig{NotFound: ErrNotFound}, func(r *Record) (bool, error) {
return kvstore.Update(ctx, kv, Key(cluster, principalARN), kvstore.CASConfig{NotFound: ErrNotFound}, func(r *Record) (bool, error) {
return mutate(r), nil
})
}
+2 -2
View File
@@ -8,7 +8,7 @@ import (
"slices"
"strings"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/nats-io/nats.go/jetstream"
)
@@ -193,7 +193,7 @@ func deleteRecord(ctx context.Context, kv jetstream.KeyValue, cluster, addon str
// casUpdate does a revision-checked read-modify-write. mutate returns true
// when a field changed. Returns ErrNotFound if absent.
func casUpdate(ctx context.Context, kv jetstream.KeyValue, cluster, addon string, mutate func(*Record) bool) (*Record, error) {
return kvutil.Update(ctx, kv, Key(cluster, addon), kvutil.CASConfig{NotFound: ErrNotFound}, func(r *Record) (bool, error) {
return kvstore.Update(ctx, kv, Key(cluster, addon), kvstore.CASConfig{NotFound: ErrNotFound}, func(r *Record) (bool, error) {
return mutate(r), nil
})
}
@@ -9,7 +9,7 @@ import (
"strings"
"time"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/nats-io/nats.go/jetstream"
)
@@ -280,7 +280,7 @@ func casUpdateMeta(ctx context.Context, kv jetstream.KeyValue, name string, muta
if name == "" {
return errors.New("eks: casUpdateMeta empty name")
}
_, err := kvutil.Update(ctx, kv, ClusterMetaKey(name), kvutil.CASConfig{
_, err := kvstore.Update(ctx, kv, ClusterMetaKey(name), kvstore.CASConfig{
Attempts: maxClusterStateCASRetries,
NotFound: ErrClusterNotFound,
Exhausted: func(string, int) error {
@@ -435,7 +435,7 @@ func putIfGeneration(ctx context.Context, kv jetstream.KeyValue, meta *ClusterMe
if err == nil {
return nil
}
if !kvutil.IsRevisionConflict(err) {
if !kvstore.IsRevisionConflict(err) {
return fmt.Errorf("kv write %s: %w", key, err)
}
}
@@ -8,7 +8,7 @@ import (
"time"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvlease"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
telemetry "github.com/mulgadc/spinifex/spinifex/foundation/telemetry"
"github.com/nats-io/nats.go/jetstream"
)
@@ -292,7 +292,7 @@ func (s *EKSServiceImpl) removeClusterRecord(ctx context.Context, acctKV jetstre
}
errRefsAdded := errors.New("refs added")
_, err = kvutil.DeleteIf(ctx, acctKV, ClusterMetaKey(name), kvutil.CASConfig{
_, err = kvstore.DeleteIf(ctx, acctKV, ClusterMetaKey(name), kvstore.CASConfig{
Attempts: maxClusterStateCASRetries,
NotFound: ErrClusterNotFound,
}, func(m *ClusterMeta) error {
+2 -2
View File
@@ -33,7 +33,7 @@ import (
"github.com/mulgadc/spinifex/spinifex/foundation/aws/errors"
"github.com/mulgadc/spinifex/spinifex/foundation/aws/paging"
"github.com/mulgadc/spinifex/spinifex/foundation/lifecycle/idempotency"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/mulgadc/spinifex/spinifex/providers/objectstore"
"github.com/nats-io/nats.go"
"github.com/nats-io/nats.go/jetstream"
@@ -2138,7 +2138,7 @@ func (s *EKSServiceImpl) casUpdateNodegroupTags(ctx context.Context, accountID,
if err != nil {
return err
}
_, err = kvutil.Update(ctx, acctKV, NodegroupKey(cluster, ng), kvutil.CASConfig{
_, err = kvstore.Update(ctx, acctKV, NodegroupKey(cluster, ng), kvstore.CASConfig{
Attempts: ngCASMaxRetries,
NotFound: errors.New(awserrors.ErrorEKSResourceNotFound),
Exhausted: func(string, int) error { return errors.New(awserrors.ErrorServerInternal) },
+2 -3
View File
@@ -9,7 +9,6 @@ import (
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvlease"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/migrate"
"github.com/nats-io/nats.go"
"github.com/nats-io/nats.go/jetstream"
@@ -157,7 +156,7 @@ func accountBucketNames(ctx context.Context, nc *nats.Conn) ([]string, error) {
if err != nil {
return nil, fmt.Errorf("jetstream: %w", err)
}
all, err := kvutil.BucketNames(ctx, js)
all, err := kvstore.BucketNames(ctx, js)
if err != nil {
return nil, err
}
@@ -196,7 +195,7 @@ func AccountWatchBuckets(ctx context.Context, nc *nats.Conn) ([]*kvstore.Bucket,
// existing handle.
func GetOrCreateAccountBucket(ctx context.Context, js jetstream.JetStream, accountID string) (jetstream.KeyValue, error) {
bucket := AccountBucketName(accountID)
kv, err := kvutil.GetOrCreateBucket(ctx, js, bucket, KVBucketEKSAccountHistory)
kv, err := kvstore.GetOrCreateBucket(ctx, js, bucket, KVBucketEKSAccountHistory)
if err != nil {
return nil, fmt.Errorf("failed to create EKS per-account KV bucket %s: %w", bucket, err)
}
@@ -6,7 +6,7 @@ import (
"github.com/mulgadc/spinifex/internal/testkit"
ekscluster "github.com/mulgadc/spinifex/spinifex/domains/eks/cluster"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -23,7 +23,7 @@ func TestAccountWatchBuckets_ReturnsOnePerAccountBucket(t *testing.T) {
require.NoError(t, err)
_, err = ekscluster.GetOrCreateAccountBucket(t.Context(), js, "222222222222")
require.NoError(t, err)
_, err = kvutil.GetOrCreateBucket(t.Context(), js, "something-else", 1)
_, err = kvstore.GetOrCreateBucket(t.Context(), js, "something-else", 1)
require.NoError(t, err)
buckets, err := ekscluster.AccountWatchBuckets(t.Context(), nc)
+5 -6
View File
@@ -11,7 +11,6 @@ import (
"time"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/migrate"
"github.com/nats-io/nats.go"
"github.com/nats-io/nats.go/jetstream"
@@ -35,7 +34,7 @@ const (
maxLBNameClaimRetries = 5
// targetGroupCASAttempts bounds a target-group read-modify-write. It sits
// above the kvutil default because Terraform registers targets at its own
// above the kvstore default because Terraform registers targets at its own
// parallelism (10 by default), every one contending for the same record.
targetGroupCASAttempts = 12
@@ -90,7 +89,7 @@ func NewStore(ctx context.Context, nc *nats.Conn) (*Store, error) {
return nil, fmt.Errorf("failed to get JetStream context: %w", err)
}
kv, err := kvutil.GetOrCreateBucket(ctx, js, KVBucketELBv2, 1)
kv, err := kvstore.GetOrCreateBucket(ctx, js, KVBucketELBv2, 1)
if err != nil {
return nil, fmt.Errorf("failed to create KV bucket %s: %w", KVBucketELBv2, err)
}
@@ -189,7 +188,7 @@ var ErrLoadBalancerNotFound = errors.New("elbv2: load balancer not found")
// concurrency: a writer that loses the race re-reads and retries instead of
// overwriting the winner. mutate returns false to commit nothing.
func (s *Store) UpdateLoadBalancer(ctx context.Context, lbID string, mutate func(*LoadBalancerRecord) (bool, error)) (*LoadBalancerRecord, error) {
return kvutil.Update(ctx, s.kv, KeyPrefixLB+lbID, kvutil.CASConfig{
return kvstore.Update(ctx, s.kv, KeyPrefixLB+lbID, kvstore.CASConfig{
Attempts: targetGroupCASAttempts,
NotFound: ErrLoadBalancerNotFound,
Exhausted: func(_ string, attempts int) error {
@@ -294,7 +293,7 @@ var ErrTargetGroupNotFound = errors.New("elbv2: target group not found")
// concurrency: a writer that loses the race re-reads and retries instead of
// overwriting the winner. mutate returns false to commit nothing.
func (s *Store) UpdateTargetGroup(ctx context.Context, tgID string, mutate func(*TargetGroupRecord) (bool, error)) (*TargetGroupRecord, error) {
return kvutil.Update(ctx, s.kv, KeyPrefixTG+tgID, kvutil.CASConfig{
return kvstore.Update(ctx, s.kv, KeyPrefixTG+tgID, kvstore.CASConfig{
Attempts: targetGroupCASAttempts,
NotFound: ErrTargetGroupNotFound,
Exhausted: func(_ string, attempts int) error {
@@ -652,7 +651,7 @@ func listByPrefixOpt[T any](ctx context.Context, kv jetstream.KeyValue, prefix s
var result []*T
for _, key := range keys {
if key == kvutil.VersionKey {
if key == kvstore.VersionKey {
continue
}
if !strings.HasPrefix(key, prefix) {
+2 -2
View File
@@ -9,7 +9,7 @@ import (
"fmt"
"time"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/nats-io/nats.go/jetstream"
)
@@ -41,7 +41,7 @@ type AccountNameIndex struct {
// NewAccountNameIndex opens (or creates) the reservation bucket at the
// cluster's replica count.
func NewAccountNameIndex(ctx context.Context, js jetstream.JetStream) (*AccountNameIndex, error) {
kv, err := kvutil.GetOrCreateBucket(ctx, js, KVBucketAccountNames, 5)
kv, err := kvstore.GetOrCreateBucket(ctx, js, KVBucketAccountNames, 5)
if err != nil {
return nil, fmt.Errorf("init account names bucket: %w", err)
}
+6 -6
View File
@@ -16,7 +16,7 @@ import (
"github.com/mulgadc/spinifex/spinifex/foundation/aws/arn"
"github.com/mulgadc/spinifex/spinifex/foundation/aws/errors"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
)
// maxGroupsPerUser caps how many groups a single user may belong to. Mirrors the
@@ -106,7 +106,7 @@ func (s *IAMServiceImpl) GetGroup(accountID string, input *iam.GetGroupInput) (*
func (s *IAMServiceImpl) ListGroups(accountID string, input *iam.ListGroupsInput) (*iam.ListGroupsOutput, error) {
ctx := context.Background()
keys, err := kvutil.Keys(ctx, s.groupsBucket)
keys, err := kvstore.Keys(ctx, s.groupsBucket)
if err != nil {
if errors.Is(err, jetstream.ErrNoKeysFound) {
return &iam.ListGroupsOutput{
@@ -127,7 +127,7 @@ func (s *IAMServiceImpl) ListGroups(accountID string, input *iam.ListGroupsInput
// element at all rather than an empty one.
groups := []*iam.Group{}
for _, key := range keys {
if key == kvutil.VersionKey {
if key == kvstore.VersionKey {
continue
}
if !strings.HasPrefix(key, keyPrefix) {
@@ -456,7 +456,7 @@ func (s *IAMServiceImpl) getGroup(ctx context.Context, accountID, groupName stri
// updateUserCAS.
func (s *IAMServiceImpl) updateGroupCAS(ctx context.Context, accountID, groupName string, mutate func(*Group) (bool, error)) error {
defer s.recordLocks.lock("group/" + accountID + "." + groupName)()
_, err := kvutil.Update(ctx, s.groupsBucket, accountID+"."+groupName, iamRecordCASConfig("group", accountID, groupName), mutate)
_, err := kvstore.Update(ctx, s.groupsBucket, accountID+"."+groupName, iamRecordCASConfig("group", accountID, groupName), mutate)
return err
}
@@ -465,7 +465,7 @@ func (s *IAMServiceImpl) updateGroupCAS(ctx context.Context, accountID, groupNam
// unmarshal errors so DeleteGroup cannot succeed while a real-but-unreadable
// member exists. Mirrors findInstanceProfilesForRole.
func (s *IAMServiceImpl) findGroupMembers(ctx context.Context, accountID, groupName string) ([]*User, error) {
keys, err := kvutil.Keys(ctx, s.usersBucket)
keys, err := kvstore.Keys(ctx, s.usersBucket)
if err != nil {
if errors.Is(err, jetstream.ErrNoKeysFound) {
return nil, nil
@@ -476,7 +476,7 @@ func (s *IAMServiceImpl) findGroupMembers(ctx context.Context, accountID, groupN
keyPrefix := accountID + "."
var members []*User
for _, key := range keys {
if key == kvutil.VersionKey {
if key == kvstore.VersionKey {
continue
}
if !strings.HasPrefix(key, keyPrefix) {
+7 -7
View File
@@ -17,7 +17,7 @@ import (
"github.com/mulgadc/spinifex/spinifex/foundation/aws/arn"
"github.com/mulgadc/spinifex/spinifex/foundation/aws/errors"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
)
// Bound on optimistic-concurrency retries when a concurrent writer wins the
@@ -97,7 +97,7 @@ func (s *IAMServiceImpl) GetInstanceProfile(accountID string, input *iam.GetInst
func (s *IAMServiceImpl) ListInstanceProfiles(accountID string, input *iam.ListInstanceProfilesInput) (*iam.ListInstanceProfilesOutput, error) {
ctx := context.Background()
keys, err := kvutil.Keys(ctx, s.instanceProfilesBucket)
keys, err := kvstore.Keys(ctx, s.instanceProfilesBucket)
if err != nil {
if errors.Is(err, jetstream.ErrNoKeysFound) {
return &iam.ListInstanceProfilesOutput{
@@ -118,7 +118,7 @@ func (s *IAMServiceImpl) ListInstanceProfiles(accountID string, input *iam.ListI
// to no element at all rather than an empty one.
profiles := []*iam.InstanceProfile{}
for _, key := range keys {
if key == kvutil.VersionKey {
if key == kvstore.VersionKey {
continue
}
if !strings.HasPrefix(key, keyPrefix) {
@@ -169,7 +169,7 @@ func (s *IAMServiceImpl) DeleteInstanceProfile(accountID string, input *iam.Dele
profileName := *input.InstanceProfileName
// Revision-guarded, so a role attached after the check sends the delete back through it.
_, err := kvutil.DeleteIf(ctx, s.instanceProfilesBucket, accountID+"."+profileName, s.instanceProfileCASConfig(accountID, profileName),
_, err := kvstore.DeleteIf(ctx, s.instanceProfilesBucket, accountID+"."+profileName, s.instanceProfileCASConfig(accountID, profileName),
func(p *InstanceProfile) error {
if p.RoleName != "" {
return awserrors.Errorf(awserrors.ErrorIAMDeleteConflict, "Cannot delete entity, must remove roles from instance profile first.")
@@ -378,12 +378,12 @@ func (s *IAMServiceImpl) getInstanceProfile(ctx context.Context, accountID, prof
// mutate reports whether it changed the record; a false return commits nothing.
func (s *IAMServiceImpl) updateInstanceProfileCAS(ctx context.Context, accountID, profileName string, mutate func(*InstanceProfile) (bool, error)) error {
defer s.recordLocks.lock("instance-profile/" + accountID + "." + profileName)()
_, err := kvutil.Update(ctx, s.instanceProfilesBucket, accountID+"."+profileName, s.instanceProfileCASConfig(accountID, profileName), mutate)
_, err := kvstore.Update(ctx, s.instanceProfilesBucket, accountID+"."+profileName, s.instanceProfileCASConfig(accountID, profileName), mutate)
return err
}
func (s *IAMServiceImpl) instanceProfileCASConfig(accountID, profileName string) kvutil.CASConfig {
return kvutil.CASConfig{
func (s *IAMServiceImpl) instanceProfileCASConfig(accountID, profileName string) kvstore.CASConfig {
return kvstore.CASConfig{
Attempts: instanceProfileCASMaxRetries,
NotFound: instanceProfileNotFound(profileName),
Exhausted: func(string, int) error {
+5 -5
View File
@@ -20,7 +20,7 @@ import (
resourcearn "github.com/mulgadc/spinifex/spinifex/foundation/aws/arn"
"github.com/mulgadc/spinifex/spinifex/foundation/aws/errors"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
)
// Per-account IAM bucket holds account-scoped resources (currently the OIDC provider registry).
@@ -52,7 +52,7 @@ func OIDCProviderARN(accountID, issuerHostPath string) string {
// GetOrCreateIAMAccountBucket opens the per-account IAM bucket, creating it on first use.
func GetOrCreateIAMAccountBucket(ctx context.Context, js jetstream.JetStream, accountID string) (jetstream.KeyValue, error) {
return kvutil.GetOrCreateBucket(ctx, js, IAMAccountBucketName(accountID), 1)
return kvstore.GetOrCreateBucket(ctx, js, IAMAccountBucketName(accountID), 1)
}
// OIDCProviderRecord is the stored shape of a registered OIDC identity provider.
@@ -224,7 +224,7 @@ func (s *IAMServiceImpl) ListOpenIDConnectProviders(accountID string, _ *iam.Lis
return nil, fmt.Errorf("open IAM account bucket: %w", err)
}
keys, err := kvutil.Keys(ctx, kv)
keys, err := kvstore.Keys(ctx, kv)
if err != nil {
if errors.Is(err, jetstream.ErrNoKeysFound) {
return out, nil
@@ -233,7 +233,7 @@ func (s *IAMServiceImpl) ListOpenIDConnectProviders(accountID string, _ *iam.Lis
}
for _, key := range keys {
if key == kvutil.VersionKey || !strings.HasPrefix(key, oidcProvidersKeyPrefix) {
if key == kvstore.VersionKey || !strings.HasPrefix(key, oidcProvidersKeyPrefix) {
continue
}
entry, err := kv.Get(ctx, key)
@@ -356,7 +356,7 @@ func (s *IAMServiceImpl) updateOIDCProviderCAS(ctx context.Context, accountID, a
return fmt.Errorf("open IAM account bucket: %w", err)
}
defer s.recordLocks.lock("oidc-provider/" + accountID + "/" + OIDCProviderKey(issuer))()
_, err = kvutil.Update(ctx, kv, OIDCProviderKey(issuer), iamRecordCASConfig("oidc-provider", accountID, arn), mutate)
_, err = kvstore.Update(ctx, kv, OIDCProviderKey(issuer), iamRecordCASConfig("oidc-provider", accountID, arn), mutate)
return err
}
+5 -5
View File
@@ -16,7 +16,7 @@ import (
iamarn "github.com/mulgadc/bluebottle/pkg/auth"
"github.com/mulgadc/spinifex/spinifex/foundation/aws/errors"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
)
const (
@@ -94,7 +94,7 @@ func (s *IAMServiceImpl) updatePolicyCAS(ctx context.Context, accountID, policyA
return err
}
defer s.recordLocks.lock("policy/" + key)()
_, err = kvutil.Update(ctx, s.policiesBucket, key, cfg, func(p *Policy) (bool, error) {
_, err = kvstore.Update(ctx, s.policiesBucket, key, cfg, func(p *Policy) (bool, error) {
// The key omits the path, so an ARN with the wrong path names no policy.
if p.ARN != policyARN {
return false, errors.New(awserrors.ErrorIAMNoSuchEntity)
@@ -106,14 +106,14 @@ func (s *IAMServiceImpl) updatePolicyCAS(ctx context.Context, accountID, policyA
// policyCASTarget resolves policyARN to its KV key and the CAS settings every
// write to a policy record shares.
func policyCASTarget(accountID, policyARN string) (string, kvutil.CASConfig, error) {
func policyCASTarget(accountID, policyARN string) (string, kvstore.CASConfig, error) {
_, policyName, err := iamarn.ParsePolicyARN(policyARN)
if err != nil {
slog.Debug("policyCASTarget: unparseable policy ARN",
"accountID", accountID, "policyArn", policyARN, "err", err)
return "", kvutil.CASConfig{}, policyNotFound(policyARN)
return "", kvstore.CASConfig{}, policyNotFound(policyARN)
}
return accountID + "." + policyName, kvutil.CASConfig{
return accountID + "." + policyName, kvstore.CASConfig{
Attempts: policyCASMaxRetries,
NotFound: policyNotFound(policyARN),
Exhausted: func(string, int) error {
+6 -6
View File
@@ -18,7 +18,7 @@ import (
iamarn "github.com/mulgadc/bluebottle/pkg/auth"
"github.com/mulgadc/spinifex/spinifex/foundation/aws/arn"
"github.com/mulgadc/spinifex/spinifex/foundation/aws/errors"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
)
const (
@@ -128,7 +128,7 @@ func (s *IAMServiceImpl) GetRole(accountID string, input *iam.GetRoleInput) (*ia
func (s *IAMServiceImpl) ListRoles(accountID string, input *iam.ListRolesInput) (*iam.ListRolesOutput, error) {
ctx := context.Background()
keys, err := kvutil.Keys(ctx, s.rolesBucket)
keys, err := kvstore.Keys(ctx, s.rolesBucket)
if err != nil {
if errors.Is(err, jetstream.ErrNoKeysFound) {
return &iam.ListRolesOutput{
@@ -149,7 +149,7 @@ func (s *IAMServiceImpl) ListRoles(accountID string, input *iam.ListRolesInput)
// element at all rather than an empty one.
roles := []*iam.Role{}
for _, key := range keys {
if key == kvutil.VersionKey {
if key == kvstore.VersionKey {
continue
}
if !strings.HasPrefix(key, keyPrefix) {
@@ -574,7 +574,7 @@ func (s *IAMServiceImpl) getRole(ctx context.Context, accountID, roleName string
// whether it changed the record; a false return commits nothing.
func (s *IAMServiceImpl) updateRoleCAS(ctx context.Context, accountID, roleName string, mutate func(*Role) (bool, error)) error {
defer s.recordLocks.lock("role/" + accountID + "." + roleName)()
_, err := kvutil.Update(ctx, s.rolesBucket, accountID+"."+roleName, kvutil.CASConfig{
_, err := kvstore.Update(ctx, s.rolesBucket, accountID+"."+roleName, kvstore.CASConfig{
Attempts: roleCASMaxRetries,
NotFound: noSuchEntity("role", roleName),
Exhausted: func(string, int) error {
@@ -593,7 +593,7 @@ func (s *IAMServiceImpl) updateRoleCAS(ctx context.Context, accountID, roleName
// per-key Get or unmarshal errors so DeleteRole cannot succeed while a
// real-but-unreadable reference exists.
func (s *IAMServiceImpl) findInstanceProfilesForRole(ctx context.Context, accountID, roleName string) ([]*InstanceProfile, error) {
keys, err := kvutil.Keys(ctx, s.instanceProfilesBucket)
keys, err := kvstore.Keys(ctx, s.instanceProfilesBucket)
if err != nil {
if errors.Is(err, jetstream.ErrNoKeysFound) {
return nil, nil
@@ -604,7 +604,7 @@ func (s *IAMServiceImpl) findInstanceProfilesForRole(ctx context.Context, accoun
keyPrefix := accountID + "."
var profiles []*InstanceProfile
for _, key := range keys {
if key == kvutil.VersionKey {
if key == kvstore.VersionKey {
continue
}
if !strings.HasPrefix(key, keyPrefix) {
+32 -32
View File
@@ -29,7 +29,7 @@ import (
"github.com/mulgadc/spinifex/spinifex/foundation/aws/arn"
awscredentials "github.com/mulgadc/spinifex/spinifex/foundation/aws/credentials"
"github.com/mulgadc/spinifex/spinifex/foundation/aws/errors"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/mulgadc/spinifex/spinifex/foundation/state/migrate"
)
@@ -143,7 +143,7 @@ func NewIAMServiceImpl(ctx context.Context, natsConn *nats.Conn, masterKey []byt
return nil, fmt.Errorf("get JetStream context: %w", err)
}
usersBucket, err := kvutil.GetOrCreateBucket(ctx, js, KVBucketUsers, 10)
usersBucket, err := kvstore.GetOrCreateBucket(ctx, js, KVBucketUsers, 10)
if err != nil {
return nil, fmt.Errorf("init users bucket: %w", err)
}
@@ -151,7 +151,7 @@ func NewIAMServiceImpl(ctx context.Context, natsConn *nats.Conn, masterKey []byt
return nil, fmt.Errorf("migrate %s: %w", KVBucketUsers, err)
}
accessKeysBucket, err := kvutil.GetOrCreateBucket(ctx, js, KVBucketAccessKeys, 5)
accessKeysBucket, err := kvstore.GetOrCreateBucket(ctx, js, KVBucketAccessKeys, 5)
if err != nil {
return nil, fmt.Errorf("init access keys bucket: %w", err)
}
@@ -159,7 +159,7 @@ func NewIAMServiceImpl(ctx context.Context, natsConn *nats.Conn, masterKey []byt
return nil, fmt.Errorf("migrate %s: %w", KVBucketAccessKeys, err)
}
policiesBucket, err := kvutil.GetOrCreateBucket(ctx, js, KVBucketPolicies, 10)
policiesBucket, err := kvstore.GetOrCreateBucket(ctx, js, KVBucketPolicies, 10)
if err != nil {
return nil, fmt.Errorf("init policies bucket: %w", err)
}
@@ -167,7 +167,7 @@ func NewIAMServiceImpl(ctx context.Context, natsConn *nats.Conn, masterKey []byt
return nil, fmt.Errorf("migrate %s: %w", KVBucketPolicies, err)
}
accountsBucket, err := kvutil.GetOrCreateBucket(ctx, js, KVBucketAccounts, 5)
accountsBucket, err := kvstore.GetOrCreateBucket(ctx, js, KVBucketAccounts, 5)
if err != nil {
return nil, fmt.Errorf("init accounts bucket: %w", err)
}
@@ -175,7 +175,7 @@ func NewIAMServiceImpl(ctx context.Context, natsConn *nats.Conn, masterKey []byt
return nil, fmt.Errorf("migrate %s: %w", KVBucketAccounts, err)
}
accountCounterBucket, err := kvutil.GetOrCreateBucket(ctx, js, KVBucketAccountCounter, 5)
accountCounterBucket, err := kvstore.GetOrCreateBucket(ctx, js, KVBucketAccountCounter, 5)
if err != nil {
return nil, fmt.Errorf("init account counter bucket: %w", err)
}
@@ -183,7 +183,7 @@ func NewIAMServiceImpl(ctx context.Context, natsConn *nats.Conn, masterKey []byt
return nil, fmt.Errorf("migrate %s: %w", KVBucketAccountCounter, err)
}
rolesBucket, err := kvutil.GetOrCreateBucket(ctx, js, KVBucketRoles, 10)
rolesBucket, err := kvstore.GetOrCreateBucket(ctx, js, KVBucketRoles, 10)
if err != nil {
return nil, fmt.Errorf("init roles bucket: %w", err)
}
@@ -191,7 +191,7 @@ func NewIAMServiceImpl(ctx context.Context, natsConn *nats.Conn, masterKey []byt
return nil, fmt.Errorf("migrate %s: %w", KVBucketRoles, err)
}
instanceProfilesBucket, err := kvutil.GetOrCreateBucket(ctx, js, KVBucketInstanceProfiles, 10)
instanceProfilesBucket, err := kvstore.GetOrCreateBucket(ctx, js, KVBucketInstanceProfiles, 10)
if err != nil {
return nil, fmt.Errorf("init instance profiles bucket: %w", err)
}
@@ -199,7 +199,7 @@ func NewIAMServiceImpl(ctx context.Context, natsConn *nats.Conn, masterKey []byt
return nil, fmt.Errorf("migrate %s: %w", KVBucketInstanceProfiles, err)
}
groupsBucket, err := kvutil.GetOrCreateBucket(ctx, js, KVBucketGroups, 10)
groupsBucket, err := kvstore.GetOrCreateBucket(ctx, js, KVBucketGroups, 10)
if err != nil {
return nil, fmt.Errorf("init groups bucket: %w", err)
}
@@ -508,7 +508,7 @@ func (s *IAMServiceImpl) GetUser(accountID string, input *iam.GetUserInput) (*ia
func (s *IAMServiceImpl) ListUsers(accountID string, input *iam.ListUsersInput) (*iam.ListUsersOutput, error) {
ctx := context.Background()
keys, err := kvutil.Keys(ctx, s.usersBucket)
keys, err := kvstore.Keys(ctx, s.usersBucket)
if err != nil {
if errors.Is(err, jetstream.ErrNoKeysFound) {
return &iam.ListUsersOutput{
@@ -529,7 +529,7 @@ func (s *IAMServiceImpl) ListUsers(accountID string, input *iam.ListUsersInput)
// element at all rather than an empty one.
users := []*iam.User{}
for _, key := range keys {
if key == kvutil.VersionKey {
if key == kvstore.VersionKey {
continue
}
if !strings.HasPrefix(key, keyPrefix) {
@@ -732,7 +732,7 @@ func (s *IAMServiceImpl) setAccessKeysOwner(ctx context.Context, accountID strin
for _, keyID := range keyIDs {
cfg := iamRecordCASConfig("access key", accountID, keyID)
cfg.NotFound = nil
_, err := kvutil.Update(ctx, s.accessKeysBucket, keyID, cfg, func(ak *AccessKey) (bool, error) {
_, err := kvstore.Update(ctx, s.accessKeysBucket, keyID, cfg, func(ak *AccessKey) (bool, error) {
if ak.UserName == userName {
return false, nil
}
@@ -1189,7 +1189,7 @@ func (s *IAMServiceImpl) seedAdminAccount(ctx context.Context, admin *AdminBoots
// IsEmpty returns true if the users bucket has no entries.
func (s *IAMServiceImpl) IsEmpty() (bool, error) {
ctx := context.Background()
keys, err := kvutil.Keys(ctx, s.usersBucket)
keys, err := kvstore.Keys(ctx, s.usersBucket)
if err != nil {
if errors.Is(err, jetstream.ErrNoKeysFound) {
return true, nil
@@ -1197,7 +1197,7 @@ func (s *IAMServiceImpl) IsEmpty() (bool, error) {
return false, fmt.Errorf("check users bucket: %w", err)
}
for _, key := range keys {
if key != kvutil.VersionKey {
if key != kvstore.VersionKey {
return false, nil
}
}
@@ -1303,7 +1303,7 @@ func (s *IAMServiceImpl) GetAccount(accountID string) (*Account, error) {
// ListAccounts returns all accounts.
func (s *IAMServiceImpl) ListAccounts() ([]*Account, error) {
ctx := context.Background()
keys, err := kvutil.Keys(ctx, s.accountsBucket)
keys, err := kvstore.Keys(ctx, s.accountsBucket)
if err != nil {
if errors.Is(err, jetstream.ErrNoKeysFound) {
return []*Account{}, nil
@@ -1313,7 +1313,7 @@ func (s *IAMServiceImpl) ListAccounts() ([]*Account, error) {
accounts := make([]*Account, 0, len(keys))
for _, key := range keys {
if key == kvutil.VersionKey {
if key == kvstore.VersionKey {
continue
}
entry, err := s.accountsBucket.Get(ctx, key)
@@ -1367,8 +1367,8 @@ func (s *IAMServiceImpl) QuiesceAccount(accountID, operationID string, fence uin
return 0, fmt.Errorf("%s (%s) cannot be %s: %w", accountID, reason, AccountStatusTerminating, ErrAccountUndeletable)
}
account, err := kvutil.Update(context.Background(), s.accountsBucket, accountID,
kvutil.CASConfig{NotFound: errors.New(awserrors.ErrorIAMNoSuchEntity)},
account, err := kvstore.Update(context.Background(), s.accountsBucket, accountID,
kvstore.CASConfig{NotFound: errors.New(awserrors.ErrorIAMNoSuchEntity)},
func(a *Account) (bool, error) {
if a.Status != AccountStatusTerminating {
a.Status = AccountStatusTerminating
@@ -1390,7 +1390,7 @@ func (s *IAMServiceImpl) QuiesceAccount(accountID, operationID string, fence uin
return false, nil
})
if err != nil {
if errors.Is(err, kvutil.ErrWrite) || errors.Is(err, kvutil.ErrRead) || errors.Is(err, kvutil.ErrDecode) {
if errors.Is(err, kvstore.ErrWrite) || errors.Is(err, kvstore.ErrRead) || errors.Is(err, kvstore.ErrDecode) {
return 0, fmt.Errorf("store account quiesce: %w", err)
}
return 0, err
@@ -1421,8 +1421,8 @@ func (s *IAMServiceImpl) SetAccountStatus(accountID, status string) (*Account, e
return nil, fmt.Errorf("account %s: %w", accountID, ErrUseQuiesceAccount)
}
account, err := kvutil.Update(context.Background(), s.accountsBucket, accountID,
kvutil.CASConfig{NotFound: errors.New(awserrors.ErrorIAMNoSuchEntity)},
account, err := kvstore.Update(context.Background(), s.accountsBucket, accountID,
kvstore.CASConfig{NotFound: errors.New(awserrors.ErrorIAMNoSuchEntity)},
func(a *Account) (bool, error) {
if a.Status == AccountStatusTerminating && status != AccountStatusTerminating {
return false, fmt.Errorf("account %s is terminating: %w", accountID, ErrAccountUndeletable)
@@ -1435,7 +1435,7 @@ func (s *IAMServiceImpl) SetAccountStatus(accountID, status string) (*Account, e
return true, nil
})
if err != nil {
if errors.Is(err, kvutil.ErrWrite) || errors.Is(err, kvutil.ErrRead) || errors.Is(err, kvutil.ErrDecode) {
if errors.Is(err, kvstore.ErrWrite) || errors.Is(err, kvstore.ErrRead) || errors.Is(err, kvstore.ErrDecode) {
return nil, fmt.Errorf("store account status: %w", err)
}
return nil, err
@@ -1606,7 +1606,7 @@ func (s *IAMServiceImpl) ListPolicies(accountID string, input *iam.ListPoliciesI
if err := checkEnum("policyUsageFilter", input.PolicyUsageFilter, policyUsageValues); err != nil {
return nil, err
}
keys, err := kvutil.Keys(ctx, s.policiesBucket)
keys, err := kvstore.Keys(ctx, s.policiesBucket)
if err != nil {
if errors.Is(err, jetstream.ErrNoKeysFound) {
return &iam.ListPoliciesOutput{
@@ -1628,7 +1628,7 @@ func (s *IAMServiceImpl) ListPolicies(accountID string, input *iam.ListPoliciesI
onlyAttached := aws.BoolValue(input.OnlyAttached)
policies := []*iam.Policy{}
for _, key := range keys {
if key == kvutil.VersionKey {
if key == kvstore.VersionKey {
continue
}
if !strings.HasPrefix(key, keyPrefix) {
@@ -1712,7 +1712,7 @@ func (s *IAMServiceImpl) DeletePolicy(accountID string, input *iam.DeletePolicyI
}
// Revision-guarded, so a version created after the checks cannot be deleted unseen.
policy, err := kvutil.DeleteIf(ctx, s.policiesBucket, key, cfg, func(p *Policy) error {
policy, err := kvstore.DeleteIf(ctx, s.policiesBucket, key, cfg, func(p *Policy) error {
if p.ARN != policyARN {
return policyNotFound(policyARN)
}
@@ -2267,7 +2267,7 @@ func (s *IAMServiceImpl) buildPolicyAttachments(ctx context.Context, accountID s
keyPrefix := accountID + "."
for _, scan := range scans {
keys, err := kvutil.Keys(ctx, scan.bucket)
keys, err := kvstore.Keys(ctx, scan.bucket)
if err != nil {
if errors.Is(err, jetstream.ErrNoKeysFound) {
continue
@@ -2276,7 +2276,7 @@ func (s *IAMServiceImpl) buildPolicyAttachments(ctx context.Context, accountID s
}
for _, key := range keys {
if key == kvutil.VersionKey {
if key == kvstore.VersionKey {
continue
}
if !strings.HasPrefix(key, keyPrefix) {
@@ -2387,18 +2387,18 @@ func (s *IAMServiceImpl) getUser(ctx context.Context, accountID, userName string
// restore a policy or group membership that a concurrent writer removed.
func (s *IAMServiceImpl) updateUserCAS(ctx context.Context, accountID, userName string, mutate func(*User) (bool, error)) error {
defer s.recordLocks.lock("user/" + accountID + "." + userName)()
_, err := kvutil.Update(ctx, s.usersBucket, accountID+"."+userName, iamRecordCASConfig("user", accountID, userName), mutate)
_, err := kvstore.Update(ctx, s.usersBucket, accountID+"."+userName, iamRecordCASConfig("user", accountID, userName), mutate)
return err
}
// iamRecordCASConfig is the CAS policy for an IAM user, group or OIDC provider
// record: a missing record is NoSuchEntity and exhausted retries are logged.
func iamRecordCASConfig(kind, accountID, name string) kvutil.CASConfig {
func iamRecordCASConfig(kind, accountID, name string) kvstore.CASConfig {
notFound := noSuchEntity(kind, name)
if kind == "oidc-provider" {
notFound = oidcProviderNotFound(name)
}
return kvutil.CASConfig{
return kvstore.CASConfig{
Attempts: roleCASMaxRetries,
NotFound: notFound,
Exhausted: func(string, int) error {
@@ -2828,7 +2828,7 @@ var summaryQuotaDefaults = map[string]int64{
// Get/unmarshal so it stays cheap with many resources. The version key is
// skipped and an empty bucket counts as zero.
func countBucket(ctx context.Context, bucket jetstream.KeyValue, prefix string) (int64, error) {
keys, err := kvutil.Keys(ctx, bucket)
keys, err := kvstore.Keys(ctx, bucket)
if err != nil {
if errors.Is(err, jetstream.ErrNoKeysFound) {
return 0, nil
@@ -2838,7 +2838,7 @@ func countBucket(ctx context.Context, bucket jetstream.KeyValue, prefix string)
var count int64
for _, key := range keys {
if key == kvutil.VersionKey {
if key == kvstore.VersionKey {
continue
}
if strings.HasPrefix(key, prefix) {
+2 -2
View File
@@ -13,7 +13,7 @@ import (
"regexp"
"time"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/nats-io/nats.go/jetstream"
)
@@ -132,7 +132,7 @@ type Store struct {
// Open opens (or creates) the execution bucket.
func Open(ctx context.Context, js jetstream.KeyValueManager) (*Store, error) {
kv, err := kvutil.GetOrCreateBucket(ctx, js, KVBucket, 1)
kv, err := kvstore.GetOrCreateBucket(ctx, js, KVBucket, 1)
if err != nil {
return nil, fmt.Errorf("execution: open bucket: %w", err)
}
+2 -2
View File
@@ -11,7 +11,7 @@ import (
"time"
awserrors "github.com/mulgadc/spinifex/spinifex/foundation/aws/errors"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/nats-io/nats.go/jetstream"
)
@@ -58,7 +58,7 @@ func (s *MetaService) bucket(ctx context.Context, accountID string) (jetstream.K
return kv, nil
}
name := KVAccountBucket(accountID)
kv, err := kvutil.GetOrCreateBucket(ctx, s.js, name, KVBucketAccountHistory)
kv, err := kvstore.GetOrCreateBucket(ctx, s.js, name, KVBucketAccountHistory)
if err != nil {
return nil, fmt.Errorf("lambda: open account bucket %s: %w", name, err)
}
+3 -3
View File
@@ -10,7 +10,7 @@ import (
"strings"
"time"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/nats-io/nats.go/jetstream"
)
@@ -49,7 +49,7 @@ type Store struct {
// NewStore creates/opens the per-AZ lease bucket and returns a Store. ctx bounds
// the bucket get-or-create only; each operation carries its own.
func NewStore(ctx context.Context, js jetstream.KeyValueManager, az string) (*Store, error) {
kv, err := kvutil.GetOrCreateBucket(ctx, js, BucketName(az), 5)
kv, err := kvstore.GetOrCreateBucket(ctx, js, BucketName(az), 5)
if err != nil {
return nil, fmt.Errorf("create dhcp lease KV bucket: %w", err)
}
@@ -152,7 +152,7 @@ func (s *Store) List(ctx context.Context) ([]Entry, error) {
}
var out []Entry
for _, k := range keys {
if k == kvutil.VersionKey {
if k == kvstore.VersionKey {
continue
}
e, err := s.Get(ctx, k)
@@ -14,7 +14,7 @@ import (
"github.com/mulgadc/spinifex/internal/testkit"
ec2igw "github.com/mulgadc/spinifex/spinifex/domains/ec2/igw"
ec2vpc "github.com/mulgadc/spinifex/spinifex/domains/ec2/vpc"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/nats-io/nats.go/jetstream"
)
@@ -22,7 +22,7 @@ import (
// so a test can write to it and have the source find it.
func createIntentBucket(t *testing.T, js jetstream.JetStream, name string) jetstream.KeyValue {
t.Helper()
kv, err := kvutil.GetOrCreateBucket(t.Context(), js, name, 1)
kv, err := kvstore.GetOrCreateBucket(t.Context(), js, name, 1)
if err != nil {
t.Fatalf("create bucket %s: %v", name, err)
}
@@ -19,7 +19,7 @@ import (
"github.com/mulgadc/spinifex/spinifex/domains/network/policy"
"github.com/mulgadc/spinifex/spinifex/domains/network/reconcile"
"github.com/mulgadc/spinifex/spinifex/domains/network/topology"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/nats-io/nats.go/jetstream"
)
@@ -360,11 +360,11 @@ func TestHostIngress_IntentReadFailureNeverPrunes(t *testing.T) {
func TestRunHostIngress_IntentWriteConvergesEveryNodeWithoutLeaseOrEvent(t *testing.T) {
_, nc, js := testutil.StartTestJetStream(t)
ctx := t.Context()
vpcs, err := kvutil.GetOrCreateBucket(ctx, js, ec2vpc.KVBucketVPCs, 1)
vpcs, err := kvstore.GetOrCreateBucket(ctx, js, ec2vpc.KVBucketVPCs, 1)
if err != nil {
t.Fatalf("create VPC bucket: %v", err)
}
igws, err := kvutil.GetOrCreateBucket(ctx, js, ec2igw.KVBucketIGW, 1)
igws, err := kvstore.GetOrCreateBucket(ctx, js, ec2igw.KVBucketIGW, 1)
if err != nil {
t.Fatalf("create IGW bucket: %v", err)
}
+2 -2
View File
@@ -19,7 +19,7 @@ import (
"github.com/mulgadc/spinifex/spinifex/domains/network/external"
"github.com/mulgadc/spinifex/spinifex/domains/network/policy"
"github.com/mulgadc/spinifex/spinifex/domains/network/topology"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/mulgadc/spinifex/spinifex/runtime/compute/vm"
"github.com/nats-io/nats.go/jetstream"
)
@@ -236,7 +236,7 @@ func matchesLocalAZ(vpcAZ, localAZ string) bool {
return vpcAZ == "" || vpcAZ == localAZ
}
func keyIsVersion(key string) bool { return key == kvutil.VersionKey }
func keyIsVersion(key string) bool { return key == kvstore.VersionKey }
func loadVPCs(ctx context.Context, js jetstream.JetStream, localAZ string, out map[string]topology.VPCSpec) (map[string]struct{}, error) {
localVPCs := make(map[string]struct{})
@@ -11,7 +11,7 @@ import (
"testing"
vpc "github.com/mulgadc/spinifex/spinifex/domains/ec2/vpc"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/nats-io/nats.go/jetstream"
)
@@ -20,7 +20,7 @@ import (
// creating the bucket if absent — mirroring how the VPC handlers persist state.
func seedKV(t *testing.T, js jetstream.JetStream, bucket, key string, rec any) {
t.Helper()
kv, err := kvutil.GetOrCreateBucket(t.Context(), js, bucket, 10)
kv, err := kvstore.GetOrCreateBucket(t.Context(), js, bucket, 10)
if err != nil {
t.Fatalf("GetOrCreateBucket %s: %v", bucket, err)
}
+1 -2
View File
@@ -10,7 +10,6 @@ import (
"time"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/nats-io/nats.go/jetstream"
)
@@ -196,7 +195,7 @@ func (s *UsageStore) TokensThisPeriod(ctx context.Context, accountID string) (in
if err != nil {
return 0, err
}
keys, err := kvutil.Keys(ctx, kv)
keys, err := kvstore.Keys(ctx, kv)
if err != nil {
if errors.Is(err, jetstream.ErrNoKeysFound) {
return 0, nil
+1 -2
View File
@@ -13,7 +13,6 @@ import (
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvlease"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/migrate"
"github.com/nats-io/nats.go"
"github.com/nats-io/nats.go/jetstream"
@@ -244,7 +243,7 @@ func InitLeaderBucket(ctx context.Context, js jetstream.JetStream) (jetstream.Ke
// AccountBucketNames lists every RDS per-account bucket in the cluster. The reconciler and the DNS
// desired-set both need a cross-tenant view, which only this provides.
func AccountBucketNames(ctx context.Context, js jetstream.JetStream) ([]string, error) {
all, err := kvutil.BucketNames(ctx, js)
all, err := kvstore.BucketNames(ctx, js)
if err != nil {
return nil, err
}
@@ -6,7 +6,7 @@ import (
"github.com/mulgadc/spinifex/internal/testkit"
rdsinstance "github.com/mulgadc/spinifex/spinifex/domains/rds/instance"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -23,7 +23,7 @@ func TestAccountWatchBuckets_ReturnsOnePerAccountBucket(t *testing.T) {
require.NoError(t, err)
_, err = rdsinstance.GetOrCreateAccountBucket(t.Context(), js, "222222222222")
require.NoError(t, err)
_, err = kvutil.GetOrCreateBucket(t.Context(), js, "something-else", 1)
_, err = kvstore.GetOrCreateBucket(t.Context(), js, "something-else", 1)
require.NoError(t, err)
buckets, err := rdsinstance.AccountWatchBuckets(t.Context(), js)
+4 -5
View File
@@ -7,7 +7,6 @@ import (
"time"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/nats-io/nats.go/jetstream"
)
@@ -145,11 +144,11 @@ func NewStore(js jetstream.JetStream) *Store {
}
}
func zoneKey(account, id string) string { return keyPrefixZone + kvutil.AccountKey(account, id) }
func rrsetsKey(account, id string) string { return keyPrefixRRSets + kvutil.AccountKey(account, id) }
func changeKey(account, id string) string { return keyPrefixChange + kvutil.AccountKey(account, id) }
func zoneKey(account, id string) string { return keyPrefixZone + kvstore.AccountKey(account, id) }
func rrsetsKey(account, id string) string { return keyPrefixRRSets + kvstore.AccountKey(account, id) }
func changeKey(account, id string) string { return keyPrefixChange + kvstore.AccountKey(account, id) }
func refKey(account, ref string) string {
return keyPrefixCallerRf + kvutil.AccountKey(account, refDigest(ref))
return keyPrefixCallerRf + kvstore.AccountKey(account, refDigest(ref))
}
// maxIssuedIDLength bounds the IDs a caller can look up. An overlong key
@@ -3,7 +3,7 @@ package jetstream
import (
"context"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
jsapi "github.com/nats-io/nats.go/jetstream"
)
@@ -12,18 +12,18 @@ import (
// whose stored configuration could expire, cap or evict records, or holds
// fewer replicas than the cluster needs.
func OpenDurableKV(ctx context.Context, js jsapi.JetStream, bucket, description string) (jsapi.KeyValue, error) {
kv, err := kvutil.GetOrCreateBucketWithOptions(ctx, js, kvutil.BucketOptions{
kv, err := kvstore.GetOrCreateBucketWithOptions(ctx, js, kvstore.BucketOptions{
Name: bucket, Description: description, History: 1,
})
if err != nil {
return nil, err
}
return kvutil.VerifyDurable(ctx, kv, bucket)
return kvstore.VerifyDurable(ctx, kv, bucket)
}
// BindDurableKV binds an existing SQS authority bucket without ever creating
// one, so a reader that must not provision state fails closed when it is absent,
// and refuses it on the same durability terms as OpenDurableKV.
func BindDurableKV(ctx context.Context, js jsapi.JetStream, bucket string) (jsapi.KeyValue, error) {
return kvutil.BindDurable(ctx, js, bucket)
return kvstore.BindDurable(ctx, js, bucket)
}
@@ -6,7 +6,7 @@ import (
"fmt"
"strings"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/nats-io/nats.go"
jsapi "github.com/nats-io/nats.go/jetstream"
)
@@ -15,7 +15,7 @@ import (
// message get, which only the stream leader answers. nats.go switches to a
// direct get, which followers also answer, whenever the stream allows it.
func leaderLastMsg(ctx context.Context, js jsapi.JetStream, stream, subject string) (*jsapi.RawStreamMsg, error) {
return kvutil.LeaderMsgGet(ctx, js, stream, kvutil.MsgGetRequest{LastFor: subject})
return kvstore.LeaderMsgGet(ctx, js, stream, kvstore.MsgGetRequest{LastFor: subject})
}
// LeaderKVGet reads key from an SQS KV bucket through the stream leader and
@@ -38,11 +38,11 @@ func LeaderKVGet(ctx context.Context, js jsapi.JetStream, bucket, key string) ([
// to sequence bound, with one leader message get per record.
func leaderStateKeys(ctx context.Context, js jsapi.JetStream, bucket, keyPrefix string, bound uint64) (map[string]struct{}, error) {
stream, subjectPrefix := "KV_"+bucket, "$KV."+bucket+"."
req := kvutil.MsgGetRequest{NextFor: subjectPrefix + keyPrefix + ">"}
req := kvstore.MsgGetRequest{NextFor: subjectPrefix + keyPrefix + ">"}
keys := map[string]struct{}{}
for after := uint64(0); after < bound; {
req.Seq = after + 1
m, err := kvutil.LeaderMsgGet(ctx, js, stream, req)
m, err := kvstore.LeaderMsgGet(ctx, js, stream, req)
switch {
case errors.Is(err, jsapi.ErrMsgNotFound):
return keys, nil
@@ -16,7 +16,7 @@ import (
"github.com/mulgadc/bluebottle/pkg/safecast"
"github.com/mulgadc/spinifex/spinifex/domains/sqs/message"
"github.com/mulgadc/spinifex/spinifex/foundation/state/clustersize"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/nats-io/nats.go"
jsapi "github.com/nats-io/nats.go/jetstream"
)
@@ -101,7 +101,7 @@ func New(ctx context.Context, js jsapi.JetStream, cfg Config) (*Store, error) {
}
for i := range cfg.StateBuckets {
shard := message.Shard(safecast.IntToUint32(i))
kv, err := kvutil.GetOrCreateBucketWithOptions(ctx, js, kvutil.BucketOptions{
kv, err := kvstore.GetOrCreateBucketWithOptions(ctx, js, kvstore.BucketOptions{
Name: BucketName(shard),
Description: "SQS per-message state, shared by every queue allocated this state shard",
History: 1,
@@ -960,10 +960,10 @@ func shardFilter(shard message.Shard) string {
// (after, bound] until fn returns false. Each match is a leader message get,
// which returns the whole body: a follower's consumer may lag applied purges.
func readRange(ctx context.Context, js jsapi.JetStream, stream, filter string, after, bound uint64, fn func(*jsapi.RawStreamMsg) (bool, error)) (uint64, bool, error) {
req := kvutil.MsgGetRequest{NextFor: filter}
req := kvstore.MsgGetRequest{NextFor: filter}
for after < bound {
req.Seq = after + 1
msg, err := kvutil.LeaderMsgGet(ctx, js, stream, req)
msg, err := kvstore.LeaderMsgGet(ctx, js, stream, req)
switch {
case errors.Is(err, jsapi.ErrMsgNotFound):
return bound, true, nil
@@ -17,7 +17,7 @@ import (
"github.com/golang-jwt/jwt/v5"
iamdomain "github.com/mulgadc/spinifex/spinifex/domains/iam"
"github.com/mulgadc/spinifex/spinifex/foundation/aws/errors"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -82,7 +82,7 @@ func newWebIdentityFixture(t *testing.T, svc *STSServiceImpl, accountID string)
// Register the OIDC provider on the role-account's IAM bucket. Bucket is
// created on-the-fly here — the IAM CreateOpenIDConnectProvider API lands
// in Sprint 6e.
iamKV, err := kvutil.GetOrCreateBucket(t.Context(), svc.js,
iamKV, err := kvstore.GetOrCreateBucket(t.Context(), svc.js,
iamdomain.IAMAccountBucketName(accountID), iamdomain.KVBucketIAMAccountVersion)
require.NoError(t, err)
_, err = iamKV.Put(t.Context(), iamdomain.OIDCProviderKey(issuer), []byte(`{"registered":true}`))
@@ -19,7 +19,7 @@ import (
iamdomain "github.com/mulgadc/spinifex/spinifex/domains/iam"
awserrors "github.com/mulgadc/spinifex/spinifex/foundation/aws/errors"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
)
// The TestCurrentBehaviour_ tests in this file pin today's AssumeRoleWithWebIdentity
@@ -43,7 +43,7 @@ func assumeWebIdentity(svc *STSServiceImpl, role *iam.Role, token string) (*sts.
// registerIssuer records issuer as an OIDC provider in the role account's IAM bucket.
func registerIssuer(t *testing.T, svc *STSServiceImpl, roleAccountID, issuer string) {
t.Helper()
iamKV, err := kvutil.GetOrCreateBucket(t.Context(), svc.js,
iamKV, err := kvstore.GetOrCreateBucket(t.Context(), svc.js,
iamdomain.IAMAccountBucketName(roleAccountID), iamdomain.KVBucketIAMAccountVersion)
require.NoError(t, err)
_, err = iamKV.Put(t.Context(), iamdomain.OIDCProviderKey(issuer), []byte(`{"registered":true}`))
+2 -3
View File
@@ -12,7 +12,6 @@ import (
"time"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/migrate"
"github.com/mulgadc/spinifex/spinifex/foundation/telemetry"
"github.com/nats-io/nats.go/jetstream"
@@ -236,7 +235,7 @@ func (s *STSServiceImpl) deleteSessions(ctx context.Context, match func(*Session
return 0, fmt.Errorf("open session credential bucket: %w", err)
}
keys, err := kvutil.Keys(ctx, bucket)
keys, err := kvstore.Keys(ctx, bucket)
if err != nil {
if errors.Is(err, jetstream.ErrNoKeysFound) {
return 0, nil
@@ -247,7 +246,7 @@ func (s *STSServiceImpl) deleteSessions(ctx context.Context, match func(*Session
var deleted int
var errs []error
for _, key := range keys {
if key == kvutil.VersionKey {
if key == kvstore.VersionKey {
continue
}
entry, err := bucket.Get(ctx, key)
@@ -11,7 +11,6 @@ import (
"github.com/mulgadc/spinifex/internal/testkit"
iamdomain "github.com/mulgadc/spinifex/spinifex/domains/iam"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/nats-io/nats.go/jetstream"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -62,7 +61,7 @@ func TestInitSessionCredentialsBucket_StampsVersion(t *testing.T) {
kv, err := store.KV(t.Context())
require.NoError(t, err)
version, err := kvutil.ReadVersion(t.Context(), kv)
version, err := kvstore.ReadVersion(t.Context(), kv)
require.NoError(t, err)
assert.Equal(t, KVBucketSessionCredentialsVersion, version)
}
@@ -131,7 +130,7 @@ func TestPutSessionCredential_RejectsNonASIAPrefix(t *testing.T) {
if !errors.Is(err, jetstream.ErrNoKeysFound) {
require.NoError(t, err)
for _, k := range keys {
if k == kvutil.VersionKey {
if k == kvstore.VersionKey {
continue
}
t.Fatalf("unexpected key written to session bucket: %q", k)
@@ -280,7 +279,7 @@ func TestSweepExpired_IgnoresVersionKey(t *testing.T) {
svc, _ := newTestSetup(t)
assert.Equal(t, 0, svc.sweep(t.Context(), time.Now().UTC()))
_, err := sessionsKV(t, svc).Get(t.Context(), kvutil.VersionKey)
_, err := sessionsKV(t, svc).Get(t.Context(), kvstore.VersionKey)
require.NoError(t, err, "version key must survive the sweep")
}
@@ -11,7 +11,7 @@ import (
"github.com/aws/aws-sdk-go/service/sts"
iamdomain "github.com/mulgadc/spinifex/spinifex/domains/iam"
"github.com/mulgadc/spinifex/spinifex/foundation/aws/errors"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -186,7 +186,7 @@ func TestAssumeRoleWithWebIdentity_TrailingSlashIssuerProviderStillGrants(t *tes
hostPath := strings.TrimPrefix(f.issuer, "https://")
f.federatedARN = iamdomain.OIDCProviderARN(testCallerAccountID, hostPath)
iamKV, err := kvutil.GetOrCreateBucket(t.Context(), svc.js,
iamKV, err := kvstore.GetOrCreateBucket(t.Context(), svc.js,
iamdomain.IAMAccountBucketName(testCallerAccountID), iamdomain.KVBucketIAMAccountVersion)
require.NoError(t, err)
_, err = iamKV.Put(t.Context(), iamdomain.OIDCProviderKey(f.issuer), []byte(`{"registered":true}`))
@@ -15,7 +15,7 @@ import (
"github.com/nats-io/nats.go/jetstream"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
)
const (
@@ -61,7 +61,7 @@ type Store[T any] struct {
// from NewStore so several typed stores can share one bucket without rebinding
// it per request.
func OpenBucket(ctx context.Context, js jetstream.JetStream, bucket string, ttl time.Duration) (jetstream.KeyValue, error) {
kv, err := kvutil.GetOrCreateBucketWithTTL(ctx, js, bucket, 1, ttl)
kv, err := kvstore.GetOrCreateBucketWithTTL(ctx, js, bucket, 1, ttl)
if err != nil {
return nil, fmt.Errorf("open idempotency bucket %s: %w", bucket, err)
}
@@ -3,7 +3,7 @@
// nodes each of its JetStream streams is replicated across.
//
// It is its own package because the fact is declared in one place,
// config.LoadConfig, and read in another, kvutil, and those sit either side of
// config.LoadConfig, and read in another, kvstore, and those sit either side of
// packages that would otherwise import each other.
//
// The number matters because a stream on one replica lives on one
+2 -2
View File
@@ -8,7 +8,7 @@ import (
"time"
"github.com/mulgadc/spinifex/spinifex/foundation/state/clustersize"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
"github.com/mulgadc/spinifex/spinifex/foundation/state/migrate"
telemetry "github.com/mulgadc/spinifex/spinifex/foundation/telemetry"
"github.com/nats-io/nats.go"
@@ -65,7 +65,7 @@ func OpenBucket(ctx context.Context, js jetstream.JetStream, cfg BucketConfig) (
// tick, so refusing here would stop every reconciler sharing the bucket
// — the outage this replica count exists to prevent — over a bucket that
// is present and quorate at the count it already has.
kvutil.TryRaiseBucketReplicas(ctx, js, cfg.Name, replicas)
kvstore.TryRaiseBucketReplicas(ctx, js, cfg.Name, replicas)
}
if err != nil {
return nil, fmt.Errorf("kvlease: open or create lease bucket %s: %w", cfg.Name, err)
@@ -1,4 +1,4 @@
package kvutil
package kvstore
// AccountKey returns the conventional account-scoped KV key:
// "{accountID}.{resourceID}".
@@ -1,4 +1,4 @@
package kvutil
package kvstore
import "testing"
+10 -11
View File
@@ -1,6 +1,6 @@
// Package kvstore provides a typed store over a single JetStream KV bucket:
// one memoised get-or-create accessor and a JSON codec for the record type,
// replacing the accessor each caller used to hand-roll.
// Package kvstore is the shared JetStream KV layer: buckets created at the
// cluster's replica count, schema versions, CAS loops on any bucket, and a
// typed store over a single bucket. Every operation honours the caller's context.
package kvstore
import (
@@ -11,7 +11,6 @@ import (
"sync"
"time"
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil"
"github.com/nats-io/nats.go/jetstream"
)
@@ -127,7 +126,7 @@ func (b *Bucket) LastSequence(ctx context.Context, filter string) (uint64, error
return 0, errors.New(b.cfg.Missing)
}
msg, err := kvutil.LeaderMsgGet(ctx, js, "KV_"+b.cfg.Name, kvutil.MsgGetRequest{LastFor: "$KV." + b.cfg.Name + "." + filter})
msg, err := LeaderMsgGet(ctx, js, "KV_"+b.cfg.Name, MsgGetRequest{LastFor: "$KV." + b.cfg.Name + "." + filter})
if errors.Is(err, jetstream.ErrMsgNotFound) {
return 0, nil
}
@@ -162,7 +161,7 @@ func (b *Bucket) Reopen(ctx context.Context) (jetstream.KeyValue, error) {
if !b.cfg.RecreateIfMissing {
return nil, fmt.Errorf("kvstore: reopen %s: %w", b.cfg.Name, err)
}
if !errors.Is(err, jetstream.ErrBucketNotFound) && !kvutil.IsStreamUnavailable(err) {
if !errors.Is(err, jetstream.ErrBucketNotFound) && !IsStreamUnavailable(err) {
return nil, fmt.Errorf("kvstore: reopen %s: %w", b.cfg.Name, err)
}
slog.WarnContext(ctx, "kvstore: bucket stream lost, recreating", "bucket", b.cfg.Name)
@@ -191,7 +190,7 @@ func (b *Bucket) withKV(ctx context.Context, op func(jetstream.KeyValue) error)
return err
}
err = op(kv)
if err == nil || !kvutil.IsStreamUnavailable(err) {
if err == nil || !IsStreamUnavailable(err) {
return err
}
@@ -343,7 +342,7 @@ func (b *Bucket) WatchFrom(ctx context.Context, filter string, revision uint64)
return w, nil
}
// open attaches to the bucket or creates it through kvutil, which sets the
// open attaches to the bucket or creates it through GetOrCreateBucket*, which sets the
// cluster's replica count on every bucket, TTL-backed or not.
func (b *Bucket) open(ctx context.Context) (jetstream.KeyValue, error) {
switch {
@@ -354,19 +353,19 @@ func (b *Bucket) open(ctx context.Context) (jetstream.KeyValue, error) {
}
return kv, nil
case b.cfg.TTL > 0:
return kvutil.GetOrCreateBucketWithOptions(ctx, b.js, kvutil.BucketOptions{
return GetOrCreateBucketWithOptions(ctx, b.js, BucketOptions{
Name: b.cfg.Name,
Description: b.cfg.Description,
History: b.cfg.History,
TTL: b.cfg.TTL,
})
case b.cfg.Description != "":
return kvutil.GetOrCreateBucketWithOptions(ctx, b.js, kvutil.BucketOptions{
return GetOrCreateBucketWithOptions(ctx, b.js, BucketOptions{
Name: b.cfg.Name,
Description: b.cfg.Description,
History: b.cfg.History,
})
default:
return kvutil.GetOrCreateBucket(ctx, b.js, b.cfg.Name, b.cfg.History)
return GetOrCreateBucket(ctx, b.js, b.cfg.Name, b.cfg.History)
}
}

Some files were not shown because too many files have changed in this diff Show More