mirror of
https://github.com/mulgadc/spinifex.git
synced 2026-10-15 09:01:31 +00:00
fix: return AWS's error messages for placement group, key pair, launch template and address errors
This commit is contained in:
@@ -596,9 +596,9 @@ func (s *EIPServiceImpl) DescribeAddresses(ctx context.Context, input *ec2.Descr
|
||||
found[*addr.AllocationId] = true
|
||||
}
|
||||
}
|
||||
for id := range allocIDs {
|
||||
if !found[id] {
|
||||
return nil, errors.New(awserrors.ErrorInvalidAllocationIDNotFound)
|
||||
for _, id := range input.AllocationIds {
|
||||
if id != nil && !found[*id] {
|
||||
return nil, awserrors.Errorf(awserrors.ErrorInvalidAllocationIDNotFound, "The allocation ID '%s' does not exist", *id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -571,6 +571,25 @@ func TestEIP_DescribeAddresses_PublicIpNotFound(t *testing.T) {
|
||||
assert.Equal(t, "Address '"+*released.PublicIp+"' not found.", msg)
|
||||
}
|
||||
|
||||
func TestEIP_DescribeAddresses_AllocationIdNotFound(t *testing.T) {
|
||||
svc, _, _ := setupTestEIP(t)
|
||||
|
||||
kept, err := svc.AllocateAddress(context.Background(), &ec2.AllocateAddressInput{}, testAccountID)
|
||||
require.NoError(t, err)
|
||||
released, err := svc.AllocateAddress(context.Background(), &ec2.AllocateAddressInput{}, testAccountID)
|
||||
require.NoError(t, err)
|
||||
_, err = svc.ReleaseAddress(context.Background(), &ec2.ReleaseAddressInput{AllocationId: released.AllocationId}, testAccountID)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = svc.DescribeAddresses(context.Background(), &ec2.DescribeAddressesInput{
|
||||
AllocationIds: []*string{kept.AllocationId, released.AllocationId},
|
||||
}, testAccountID)
|
||||
code, msg, ok := awserrors.ResolveErrorDetail(err)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, awserrors.ErrorInvalidAllocationIDNotFound, code)
|
||||
assert.Equal(t, "The allocation ID '"+*released.AllocationId+"' does not exist", msg)
|
||||
}
|
||||
|
||||
func TestEIP_DescribeAddresses_FilterByAllocationId(t *testing.T) {
|
||||
svc, _, _ := setupTestEIP(t)
|
||||
|
||||
|
||||
@@ -132,7 +132,7 @@ func (s *KeyServiceImpl) CreateKeyPair(ctx context.Context, input *ec2.CreateKey
|
||||
if err == nil {
|
||||
// Object exists - return duplicate error
|
||||
slog.ErrorContext(ctx, "Key pair already exists", "keyName", keyName)
|
||||
return nil, errors.New(awserrors.ErrorInvalidKeyPairDuplicate)
|
||||
return nil, errDuplicateKeyPair()
|
||||
}
|
||||
|
||||
// Determine key type (default: ed25519, optional: rsa). This deviates from
|
||||
@@ -415,6 +415,17 @@ func (s *KeyServiceImpl) storeKeyPairMetadata(ctx context.Context, accountID, ke
|
||||
return nil
|
||||
}
|
||||
|
||||
// errDuplicateKeyPair is AWS's error for a name already in use, on create and
|
||||
// import alike.
|
||||
func errDuplicateKeyPair() error {
|
||||
return awserrors.Errorf(awserrors.ErrorInvalidKeyPairDuplicate, "The keypair already exists")
|
||||
}
|
||||
|
||||
// errInvalidKeyFormat is AWS's error for import material it cannot use.
|
||||
func errInvalidKeyFormat() error {
|
||||
return awserrors.Errorf(awserrors.ErrorInvalidKeyFormat, "Key is not in valid OpenSSH public key format")
|
||||
}
|
||||
|
||||
// getKeyNameFromKeyPairId retrieves the key name by directly reading the metadata file for a given keyPairId.
|
||||
func (s *KeyServiceImpl) getKeyNameFromKeyPairId(ctx context.Context, accountID, keyPairID string) (string, error) {
|
||||
metadataPath := fmt.Sprintf("keys/%s/%s.json", accountID, keyPairID)
|
||||
@@ -841,12 +852,12 @@ func (s *KeyServiceImpl) DescribeKeyPairs(ctx context.Context, input *ec2.Descri
|
||||
}
|
||||
for _, name := range input.KeyNames {
|
||||
if name != nil && !foundNames[*name] {
|
||||
return nil, errors.New(awserrors.ErrorInvalidKeyPairNotFound)
|
||||
return nil, awserrors.Errorf(awserrors.ErrorInvalidKeyPairNotFound, "The key pair '%s' does not exist", *name)
|
||||
}
|
||||
}
|
||||
for _, id := range input.KeyPairIds {
|
||||
if id != nil && !foundIDs[*id] {
|
||||
return nil, errors.New(awserrors.ErrorInvalidKeyPairNotFound)
|
||||
return nil, awserrors.Errorf(awserrors.ErrorInvalidKeyPairNotFound, "The keyPairId '%s' does not exist", *id)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -922,7 +933,7 @@ func (s *KeyServiceImpl) ImportKeyPair(ctx context.Context, input *ec2.ImportKey
|
||||
if err == nil {
|
||||
// Object exists - return duplicate error
|
||||
slog.ErrorContext(ctx, "Key pair already exists", "keyName", keyName)
|
||||
return nil, errors.New(awserrors.ErrorInvalidKeyPairDuplicate)
|
||||
return nil, errDuplicateKeyPair()
|
||||
}
|
||||
|
||||
// The material is stored verbatim and later served to instances as their
|
||||
@@ -940,7 +951,7 @@ func (s *KeyServiceImpl) ImportKeyPair(ctx context.Context, input *ec2.ImportKey
|
||||
publicKeyData := bytes.TrimSpace(input.PublicKeyMaterial)
|
||||
if bytes.ContainsAny(publicKeyData, "\r\n") {
|
||||
slog.ErrorContext(ctx, "Public key material is not a single key", "keyName", keyName)
|
||||
return nil, errors.New(awserrors.ErrorInvalidKeyFormat)
|
||||
return nil, errInvalidKeyFormat()
|
||||
}
|
||||
|
||||
// Parse the authorized-key line ("ssh-rsa AAAAB... comment"), which also
|
||||
@@ -948,7 +959,7 @@ func (s *KeyServiceImpl) ImportKeyPair(ctx context.Context, input *ec2.ImportKey
|
||||
publicKey, _, options, _, err := ssh.ParseAuthorizedKey(publicKeyData)
|
||||
if err != nil {
|
||||
slog.ErrorContext(ctx, "Invalid public key format", "keyName", keyName, "err", err)
|
||||
return nil, errors.New(awserrors.ErrorInvalidKeyFormat)
|
||||
return nil, errInvalidKeyFormat()
|
||||
}
|
||||
|
||||
// An option prefix ("command=...", "from=...") is not covered by the
|
||||
@@ -956,13 +967,13 @@ func (s *KeyServiceImpl) ImportKeyPair(ctx context.Context, input *ec2.ImportKey
|
||||
// launched with this key pair. Refuse to import access the API cannot report.
|
||||
if len(options) > 0 {
|
||||
slog.ErrorContext(ctx, "Public key material carries authorized_keys options", "keyName", keyName, "options", options)
|
||||
return nil, errors.New(awserrors.ErrorInvalidKeyFormat)
|
||||
return nil, errInvalidKeyFormat()
|
||||
}
|
||||
|
||||
keyType, err := keyPairType(publicKey)
|
||||
if err != nil {
|
||||
slog.ErrorContext(ctx, "Unsupported key type", "algorithm", publicKey.Type(), "keyName", keyName, "err", err)
|
||||
return nil, errors.New(awserrors.ErrorInvalidKeyFormat)
|
||||
return nil, errInvalidKeyFormat()
|
||||
}
|
||||
|
||||
fingerprint, err := importedKeyFingerprint(publicKey)
|
||||
|
||||
@@ -255,7 +255,7 @@ func TestCreateKeyPair_Duplicate(t *testing.T) {
|
||||
}, testAccountID)
|
||||
require.Error(t, err)
|
||||
assert.Nil(t, out)
|
||||
assert.Equal(t, awserrors.ErrorInvalidKeyPairDuplicate, err.Error())
|
||||
assertAWSError(t, err, awserrors.ErrorInvalidKeyPairDuplicate, "The keypair already exists")
|
||||
}
|
||||
|
||||
func TestCreateKeyPair_InvalidKeyType(t *testing.T) {
|
||||
@@ -377,7 +377,7 @@ func TestImportKeyPair_Duplicate(t *testing.T) {
|
||||
}, testAccountID)
|
||||
require.Error(t, err)
|
||||
assert.Nil(t, out)
|
||||
assert.Equal(t, awserrors.ErrorInvalidKeyPairDuplicate, err.Error())
|
||||
assertAWSError(t, err, awserrors.ErrorInvalidKeyPairDuplicate, "The keypair already exists")
|
||||
}
|
||||
|
||||
func TestImportKeyPair_InvalidKeyName(t *testing.T) {
|
||||
@@ -466,7 +466,7 @@ func TestImportKeyPairInvalidKeyFormat(t *testing.T) {
|
||||
PublicKeyMaterial: []byte(tt.publicKey),
|
||||
}, testAccountID)
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, tt.expectedErrMsg, err.Error())
|
||||
assertAWSError(t, err, tt.expectedErrMsg, "Key is not in valid OpenSSH public key format")
|
||||
|
||||
// Rejection must precede the upload, or the guest is served material
|
||||
// the API refused.
|
||||
@@ -795,7 +795,7 @@ func TestDescribeKeyPairs_NotFound_ByKeyName(t *testing.T) {
|
||||
KeyNames: []*string{aws.String("does-not-exist")},
|
||||
}, testAccountID)
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "InvalidKeyPair.NotFound")
|
||||
assertAWSError(t, err, awserrors.ErrorInvalidKeyPairNotFound, "The key pair 'does-not-exist' does not exist")
|
||||
}
|
||||
|
||||
// TestDescribeKeyPairs_NotFound_ByKeyPairId is the KeyPairIds counterpart of
|
||||
@@ -809,7 +809,7 @@ func TestDescribeKeyPairs_NotFound_ByKeyPairId(t *testing.T) {
|
||||
KeyPairIds: []*string{aws.String("key-doesnotexist")},
|
||||
}, testAccountID)
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "InvalidKeyPair.NotFound")
|
||||
assertAWSError(t, err, awserrors.ErrorInvalidKeyPairNotFound, "The keyPairId 'key-doesnotexist' does not exist")
|
||||
}
|
||||
|
||||
// TestDescribeKeyPairs_NotFound_PartialMatch asserts that naming one existing
|
||||
@@ -1243,3 +1243,12 @@ func TestKeyPair_CentralTagStoreFailureDoesNotFailOperation(t *testing.T) {
|
||||
assert.Equal(t, 2, central.puts)
|
||||
assert.Equal(t, 1, central.deletes)
|
||||
}
|
||||
|
||||
// assertAWSError checks the code a client receives and the message with it.
|
||||
func assertAWSError(t *testing.T, err error, wantCode, wantMessage string) {
|
||||
t.Helper()
|
||||
code, msg, ok := awserrors.ResolveErrorDetail(err)
|
||||
require.True(t, ok, "unresolvable error: %v", err)
|
||||
assert.Equal(t, wantCode, code)
|
||||
assert.Equal(t, wantMessage, msg)
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ func TestExpandRunInstances_MissingVersion(t *testing.T) {
|
||||
}
|
||||
err := ExpandRunInstances(context.Background(), svc, input, testAccountID)
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, awserrors.ErrorInvalidLaunchTemplateIdVersionNotFound, err.Error())
|
||||
assertCode(t, err, awserrors.ErrorInvalidLaunchTemplateIdVersionNotFound)
|
||||
}
|
||||
|
||||
func TestExpandRunInstances_IdNameConflict(t *testing.T) {
|
||||
@@ -115,5 +115,14 @@ func TestExpandRunInstances_UnknownTemplate(t *testing.T) {
|
||||
}
|
||||
err := ExpandRunInstances(context.Background(), svc, input, testAccountID)
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, awserrors.ErrorInvalidLaunchTemplateIdNotFound, err.Error())
|
||||
assertCode(t, err, awserrors.ErrorInvalidLaunchTemplateIdNotFound)
|
||||
}
|
||||
|
||||
// assertCode checks the code a client receives; the message is the
|
||||
// DescribeLaunchTemplateVersions one the expansion passes through.
|
||||
func assertCode(t *testing.T, err error, want string) {
|
||||
t.Helper()
|
||||
code, ok := awserrors.ResolveErrorCode(err)
|
||||
require.True(t, ok, "unresolvable error: %v", err)
|
||||
assert.Equal(t, want, code)
|
||||
}
|
||||
|
||||
@@ -143,9 +143,19 @@ func (s *LaunchTemplateServiceImpl) resolveHeader(ctx context.Context, accountID
|
||||
if err := validateTemplateID(id); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return s.getHeaderByID(ctx, accountID, id)
|
||||
h, entry, err := s.getHeaderByID(ctx, accountID, id)
|
||||
if err != nil && err.Error() == awserrors.ErrorInvalidLaunchTemplateIdNotFound {
|
||||
return nil, nil, awserrors.Errorf(awserrors.ErrorInvalidLaunchTemplateIdNotFound,
|
||||
"The specified launch template, with template ID %s, does not exist.", id)
|
||||
}
|
||||
return h, entry, err
|
||||
case nm != "":
|
||||
return s.getHeaderByName(ctx, accountID, nm)
|
||||
h, entry, err := s.getHeaderByName(ctx, accountID, nm)
|
||||
if err != nil && err.Error() == awserrors.ErrorInvalidLaunchTemplateNameNotFoundException {
|
||||
return nil, nil, awserrors.Errorf(awserrors.ErrorInvalidLaunchTemplateNameNotFoundException,
|
||||
"The specified launch template, with template name %s, does not exist.", nm)
|
||||
}
|
||||
return h, entry, err
|
||||
default:
|
||||
return nil, nil, errors.New(awserrors.ErrorMissingParameter)
|
||||
}
|
||||
@@ -308,6 +318,11 @@ func (s *LaunchTemplateServiceImpl) CreateLaunchTemplate(ctx context.Context, in
|
||||
}, nil
|
||||
}
|
||||
|
||||
// errNameInUse is AWS's error for a launch template name already taken.
|
||||
func errNameInUse() error {
|
||||
return awserrors.Errorf(awserrors.ErrorInvalidLaunchTemplateNameAlreadyExistsException, "Launch template name already in use.")
|
||||
}
|
||||
|
||||
// claimName atomically reserves a template name via kv.Create, with
|
||||
// repair-on-write: if the name already exists but its header is gone (a crash
|
||||
// orphan), reclaim it via a revision-guarded CAS update. Only a name whose header
|
||||
@@ -328,12 +343,12 @@ func (s *LaunchTemplateServiceImpl) claimName(ctx context.Context, accountID, na
|
||||
_, herr := s.kv.Get(ctx, headerKey(accountID, string(entry.Value())))
|
||||
switch {
|
||||
case herr == nil:
|
||||
return errors.New(awserrors.ErrorInvalidLaunchTemplateNameAlreadyExistsException)
|
||||
return errNameInUse()
|
||||
case !errors.Is(herr, jetstream.ErrKeyNotFound):
|
||||
return errors.New(awserrors.ErrorServerInternal)
|
||||
}
|
||||
if _, err := s.kv.Update(ctx, key, []byte(ltID), entry.Revision()); err != nil {
|
||||
return errors.New(awserrors.ErrorInvalidLaunchTemplateNameAlreadyExistsException)
|
||||
return errNameInUse()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -381,11 +396,11 @@ func (s *LaunchTemplateServiceImpl) CreateLaunchTemplateVersion(ctx context.Cont
|
||||
if src := aws.StringValue(input.SourceVersion); src != "" {
|
||||
n, err := s.resolveVersionNumber(ctx, accountID, header, src)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, withVersionNotFoundMessage(err, "Launch template version does not exist.")
|
||||
}
|
||||
base, err := s.getVersion(ctx, accountID, header.LaunchTemplateId, n)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, withVersionNotFoundMessage(err, "Launch template version does not exist.")
|
||||
}
|
||||
data = mergeResponseData(base.Data, override)
|
||||
}
|
||||
@@ -440,14 +455,15 @@ func (s *LaunchTemplateServiceImpl) ModifyLaunchTemplate(ctx context.Context, in
|
||||
}
|
||||
|
||||
if sel := aws.StringValue(input.DefaultVersion); sel != "" {
|
||||
notFound := fmt.Sprintf("The launch template version %s is not found for the specified launch template.", sel)
|
||||
n, err := s.resolveVersionNumber(ctx, accountID, header, sel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, withVersionNotFoundMessage(err, notFound)
|
||||
}
|
||||
// Verify the target body exists immediately before the header CAS so a
|
||||
// concurrent delete degrades to VersionNotFound, never a dangling default.
|
||||
if _, err := s.getVersion(ctx, accountID, header.LaunchTemplateId, n); err != nil {
|
||||
return nil, err
|
||||
return nil, withVersionNotFoundMessage(err, notFound)
|
||||
}
|
||||
header.DefaultVersionNumber = n
|
||||
data, err := json.Marshal(header)
|
||||
@@ -635,14 +651,15 @@ func (s *LaunchTemplateServiceImpl) DescribeLaunchTemplates(ctx context.Context,
|
||||
templates = append(templates, headerToEC2(&h, latestByID[h.LaunchTemplateId]))
|
||||
}
|
||||
|
||||
const missing = "At least one of the launch templates specified in the request does not exist."
|
||||
for name := range nameSet {
|
||||
if !foundNames[name] {
|
||||
return nil, errors.New(awserrors.ErrorInvalidLaunchTemplateNameNotFoundException)
|
||||
return nil, awserrors.Errorf(awserrors.ErrorInvalidLaunchTemplateNameNotFoundException, missing)
|
||||
}
|
||||
}
|
||||
for id := range idSet {
|
||||
if !foundIDs[id] {
|
||||
return nil, errors.New(awserrors.ErrorInvalidLaunchTemplateIdNotFound)
|
||||
return nil, awserrors.Errorf(awserrors.ErrorInvalidLaunchTemplateIdNotFound, missing)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -738,12 +755,14 @@ func (s *LaunchTemplateServiceImpl) selectVersionNumbers(ctx context.Context, ac
|
||||
seen := make(map[int64]bool)
|
||||
var out []int64
|
||||
for _, v := range input.Versions {
|
||||
notFound := fmt.Sprintf("Could not find the specified version %s for the launch template with ID %s.",
|
||||
aws.StringValue(v), header.LaunchTemplateId)
|
||||
n, err := s.resolveVersionNumber(ctx, accountID, header, aws.StringValue(v))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, withVersionNotFoundMessage(err, notFound)
|
||||
}
|
||||
if _, err := s.getVersion(ctx, accountID, header.LaunchTemplateId, n); err != nil {
|
||||
return nil, err
|
||||
return nil, withVersionNotFoundMessage(err, notFound)
|
||||
}
|
||||
if !seen[n] {
|
||||
seen[n] = true
|
||||
@@ -864,6 +883,15 @@ func versionRecToEC2(h *LaunchTemplateHeader, rec *LaunchTemplateVersionRec, isD
|
||||
|
||||
// --- misc helpers ---
|
||||
|
||||
// withVersionNotFoundMessage attaches AWS's message, which differs by action,
|
||||
// to a VersionNotFound error and passes any other error through.
|
||||
func withVersionNotFoundMessage(err error, message string) error {
|
||||
if err.Error() != awserrors.ErrorInvalidLaunchTemplateIdVersionNotFound {
|
||||
return err
|
||||
}
|
||||
return awserrors.Errorf(awserrors.ErrorInvalidLaunchTemplateIdVersionNotFound, "%s", message)
|
||||
}
|
||||
|
||||
// isHeaderKey reports whether k is a template header key (account.lt-<id>) as
|
||||
// opposed to a name index, version body (account.lt-<id>.v<n>), or _version key.
|
||||
func isHeaderKey(k, prefix string) bool {
|
||||
|
||||
@@ -84,7 +84,7 @@ func TestCreateLaunchTemplate_DuplicateName(t *testing.T) {
|
||||
LaunchTemplateData: &ec2.RequestLaunchTemplateData{ImageId: aws.String("ami-1")},
|
||||
}, testAccountID)
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, awserrors.ErrorInvalidLaunchTemplateNameAlreadyExistsException, err.Error())
|
||||
assertAWSError(t, err, awserrors.ErrorInvalidLaunchTemplateNameAlreadyExistsException, "Launch template name already in use.")
|
||||
}
|
||||
|
||||
func TestLaunchTemplateNameWithKVUnsafeCharacters(t *testing.T) {
|
||||
@@ -158,7 +158,8 @@ func TestDescribeLaunchTemplates_UnknownName(t *testing.T) {
|
||||
LaunchTemplateNames: []*string{aws.String("missing")},
|
||||
}, testAccountID)
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, awserrors.ErrorInvalidLaunchTemplateNameNotFoundException, err.Error())
|
||||
assertAWSError(t, err, awserrors.ErrorInvalidLaunchTemplateNameNotFoundException,
|
||||
"At least one of the launch templates specified in the request does not exist.")
|
||||
}
|
||||
|
||||
func TestDescribeLaunchTemplates_TagFilter(t *testing.T) {
|
||||
@@ -331,7 +332,39 @@ func TestDescribeLaunchTemplateVersions_MissingVersion(t *testing.T) {
|
||||
Versions: []*string{aws.String("99")},
|
||||
}, testAccountID)
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, awserrors.ErrorInvalidLaunchTemplateIdVersionNotFound, err.Error())
|
||||
assertAWSError(t, err, awserrors.ErrorInvalidLaunchTemplateIdVersionNotFound,
|
||||
"Could not find the specified version 99 for the launch template with ID "+aws.StringValue(lt.LaunchTemplateId)+".")
|
||||
}
|
||||
|
||||
func TestLaunchTemplate_NotFoundMessagesNameTheTemplate(t *testing.T) {
|
||||
svc := setupTestService(t)
|
||||
lt := createTemplate(t, svc, "web", "t3.micro")
|
||||
|
||||
_, err := svc.DeleteLaunchTemplate(context.Background(), &ec2.DeleteLaunchTemplateInput{
|
||||
LaunchTemplateName: aws.String("missing"),
|
||||
}, testAccountID)
|
||||
assertAWSError(t, err, awserrors.ErrorInvalidLaunchTemplateNameNotFoundException,
|
||||
"The specified launch template, with template name missing, does not exist.")
|
||||
|
||||
_, err = svc.DescribeLaunchTemplateVersions(context.Background(), &ec2.DescribeLaunchTemplateVersionsInput{
|
||||
LaunchTemplateId: aws.String("lt-0123456789abcdef0"),
|
||||
}, testAccountID)
|
||||
assertAWSError(t, err, awserrors.ErrorInvalidLaunchTemplateIdNotFound,
|
||||
"The specified launch template, with template ID lt-0123456789abcdef0, does not exist.")
|
||||
|
||||
_, err = svc.ModifyLaunchTemplate(context.Background(), &ec2.ModifyLaunchTemplateInput{
|
||||
LaunchTemplateId: lt.LaunchTemplateId,
|
||||
DefaultVersion: aws.String("42"),
|
||||
}, testAccountID)
|
||||
assertAWSError(t, err, awserrors.ErrorInvalidLaunchTemplateIdVersionNotFound,
|
||||
"The launch template version 42 is not found for the specified launch template.")
|
||||
|
||||
_, err = svc.CreateLaunchTemplateVersion(context.Background(), &ec2.CreateLaunchTemplateVersionInput{
|
||||
LaunchTemplateId: lt.LaunchTemplateId,
|
||||
SourceVersion: aws.String("42"),
|
||||
LaunchTemplateData: &ec2.RequestLaunchTemplateData{InstanceType: aws.String("t3.small")},
|
||||
}, testAccountID)
|
||||
assertAWSError(t, err, awserrors.ErrorInvalidLaunchTemplateIdVersionNotFound, "Launch template version does not exist.")
|
||||
}
|
||||
|
||||
func TestDescribeLaunchTemplateVersions_LatestAfterTailDelete(t *testing.T) {
|
||||
@@ -388,7 +421,8 @@ func TestModifyLaunchTemplate_DefaultToMissingVersion(t *testing.T) {
|
||||
DefaultVersion: aws.String("42"),
|
||||
}, testAccountID)
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, awserrors.ErrorInvalidLaunchTemplateIdVersionNotFound, err.Error())
|
||||
code, _ := awserrors.ResolveErrorCode(err)
|
||||
assert.Equal(t, awserrors.ErrorInvalidLaunchTemplateIdVersionNotFound, code)
|
||||
}
|
||||
|
||||
// --- DeleteLaunchTemplateVersions ---
|
||||
@@ -453,7 +487,8 @@ func TestDeleteLaunchTemplate_RemovesEverything(t *testing.T) {
|
||||
LaunchTemplateIds: []*string{aws.String(id)},
|
||||
}, testAccountID)
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, awserrors.ErrorInvalidLaunchTemplateIdNotFound, err.Error())
|
||||
code, _ := awserrors.ResolveErrorCode(err)
|
||||
assert.Equal(t, awserrors.ErrorInvalidLaunchTemplateIdNotFound, code)
|
||||
|
||||
// Version bodies and name index gone.
|
||||
nums, err := svc.listVersionNumbers(t.Context(), testAccountID, id)
|
||||
@@ -472,7 +507,8 @@ func TestDeleteLaunchTemplate_Unknown(t *testing.T) {
|
||||
LaunchTemplateId: aws.String("lt-doesnotexist000"),
|
||||
}, testAccountID)
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, awserrors.ErrorInvalidLaunchTemplateIdNotFound, err.Error())
|
||||
code, _ := awserrors.ResolveErrorCode(err)
|
||||
assert.Equal(t, awserrors.ErrorInvalidLaunchTemplateIdNotFound, code)
|
||||
}
|
||||
|
||||
func TestResolveHeader_IdNameConflict(t *testing.T) {
|
||||
@@ -503,3 +539,13 @@ func TestDescribeLaunchTemplates_MalformedId(t *testing.T) {
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, awserrors.ErrorInvalidLaunchTemplateIdMalformed, err.Error())
|
||||
}
|
||||
|
||||
// assertAWSError checks the code a client receives and the message with it.
|
||||
func assertAWSError(t *testing.T, err error, wantCode, wantMessage string) {
|
||||
t.Helper()
|
||||
require.Error(t, err)
|
||||
code, msg, ok := awserrors.ResolveErrorDetail(err)
|
||||
require.True(t, ok, "unresolvable error: %v", err)
|
||||
assert.Equal(t, wantCode, code)
|
||||
assert.Equal(t, wantMessage, msg)
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ func (s *PlacementGroupServiceImpl) CreatePlacementGroup(ctx context.Context, in
|
||||
// Atomic create-if-not-exists to prevent TOCTOU race on duplicate names
|
||||
if _, err := s.kv.Create(ctx, key, data); err != nil {
|
||||
// Create fails if key already exists
|
||||
return nil, errors.New(awserrors.ErrorInvalidPlacementGroupDuplicate)
|
||||
return nil, awserrors.Errorf(awserrors.ErrorInvalidPlacementGroupDuplicate, "The placement group '%s' already exists.", groupName)
|
||||
}
|
||||
|
||||
slog.InfoContext(ctx, "CreatePlacementGroup completed", "groupId", groupID, "groupName", groupName, "strategy", strategy, "accountID", accountID)
|
||||
@@ -152,7 +152,8 @@ func (s *PlacementGroupServiceImpl) DeletePlacementGroup(ctx context.Context, in
|
||||
|
||||
entry, err := s.kv.Get(ctx, key)
|
||||
if err != nil {
|
||||
return nil, errors.New(awserrors.ErrorInvalidPlacementGroupUnknown)
|
||||
// Lower case, unlike DescribePlacementGroups' message for the same code.
|
||||
return nil, awserrors.Errorf(awserrors.ErrorInvalidPlacementGroupUnknown, "The placement group '%s' is unknown.", groupName)
|
||||
}
|
||||
|
||||
var record PlacementGroupRecord
|
||||
@@ -260,9 +261,9 @@ func (s *PlacementGroupServiceImpl) DescribePlacementGroups(ctx context.Context,
|
||||
found[*g.GroupName] = true
|
||||
}
|
||||
}
|
||||
for name := range nameSet {
|
||||
if !found[name] {
|
||||
return nil, errors.New(awserrors.ErrorInvalidPlacementGroupUnknown)
|
||||
for _, name := range input.GroupNames {
|
||||
if name != nil && !found[*name] {
|
||||
return nil, awserrors.Errorf(awserrors.ErrorInvalidPlacementGroupUnknown, "The Placement Group '%s' is unknown.", *name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ func TestCreatePlacementGroup_DuplicateName(t *testing.T) {
|
||||
Strategy: aws.String("spread"),
|
||||
}, testAccountID)
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, awserrors.ErrorInvalidPlacementGroupDuplicate, err.Error())
|
||||
assertAWSError(t, err, awserrors.ErrorInvalidPlacementGroupDuplicate, "The placement group 'dup-group' already exists.")
|
||||
}
|
||||
|
||||
func TestCreatePlacementGroup_PartitionRejected(t *testing.T) {
|
||||
@@ -243,7 +243,7 @@ func TestDeletePlacementGroup_NotFound(t *testing.T) {
|
||||
GroupName: aws.String("nonexistent"),
|
||||
}, testAccountID)
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, awserrors.ErrorInvalidPlacementGroupUnknown, err.Error())
|
||||
assertAWSError(t, err, awserrors.ErrorInvalidPlacementGroupUnknown, "The placement group 'nonexistent' is unknown.")
|
||||
}
|
||||
|
||||
func TestDeletePlacementGroup_InUse(t *testing.T) {
|
||||
@@ -380,7 +380,7 @@ func TestDescribePlacementGroups_NameNotFound(t *testing.T) {
|
||||
GroupNames: []*string{aws.String("ghost")},
|
||||
}, testAccountID)
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, awserrors.ErrorInvalidPlacementGroupUnknown, err.Error())
|
||||
assertAWSError(t, err, awserrors.ErrorInvalidPlacementGroupUnknown, "The Placement Group 'ghost' is unknown.")
|
||||
}
|
||||
|
||||
func TestDescribePlacementGroups_AccountScoped(t *testing.T) {
|
||||
@@ -906,3 +906,12 @@ func TestRemoveInstance_ReadFailureIsNotSuccess(t *testing.T) {
|
||||
require.Error(t, err)
|
||||
assert.True(t, awserrors.IsErrorCode(err, awserrors.ErrorServerInternal), "got %v", err)
|
||||
}
|
||||
|
||||
// assertAWSError checks the code a client receives and the message with it.
|
||||
func assertAWSError(t *testing.T, err error, wantCode, wantMessage string) {
|
||||
t.Helper()
|
||||
code, msg, ok := awserrors.ResolveErrorDetail(err)
|
||||
require.True(t, ok, "unresolvable error: %v", err)
|
||||
assert.Equal(t, wantCode, code)
|
||||
assert.Equal(t, wantMessage, msg)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user