Repository: go-to-k/delstack
Pull request #118 (2023-03-05 12:49:54)

feat: use ClientError at errors


Patch:
diff --git a/pkg/client/backup.go b/pkg/client/backup.go
index 444a263..51ca386 100644
--- a/pkg/client/backup.go
+++ b/pkg/client/backup.go
@@ -35,7 +35,10 @@ func (b *Backup) ListRecoveryPointsByBackupVault(ctx context.Context, backupVaul
 	for {
 		select {
 		case <-ctx.Done():
-			return recoveryPoints, ctx.Err()
+			return recoveryPoints, &ClientError{
+				ResourceName: backupVaultName,
+				Err:          ctx.Err(),
+			}
 		default:
 		}
 
@@ -46,7 +49,10 @@ func (b *Backup) ListRecoveryPointsByBackupVault(ctx context.Context, backupVaul
 
 		output, err := b.client.ListRecoveryPointsByBackupVault(ctx, input)
 		if err != nil {
-			return nil, err
+			return nil, &ClientError{
+				ResourceName: backupVaultName,
+				Err:          err,
+			}
 		}
 		recoveryPoints = append(recoveryPoints, output.RecoveryPoints...)
 
@@ -62,7 +68,7 @@ func (b *Backup) ListRecoveryPointsByBackupVault(ctx context.Context, backupVaul
 func (b *Backup) DeleteRecoveryPoints(ctx context.Context, backupVaultName *string, recoveryPoints []types.RecoveryPointByBackupVault) error {
 	for _, recoveryPoint := range recoveryPoints {
 		if err := b.DeleteRecoveryPoint(ctx, backupVaultName, recoveryPoint.RecoveryPointArn); err != nil {
-			return err
+			return err // return non wrapping error because already wrapped error in DeleteRecoveryPoint
 		}
 	}
 	return nil
@@ -75,8 +81,13 @@ func (b *Backup) DeleteRecoveryPoint(ctx context.Context, backupVaultName *strin
 	}
 
 	_, err := b.client.DeleteRecoveryPoint(ctx, input)
-
-	return err
+	if err != nil {
+		return &ClientError{
+			ResourceName: backupVaultName,
+			Err:          err,
+		}
+	}
+	return nil
 }
 
 func (b *Backup) DeleteBackupVault(ctx context.Context, backupVaultName *string) error {
@@ -85,8 +96,13 @@ func (b *Backup) DeleteBackupVault(ctx context.Context, backupVaultName *string)
 	}
 
 	_, err := b.client.DeleteBackupVault(ctx, input)
-
-	return err
+	if err != nil {
+		return &ClientError{
+			ResourceName: backupVaultName,
+			Err:          err,
+		}
+	}
+	return nil
 }
 
 func (b *Backup) CheckBackupVaultExists(ctx context.Context, backupVaultName *string) (bool, error) {
@@ -95,7 +111,10 @@ func (b *Backup) CheckBackupVaultExists(ctx context.Context, backupVaultName *st
 	for {
 		select {
 		case <-ctx.Done():
-			return false, ctx.Err()
+			return false, &ClientError{
+				ResourceName: backupVaultName,
+				Err:          ctx.Err(),
+			}
 		default:
 		}
 
@@ -105,7 +124,10 @@ func (b *Backup) CheckBackupVaultExists(ctx context.Context, backupVaultName *st
 
 		output, err := b.client.ListBackupVaults(ctx, input)
 		if err != nil {
-			return false, err
+			return false, &ClientError{
+				ResourceName: backupVaultName,
+				Err:          err,
+			}
 		}
 
 		for _, vault := range output.BackupVaultList {
diff --git a/pkg/client/cloudformation.go b/pkg/client/cloudformation.go
index 9e1df3e..144ee8f 100644
--- a/pkg/client/cloudformation.go
+++ b/pkg/client/cloudformation.go
@@ -40,11 +40,17 @@ func (c *CloudFormation) DeleteStack(ctx context.Context, stackName *string, ret
 	}
 
 	if _, err := c.client.DeleteStack(ctx, input); err != nil {
-		return err
+		return &ClientError{
+			ResourceName: stackName,
+			Err:          err,
+		}
 	}
 
 	if err := c.waitDeleteStack(ctx, stackName); err != nil {
-		return err
+		return &ClientError{
+			ResourceName: stackName,
+			Err:          err,
+		}
 	}
 
 	return nil
@@ -59,8 +65,13 @@ func (c *CloudFormation) DescribeStacks(ctx context.Context, stackName *string)
 	if err != nil && strings.Contains(err.Error(), "does not exist") {
 		return output, false, nil
 	}
-
-	return output, true, err
+	if err != nil {
+		return output, true, &ClientError{
+			ResourceName: stackName,
+			Err:          err,
+		}
+	}
+	return output, true, nil
 }
 
 func (c *CloudFormation) waitDeleteStack(ctx context.Context, stackName *string) error {
@@ -70,7 +81,7 @@ func (c *CloudFormation) waitDeleteStack(ctx context.Context, stackName *string)
 
 	err := c.waiter.Wait(ctx, input, CloudFormationWaitNanoSecTime)
 	if err != nil && !strings.Contains(err.Error(), "waiter state transitioned to Failure") {
-		return err
+		return err // return non wrapping error because wrap in public callers
 	}
 
 	return nil
@@ -83,7 +94,10 @@ func (c *CloudFormation) ListStackResources(ctx context.Context, stackName *stri
 	for {
 		select {
 		case <-ctx.Done():
-			return stackResourceSummaries, ctx.Err()
+			return stackResourceSummaries, &ClientError{
+				ResourceName: stackName,
+				Err:          ctx.Err(),
+			}
 		default:
 		}
 
@@ -94,7 +108,10 @@ func (c *CloudFormation) ListStackResources(ctx context.Context, stackName *stri
 
 		output, err := c.client.ListStackResources(ctx, input)
 		if err != nil {
-			return stackResourceSummaries, err
+			return stackResourceSummaries, &ClientError{
+				ResourceName: stackName,
+				Err:          err,
+			}
 		}
 
 		stackResourceSummaries = append(stackResourceSummaries, output.StackResourceSummaries...)
@@ -115,7 +132,9 @@ func (c *CloudFormation) ListStacks(ctx context.Context) ([]types.StackSummary,
 	for {
 		select {
 		case <-ctx.Done():
-			return stackSummaries, ctx.Err()
+			return stackSummaries, &ClientError{
+				Err: ctx.Err(),
+			}
 		default:
 		}
 
@@ -149,7 +168,9 @@ func (c *CloudFormation) ListStacks(ctx context.Context) ([]types.StackSummary,
 
 		output, err := c.client.ListStacks(ctx, input)
 		if err != nil {
-			return stackSummaries, err
+			return stackSummaries, &ClientError{
+				Err: err,
+			}
 		}
 
 		stackSummaries = append(stackSummaries, output.StackSummaries...)
diff --git a/pkg/client/ecr.go b/pkg/client/ecr.go
index b300601..0a453c8 100644
--- a/pkg/client/ecr.go
+++ b/pkg/client/ecr.go
@@ -32,8 +32,13 @@ func (e *Ecr) DeleteRepository(ctx context.Context, repositoryName *string) erro
 	}
 
 	_, err := e.client.DeleteRepository(ctx, input)
-
-	return err
+	if err != nil {
+		return &ClientError{
+			ResourceName: repositoryName,
+			Err:          err,
+		}
+	}
+	return nil
 }
 
 func (e *Ecr) CheckEcrExists(ctx context.Context, repositoryName *string) (bool, error) {
@@ -42,7 +47,10 @@ func (e *Ecr) CheckEcrExists(ctx context.Context, repositoryName *string) (bool,
 	for {
 		select {
 		case <-ctx.Done():
-			return false, ctx.Err()
+			return false, &ClientError{
+				ResourceName: repositoryName,
+				Err:          ctx.Err(),
+			}
 		default:
 		}
 
@@ -58,7 +66,10 @@ func (e *Ecr) CheckEcrExists(ctx context.Context, repositoryName *string) (bool,
 			return false, nil
 		}
 		if err != nil {
-			return false, err
+			return false, &ClientError{
+				ResourceName: repositoryName,
+				Err:          err,
+			}
 		}
 
 		for _, repository := range output.Repositories {
diff --git a/pkg/client/errors.go b/pkg/client/errors.go
new file mode 100644
index 0000000..120fa7a
--- /dev/null
+++ b/pkg/client/errors.go
@@ -0,0 +1,25 @@
+package client
+
+import "fmt"
+
+var _ error = (*ClientError)(nil)
+
+// ClientError provides the error with a resource name
+type ClientError struct {
+	ResourceName *string
+	Err          error
+}
+
+func (e *ClientError) Error() string {
+	var msg string
+	if e.ResourceName == nil {
+		msg = fmt.Sprintf("[no resource] %v", e.Err)
+	} else {
+		msg = fmt.Sprintf("[resource %s] %v", *e.ResourceName, e.Err)
+	}
+	return msg
+}
+
+func (e *ClientError) Unwrap() error {
+	return e.Err
+}
diff --git a/pkg/client/iam.go b/pkg/client/iam.go
index 689c64d..d416424 100644
--- a/pkg/client/iam.go
+++ b/pkg/client/iam.go
@@ -50,7 +50,13 @@ func (i *Iam) DeleteRole(ctx context.Context, roleName *string) error {
 			RetryableChecker: retryable,
 		},
 	)
-	return err
+	if err != nil {
+		return &ClientError{
+			ResourceName: roleName,
+			Err:          err,
+		}
+	}
+	return nil
 }
 
 func (i *Iam) ListAttachedRolePolicies(ctx context.Context, roleName *string) ([]types.AttachedPolicy, error) {
@@ -60,7 +66,10 @@ func (i *Iam) ListAttachedRolePolicies(ctx context.Context, roleName *string) ([
 	for {
 		select {
 		case <-ctx.Done():
-			return policies, ctx.Err()
+			return policies, &ClientError{
+				ResourceName: roleName,
+				Err:          ctx.Err(),
+			}
 		default:
 		}
 
@@ -83,9 +92,11 @@ func (i *Iam) ListAttachedRolePolicies(ctx context.Context, roleName *string) ([
 				RetryableChecker: retryable,
 			},
 		)
-
 		if err != nil {
-			return nil, err
+			return nil, &ClientError{
+				ResourceName: roleName,
+				Err:          err,
+			}
 		}
 
 		policies = append(policies, output.AttachedPolicies...)
@@ -102,7 +113,7 @@ func (i *Iam) ListAttachedRolePolicies(ctx context.Context, roleName *string) ([
 func (i *Iam) DetachRolePolicies(ctx context.Context, roleName *string, policies []types.AttachedPolicy) error {
 	for _, policy := range policies {
 		if err := i.DetachRolePolicy(ctx, roleName, policy.PolicyArn); err != nil {
-			return err
+			return err // return non wrapping error because already wrapped error in DetachRolePolicy
 		}
 	}
 
@@ -129,7 +140,13 @@ func (i *Iam) DetachRolePolicy(ctx context.Context, roleName *string, PolicyArn
 			RetryableChecker: retryable,
 		},
 	)
-	return err
+	if err != nil {
+		return &ClientError{
+			ResourceName: roleName,
+			Err:          err,
+		}
+	}
+	return nil
 }
 
 func (i *Iam) CheckRoleExists(ctx context.Context, roleName *string) (bool, error) {
@@ -156,7 +173,10 @@ func (i *Iam) CheckRoleExists(ctx context.Context, roleName *string) (bool, erro
 		return false, nil
 	}
 	if err != nil {
-		return false, err
+		return false, &ClientError{
+			ResourceName: roleName,
+			Err:          err,
+		}
 	}
 
 	return true, nil
diff --git a/pkg/client/s3.go b/pkg/client/s3.go
index 60d156f..8c13c60 100644
--- a/pkg/client/s3.go
+++ b/pkg/client/s3.go
@@ -43,8 +43,13 @@ func (s *S3) DeleteBucket(ctx context.Context, bucketName *string) error {
 	}
 
 	_, err := s.client.DeleteBucket(ctx, input)
-
-	return err
+	if err != nil {
+		return &ClientError{
+			ResourceName: bucketName,
+			Err:          err,
+		}
+	}
+	return nil
 }
 
 func (s *S3) DeleteObjects(ctx context.Context, bucketName *string, objects []types.ObjectIdentifier) ([]types.Error, error) {
@@ -92,7 +97,10 @@ func (s *S3) DeleteObjects(ctx context.Context, bucketName *string, objects []ty
 		}
 
 		if err := sem.Acquire(ctx, 1); err != nil {
-			return errors, err
+			return errors, &ClientError{
+				ResourceName: bucketName,
+				Err:          err,
+			}
 		}
 		eg.Go(func() error {
 			defer sem.Release(1)
@@ -112,7 +120,7 @@ func (s *S3) DeleteObjects(ctx context.Context, bucketName *string, objects []ty
 				},
 			)
 			if err != nil {
-				return err
+				return err // return non wrapping error because wrap after eg.Wait()
 			}
 
 			outputsCh <- output
@@ -130,7 +138,10 @@ func (s *S3) DeleteObjects(ctx context.Context, bucketName *string, objects []ty
 	}()
 
 	if err := eg.Wait(); err != nil {
-		return nil, err
+		return nil, &ClientError{
+			ResourceName: bucketName,
+			Err:          err,
+		}
 	}
 
 	// wait errors set before access an errors var at below return (for race)
@@ -147,7 +158,10 @@ func (s *S3) ListObjectVersions(ctx context.Context, bucketName *string) ([]type
 	for {
 		select {
 		case <-ctx.Done():
-			return objectIdentifiers, ctx.Err()
+			return objectIdentifiers, &ClientError{
+				ResourceName: bucketName,
+				Err:          ctx.Err(),
+			}
 		default:
 		}
 
@@ -159,7 +173,10 @@ func (s *S3) ListObjectVersions(ctx context.Context, bucketName *string) ([]type
 
 		output, err := s.client.ListObjectVersions(ctx, input)
 		if err != nil {
-			return nil, err
+			return nil, &ClientError{
+				ResourceName: bucketName,
+				Err:          err,
+			}
 		}
 
 		for _, version := range output.Versions {
@@ -194,7 +211,10 @@ func (s *S3) CheckBucketExists(ctx context.Context, bucketName *string) (bool, e
 
 	output, err := s.client.ListBuckets(ctx, input)
 	if err != nil {
-		return false, err
+		return false, &ClientError{
+			ResourceName: bucketName,
+			Err:          err,
+		}
 	}
 
 	for _, bucket := range output.Buckets {


Test patch:
diff --git a/pkg/client/backup_test.go b/pkg/client/backup_test.go
index 4494e85..b14b1e2 100644
--- a/pkg/client/backup_test.go
+++ b/pkg/client/backup_test.go
@@ -117,7 +117,10 @@ func TestBackup_ListRecoveryPointsByBackupVault(t *testing.T) {
 			},
 			want: want{
 				output: nil,
-				err:    fmt.Errorf("operation error Backup: ListRecoveryPointsByBackupVault, ListRecoveryPointsByBackupVaultError"),
+				err: &ClientError{
+					ResourceName: aws.String("test"),
+					Err:          fmt.Errorf("operation error Backup: ListRecoveryPointsByBackupVault, ListRecoveryPointsByBackupVaultError"),
+				},
 			},
 			wantErr: true,
 		},
@@ -267,7 +270,10 @@ func TestBackup_ListRecoveryPointsByBackupVault(t *testing.T) {
 			},
 			want: want{
 				output: nil,
-				err:    fmt.Errorf("operation error Backup: ListRecoveryPointsByBackupVault, ListRecoveryPointsByBackupVaultError"),
+				err: &ClientError{
+					ResourceName: aws.String("test"),
+					Err:          fmt.Errorf("operation error Backup: ListRecoveryPointsByBackupVault, ListRecoveryPointsByBackupVaultError"),
+				},
 			},
 			wantErr: true,
 		},
@@ -397,7 +403,10 @@ func TestBackup_DeleteRecoveryPoints(t *testing.T) {
 					)
 				},
 			},
-			want:    fmt.Errorf("operation error Backup: DeleteRecoveryPoint, DeleteRecoveryPointError"),
+			want: &ClientError{
+				ResourceName: aws.String("test"),
+				Err:          fmt.Errorf("operation error Backup: DeleteRecoveryPoint, DeleteRecoveryPointError"),
+			},
 			wantErr: true,
 		},
 	}
@@ -485,7 +494,10 @@ func TestBackup_DeleteRecoveryPoint(t *testing.T) {
 					)
 				},
 			},
-			want:    fmt.Errorf("operation error Backup: DeleteRecoveryPoint, DeleteRecoveryPointError"),
+			want: &ClientError{
+				ResourceName: aws.String("test"),
+				Err:          fmt.Errorf("operation error Backup: DeleteRecoveryPoint, DeleteRecoveryPointError"),
+			},
 			wantErr: true,
 		},
 	}
@@ -570,7 +582,10 @@ func TestBackup_DeleteBackupVault(t *testing.T) {
 					)
 				},
 			},
-			want:    fmt.Errorf("operation error Backup: DeleteBackupVault, DeleteBackupVaultError"),
+			want: &ClientError{
+				ResourceName: aws.String("test"),
+				Err:          fmt.Errorf("operation error Backup: DeleteBackupVault, DeleteBackupVaultError"),
+			},
 			wantErr: true,
 		},
 	}
@@ -709,7 +724,10 @@ func TestBackup_CheckBackupVaultExists(t *testing.T) {
 			},
 			want: want{
 				exists: false,
-				err:    fmt.Errorf("operation error Backup: ListBackupVaults, ListBackupVaultsError"),
+				err: &ClientError{
+					ResourceName: aws.String("test"),
+					Err:          fmt.Errorf("operation error Backup: ListBackupVaults, ListBackupVaultsError"),
+				},
 			},
 			wantErr: true,
 		},
@@ -905,7 +923,10 @@ func TestBackup_CheckBackupVaultExists(t *testing.T) {
 			},
 			want: want{
 				exists: false,
-				err:    fmt.Errorf("operation error Backup: ListBackupVaults, ListBackupVaultsError"),
+				err: &ClientError{
+					ResourceName: aws.String("test"),
+					Err:          fmt.Errorf("operation error Backup: ListBackupVaults, ListBackupVaultsError"),
+				},
 			},
 			wantErr: true,
 		},
diff --git a/pkg/client/cloudformation_test.go b/pkg/client/cloudformation_test.go
index 10e152d..85d9407 100644
--- a/pkg/client/cloudformation_test.go
+++ b/pkg/client/cloudformation_test.go
@@ -178,7 +178,10 @@ func TestCloudFormation_DeleteStack(t *testing.T) {
 					)
 				},
 			},
-			want:    fmt.Errorf("operation error CloudFormation: DeleteStack, DeleteStackError"),
+			want: &ClientError{
+				ResourceName: aws.String("test"),
+				Err:          fmt.Errorf("operation error CloudFormation: DeleteStack, DeleteStackError"),
+			},
 			wantErr: true,
 		},
 		{
@@ -210,7 +213,10 @@ func TestCloudFormation_DeleteStack(t *testing.T) {
 					)
 				},
 			},
-			want:    fmt.Errorf("expected err to be of type smithy.APIError, got %w", fmt.Errorf("operation error CloudFormation: DescribeStacks, WaitError")),
+			want: &ClientError{
+				ResourceName: aws.String("test"),
+				Err:          fmt.Errorf("expected err to be of type smithy.APIError, got %w", fmt.Errorf("operation error CloudFormation: DescribeStacks, WaitError")),
+			},
 			wantErr: true,
 		},
 	}
@@ -326,7 +332,10 @@ func TestCloudFormation_DescribeStacks(t *testing.T) {
 			want: want{
 				output: &cloudformation.DescribeStacksOutput{},
 				exists: true,
-				err:    fmt.Errorf("operation error CloudFormation: DescribeStacks, DescribeStacksError"),
+				err: &ClientError{
+					ResourceName: aws.String("test"),
+					Err:          fmt.Errorf("operation error CloudFormation: DescribeStacks, DescribeStacksError"),
+				},
 			},
 			wantErr: true,
 		},
@@ -606,7 +615,10 @@ func TestCloudFormation_ListStackResources(t *testing.T) {
 			},
 			want: want{
 				output: []types.StackResourceSummary{},
-				err:    fmt.Errorf("operation error CloudFormation: ListStackResources, ListStackResourcesError"),
+				err: &ClientError{
+					ResourceName: aws.String("test"),
+					Err:          fmt.Errorf("operation error CloudFormation: ListStackResources, ListStackResourcesError"),
+				},
 			},
 			wantErr: true,
 		},
@@ -789,7 +801,10 @@ func TestCloudFormation_ListStackResources(t *testing.T) {
 						PhysicalResourceId: aws.String("PhysicalResourceId2"),
 					},
 				},
-				err: fmt.Errorf("operation error CloudFormation: ListStackResources, ListStackResourcesError"),
+				err: &ClientError{
+					ResourceName: aws.String("test"),
+					Err:          fmt.Errorf("operation error CloudFormation: ListStackResources, ListStackResourcesError"),
+				},
 			},
 			wantErr: true,
 		},
@@ -936,7 +951,9 @@ func TestCloudFormation_ListStacks(t *testing.T) {
 			},
 			want: want{
 				output: []types.StackSummary{},
-				err:    fmt.Errorf("operation error CloudFormation: ListStacks, ListStacksError"),
+				err: &ClientError{
+					Err: fmt.Errorf("operation error CloudFormation: ListStacks, ListStacksError"),
+				},
 			},
 			wantErr: true,
 		},
