Skip to content

Commit 75b5419

Browse files
committed
Abstract AWS SDK types from deployer layer
- Add ExecuteChangeSetByID method to CloudFormationOperations interface - Implement abstraction in DefaultCloudFormationOperations - Remove direct AWS SDK dependencies from AWSDeployer - Update deployer to use abstracted ExecuteChangeSetByID method - Remove unnecessary awsinternal import alias - Add comprehensive tests for new abstraction - Update all test mocks to support new interface method This change improves separation of concerns by ensuring the deployer layer only interacts with internal abstractions rather than AWS SDK types directly.
1 parent caddf05 commit 75b5419

File tree

8 files changed

+91
-17
lines changed

8 files changed

+91
-17
lines changed

internal/aws/client_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,10 @@ func (m *MockCloudFormationClient) ExecuteChangeSet(ctx context.Context, params
107107
return nil, nil
108108
}
109109

110+
func (m *MockCloudFormationClient) ExecuteChangeSetByID(ctx context.Context, changeSetID string) error {
111+
return nil
112+
}
113+
110114
func (m *MockCloudFormationClient) DeleteChangeSet(ctx context.Context, params *cloudformation.DeleteChangeSetInput, optFns ...func(*cloudformation.Options)) (*cloudformation.DeleteChangeSetOutput, error) {
111115
return nil, nil
112116
}

internal/aws/cloudformation.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,20 @@ func (cf *DefaultCloudFormationOperations) ExecuteChangeSet(ctx context.Context,
441441
return cf.client.ExecuteChangeSet(ctx, params, optFns...)
442442
}
443443

