diff --git a/cmd/spinifex/cmd/admin.go b/cmd/spinifex/cmd/admin.go index d3e2c1e76..e43ac0cad 100644 --- a/cmd/spinifex/cmd/admin.go +++ b/cmd/spinifex/cmd/admin.go @@ -32,6 +32,7 @@ import ( "github.com/mulgadc/spinifex/spinifex/admin" "github.com/mulgadc/spinifex/spinifex/bootstrap/config" "github.com/mulgadc/spinifex/spinifex/domains/ec2/ebs/metadata" + awsidentifiers "github.com/mulgadc/spinifex/spinifex/foundation/aws/identifiers" "github.com/mulgadc/spinifex/spinifex/foundation/state/clustersize" handlers_ec2_vpc "github.com/mulgadc/spinifex/spinifex/handlers/ec2/vpc" handlers_iam "github.com/mulgadc/spinifex/spinifex/handlers/iam" @@ -778,7 +779,7 @@ func runimagesImportCmd(cmd *cobra.Command, args []string) { } // Describe the image/AMI as the control-plane document it will become. - volumeId := utils.GenerateResourceID("ami") + volumeId := awsidentifiers.GenerateResourceID("ami") amiName := fmt.Sprintf("ami-%s-%s-%s", image.Distro, image.Version, image.Arch) if amiNameOverride != "" { amiName = amiNameOverride @@ -1726,9 +1727,9 @@ func runAdminInit(cmd *cobra.Command, args []string) { IPSecEnabled: ipsecEnabled, } if externalMode != "" { - bootstrapVpcId := utils.GenerateResourceID("vpc") - bootstrapSubnetId := utils.GenerateResourceID("subnet") - bootstrapIgwId := utils.GenerateResourceID("igw") + bootstrapVpcId := awsidentifiers.GenerateResourceID("vpc") + bootstrapSubnetId := awsidentifiers.GenerateResourceID("subnet") + bootstrapIgwId := awsidentifiers.GenerateResourceID("igw") networkConfig.ExternalMode = externalMode networkConfig.Pools = externalPools networkConfig.PoolDNSServers = dnsServers @@ -1815,9 +1816,9 @@ func runAdminInit(cmd *cobra.Command, args []string) { // These are written to [bootstrap] in spinifex.toml so vpcd can // create OVN topology on first boot. The daemon uses the same IDs // when it creates the records in NATS KV via EnsureDefaultVPC. - bootstrapVpcId := utils.GenerateResourceID("vpc") - bootstrapSubnetId := utils.GenerateResourceID("subnet") - bootstrapIgwId := utils.GenerateResourceID("igw") + bootstrapVpcId := awsidentifiers.GenerateResourceID("vpc") + bootstrapSubnetId := awsidentifiers.GenerateResourceID("subnet") + bootstrapIgwId := awsidentifiers.GenerateResourceID("igw") configSettings := admin.ConfigSettings{ AccessKey: accessKey, diff --git a/cmd/spinifex/cmd/admin_ochre.go b/cmd/spinifex/cmd/admin_ochre.go index f33cf4ba2..6e798df75 100644 --- a/cmd/spinifex/cmd/admin_ochre.go +++ b/cmd/spinifex/cmd/admin_ochre.go @@ -21,6 +21,7 @@ import ( "github.com/mulgadc/spinifex/spinifex/admin" "github.com/mulgadc/spinifex/spinifex/bootstrap/config" "github.com/mulgadc/spinifex/spinifex/domains/ec2/ebs/metadata" + awsidentifiers "github.com/mulgadc/spinifex/spinifex/foundation/aws/identifiers" gateway_bedrock "github.com/mulgadc/spinifex/spinifex/gateway/bedrock" "github.com/mulgadc/spinifex/spinifex/gateway/bedrock/hfhub" "github.com/mulgadc/spinifex/spinifex/providers/ebs" @@ -757,7 +758,7 @@ func runStageWeights(ctx context.Context, store objectstore.ObjectStore, weights // a live provider -- so it is exercised live rather than in unit tests. func materializeWeightsVolume(ctx context.Context, provider ebsprovider.EBSProvider, nodeID string, node config.Config, downloadDir string, contentBytes int64) (string, error) { tmpDir := filepath.Dir(downloadDir) - volumeId := utils.GenerateResourceID("vol") + volumeId := awsidentifiers.GenerateResourceID("vol") imagePath := filepath.Join(tmpDir, volumeId+".img") fmt.Println("Building filesystem image ...") diff --git a/cmd/spinifex/cmd/admin_ochre_vector.go b/cmd/spinifex/cmd/admin_ochre_vector.go index 321eed7cd..799aa655c 100644 --- a/cmd/spinifex/cmd/admin_ochre_vector.go +++ b/cmd/spinifex/cmd/admin_ochre_vector.go @@ -8,6 +8,7 @@ import ( "strconv" "time" + awsidentifiers "github.com/mulgadc/spinifex/spinifex/foundation/aws/identifiers" handlers_ochrevector "github.com/mulgadc/spinifex/spinifex/handlers/ochrevector" "github.com/mulgadc/spinifex/spinifex/utils" "github.com/pterm/pterm" @@ -326,7 +327,7 @@ func parseFilterFlag(raw string) (*handlers_ochrevector.Filter, error) { // creates it. Returns the message to print. func runIndexCreate(ctx context.Context, svc handlers_ochrevector.VectorService, name string, dimension int, model string) (string, error) { out, err := svc.CreateIndex(ctx, &handlers_ochrevector.CreateIndexRequest{ - IndexID: utils.GenerateResourceID("idx"), + IndexID: awsidentifiers.GenerateResourceID("idx"), Name: name, Dimension: dimension, EmbeddingModel: model, diff --git a/spinifex/daemon/daemon_handlers_capacityreservation.go b/spinifex/daemon/daemon_handlers_capacityreservation.go index 0db6c7dcf..158ae5b6f 100644 --- a/spinifex/daemon/daemon_handlers_capacityreservation.go +++ b/spinifex/daemon/daemon_handlers_capacityreservation.go @@ -8,6 +8,7 @@ import ( "github.com/aws/aws-sdk-go/service/ec2" "github.com/mulgadc/spinifex/spinifex/domains/ec2/instancetypes" "github.com/mulgadc/spinifex/spinifex/foundation/aws/errors" + awsidentifiers "github.com/mulgadc/spinifex/spinifex/foundation/aws/identifiers" "github.com/mulgadc/spinifex/spinifex/utils" "github.com/nats-io/nats.go" ) @@ -44,7 +45,7 @@ func (d *Daemon) handleEC2CreateCapacityReservation(msg *nats.Msg) string { } rec := &capacityReservation{ - ID: utils.GenerateResourceID("cr"), + ID: awsidentifiers.GenerateResourceID("cr"), AccountID: accountID, InstanceType: instanceType, AvailabilityZone: aws.StringValue(input.AvailabilityZone), diff --git a/spinifex/daemon/daemon_system_instance.go b/spinifex/daemon/daemon_system_instance.go index c070cac27..afd7fc979 100644 --- a/spinifex/daemon/daemon_system_instance.go +++ b/spinifex/daemon/daemon_system_instance.go @@ -15,6 +15,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" "github.com/mulgadc/spinifex/contracts/ec2/v1" + awsidentifiers "github.com/mulgadc/spinifex/spinifex/foundation/aws/identifiers" "github.com/mulgadc/spinifex/spinifex/foundation/aws/tags" handlers_ec2_vpc "github.com/mulgadc/spinifex/spinifex/handlers/ec2/vpc" handlers_elbv2 "github.com/mulgadc/spinifex/spinifex/handlers/elbv2" @@ -135,7 +136,7 @@ func (d *Daemon) LaunchSystemInstance(input *handlers_elbv2.SystemInstanceInput) {Key: aws.String(tags.ManagedByKey), Value: aws.String(tags.ManagedByELBv2)}, } instance.Reservation = &ec2.Reservation{} - instance.Reservation.SetReservationId(utils.GenerateResourceID("r")) + instance.Reservation.SetReservationId(awsidentifiers.GenerateResourceID("r")) instance.Reservation.SetOwnerId(accountID) instance.Reservation.Instances = []*ec2.Instance{ec2Instance} // Mirror the customer-instance path (handlers/ec2/instance/service_impl.go:334) diff --git a/spinifex/foundation/aws/identifiers/resource_id.go b/spinifex/foundation/aws/identifiers/resource_id.go new file mode 100644 index 000000000..14ebbef78 --- /dev/null +++ b/spinifex/foundation/aws/identifiers/resource_id.go @@ -0,0 +1,17 @@ +package identifiers + +import ( + "crypto/rand" + "encoding/hex" +) + +// GenerateResourceID generates a unique resource ID with the given prefix. +// Format: {prefix}-{17 hex chars} using crypto/rand. +func GenerateResourceID(prefix string) string { + b := make([]byte, 9) + if _, err := rand.Read(b); err != nil { + //nolint:forbidigo // Continuing after the CSPRNG fails could create predictable resource IDs. + panic("crypto/rand failed: " + err.Error()) + } + return prefix + "-" + hex.EncodeToString(b)[:17] +} diff --git a/spinifex/foundation/aws/identifiers/resource_id_test.go b/spinifex/foundation/aws/identifiers/resource_id_test.go new file mode 100644 index 000000000..5f50dc094 --- /dev/null +++ b/spinifex/foundation/aws/identifiers/resource_id_test.go @@ -0,0 +1,35 @@ +package identifiers + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGenerateResourceID(t *testing.T) { + tests := []struct { + prefix string + }{ + {"i"}, + {"r"}, + {"vol"}, + {"snap"}, + {"key"}, + {"eigw"}, + {"ami"}, + } + + for _, tt := range tests { + t.Run(tt.prefix, func(t *testing.T) { + id := GenerateResourceID(tt.prefix) + assert.True(t, strings.HasPrefix(id, tt.prefix+"-")) + // prefix + "-" + 17 hex chars + assert.Len(t, id, len(tt.prefix)+1+17) + + // Verify uniqueness + id2 := GenerateResourceID(tt.prefix) + assert.NotEqual(t, id, id2) + }) + } +} diff --git a/spinifex/gateway/ec2/instance/IamInstanceProfileAssociation_test.go b/spinifex/gateway/ec2/instance/IamInstanceProfileAssociation_test.go index 967f2be80..70d2e8a6b 100644 --- a/spinifex/gateway/ec2/instance/IamInstanceProfileAssociation_test.go +++ b/spinifex/gateway/ec2/instance/IamInstanceProfileAssociation_test.go @@ -13,6 +13,7 @@ import ( "github.com/mulgadc/spinifex/contracts/ec2/v1" "github.com/mulgadc/spinifex/spinifex/foundation/aws/arn" "github.com/mulgadc/spinifex/spinifex/foundation/aws/errors" + awsidentifiers "github.com/mulgadc/spinifex/spinifex/foundation/aws/identifiers" handlers_iam "github.com/mulgadc/spinifex/spinifex/handlers/iam" "github.com/mulgadc/spinifex/spinifex/utils" "github.com/nats-io/nats.go" @@ -686,7 +687,7 @@ func TestCountInstanceProfileAssociations_NoMatches(t *testing.T) { // utils generator would break replay parsing of Disassociate/Replace. func TestAssociationIDFormat(t *testing.T) { t.Parallel() - id := utils.GenerateResourceID("iip-assoc") + id := awsidentifiers.GenerateResourceID("iip-assoc") assert.True(t, strings.HasPrefix(id, "iip-assoc-"), "GenerateResourceID must preserve the AWS-style hyphen between prefix and suffix") suffix := strings.TrimPrefix(id, "iip-assoc-") diff --git a/spinifex/gateway/ec2/instance/placement.go b/spinifex/gateway/ec2/instance/placement.go index 7fa70549a..db69731e4 100644 --- a/spinifex/gateway/ec2/instance/placement.go +++ b/spinifex/gateway/ec2/instance/placement.go @@ -15,6 +15,7 @@ import ( "github.com/aws/aws-sdk-go/service/ec2" clusterv1 "github.com/mulgadc/spinifex/contracts/cluster/v1" "github.com/mulgadc/spinifex/spinifex/foundation/aws/errors" + awsidentifiers "github.com/mulgadc/spinifex/spinifex/foundation/aws/identifiers" handlers_ec2_placementgroup "github.com/mulgadc/spinifex/spinifex/handlers/ec2/placementgroup" "github.com/mulgadc/spinifex/spinifex/utils" @@ -57,7 +58,7 @@ func distributeInstances(ctx context.Context, input *ec2.RunInstancesInput, nats allocations := spreadAllocate(nodes, launchCount) // Minted once here and handed to every node, so a launch that spreads // across nodes comes back as one reservation instead of one per node. - reservationID := utils.GenerateResourceID("r") + reservationID := awsidentifiers.GenerateResourceID("r") results := launchOnNodes(ctx, allocations, input, natsConn, accountID, reservationID) return aggregateResults(ctx, results, minCount, natsConn, accountID) } @@ -281,7 +282,7 @@ func distributeInstancesSpread(ctx context.Context, input *ec2.RunInstancesInput // A spread group puts one instance on each node, so without a shared ID // every node mints its own and one launch comes back as several // reservations. - spreadReservationID := utils.GenerateResourceID("r") + spreadReservationID := awsidentifiers.GenerateResourceID("r") results := launchOnNodes(ctx, allocations, input, natsConn, accountID, spreadReservationID) var allInstances []*ec2.Instance diff --git a/spinifex/gateway/ec2/spotinstance/RequestSpotInstances.go b/spinifex/gateway/ec2/spotinstance/RequestSpotInstances.go index 17d048f35..3ca487e7e 100644 --- a/spinifex/gateway/ec2/spotinstance/RequestSpotInstances.go +++ b/spinifex/gateway/ec2/spotinstance/RequestSpotInstances.go @@ -15,12 +15,12 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" "github.com/mulgadc/spinifex/spinifex/foundation/aws/errors" + awsidentifiers "github.com/mulgadc/spinifex/spinifex/foundation/aws/identifiers" awstags "github.com/mulgadc/spinifex/spinifex/foundation/aws/tags" gateway_ec2_instance "github.com/mulgadc/spinifex/spinifex/gateway/ec2/instance" handlers_ec2_spotinstance "github.com/mulgadc/spinifex/spinifex/handlers/ec2/spotinstance" handlers_iam "github.com/mulgadc/spinifex/spinifex/handlers/iam" handlers_quota "github.com/mulgadc/spinifex/spinifex/handlers/quota" - "github.com/mulgadc/spinifex/spinifex/utils" "github.com/nats-io/nats.go" ) @@ -192,7 +192,7 @@ func buildSpotRequests(input *ec2.RequestSpotInstancesInput, runInput *ec2.RunIn requests := make([]*ec2.SpotInstanceRequest, 0, count) for range count { req := &ec2.SpotInstanceRequest{ - SpotInstanceRequestId: aws.String(utils.GenerateResourceID("sir")), + SpotInstanceRequestId: aws.String(awsidentifiers.GenerateResourceID("sir")), State: aws.String(ec2.SpotInstanceStateActive), Type: aws.String(spotType), ProductDescription: aws.String(ec2.RIProductDescriptionLinuxUnix), diff --git a/spinifex/handlers/ec2/eigw/service_impl.go b/spinifex/handlers/ec2/eigw/service_impl.go index feb403088..d0df12595 100644 --- a/spinifex/handlers/ec2/eigw/service_impl.go +++ b/spinifex/handlers/ec2/eigw/service_impl.go @@ -15,6 +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" + 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/migrate" @@ -94,7 +95,7 @@ func (s *EgressOnlyIGWServiceImpl) CreateEgressOnlyInternetGateway(ctx context.C return nil, awserrors.IDNotFound(awserrors.ErrorInvalidVpcIDNotFound, "vpc", *input.VpcId) } - eigwID := utils.GenerateResourceID("eigw") + eigwID := awsidentifiers.GenerateResourceID("eigw") record := EgressOnlyIGWRecord{ EgressOnlyInternetGatewayId: eigwID, diff --git a/spinifex/handlers/ec2/eip/service_impl.go b/spinifex/handlers/ec2/eip/service_impl.go index d020fd39f..56ac270bd 100644 --- a/spinifex/handlers/ec2/eip/service_impl.go +++ b/spinifex/handlers/ec2/eip/service_impl.go @@ -13,6 +13,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" + 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/migrate" @@ -109,7 +110,7 @@ func (s *EIPServiceImpl) AllocateAddress(ctx context.Context, input *ec2.Allocat } func (s *EIPServiceImpl) allocateAddress(ctx context.Context, input *ec2.AllocateAddressInput, accountID string) (*ec2.AllocateAddressOutput, error) { - allocID := utils.GenerateResourceID("eipalloc") + allocID := awsidentifiers.GenerateResourceID("eipalloc") var publicIP, poolName string var err error @@ -256,7 +257,7 @@ func (s *EIPServiceImpl) AssociateAddress(ctx context.Context, input *ec2.Associ privateIP = *input.PrivateIpAddress } - associationID := utils.GenerateResourceID("eipassoc") + associationID := awsidentifiers.GenerateResourceID("eipassoc") record.AssociationId = associationID record.ENIId = eniID diff --git a/spinifex/handlers/ec2/igw/service_impl.go b/spinifex/handlers/ec2/igw/service_impl.go index 5d39cd5be..b037d4d13 100644 --- a/spinifex/handlers/ec2/igw/service_impl.go +++ b/spinifex/handlers/ec2/igw/service_impl.go @@ -15,6 +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" + 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" @@ -115,7 +116,7 @@ func NewIGWServiceImplWithNATS(ctx context.Context, cfg *config.Config, natsConn // CreateInternetGateway creates a new Internet Gateway (initially detached). func (s *IGWServiceImpl) CreateInternetGateway(ctx context.Context, input *ec2.CreateInternetGatewayInput, accountID string) (*ec2.CreateInternetGatewayOutput, error) { - igwID := utils.GenerateResourceID("igw") + igwID := awsidentifiers.GenerateResourceID("igw") return s.createIGW(ctx, input, accountID, igwID) } diff --git a/spinifex/handlers/ec2/image/service_impl.go b/spinifex/handlers/ec2/image/service_impl.go index 5661a940a..24917ba5e 100644 --- a/spinifex/handlers/ec2/image/service_impl.go +++ b/spinifex/handlers/ec2/image/service_impl.go @@ -434,8 +434,8 @@ func (s *ImageServiceImpl) CreateImageFromInstance(params CreateImageParams, acc } } - amiID := utils.GenerateResourceID("ami") - snapshotID := utils.GenerateResourceID("snap") + amiID := awsidentifiers.GenerateResourceID("ami") + snapshotID := awsidentifiers.GenerateResourceID("snap") slog.Info("CreateImageFromInstance", "instanceId", *input.InstanceId, "rootVolumeId", params.RootVolumeID, "amiId", amiID, "snapshotId", snapshotID, @@ -862,8 +862,8 @@ func (s *ImageServiceImpl) CopyImage(ctx context.Context, input *ec2.CopyImageIn return nil, errors.New(awserrors.ErrorInvalidAMINameDuplicate) } - newSnapshotID := utils.GenerateResourceID("snap") - newImageID := utils.GenerateResourceID("ami") + newSnapshotID := awsidentifiers.GenerateResourceID("snap") + newImageID := awsidentifiers.GenerateResourceID("ami") // New snap shares source VolumeID — no block copy. snapSizeGiB := uint64(0) @@ -1092,7 +1092,7 @@ func (s *ImageServiceImpl) RegisterImage(ctx context.Context, input *ec2.Registe tags := awstags.Extract(input.TagSpecifications, "image") - amiID := utils.GenerateResourceID("ami") + amiID := awsidentifiers.GenerateResourceID("ami") meta := ebsmetadata.AMI{ ImageID: amiID, Name: name, diff --git a/spinifex/handlers/ec2/instance/service_impl.go b/spinifex/handlers/ec2/instance/service_impl.go index 8ce2e0640..db09bcca2 100644 --- a/spinifex/handlers/ec2/instance/service_impl.go +++ b/spinifex/handlers/ec2/instance/service_impl.go @@ -27,6 +27,7 @@ import ( ec2platform "github.com/mulgadc/spinifex/spinifex/domains/ec2/platform" "github.com/mulgadc/spinifex/spinifex/foundation/aws/errors" awsfilters "github.com/mulgadc/spinifex/spinifex/foundation/aws/filters" + awsidentifiers "github.com/mulgadc/spinifex/spinifex/foundation/aws/identifiers" "github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore" handlers_dns "github.com/mulgadc/spinifex/spinifex/handlers/dns" handlers_ec2_vpc "github.com/mulgadc/spinifex/spinifex/handlers/ec2/vpc" @@ -206,7 +207,7 @@ func parseVolumeParams(input *ec2.RunInstancesInput) volumeParams { } if strings.HasPrefix(*input.ImageId, "ami-") { - p.imageId = utils.GenerateResourceID("vol") + p.imageId = awsidentifiers.GenerateResourceID("vol") p.snapshotId = *input.ImageId } else { p.imageId = *input.ImageId @@ -326,7 +327,7 @@ func (s *InstanceServiceImpl) RunInstance(input *ec2.RunInstancesInput) (*vm.VM, return nil, nil, errors.New(awserrors.ErrorInvalidInstanceType) } - instanceId := utils.GenerateResourceID("i") + instanceId := awsidentifiers.GenerateResourceID("i") // Create new instance structure instance := &vm.VM{ @@ -382,7 +383,7 @@ func (s *InstanceServiceImpl) RunInstance(input *ec2.RunInstancesInput) (*vm.VM, arn := aws.StringValue(input.IamInstanceProfile.Arn) if arn != "" { instance.IamInstanceProfileArn = arn - instance.IamInstanceProfileAssociationId = utils.GenerateResourceID("iip-assoc") + instance.IamInstanceProfileAssociationId = awsidentifiers.GenerateResourceID("iip-assoc") ec2Instance.IamInstanceProfile = &ec2.IamInstanceProfile{ Arn: aws.String(arn), } @@ -987,7 +988,7 @@ func (s *InstanceServiceImpl) PrepareRunInstances(ctx context.Context, input *ec } reservation := &ec2.Reservation{} - reservation.SetReservationId(utils.GenerateResourceID("r")) + reservation.SetReservationId(awsidentifiers.GenerateResourceID("r")) reservation.SetOwnerId(accountID) reservation.Instances = allEC2Instances @@ -1346,7 +1347,7 @@ func (s *InstanceServiceImpl) AssociateIamInstanceProfile(ctx context.Context, i return nil, errors.New(awserrors.ErrorMissingParameter) } profileArn := command.IamProfileAssociationData.InstanceProfileArn - newID := utils.GenerateResourceID("iip-assoc") + newID := awsidentifiers.GenerateResourceID("iip-assoc") timestamp := time.Now().UTC() var alreadyAssociated bool @@ -1441,7 +1442,7 @@ func (s *InstanceServiceImpl) ReplaceIamProfileAssociation(ctx context.Context, return nil, nil } - newID := utils.GenerateResourceID("iip-assoc") + newID := awsidentifiers.GenerateResourceID("iip-assoc") timestamp := time.Now().UTC() var swapped bool _, err := s.vmMgr.UpdateAndPersist(owner, func(v *vm.VM) bool { diff --git a/spinifex/handlers/ec2/key/service_impl.go b/spinifex/handlers/ec2/key/service_impl.go index d6662f0a3..a367f6e7d 100644 --- a/spinifex/handlers/ec2/key/service_impl.go +++ b/spinifex/handlers/ec2/key/service_impl.go @@ -28,6 +28,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" + awsidentifiers "github.com/mulgadc/spinifex/spinifex/foundation/aws/identifiers" awstags "github.com/mulgadc/spinifex/spinifex/foundation/aws/tags" "github.com/mulgadc/spinifex/spinifex/providers/objectstore" "github.com/mulgadc/spinifex/spinifex/utils" @@ -210,7 +211,7 @@ func (s *KeyServiceImpl) CreateKeyPair(ctx context.Context, input *ec2.CreateKey } // Build response (similar to AWS EC2) - keyPairID := utils.GenerateResourceID("key") + keyPairID := awsidentifiers.GenerateResourceID("key") tagMap := awstags.Extract(input.TagSpecifications, "key-pair") tags := awstags.MapToEC2(tagMap) // AWS ends RSA material at its END line, while OpenSSH-format Ed25519 @@ -986,7 +987,7 @@ func (s *KeyServiceImpl) ImportKeyPair(ctx context.Context, input *ec2.ImportKey } // Generate key pair ID - keyPairID := utils.GenerateResourceID("key") + keyPairID := awsidentifiers.GenerateResourceID("key") // Build response output tagMap := awstags.Extract(input.TagSpecifications, "key-pair") diff --git a/spinifex/handlers/ec2/launchtemplate/service_impl.go b/spinifex/handlers/ec2/launchtemplate/service_impl.go index 07a2ded7f..ddcf1ca51 100644 --- a/spinifex/handlers/ec2/launchtemplate/service_impl.go +++ b/spinifex/handlers/ec2/launchtemplate/service_impl.go @@ -17,10 +17,10 @@ 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" + 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/migrate" - "github.com/mulgadc/spinifex/spinifex/utils" "github.com/nats-io/nats.go" "github.com/nats-io/nats.go/jetstream" ) @@ -277,7 +277,7 @@ func (s *LaunchTemplateServiceImpl) CreateLaunchTemplate(ctx context.Context, in return nil, errors.New(awserrors.ErrorInvalidParameterValue) } - ltID := utils.GenerateResourceID("lt") + ltID := awsidentifiers.GenerateResourceID("lt") if err := s.claimName(ctx, accountID, name, ltID); err != nil { return nil, err } diff --git a/spinifex/handlers/ec2/natgw/service_impl.go b/spinifex/handlers/ec2/natgw/service_impl.go index 050f1ae4a..5656c9eff 100644 --- a/spinifex/handlers/ec2/natgw/service_impl.go +++ b/spinifex/handlers/ec2/natgw/service_impl.go @@ -13,6 +13,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" + 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/migrate" @@ -140,7 +141,7 @@ func (s *NatGatewayServiceImpl) CreateNatGateway(ctx context.Context, input *ec2 return nil, errors.New(awserrors.ErrorResourceAlreadyAssociated) } - natgwID := utils.GenerateResourceID("nat") + natgwID := awsidentifiers.GenerateResourceID("nat") record := NatGatewayRecord{ NatGatewayId: natgwID, VpcId: subnetRecord.VpcId, @@ -166,7 +167,7 @@ func (s *NatGatewayServiceImpl) CreateNatGateway(ctx context.Context, input *ec2 // NAT GW reusing the same allocation). Linkage is the shared AllocationId; // DeleteNatGateway clears it back to allocated. Non-fatal: the NAT GW is // already stored, so a failed mark only weakens the reuse guard until then. - eipRecord.AssociationId = utils.GenerateResourceID("eipassoc") + eipRecord.AssociationId = awsidentifiers.GenerateResourceID("eipassoc") eipRecord.VpcId = subnetRecord.VpcId eipRecord.State = "associated" if eipData, err := json.Marshal(eipRecord); err == nil { diff --git a/spinifex/handlers/ec2/placementgroup/service_impl.go b/spinifex/handlers/ec2/placementgroup/service_impl.go index 64d9b5988..b6fc241b4 100644 --- a/spinifex/handlers/ec2/placementgroup/service_impl.go +++ b/spinifex/handlers/ec2/placementgroup/service_impl.go @@ -15,10 +15,10 @@ 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" + 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/migrate" - "github.com/mulgadc/spinifex/spinifex/utils" "github.com/nats-io/nats.go" "github.com/nats-io/nats.go/jetstream" ) @@ -112,7 +112,7 @@ func (s *PlacementGroupServiceImpl) CreatePlacementGroup(ctx context.Context, in groupName := *input.GroupName key := kvutil.AccountKey(accountID, groupName) - groupID := utils.GenerateResourceID("pg") + groupID := awsidentifiers.GenerateResourceID("pg") record := PlacementGroupRecord{ GroupId: groupID, diff --git a/spinifex/handlers/ec2/routetable/service_impl.go b/spinifex/handlers/ec2/routetable/service_impl.go index 746f63d93..ca9e91e8b 100644 --- a/spinifex/handlers/ec2/routetable/service_impl.go +++ b/spinifex/handlers/ec2/routetable/service_impl.go @@ -16,6 +16,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" + 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" @@ -489,7 +490,7 @@ func (s *RouteTableServiceImpl) allRouteTablesForVPC(ctx context.Context, accoun // Exported for use by VPC service when creating default VPCs. func (s *RouteTableServiceImpl) CreateRouteTableForVPC(vpcID, vpcCidr, accountID string, isMain bool, rtbID string) (*RouteTableRecord, error) { if rtbID == "" { - rtbID = utils.GenerateResourceID("rtb") + rtbID = awsidentifiers.GenerateResourceID("rtb") } record := RouteTableRecord{ @@ -512,7 +513,7 @@ func (s *RouteTableServiceImpl) CreateRouteTableForVPC(vpcID, vpcCidr, accountID if isMain { record.Associations = []AssociationRecord{ { - AssociationId: utils.GenerateResourceID("rtbassoc"), + AssociationId: awsidentifiers.GenerateResourceID("rtbassoc"), Main: true, }, } @@ -1020,7 +1021,7 @@ func (s *RouteTableServiceImpl) AssociateRouteTable(ctx context.Context, input * } } - assocID := utils.GenerateResourceID("rtbassoc") + assocID := awsidentifiers.GenerateResourceID("rtbassoc") if err := s.mutateRouteTableCAS(ctx, accountID, rtbID, func(rec *RouteTableRecord) (bool, error) { // Re-check same-table association under the fresh read: a concurrent // associate for this subnet may have landed since the precheck. @@ -1224,7 +1225,7 @@ func (s *RouteTableServiceImpl) ReplaceRouteTableAssociation(ctx context.Context s.publishIGWRouteEventsForAssociation(ctx, accountID, "vpc.delete-igw-route", &oldRecord, assoc.SubnetId) // Add to new table with new ID - newAssocID := utils.GenerateResourceID("rtbassoc") + newAssocID := awsidentifiers.GenerateResourceID("rtbassoc") newRecord.Associations = append(newRecord.Associations, AssociationRecord{ AssociationId: newAssocID, SubnetId: assoc.SubnetId, diff --git a/spinifex/handlers/ec2/snapshot/service_impl.go b/spinifex/handlers/ec2/snapshot/service_impl.go index 11bb1e22e..e8abb2d79 100644 --- a/spinifex/handlers/ec2/snapshot/service_impl.go +++ b/spinifex/handlers/ec2/snapshot/service_impl.go @@ -188,7 +188,7 @@ func (s *SnapshotServiceImpl) CreateSnapshot(ctx context.Context, input *ec2.Cre slog.InfoContext(ctx, "CreateSnapshot request", "volumeId", volumeID) - snapshotID := utils.GenerateResourceID("snap") + snapshotID := awsidentifiers.GenerateResourceID("snap") if s.provider == nil { slog.ErrorContext(ctx, "no EBS provider configured", "op", "CreateSnapshot") return nil, errors.New(awserrors.ErrorServerInternal) @@ -822,7 +822,7 @@ func (s *SnapshotServiceImpl) CopySnapshot(ctx context.Context, input *ec2.CopyS return nil, errors.New(awserrors.ErrorUnsupportedOperation) } - newSnapshotID := utils.GenerateResourceID("snap") + newSnapshotID := awsidentifiers.GenerateResourceID("snap") copied, err := s.provider.CopySnapshot(ctx, ebsprovider.CopySnapshotRequest{ Versioned: ebsprovider.NewVersioned(), SourceSnapshotID: sourceSnapshotID, diff --git a/spinifex/handlers/ec2/volume/service_impl.go b/spinifex/handlers/ec2/volume/service_impl.go index c487766b1..0ae30e2aa 100644 --- a/spinifex/handlers/ec2/volume/service_impl.go +++ b/spinifex/handlers/ec2/volume/service_impl.go @@ -21,6 +21,7 @@ import ( ebspolicy "github.com/mulgadc/spinifex/spinifex/domains/ec2/ebs/policy" "github.com/mulgadc/spinifex/spinifex/foundation/aws/errors" 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" handlers_ec2_instance "github.com/mulgadc/spinifex/spinifex/handlers/ec2/instance" "github.com/mulgadc/spinifex/spinifex/providers/ebs" @@ -177,7 +178,7 @@ func (s *VolumeServiceImpl) CreateVolume(ctx context.Context, input *ec2.CreateV } now := time.Now() - volumeID := utils.GenerateResourceID("vol") + volumeID := awsidentifiers.GenerateResourceID("vol") // Honor caller-supplied Iops for gp3, else the 3000 baseline. The ceiling is // min(16000, 500*size) but never below the free baseline, so small volumes diff --git a/spinifex/handlers/ec2/volume/service_impl_test.go b/spinifex/handlers/ec2/volume/service_impl_test.go index fa9f67744..7509fc132 100644 --- a/spinifex/handlers/ec2/volume/service_impl_test.go +++ b/spinifex/handlers/ec2/volume/service_impl_test.go @@ -603,7 +603,7 @@ func TestApplyRecordTags_Provider_VisibleViaDescribeVolumes(t *testing.T) { // prefixFailingObjectStore fails PutObject for any key under failPrefix and // records the last such key, so a test can recover a randomly generated // resource ID from the write CreateVolume/CreateSnapshot attempted, without -// needing to predict utils.GenerateResourceID's output up front. +// needing to predict awsidentifiers.GenerateResourceID's output up front. type prefixFailingObjectStore struct { objectstore.ObjectStore diff --git a/spinifex/handlers/ec2/vpc/default_vpc.go b/spinifex/handlers/ec2/vpc/default_vpc.go index 4cfef33eb..5a02b99c3 100644 --- a/spinifex/handlers/ec2/vpc/default_vpc.go +++ b/spinifex/handlers/ec2/vpc/default_vpc.go @@ -10,8 +10,8 @@ import ( "strings" "time" + awsidentifiers "github.com/mulgadc/spinifex/spinifex/foundation/aws/identifiers" "github.com/mulgadc/spinifex/spinifex/foundation/state/kvutil" - "github.com/mulgadc/spinifex/spinifex/utils" "github.com/nats-io/nats.go/jetstream" ) @@ -161,11 +161,11 @@ func (s *VPCServiceImpl) proposeDefaultVPC(ctx context.Context, accountID string return fmt.Errorf("allocate VNI for default VPC: %w", err) } claim = &defaultVPCClaim{ - VpcId: cmp.Or(ids.VpcId, utils.GenerateResourceID("vpc")), - SubnetId: cmp.Or(ids.SubnetId, utils.GenerateResourceID("subnet")), - GroupId: utils.GenerateResourceID("sg"), - RouteTableId: utils.GenerateResourceID("rtb"), - InternetGatewayId: cmp.Or(ids.IgwId, utils.GenerateResourceID("igw")), + VpcId: cmp.Or(ids.VpcId, awsidentifiers.GenerateResourceID("vpc")), + SubnetId: cmp.Or(ids.SubnetId, awsidentifiers.GenerateResourceID("subnet")), + GroupId: awsidentifiers.GenerateResourceID("sg"), + RouteTableId: awsidentifiers.GenerateResourceID("rtb"), + InternetGatewayId: cmp.Or(ids.IgwId, awsidentifiers.GenerateResourceID("igw")), Cidr: DefaultVPCCidr, SubnetCidr: DefaultSubnetCidr, VNI: vni, @@ -202,7 +202,7 @@ func (s *VPCServiceImpl) adoptExistingDefaultVPC(ctx context.Context, accountID claim := &defaultVPCClaim{ VpcId: vpc.VpcId, - InternetGatewayId: cmp.Or(ids.IgwId, utils.GenerateResourceID("igw")), + InternetGatewayId: cmp.Or(ids.IgwId, awsidentifiers.GenerateResourceID("igw")), Cidr: vpc.CidrBlock, VNI: vpc.VNI, Complete: true, diff --git a/spinifex/handlers/ec2/vpc/eni.go b/spinifex/handlers/ec2/vpc/eni.go index eac97b29d..24db02185 100644 --- a/spinifex/handlers/ec2/vpc/eni.go +++ b/spinifex/handlers/ec2/vpc/eni.go @@ -15,6 +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" + 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" @@ -143,7 +144,7 @@ func (s *VPCServiceImpl) CreateNetworkInterface(ctx context.Context, input *ec2. return nil, err } - eniId := utils.GenerateResourceID("eni") + eniId := awsidentifiers.GenerateResourceID("eni") // Allocate IP from subnet, or claim the caller's requested address. var privateIP string @@ -711,7 +712,7 @@ func (s *VPCServiceImpl) attachENI(ctx context.Context, accountID, eniId, instan return "", errors.New(awserrors.ErrorInvalidNetworkInterfaceInUse) } - attachmentId := utils.GenerateResourceID("eni-attach") + attachmentId := awsidentifiers.GenerateResourceID("eni-attach") record.Status = "in-use" record.AttachmentId = attachmentId record.InstanceId = instanceId diff --git a/spinifex/handlers/ec2/vpc/security_group.go b/spinifex/handlers/ec2/vpc/security_group.go index a5c537576..5eedd80b3 100644 --- a/spinifex/handlers/ec2/vpc/security_group.go +++ b/spinifex/handlers/ec2/vpc/security_group.go @@ -18,6 +18,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" + 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" @@ -25,10 +26,10 @@ import ( "github.com/nats-io/nats.go/jetstream" ) -// sgIDRegex must stay in lockstep with utils.GenerateResourceID("sg"). +// sgIDRegex must stay in lockstep with awsidentifiers.GenerateResourceID("sg"). var sgIDRegex = regexp.MustCompile(`^sg-[0-9a-f]{17}$`) -// SGRuleIDRegex must stay in lockstep with utils.GenerateResourceID("sgr"). +// SGRuleIDRegex must stay in lockstep with awsidentifiers.GenerateResourceID("sgr"). // Exported so the EC2 gateway can validate SecurityGroupRuleIds without // re-implementing the format check. var SGRuleIDRegex = regexp.MustCompile(`^sgr-[0-9a-f]{17}$`) @@ -241,7 +242,7 @@ func (s *VPCServiceImpl) CreateSecurityGroup(ctx context.Context, input *ec2.Cre return nil, errors.New(awserrors.ErrorResourceLimitExceeded) } - groupId := utils.GenerateResourceID("sg") + groupId := awsidentifiers.GenerateResourceID("sg") description := "" if input.Description != nil { @@ -250,7 +251,7 @@ func (s *VPCServiceImpl) CreateSecurityGroup(ctx context.Context, input *ec2.Cre // Default egress rule: allow all outbound traffic defaultEgress := []SGRule{ - {RuleId: utils.GenerateResourceID("sgr"), IpProtocol: "-1", FromPort: 0, ToPort: 0, CidrIp: "0.0.0.0/0"}, + {RuleId: awsidentifiers.GenerateResourceID("sgr"), IpProtocol: "-1", FromPort: 0, ToPort: 0, CidrIp: "0.0.0.0/0"}, } record := SecurityGroupRecord{ @@ -1942,7 +1943,7 @@ func ipPermissionsToSGRules(perms []*ec2.IpPermission, mode sgParseMode) ([]SGRu return nil, err } if mode == sgParseAuthorize { - r.RuleId = utils.GenerateResourceID("sgr") + r.RuleId = awsidentifiers.GenerateResourceID("sgr") } rules = append(rules, r) appended = true @@ -1960,7 +1961,7 @@ func ipPermissionsToSGRules(perms []*ec2.IpPermission, mode sgParseMode) ([]SGRu return nil, err } if mode == sgParseAuthorize { - r.RuleId = utils.GenerateResourceID("sgr") + r.RuleId = awsidentifiers.GenerateResourceID("sgr") } rules = append(rules, r) appended = true @@ -1978,7 +1979,7 @@ func ipPermissionsToSGRules(perms []*ec2.IpPermission, mode sgParseMode) ([]SGRu return nil, err } if mode == sgParseAuthorize { - r.RuleId = utils.GenerateResourceID("sgr") + r.RuleId = awsidentifiers.GenerateResourceID("sgr") } rules = append(rules, r) appended = true @@ -2153,7 +2154,7 @@ func (s *VPCServiceImpl) requestSGEvent(topic string, evt SGEvent) error { // AWS-equivalent rules, bypassing the public-API reserved-name guard. Used by // CreateVpc. func (s *VPCServiceImpl) createDefaultSecurityGroupInternal(ctx context.Context, accountID, vpcId string) (string, error) { - record, _, err := s.storeDefaultSecurityGroup(ctx, accountID, vpcId, utils.GenerateResourceID("sg")) + record, _, err := s.storeDefaultSecurityGroup(ctx, accountID, vpcId, awsidentifiers.GenerateResourceID("sg")) if err != nil { return "", err } @@ -2172,10 +2173,10 @@ func (s *VPCServiceImpl) storeDefaultSecurityGroup(ctx context.Context, accountI Description: defaultSecurityGroupDescription, VpcId: vpcId, IngressRules: []SGRule{ - {RuleId: utils.GenerateResourceID("sgr"), IpProtocol: "-1", FromPort: 0, ToPort: 0, SourceSG: groupId}, + {RuleId: awsidentifiers.GenerateResourceID("sgr"), IpProtocol: "-1", FromPort: 0, ToPort: 0, SourceSG: groupId}, }, EgressRules: []SGRule{ - {RuleId: utils.GenerateResourceID("sgr"), IpProtocol: "-1", FromPort: 0, ToPort: 0, CidrIp: "0.0.0.0/0"}, + {RuleId: awsidentifiers.GenerateResourceID("sgr"), IpProtocol: "-1", FromPort: 0, ToPort: 0, CidrIp: "0.0.0.0/0"}, }, Tags: map[string]string{}, IsDefault: true, diff --git a/spinifex/handlers/ec2/vpc/service_impl.go b/spinifex/handlers/ec2/vpc/service_impl.go index 30e3e0976..72a683158 100644 --- a/spinifex/handlers/ec2/vpc/service_impl.go +++ b/spinifex/handlers/ec2/vpc/service_impl.go @@ -17,6 +17,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" + 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" @@ -296,7 +297,7 @@ func (s *VPCServiceImpl) CreateVpc(ctx context.Context, input *ec2.CreateVpcInpu return nil, errors.New(awserrors.ErrorServerInternal) } - vpcID := utils.GenerateResourceID("vpc") + vpcID := awsidentifiers.GenerateResourceID("vpc") record := VPCRecord{ VpcId: vpcID, @@ -778,7 +779,7 @@ func (s *VPCServiceImpl) CreateSubnet(ctx context.Context, input *ec2.CreateSubn az = s.config.AZ } - subnetID := utils.GenerateResourceID("subnet") + subnetID := awsidentifiers.GenerateResourceID("subnet") // Calculate available IPs (total hosts minus AWS reserved: network, router, DNS, future, broadcast) // ones is validated to be 16-28 above, so (32-ones) is always 4-16 and safe for uint conversion @@ -1497,7 +1498,7 @@ func (s *VPCServiceImpl) createMainRouteTable(ctx context.Context, accountID, vp "routeTableId", existing, "vpcId", vpcID, "accountID", accountID) return nil } - _, err := s.writeMainRouteTable(ctx, accountID, vpcID, vpcCidr, utils.GenerateResourceID("rtb")) + _, err := s.writeMainRouteTable(ctx, accountID, vpcID, vpcCidr, awsidentifiers.GenerateResourceID("rtb")) return err } @@ -1540,7 +1541,7 @@ func (s *VPCServiceImpl) writeMainRouteTable(ctx context.Context, accountID, vpc AssociationId string `json:"association_id"` Main bool `json:"main"` }{ - {AssociationId: utils.GenerateResourceID("rtbassoc"), Main: true}, + {AssociationId: awsidentifiers.GenerateResourceID("rtbassoc"), Main: true}, }, Tags: make(map[string]string), CreatedAt: time.Now(), diff --git a/spinifex/handlers/elbv2/service_impl.go b/spinifex/handlers/elbv2/service_impl.go index 0bef5fbdf..a7e5a05c4 100644 --- a/spinifex/handlers/elbv2/service_impl.go +++ b/spinifex/handlers/elbv2/service_impl.go @@ -23,6 +23,7 @@ import ( acmdomain "github.com/mulgadc/spinifex/spinifex/domains/acm" resourcearn "github.com/mulgadc/spinifex/spinifex/foundation/aws/arn" "github.com/mulgadc/spinifex/spinifex/foundation/aws/errors" + awsidentifiers "github.com/mulgadc/spinifex/spinifex/foundation/aws/identifiers" "github.com/mulgadc/spinifex/spinifex/foundation/aws/tags" "github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore" handlers_dns "github.com/mulgadc/spinifex/spinifex/handlers/dns" @@ -1296,7 +1297,7 @@ func (s *ELBv2ServiceImpl) createLoadBalancer(ctx context.Context, input *elbv2. } } - lbID := utils.GenerateResourceID("lb") + lbID := awsidentifiers.GenerateResourceID("lb") lbArn := resourcearn.FormatELBv2LoadBalancer(s.region, accountID, name, lbID, lbType) arnPathSegment := resourcearn.ELBv2LBPathSegment(lbType) dnsPrefix := "" @@ -1982,7 +1983,7 @@ func (s *ELBv2ServiceImpl) CreateTargetGroup(ctx context.Context, input *elbv2.C hc.Matcher = *input.Matcher.HttpCode } - tgID := utils.GenerateResourceID("tg") + tgID := awsidentifiers.GenerateResourceID("tg") tgArn := resourcearn.FormatELBv2TargetGroup(s.region, accountID, name, tgID) tags := tagsFromSDK(input.Tags) @@ -2669,7 +2670,7 @@ func (s *ELBv2ServiceImpl) CreateListener(ctx context.Context, input *elbv2.Crea return nil, err } - listenerID := utils.GenerateResourceID("lst") + listenerID := awsidentifiers.GenerateResourceID("lst") listenerArn := resourcearn.FormatELBv2Listener(s.region, accountID, lb.Name, lb.LoadBalancerID, listenerID, lb.Type) var actions []ListenerAction diff --git a/spinifex/handlers/elbv2/service_impl_rules.go b/spinifex/handlers/elbv2/service_impl_rules.go index 516b8180c..f67230405 100644 --- a/spinifex/handlers/elbv2/service_impl_rules.go +++ b/spinifex/handlers/elbv2/service_impl_rules.go @@ -14,7 +14,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/elbv2" "github.com/mulgadc/spinifex/spinifex/foundation/aws/errors" - "github.com/mulgadc/spinifex/spinifex/utils" + awsidentifiers "github.com/mulgadc/spinifex/spinifex/foundation/aws/identifiers" ) // Regex guards for rule condition values — rejected at write time so the renderer @@ -78,7 +78,7 @@ func (s *ELBv2ServiceImpl) CreateRule(ctx context.Context, input *elbv2.CreateRu } } - ruleID := utils.GenerateResourceID("rule") + ruleID := awsidentifiers.GenerateResourceID("rule") ruleArn, err := buildRuleArn(listener.ListenerArn, ruleID) if err != nil { slog.ErrorContext(ctx, "CreateRule: failed to build ARN", "listenerArn", listener.ListenerArn, "err", err) diff --git a/spinifex/handlers/ochrevector/identifiers.go b/spinifex/handlers/ochrevector/identifiers.go index 99eeb5cc6..5c5f6dea2 100644 --- a/spinifex/handlers/ochrevector/identifiers.go +++ b/spinifex/handlers/ochrevector/identifiers.go @@ -9,7 +9,7 @@ import ( ) // indexIDPattern is a conservative allowlist for anything that reaches a SQL -// identifier: utils.GenerateResourceID's own alphabet ("idx-<17 hex>") plus +// identifier: awsidentifiers.GenerateResourceID's own alphabet ("idx-<17 hex>") plus // enough headroom that a caller-supplied id still round-trips safely rather // than being silently rejected. var indexIDPattern = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_-]{0,62}$`) diff --git a/spinifex/handlers/ochrevector/ingest.go b/spinifex/handlers/ochrevector/ingest.go index 40f51df4f..cb00e53ce 100644 --- a/spinifex/handlers/ochrevector/ingest.go +++ b/spinifex/handlers/ochrevector/ingest.go @@ -11,8 +11,8 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/s3" + awsidentifiers "github.com/mulgadc/spinifex/spinifex/foundation/aws/identifiers" "github.com/mulgadc/spinifex/spinifex/providers/objectstore" - "github.com/mulgadc/spinifex/spinifex/utils" ) // Embedder is the local seam for batch text embedding: the same method set @@ -127,7 +127,7 @@ func (s *IngestService) StartIngest(ctx context.Context, accountID, indexID stri now := time.Now().UTC() rec := JobRecord{ - ID: utils.GenerateResourceID("job"), + ID: awsidentifiers.GenerateResourceID("job"), IndexID: indexID, DataSourceID: dataSourceID, Source: source, diff --git a/spinifex/handlers/rds/bootstrap.go b/spinifex/handlers/rds/bootstrap.go index aff5e780e..3bb1d585e 100644 --- a/spinifex/handlers/rds/bootstrap.go +++ b/spinifex/handlers/rds/bootstrap.go @@ -10,9 +10,9 @@ import ( "time" "github.com/mulgadc/spinifex/spinifex/foundation/aws/errors" + awsidentifiers "github.com/mulgadc/spinifex/spinifex/foundation/aws/identifiers" "github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore" handlers_iam "github.com/mulgadc/spinifex/spinifex/handlers/iam" - "github.com/mulgadc/spinifex/spinifex/utils" ) // The staged master password is encrypted at rest under bootstrap-payloads/{id} @@ -86,7 +86,7 @@ func (s *Service) writeBootstrapPayload(ctx context.Context, kv *kvstore.Bucket, return "", err } keyID := bootstrapKeyID(key) - payloadID := utils.GenerateResourceID(bootstrapPayloadIDPrefix) + payloadID := awsidentifiers.GenerateResourceID(bootstrapPayloadIDPrefix) plaintext, err := json.Marshal(bootstrapPayloadClaims{ EnvelopeVersion: bootstrapEnvelopeVersion, diff --git a/spinifex/handlers/rds/create.go b/spinifex/handlers/rds/create.go index 9ecc338f1..40301f83b 100644 --- a/spinifex/handlers/rds/create.go +++ b/spinifex/handlers/rds/create.go @@ -9,6 +9,7 @@ import ( "github.com/aws/aws-sdk-go/service/rds" "github.com/mulgadc/spinifex/spinifex/foundation/aws/errors" + awsidentifiers "github.com/mulgadc/spinifex/spinifex/foundation/aws/identifiers" "github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore" "github.com/mulgadc/spinifex/spinifex/runtime/compute/vm" "github.com/mulgadc/spinifex/spinifex/utils" @@ -150,7 +151,7 @@ func newDBInstanceRecord(accountID string, req *validatedCreate, placement *endp now := time.Now().UTC() return DBInstanceRecord{ DBInstanceIdentifier: req.Identifier, - DbiResourceID: utils.GenerateResourceID(dbiResourceIDPrefix), + DbiResourceID: awsidentifiers.GenerateResourceID(dbiResourceIDPrefix), AccountID: accountID, Status: StatusCreating, VMGeneration: firstVMGeneration, diff --git a/spinifex/network/ovn/mock/mock.go b/spinifex/network/ovn/mock/mock.go index 631a1f523..fcc5caa45 100644 --- a/spinifex/network/ovn/mock/mock.go +++ b/spinifex/network/ovn/mock/mock.go @@ -8,9 +8,9 @@ import ( "slices" "sync" + awsidentifiers "github.com/mulgadc/spinifex/spinifex/foundation/aws/identifiers" "github.com/mulgadc/spinifex/spinifex/network/ovn" "github.com/mulgadc/spinifex/spinifex/network/ovn/nbdb" - "github.com/mulgadc/spinifex/spinifex/utils" ) // Client implements ovn.Client with in-memory storage for testing. @@ -95,7 +95,7 @@ func (m *Client) CreateLogicalSwitch(_ context.Context, ls *nbdb.LogicalSwitch) return fmt.Errorf("logical switch %q already exists", ls.Name) } if ls.UUID == "" { - ls.UUID = utils.GenerateResourceID("ovn") + ls.UUID = awsidentifiers.GenerateResourceID("ovn") } stored := *ls m.Switches[ls.Name] = &stored @@ -112,7 +112,7 @@ func (m *Client) EnsureLogicalSwitch(_ context.Context, ls *nbdb.LogicalSwitch) return &result, false, nil } if ls.UUID == "" { - ls.UUID = utils.GenerateResourceID("ovn") + ls.UUID = awsidentifiers.GenerateResourceID("ovn") } stored := *ls m.Switches[ls.Name] = &stored @@ -164,7 +164,7 @@ func (m *Client) CreateLogicalSwitchPort(_ context.Context, switchName string, l return fmt.Errorf("logical switch port %q already exists", lsp.Name) } if lsp.UUID == "" { - lsp.UUID = utils.GenerateResourceID("ovn") + lsp.UUID = awsidentifiers.GenerateResourceID("ovn") } stored := *lsp m.Ports[lsp.Name] = &stored @@ -192,7 +192,7 @@ func (m *Client) CreateLogicalSwitchPortInGroups(_ context.Context, switchName s } } if lsp.UUID == "" { - lsp.UUID = utils.GenerateResourceID("ovn") + lsp.UUID = awsidentifiers.GenerateResourceID("ovn") } stored := *lsp m.Ports[lsp.Name] = &stored @@ -276,7 +276,7 @@ func (m *Client) CreateLogicalRouter(_ context.Context, lr *nbdb.LogicalRouter) return fmt.Errorf("logical router %q already exists", lr.Name) } if lr.UUID == "" { - lr.UUID = utils.GenerateResourceID("ovn") + lr.UUID = awsidentifiers.GenerateResourceID("ovn") } stored := *lr m.Routers[lr.Name] = &stored @@ -293,7 +293,7 @@ func (m *Client) EnsureLogicalRouter(_ context.Context, lr *nbdb.LogicalRouter) return &result, false, nil } if lr.UUID == "" { - lr.UUID = utils.GenerateResourceID("ovn") + lr.UUID = awsidentifiers.GenerateResourceID("ovn") } stored := *lr m.Routers[lr.Name] = &stored @@ -357,7 +357,7 @@ func (m *Client) CreateLogicalRouterPort(_ context.Context, routerName string, l return fmt.Errorf("logical router port %q already exists", lrp.Name) } if lrp.UUID == "" { - lrp.UUID = utils.GenerateResourceID("ovn") + lrp.UUID = awsidentifiers.GenerateResourceID("ovn") } stored := *lrp m.RouterPorts[lrp.Name] = &stored @@ -415,7 +415,7 @@ func (m *Client) CreateDHCPOptions(_ context.Context, opts *nbdb.DHCPOptions) (s m.Mu.Lock() defer m.Mu.Unlock() if opts.UUID == "" { - opts.UUID = utils.GenerateResourceID("dhcp") + opts.UUID = awsidentifiers.GenerateResourceID("dhcp") } stored := *opts m.DHCPOpts[opts.UUID] = &stored @@ -487,7 +487,7 @@ func (m *Client) AddNAT(_ context.Context, routerName string, nat *nbdb.NAT) err return fmt.Errorf("logical router %q not found", routerName) } if nat.UUID == "" { - nat.UUID = utils.GenerateResourceID("nat") + nat.UUID = awsidentifiers.GenerateResourceID("nat") } stored := *nat m.NATs[nat.UUID] = &stored @@ -676,7 +676,7 @@ func (m *Client) AddStaticRoute(_ context.Context, routerName string, route *nbd return fmt.Errorf("logical router %q not found", routerName) } if route.UUID == "" { - route.UUID = utils.GenerateResourceID("route") + route.UUID = awsidentifiers.GenerateResourceID("route") } stored := *route m.StaticRoutes[route.UUID] = &stored @@ -743,7 +743,7 @@ func (m *Client) AddLogicalRouterPolicy(_ context.Context, routerName string, po return fmt.Errorf("logical router %q not found", routerName) } if policy.UUID == "" { - policy.UUID = utils.GenerateResourceID("lrp") + policy.UUID = awsidentifiers.GenerateResourceID("lrp") } stored := *policy m.LRPolicies[policy.UUID] = &stored @@ -827,7 +827,7 @@ func (m *Client) CreatePortGroup(_ context.Context, name string, ports []string) return fmt.Errorf("port group %q already exists", name) } pg := &nbdb.PortGroup{ - UUID: utils.GenerateResourceID("pg"), + UUID: awsidentifiers.GenerateResourceID("pg"), Name: name, Ports: ports, } @@ -844,7 +844,7 @@ func (m *Client) EnsurePortGroup(_ context.Context, name string, ports []string) return &result, false, nil } pg := &nbdb.PortGroup{ - UUID: utils.GenerateResourceID("pg"), + UUID: awsidentifiers.GenerateResourceID("pg"), Name: name, Ports: ports, } @@ -951,7 +951,7 @@ func (m *Client) EnsureAddressSet(_ context.Context, name string, addresses []st return existing.UUID, nil } as := &nbdb.AddressSet{ - UUID: utils.GenerateResourceID("as"), + UUID: awsidentifiers.GenerateResourceID("as"), Name: name, Addresses: slices.Clone(addresses), } @@ -986,7 +986,7 @@ func (m *Client) AddACLs(_ context.Context, portGroupName string, specs []ovn.AC } for _, spec := range specs { acl := &nbdb.ACL{ - UUID: utils.GenerateResourceID("acl"), + UUID: awsidentifiers.GenerateResourceID("acl"), Direction: spec.Direction, Priority: spec.Priority, Match: spec.Match, @@ -1065,7 +1065,7 @@ func (m *Client) ReplaceACLs(_ context.Context, portGroupName string, specs []ov pg.ACLs = pg.ACLs[:0] for _, spec := range specs { acl := &nbdb.ACL{ - UUID: utils.GenerateResourceID("acl"), + UUID: awsidentifiers.GenerateResourceID("acl"), Direction: spec.Direction, Priority: spec.Priority, Match: spec.Match, @@ -1119,7 +1119,7 @@ func (m *Client) SetGatewayChassis(_ context.Context, lrpName string, chassisNam return nil } gc := &nbdb.GatewayChassis{ - UUID: utils.GenerateResourceID("gc"), + UUID: awsidentifiers.GenerateResourceID("gc"), Name: gcName, ChassisName: chassisName, Priority: priority, @@ -1181,7 +1181,7 @@ func (m *Client) SeedGatewayChassis(lrpName string, gc *nbdb.GatewayChassis) { m.Mu.Lock() defer m.Mu.Unlock() if gc.UUID == "" { - gc.UUID = utils.GenerateResourceID("gc") + gc.UUID = awsidentifiers.GenerateResourceID("gc") } m.GatewayChassis[gc.UUID] = gc if lrp, ok := m.RouterPorts[lrpName]; ok { diff --git a/spinifex/services/viperblockd/provider_handlers.go b/spinifex/services/viperblockd/provider_handlers.go index 2083d51f8..e3b183fef 100644 --- a/spinifex/services/viperblockd/provider_handlers.go +++ b/spinifex/services/viperblockd/provider_handlers.go @@ -18,11 +18,11 @@ import ( awss3 "github.com/aws/aws-sdk-go/service/s3" "github.com/mulgadc/bluebottle/pkg/safecast" viperblocklegacyv1 "github.com/mulgadc/spinifex/contracts/viperblockd/legacy/v1" + awsidentifiers "github.com/mulgadc/spinifex/spinifex/foundation/aws/identifiers" "github.com/mulgadc/spinifex/spinifex/providers/ebs" "github.com/mulgadc/spinifex/spinifex/providers/objectstore" "github.com/mulgadc/spinifex/spinifex/runtime/compute/nbd" hostprocess "github.com/mulgadc/spinifex/spinifex/runtime/host/process" - "github.com/mulgadc/spinifex/spinifex/utils" vbtypes "github.com/mulgadc/viperblock/types" "github.com/mulgadc/viperblock/viperblock" "github.com/nats-io/nats.go" @@ -1047,7 +1047,7 @@ func handleCreateSnapshot(ctx context.Context, cfg *Config, nc *nats.Conn, msg * return } - operationID := utils.GenerateResourceID("op") + operationID := awsidentifiers.GenerateResourceID("op") respondProvider(ctx, msg, ebsprovider.CreateSnapshotResponse{ Versioned: ebsprovider.NewVersioned(), OperationID: operationID, diff --git a/spinifex/utils/utils.go b/spinifex/utils/utils.go index e9c19351a..b66887285 100644 --- a/spinifex/utils/utils.go +++ b/spinifex/utils/utils.go @@ -2,8 +2,6 @@ package utils import ( "bytes" - "crypto/rand" - "encoding/hex" "encoding/json" "errors" "log/slog" @@ -13,17 +11,6 @@ import ( "github.com/mulgadc/spinifex/spinifex/foundation/aws/errors" ) -// GenerateResourceID generates a unique resource ID with the given prefix. -// Format: {prefix}-{17 hex chars} using crypto/rand. -func GenerateResourceID(prefix string) string { - b := make([]byte, 9) - if _, err := rand.Read(b); err != nil { - //nolint:forbidigo // Continuing after the CSPRNG fails could create predictable resource IDs. - panic("crypto/rand failed: " + err.Error()) - } - return prefix + "-" + hex.EncodeToString(b)[:17] -} - // GenerateErrorPayload serializes an ec2.ResponseError with the given code as JSON. func GenerateErrorPayload(code string) (jsonResponse []byte) { return GenerateErrorPayloadWithMessage(code, "") diff --git a/spinifex/utils/utils_test.go b/spinifex/utils/utils_test.go index 7a7692bd5..d688e46e3 100644 --- a/spinifex/utils/utils_test.go +++ b/spinifex/utils/utils_test.go @@ -1,39 +1,11 @@ package utils import ( - "strings" "testing" "github.com/stretchr/testify/assert" ) -func TestGenerateResourceID(t *testing.T) { - tests := []struct { - prefix string - }{ - {"i"}, - {"r"}, - {"vol"}, - {"snap"}, - {"key"}, - {"eigw"}, - {"ami"}, - } - - for _, tt := range tests { - t.Run(tt.prefix, func(t *testing.T) { - id := GenerateResourceID(tt.prefix) - assert.True(t, strings.HasPrefix(id, tt.prefix+"-")) - // prefix + "-" + 17 hex chars - assert.Len(t, id, len(tt.prefix)+1+17) - - // Verify uniqueness - id2 := GenerateResourceID(tt.prefix) - assert.NotEqual(t, id, id2) - }) - } -} - func TestUnmarshalJsonPayload(t *testing.T) { type TestStruct struct { Name string `json:"name"`