@@ -1092,7 +1109,9 @@ func TestCloudFormation_ListStacks(t *testing.T) {
 						StackName:   aws.String("TestStack2"),
 						StackStatus: types.StackStatusCreateComplete,
 					},
-				}, err: fmt.Errorf("operation error CloudFormation: ListStacks, ListStacksError"),
+				}, err: &ClientError{
+					Err: fmt.Errorf("operation error CloudFormation: ListStacks, ListStacksError"),
+				},
 			},
 			wantErr: true,
 		},
diff --git a/pkg/client/ecr_test.go b/pkg/client/ecr_test.go
index 7beeecf..39cebba 100644
--- a/pkg/client/ecr_test.go
+++ b/pkg/client/ecr_test.go
@@ -71,7 +71,10 @@ func TestEcr_DeleteRepository(t *testing.T) {
 					)
 				},
 			},
-			want:    fmt.Errorf("operation error ECR: DeleteRepository, DeleteRepositoryError"),
+			want: &ClientError{
+				ResourceName: aws.String("test"),
+				Err:          fmt.Errorf("operation error ECR: DeleteRepository, DeleteRepositoryError"),
+			},
 			wantErr: true,
 		},
 	}
@@ -201,7 +204,10 @@ func TestEcr_CheckRepository(t *testing.T) {
 			},
 			want: want{
 				exists: false,
-				err:    fmt.Errorf("operation error ECR: DescribeRepositories, DescribeRepositoriesError"),
+				err: &ClientError{
+					ResourceName: aws.String("test"),
+					Err:          fmt.Errorf("operation error ECR: DescribeRepositories, DescribeRepositoriesError"),
+				},
 			},
 			wantErr: true,
 		},