444+
// ExecuteChangeSetByID executes a CloudFormation changeset by ID, abstracting AWS SDK details
445+
func (cf *DefaultCloudFormationOperations) ExecuteChangeSetByID(ctx context.Context, changeSetID string) error {
446+
executeInput := &cloudformation.ExecuteChangeSetInput{
447+
ChangeSetName: aws.String(changeSetID),
448+
}
449+
450+
_, err := cf.client.ExecuteChangeSet(ctx, executeInput)
451+
if err != nil {
452+
return fmt.Errorf("failed to execute changeset %s: %w", changeSetID, err)
453+
}
454+
455+
return nil
456+
}
457+
444458
// DeleteChangeSet deletes a CloudFormation changeset
445459
func (cf *DefaultCloudFormationOperations) DeleteChangeSet(ctx context.Context, params *cloudformation.DeleteChangeSetInput, optFns ...func(*cloudformation.Options)) (*cloudformation.DeleteChangeSetOutput, error) {
446460
return cf.client.DeleteChangeSet(ctx, params, optFns...)

internal/aws/cloudformation_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -540,3 +540,47 @@ func TestDefaultCloudFormationOperations_ExecuteChangeSet_Error(t *testing.T) {
540540
assert.Equal(t, expectedError, err)
541541
mockClient.AssertExpectations(t)
542542
}
543+
544+
func TestDefaultCloudFormationOperations_ExecuteChangeSetByID_Success(t *testing.T) {
545+
mockClient := &MockCloudFormationClientForDeployTest{}
546+
cfOps := NewCloudFormationOperationsWithClient(mockClient)
547+
ctx := context.Background()
548+
549+
changeSetID := "arn:aws:cloudformation:us-east-1:123456789012:changeSet/test-changeset/test-stack"
550+
551+
executeInput := &cloudformation.ExecuteChangeSetInput{
552+
ChangeSetName: aws.String(changeSetID),
553+
}
554+
555+
expectedOutput := &cloudformation.ExecuteChangeSetOutput{}
556+
557+
mockClient.On("ExecuteChangeSet", ctx, executeInput).Return(expectedOutput, nil)
558+
559+
err := cfOps.ExecuteChangeSetByID(ctx, changeSetID)
560+
561+
require.NoError(t, err)
562+
mockClient.AssertExpectations(t)
563+
}
564+
565+
func TestDefaultCloudFormationOperations_ExecuteChangeSetByID_Error(t *testing.T) {
566+
mockClient := &MockCloudFormationClientForDeployTest{}
567+
cfOps := NewCloudFormationOperationsWithClient(mockClient)
568+
ctx := context.Background()
569+
570+
changeSetID := "arn:aws:cloudformation:us-east-1:123456789012:changeSet/test-changeset/test-stack"
571+
572+
executeInput := &cloudformation.ExecuteChangeSetInput{
573+
ChangeSetName: aws.String(changeSetID),
574+
}
575+
576+
expectedError := errors.New("changeset execution failed")
577+
578+
mockClient.On("ExecuteChangeSet", ctx, executeInput).Return((*cloudformation.ExecuteChangeSetOutput)(nil), expectedError)
579+
580+
err := cfOps.ExecuteChangeSetByID(ctx, changeSetID)
581+
582+
require.Error(t, err)
583+
assert.Contains(t, err.Error(), "failed to execute changeset")
584+
assert.Contains(t, err.Error(), "changeset execution failed")
585+
mockClient.AssertExpectations(t)
586+
}

internal/aws/interfaces.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ type CloudFormationOperations interface {
5555
DescribeStack(ctx context.Context, stackName string) (*StackInfo, error)
5656
CreateChangeSet(ctx context.Context, params *cloudformation.CreateChangeSetInput, optFns ...func(*cloudformation.Options)) (*cloudformation.CreateChangeSetOutput, error)
5757
ExecuteChangeSet(ctx context.Context, params *cloudformation.ExecuteChangeSetInput, optFns ...func(*cloudformation.Options)) (*cloudformation.ExecuteChangeSetOutput, error)
58+
ExecuteChangeSetByID(ctx context.Context, changeSetID string) error
5859
DeleteChangeSet(ctx context.Context, params *cloudformation.DeleteChangeSetInput, optFns ...func(*cloudformation.Options)) (*cloudformation.DeleteChangeSetOutput, error)
5960
DescribeChangeSet(ctx context.Context, params *cloudformation.DescribeChangeSetInput, optFns ...func(*cloudformation.Options)) (*cloudformation.DescribeChangeSetOutput, error)
6061
DescribeStackEvents(ctx context.Context, stackName string) ([]StackEvent, error)

internal/deploy/deployer.go

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,7 @@ import (
99
"fmt"
1010
"os"
1111

12-
"github.com/aws/aws-sdk-go-v2/aws"
13-
"github.com/aws/aws-sdk-go-v2/service/cloudformation"
14-
awsinternal "github.com/orien/stackaroo/internal/aws"
12+
"github.com/orien/stackaroo/internal/aws"
1513
"github.com/orien/stackaroo/internal/diff"
1614
"github.com/orien/stackaroo/internal/model"
1715
"github.com/orien/stackaroo/internal/prompt"
@@ -25,19 +23,19 @@ type Deployer interface {
2523

2624
// AWSDeployer implements Deployer using AWS CloudFormation
2725
type AWSDeployer struct {
28-
awsClient awsinternal.Client
26+
awsClient aws.Client
2927
}
3028

3129
// NewAWSDeployer creates a new AWSDeployer
32-
func NewAWSDeployer(awsClient awsinternal.Client) *AWSDeployer {
30+
func NewAWSDeployer(awsClient aws.Client) *AWSDeployer {
3331
return &AWSDeployer{
3432
awsClient: awsClient,
3533
}
3634
}
3735

3836
// NewDefaultDeployer creates a deployer with default AWS configuration
3937
func NewDefaultDeployer(ctx context.Context) (*AWSDeployer, error) {
40-
client, err := awsinternal.NewDefaultClient(ctx, awsinternal.Config{})
38+
client, err := aws.NewDefaultClient(ctx, aws.Config{})
4139
if err != nil {
4240
return nil, fmt.Errorf("failed to create AWS client: %w", err)
4341
}
@@ -70,9 +68,9 @@ func (d *AWSDeployer) deployNewStack(ctx context.Context, stack *model.Stack) er
7068
fmt.Printf("=== Creating new stack %s ===\n", stack.Name)
7169

7270
// Convert parameters to AWS format
73-
awsParams := make([]awsinternal.Parameter, 0, len(stack.Parameters))
71+
awsParams := make([]aws.Parameter, 0, len(stack.Parameters))
7472
for key, value := range stack.Parameters {
75-
awsParams = append(awsParams, awsinternal.Parameter{
73+
awsParams = append(awsParams, aws.Parameter{
7674
Key: key,
7775
Value: value,
7876
})
@@ -85,7 +83,7 @@ func (d *AWSDeployer) deployNewStack(ctx context.Context, stack *model.Stack) er
8583
}
8684

8785
// Set up event callback for user feedback
88-
eventCallback := func(event awsinternal.StackEvent) {
86+
eventCallback := func(event aws.StackEvent) {
8987
timestamp := event.Timestamp.Format("2006-01-02 15:04:05")
9088
fmt.Printf("[%s] %-20s %-40s %s %s\n",
9189
timestamp,
@@ -96,7 +94,7 @@ func (d *AWSDeployer) deployNewStack(ctx context.Context, stack *model.Stack) er
9694
)
9795
}
9896

99-
deployInput := awsinternal.DeployStackInput{
97+
deployInput := aws.DeployStackInput{
10098
StackName: stack.Name,
10199
TemplateBody: stack.TemplateBody,
102100
Parameters: awsParams,
@@ -174,18 +172,15 @@ func (d *AWSDeployer) deployWithChangeSet(ctx context.Context, stack *model.Stac
174172
// Execute the changeset
175173
fmt.Printf("=== Deploying stack %s ===\n", stack.Name)
176174

177-
executeInput := &cloudformation.ExecuteChangeSetInput{
178-
ChangeSetName: aws.String(changeSetInfo.ChangeSetID),
179-
}
180-
_, err = cfnOps.ExecuteChangeSet(ctx, executeInput)
175+
err = cfnOps.ExecuteChangeSetByID(ctx, changeSetInfo.ChangeSetID)
181176
if err != nil {
182177
// Clean up changeset on failure
183178
_ = changeSetMgr.DeleteChangeSet(ctx, changeSetInfo.ChangeSetID)
184179
return fmt.Errorf("failed to execute changeset: %w", err)
185180
}
186181

187182
// Wait for deployment to complete with progress updates
188-
eventCallback := func(event awsinternal.StackEvent) {
183+
eventCallback := func(event aws.StackEvent) {
189184
timestamp := event.Timestamp.Format("2006-01-02 15:04:05")
190185
fmt.Printf("[%s] %-20s %-40s %s %s\n",
191186
timestamp,

internal/deploy/deployer_test.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,12 @@ func (m *MockCloudFormationOperations) ExecuteChangeSet(ctx context.Context, par
118118
return args.Get(0).(*cloudformation.ExecuteChangeSetOutput), args.Error(1)
119119
}
120120

121+
// ExecuteChangeSetByID executes a changeset by ID (abstracted method)
122+
func (m *MockCloudFormationOperations) ExecuteChangeSetByID(ctx context.Context, changeSetID string) error {
123+
args := m.Called(ctx, changeSetID)
124+
return args.Error(0)
125+
}
126+
121127
func (m *MockCloudFormationOperations) DescribeStackEvents(ctx context.Context, stackName string) ([]awsinternal.StackEvent, error) {
122128
args := m.Called(ctx, stackName)
123129
return args.Get(0).([]awsinternal.StackEvent), args.Error(1)
@@ -413,8 +419,8 @@ func TestAWSDeployer_DeployStack_WithChanges(t *testing.T) {
413419
}
414420
mockCfnOps.On("DescribeChangeSet", mock.Anything, mock.AnythingOfType("*cloudformation.DescribeChangeSetInput")).Return(describeOutput, nil).Times(2)
415421

416-
// Mock execute changeset
417-
mockCfnOps.On("ExecuteChangeSet", mock.Anything, mock.AnythingOfType("*cloudformation.ExecuteChangeSetInput")).Return(&cloudformation.ExecuteChangeSetOutput{}, nil)
422+
// Mock execute changeset using abstracted method
423+
mockCfnOps.On("ExecuteChangeSetByID", mock.Anything, "test-changeset-id").Return(nil)
418424

419425
// Mock wait for stack operation
420426
mockCfnOps.On("WaitForStackOperation", mock.Anything, "test-stack", mock.AnythingOfType("func(aws.StackEvent)")).Return(nil)

internal/diff/changeset_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ func (m *MockChangeSetClient) ExecuteChangeSet(ctx context.Context, params *clou
4444
return args.Get(0).(*cloudformation.ExecuteChangeSetOutput), args.Error(1)
4545
}
4646

47+
func (m *MockChangeSetClient) ExecuteChangeSetByID(ctx context.Context, changeSetID string) error {
48+
args := m.Called(ctx, changeSetID)
49+
return args.Error(0)
50+
}
51+
4752
// Additional methods to satisfy CloudFormationOperations
4853

4954
func (m *MockChangeSetClient) DeployStack(ctx context.Context, input aws.DeployStackInput) error {

internal/diff/differ_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,11 @@ func (m *MockCloudFormationClient) ExecuteChangeSet(ctx context.Context, params
9494
return args.Get(0).(*cloudformation.ExecuteChangeSetOutput), args.Error(1)
9595
}
9696

97+
func (m *MockCloudFormationClient) ExecuteChangeSetByID(ctx context.Context, changeSetID string) error {
98+
args := m.Called(ctx, changeSetID)
99+
return args.Error(0)
100+
}
101+
97102
func (m *MockCloudFormationClient) DescribeStackEvents(ctx context.Context, stackName string) ([]aws.StackEvent, error) {
98103
args := m.Called(ctx, stackName)
99104
return args.Get(0).([]aws.StackEvent), args.Error(1)

0 commit comments

Comments
 (0)