diff --git a/pkg/client/iam_test.go b/pkg/client/iam_test.go
index a873be4..ce3f994 100644
--- a/pkg/client/iam_test.go
+++ b/pkg/client/iam_test.go
@@ -87,7 +87,10 @@ func TestIam_DeleteRole(t *testing.T) {
 					)
 				},
 			},
-			want:    fmt.Errorf("operation error IAM: DeleteRole, DeleteRoleError"),
+			want: &ClientError{
+				ResourceName: aws.String("test"),
+				Err:          fmt.Errorf("operation error IAM: DeleteRole, DeleteRoleError"),
+			},
 			wantErr: true,
 		},
 		{
@@ -109,7 +112,10 @@ func TestIam_DeleteRole(t *testing.T) {
 					)
 				},
 			},
-			want:    fmt.Errorf("RetryCountOverError: test, operation error IAM: DeleteRole, api error Throttling: Rate exceeded\nRetryCount(" + strconv.Itoa(MaxRetryCount) + ") over, but failed. "),
+			want: &ClientError{
+				ResourceName: aws.String("test"),
+				Err:          fmt.Errorf("RetryCountOverError: test, operation error IAM: DeleteRole, api error Throttling: Rate exceeded\nRetryCount(" + strconv.Itoa(MaxRetryCount) + ") over, but failed. "),
+			},
 			wantErr: true,
 		},
 	}
@@ -252,7 +258,10 @@ func TestIam_ListAttachedRolePolicies(t *testing.T) {
 			},
 			want: want{
 				output: nil,
-				err:    fmt.Errorf("operation error IAM: ListAttachedRolePolicies, ListAttachedRolePoliciesError"),
+				err: &ClientError{
+					ResourceName: aws.String("test"),
+					Err:          fmt.Errorf("operation error IAM: ListAttachedRolePolicies, ListAttachedRolePoliciesError"),
+				},
 			},
 			wantErr: true,
 		},
@@ -277,7 +286,10 @@ func TestIam_ListAttachedRolePolicies(t *testing.T) {
 			},
 			want: want{
 				output: nil,
-				err:    fmt.Errorf("RetryCountOverError: test, operation error IAM: ListAttachedRolePolicies, api error Throttling: Rate exceeded\nRetryCount(" + strconv.Itoa(MaxRetryCount) + ") over, but failed. "),
+				err: &ClientError{
+					ResourceName: aws.String("test"),
+					Err:          fmt.Errorf("RetryCountOverError: test, operation error IAM: ListAttachedRolePolicies, api error Throttling: Rate exceeded\nRetryCount(" + strconv.Itoa(MaxRetryCount) + ") over, but failed. "),
+				},
 			},
 			wantErr: true,
 		},
@@ -427,7 +439,10 @@ func TestIam_ListAttachedRolePolicies(t *testing.T) {
 			},
 			want: want{
 				output: nil,
-				err:    fmt.Errorf("operation error IAM: ListAttachedRolePolicies, ListAttachedRolePoliciesError"),
+				err: &ClientError{
+					ResourceName: aws.String("test"),
+					Err:          fmt.Errorf("operation error IAM: ListAttachedRolePolicies, ListAttachedRolePoliciesError"),
+				},
 			},
 			wantErr: true,
 		},
@@ -487,7 +502,10 @@ func TestIam_ListAttachedRolePolicies(t *testing.T) {
 			},
 			want: want{
 				output: nil,
-				err:    fmt.Errorf("RetryCountOverError: test, operation error IAM: ListAttachedRolePolicies, api error Throttling: Rate exceeded\nRetryCount(" + strconv.Itoa(MaxRetryCount) + ") over, but failed. "),
+				err: &ClientError{
+					ResourceName: aws.String("test"),
+					Err:          fmt.Errorf("RetryCountOverError: test, operation error IAM: ListAttachedRolePolicies, api error Throttling: Rate exceeded\nRetryCount(" + strconv.Itoa(MaxRetryCount) + ") over, but failed. "),
+				},
 			},
 			wantErr: true,
 		},
@@ -622,7 +640,10 @@ func TestIam_DetachRolePolicies(t *testing.T) {
 					)
 				},
 			},
-			want:    fmt.Errorf("operation error IAM: DetachRolePolicy, DetachRolePolicyError"),
+			want: &ClientError{
+				ResourceName: aws.String("test"),
+				Err:          fmt.Errorf("operation error IAM: DetachRolePolicy, DetachRolePolicyError"),
+			},
 			wantErr: true,
 		},
 		{
@@ -654,7 +675,10 @@ func TestIam_DetachRolePolicies(t *testing.T) {
 					)
 				},
 			},
-			want:    fmt.Errorf("RetryCountOverError: test, operation error IAM: DetachRolePolicy, api error Throttling: Rate exceeded\nRetryCount(" + strconv.Itoa(MaxRetryCount) + ") over, but failed. "),
+			want: &ClientError{
+				ResourceName: aws.String("test"),
+				Err:          fmt.Errorf("RetryCountOverError: test, operation error IAM: DetachRolePolicy, api error Throttling: Rate exceeded\nRetryCount(" + strconv.Itoa(MaxRetryCount) + ") over, but failed. "),
+			},
 			wantErr: true,
 		},
 	}
@@ -743,7 +767,10 @@ func TestIam_DetachRolePolicy(t *testing.T) {
 					)
 				},
 			},
-			want:    fmt.Errorf("operation error IAM: DetachRolePolicy, DetachRolePolicyError"),
+			want: &ClientError{
+				ResourceName: aws.String("test"),
+				Err:          fmt.Errorf("operation error IAM: DetachRolePolicy, DetachRolePolicyError"),
+			},
 			wantErr: true,
 		},
 		{
@@ -766,7 +793,10 @@ func TestIam_DetachRolePolicy(t *testing.T) {
 					)
 				},
 			},
-			want:    fmt.Errorf("RetryCountOverError: test, operation error IAM: DetachRolePolicy, api error Throttling: Rate exceeded\nRetryCount(" + strconv.Itoa(MaxRetryCount) + ") over, but failed. "),
+			want: &ClientError{
+				ResourceName: aws.String("test"),
+				Err:          fmt.Errorf("RetryCountOverError: test, operation error IAM: DetachRolePolicy, api error Throttling: Rate exceeded\nRetryCount(" + strconv.Itoa(MaxRetryCount) + ") over, but failed. "),
+			},
 			wantErr: true,
 		},
 	}
@@ -891,7 +921,10 @@ func TestIam_CheckRoleExists(t *testing.T) {
 			},
 			want: want{
 				exists: false,
-				err:    fmt.Errorf("operation error IAM: GetRole, GetRoleError"),
+				err: &ClientError{
+					ResourceName: aws.String("test"),
+					Err:          fmt.Errorf("operation error IAM: GetRole, GetRoleError"),
+				},
 			},
 			wantErr: true,
 		},
@@ -916,7 +949,10 @@ func TestIam_CheckRoleExists(t *testing.T) {
 			},
 			want: want{
 				exists: false,
-				err:    fmt.Errorf("RetryCountOverError: test, operation error IAM: GetRole, api error Throttling: Rate exceeded\nRetryCount(" + strconv.Itoa(MaxRetryCount) + ") over, but failed. "),
+				err: &ClientError{
+					ResourceName: aws.String("test"),
+					Err:          fmt.Errorf("RetryCountOverError: test, operation error IAM: GetRole, api error Throttling: Rate exceeded\nRetryCount(" + strconv.Itoa(MaxRetryCount) + ") over, but failed. "),
+				},
 			},
 			wantErr: true,
 		},
diff --git a/pkg/client/s3_test.go b/pkg/client/s3_test.go
index 8f126fb..da7209a 100644
--- a/pkg/client/s3_test.go
+++ b/pkg/client/s3_test.go
@@ -89,7 +89,10 @@ func TestS3_DeleteBucket(t *testing.T) {
 					)
 				},
 			},
-			want:    fmt.Errorf("operation error S3: DeleteBucket, DeleteBucketError"),
+			want: &ClientError{
+				ResourceName: aws.String("test"),
+				Err:          fmt.Errorf("operation error S3: DeleteBucket, DeleteBucketError"),
+			},
 			wantErr: true,
 		},
 	}
@@ -271,7 +274,10 @@ func TestS3_DeleteObjects(t *testing.T) {
 			},
 			want: want{
 				output: nil,
-				err:    fmt.Errorf("operation error S3: DeleteObjects, DeleteObjectsError"),
+				err: &ClientError{
+					ResourceName: aws.String("test"),
+					Err:          fmt.Errorf("operation error S3: DeleteObjects, DeleteObjectsError"),
+				},
 			},
 			wantErr: true,
 		},
@@ -305,7 +311,10 @@ func TestS3_DeleteObjects(t *testing.T) {
 			},
 			want: want{
 				output: nil,
-				err:    fmt.Errorf("RetryCountOverError: test, operation error S3: DeleteObjects, api error SlowDown\nRetryCount(" + strconv.Itoa(MaxRetryCount) + ") over, but failed. "),
+				err: &ClientError{
+					ResourceName: aws.String("test"),
+					Err:          fmt.Errorf("RetryCountOverError: test, operation error S3: DeleteObjects, api error SlowDown\nRetryCount(" + strconv.Itoa(MaxRetryCount) + ") over, but failed. "),
+				},
 			},
 			wantErr: true,
 		},
@@ -475,7 +484,10 @@ func TestS3_ListObjectVersions(t *testing.T) {
 			},
 			want: want{
 				output: nil,
-				err:    fmt.Errorf("operation error S3: ListObjectVersions, ListObjectVersionsError"),
+				err: &ClientError{
+					ResourceName: aws.String("test"),
+					Err:          fmt.Errorf("operation error S3: ListObjectVersions, ListObjectVersionsError"),
+				},
 			},
 			wantErr: true,
 		},
@@ -749,7 +761,10 @@ func TestS3_ListObjectVersions(t *testing.T) {
 			},
 			want: want{
 				output: nil,
-				err:    fmt.Errorf("operation error S3: ListObjectVersions, ListObjectVersionsError"),
+				err: &ClientError{
+					ResourceName: aws.String("test"),
+					Err:          fmt.Errorf("operation error S3: ListObjectVersions, ListObjectVersionsError"),
+				},
 			},
 			wantErr: true,
 		},
@@ -892,7 +907,10 @@ func TestS3_CheckBucketExists(t *testing.T) {
 			},
 			want: want{
 				exists: false,
-				err:    fmt.Errorf("operation error S3: ListBuckets, ListBucketsError"),
+				err: &ClientError{
+					ResourceName: aws.String("test"),
+					Err:          fmt.Errorf("operation error S3: ListBuckets, ListBucketsError"),
+				},
 			},
 			wantErr: true,
 		},