Skip to content

Commit 6a54581

Browse files
authored
Merge pull request #42 from nao1215/feat/cfn-list
Add cloudformation(cfn) ls subcommand
2 parents 299ff0b + 4886caf commit 6a54581

File tree

16 files changed

+784
-74
lines changed

16 files changed

+784
-74
lines changed

app/di/wire.go

+25
Original file line numberDiff line numberDiff line change
@@ -135,3 +135,28 @@ func newSpareApp(
135135
S3BucketPolicySetter: s3BucketPolicySetter,
136136
}
137137
}
138+
139+
// CFnApp is the application service for CloudFormation.
140+
type CFnApp struct {
141+
// CFnStackLister is the usecase for listing CloudFormation stacks.
142+
usecase.CFnStackLister
143+
}
144+
145+
// NewCFnApp creates a new CFnApp.
146+
func NewCFnApp(ctx context.Context, profile model.AWSProfile, region model.Region) (*CFnApp, error) {
147+
wire.Build(
148+
model.NewAWSConfig,
149+
external.NewCloudFormationClient,
150+
external.CFnStackListerSet,
151+
interactor.CFnStackListerSet,
152+
newCFnApp,
153+
)
154+
return nil, nil
155+
}
156+
157+
// newCFnApp creates a new CFnApp.
158+
func newCFnApp(cFnStackLister usecase.CFnStackLister) *CFnApp {
159+
return &CFnApp{
160+
CFnStackLister: cFnStackLister,
161+
}
162+
}

app/di/wire_gen.go

+27
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/domain/model/cloudformation.go

+97
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,103 @@ const (
1313
// StackStatus is the status of a CloudFormation stack.
1414
type StackStatus string
1515

16+
// String returns the string representation of StackStatus.
17+
func (s StackStatus) String() string {
18+
return string(s)
19+
}
20+
21+
// CloudFormation stack status constants
22+
// Ref. https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-describing-stacks.html
23+
const (
24+
// StackStatusCreateInProgress is ongoing creation of one or more stacks.
25+
StackStatusCreateInProgress StackStatus = "CREATE_IN_PROGRESS"
26+
// StackStatusCreateComplete is successful creation of one or more stacks.
27+
StackStatusCreateComplete StackStatus = "CREATE_COMPLETE"
28+
// StackStatusCreateFailed is unsuccessful creation of one or more stacks.
29+
// View the stack events to see any associated error messages. Possible reasons
30+
// for a failed creation include insufficient permissions to work with all
31+
// resources in the stack, parameter values rejected by an AWS service, or a
32+
// timeout during resource creation.
33+
StackStatusCreateFailed StackStatus = "CREATE_FAILED"
34+
35+
// StackStatusRollbackInProgress is ongoing removal of one or more stacks after a failed
36+
// stack creation or after an explicitly canceled stack creation.
37+
StackStatusRollbackInProgress StackStatus = "ROLLBACK_IN_PROGRESS"
38+
// StackStatusRollbackComplete is successful removal of one or more stacks after a failed
39+
// stack creation or after an explicitly canceled stack creation. The stack returns to
40+
// the previous working state. Any resources that were created during the create stack
41+
// operation are deleted.
42+
// This status exists only after a failed stack creation. It signifies that all operations
43+
// from the partially created stack have been appropriately cleaned up. When in this state,
44+
// only a delete operation can be performed.
45+
StackStatusRollbackComplete StackStatus = "ROLLBACK_COMPLETE"
46+
// StackStatusRollbackFailed is unsuccessful removal of one or more stacks after a failed
47+
// stack creation or after an explicitly canceled stack creation. Delete the stack or view
48+
// the stack events to see any associated error messages.
49+
StackStatusRollbackFailed StackStatus = "ROLLBACK_FAILED"
50+
51+
// StackStatusDeleteInProgress is ongoing removal of one or more stacks.
52+
StackStatusDeleteInProgress StackStatus = "DELETE_IN_PROGRESS"
53+
// StackStatusDeleteComplete is successful deletion of one or more stacks.
54+
// Deleted stacks are retained and viewable for 90 days.
55+
StackStatusDeleteComplete StackStatus = "DELETE_COMPLETE"
56+
// StackStatusDeleteFailed is unsuccessful deletion of one or more stacks.
57+
// Because the delete failed, you might have some resources that are still
58+
// running; however, you can't work with or update the stack. Delete the stack
59+
// again or view the stack events to see any associated error messages.
60+
StackStatusDeleteFailed StackStatus = "DELETE_FAILED"
61+
62+
// StackStatusUpdateInProgress is ongoing creation of one or more stacks with
63+
// an expected StackId but without any templates or resources.
64+
// A stack with this status code counts against the maximum possible number of stacks.
65+
StackStatusReviewInProgress StackStatus = "REVIEW_IN_PROGRESS"
66+
// StackStatusUpdateInProgress is ongoing update of one or more stacks.
67+
StackStatusUpdateInProgress StackStatus = "UPDATE_IN_PROGRESS"
68+
// StackStatusUpdateCompleteCleanupInProgress is ongoing removal of old resources for
69+
// one or more stacks after a successful stack update. For stack updates that require
70+
// resources to be replaced, CloudFormation creates the new resources first and then
71+
// deletes the old resources to help reduce any interruptions with your stack. In this
72+
// state, the stack has been updated and is usable, but CloudFormation is still deleting
73+
// the old resources.
74+
StackStatusUpdateCompleteCleanupInProgress StackStatus = "UPDATE_COMPLETE_CLEANUP_IN_PROGRESS"
75+
// StackStatusUpdateComplete is successful update of one or more stacks.
76+
StackStatusUpdateComplete StackStatus = "UPDATE_COMPLETE"
77+
// StackStatusUpdateFailed is unsuccessful update of one or more stacks. View the stack events
78+
// to see any associated error messages.
79+
StackStatusUpdateFailed StackStatus = "UPDATE_FAILED"
80+
// StackStatusUpdateRollbackComplete is successful return of one or more stacks to a previous
81+
// working state after a failed stack update.
82+
StackStatusUpdateRollbackComplete StackStatus = "UPDATE_ROLLBACK_COMPLETE"
83+
// StackStatusUpdateRollbackCompleteCleanupInProgress is ongoing removal of new resources
84+
// for one or more stacks after a failed stack update. In this state, the stack has been
85+
// rolled back to its previous working state and is usable, but CloudFormation is still
86+
// deleting any new resources it created during the stack update.
87+
StackStatusUpdateRollbackCompleteCleanupInProgress StackStatus = "UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS"
88+
// StackStatusUpdateRollbackFailed is unsuccessful return of one or more stacks to a
89+
// previous working state after a failed stack update. When in this state, you can
90+
// delete the stack or continue rollback. You might need to fix errors before your
91+
// stack can return to a working state.
92+
StackStatusUpdateRollbackFailed StackStatus = "UPDATE_ROLLBACK_FAILED"
93+
// StackStatusUpdateRollbackInProgress is ongoing return of one or more stacks
94+
// to the previous working state after failed stack update.
95+
StackStatusUpdateRollbackInProgress StackStatus = "UPDATE_ROLLBACK_IN_PROGRESS"
96+
97+
// StackStatusImportInProgress is the import operation is currently in progress.
98+
StackStatusImportInProgress StackStatus = "IMPORT_IN_PROGRESS"
99+
// StackStatusImportComplete is the import operation successfully completed for
100+
// all resources in the stack that support resource import.
101+
StackStatusImportComplete StackStatus = "IMPORT_COMPLETE"
102+
// StackStatusImportRollbackInProgress is import will roll back to the previous
103+
// template configuration.
104+
StackStatusImportRollbackInProgress StackStatus = "IMPORT_ROLLBACK_IN_PROGRESS"
105+
// StackStatusImportRollbackComplete is import successfully rolled back to the previous template configuration.
106+
StackStatusImportRollbackComplete StackStatus = "IMPORT_ROLLBACK_COMPLETE"
107+
// StackStatusImportRollbackFailed is the import rollback operation failed for at
108+
// least one resource in the stack. Results will be available for the resources
109+
// CloudFormation successfully imported.
110+
StackStatusImportRollbackFailed StackStatus = "IMPORT_ROLLBACK_FAILED"
111+
)
112+
16113
// StackDriftInformationSummary contains information about whether the stack's
17114
// actual configuration differs, or has drifted, from its expected configuration,
18115
// as defined in the stack template and any values specified as template parameters.

app/domain/service/cloudformation.go

+6-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ import (
77
)
88

99
// CFnStackListerInput is the input of the CFnStackLister method.
10-
type CFnStackListerInput struct{}
10+
type CFnStackListerInput struct {
11+
// Region is the region of the stack.
12+
Region model.Region
13+
}
1114

1215
// CFnStackListerOutput is the output of the CFnStackLister method.
1316
type CFnStackListerOutput struct {
@@ -24,6 +27,8 @@ type CFnStackLister interface {
2427
type CFnStackResourceListerInput struct {
2528
// StackName is the name of the stack.
2629
StackName string
30+
// Region is the region of the stack.
31+
Region model.Region
2732
}
2833

2934
// CFnStackResourceListerOutput is the output of the CFnStackResourceLister method.

app/external/cloudformation.go

+11-5
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,13 @@ func NewCFnStackLister(client *cloudformation.Client) *CFnStackLister {
4747
}
4848

4949
// CFnStackLister returns a list of CloudFormation stacks.
50-
func (l *CFnStackLister) CFnStackLister(ctx context.Context, _ *service.CFnStackListerInput) (*service.CFnStackListerOutput, error) {
50+
func (l *CFnStackLister) CFnStackLister(ctx context.Context, input *service.CFnStackListerInput) (*service.CFnStackListerOutput, error) {
5151
in := &cloudformation.ListStacksInput{}
52-
stacks := make([]*model.Stack, 0, 100)
52+
opt := func(o *cloudformation.Options) {
53+
o.Region = input.Region.String()
54+
}
5355

56+
stacks := make([]*model.Stack, 0, 100)
5457
for {
5558
select {
5659
case <-ctx.Done():
@@ -60,7 +63,7 @@ func (l *CFnStackLister) CFnStackLister(ctx context.Context, _ *service.CFnStack
6063
default:
6164
}
6265

63-
out, err := l.client.ListStacks(ctx, in)
66+
out, err := l.client.ListStacks(ctx, in, opt)
6467
if err != nil {
6568
return nil, err
6669
}
@@ -120,8 +123,11 @@ func (l *CFnStackResourceLister) CFnStackResourceLister(ctx context.Context, inp
120123
in := &cloudformation.ListStackResourcesInput{
121124
StackName: aws.String(input.StackName),
122125
}
123-
resources := make([]*model.StackResource, 0, 100)
126+
opt := func(o *cloudformation.Options) {
127+
o.Region = input.Region.String()
128+
}
124129

130+
resources := make([]*model.StackResource, 0, 100)
125131
for {
126132
select {
127133
case <-ctx.Done():
@@ -131,7 +137,7 @@ func (l *CFnStackResourceLister) CFnStackResourceLister(ctx context.Context, inp
131137
default:
132138
}
133139

134-
out, err := l.client.ListStackResources(ctx, in)
140+
out, err := l.client.ListStackResources(ctx, in, opt)
135141
if err != nil {
136142
return nil, err
137143
}

app/external/mock/cloudformation.go

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package mock
2+
3+
import (
4+
"context"
5+
6+
"github.com/nao1215/rainbow/app/domain/service"
7+
)
8+
9+
// CFnStackLister is a mock of the CFnStackLister interface.
10+
type CFnStackLister func(ctx context.Context, input *service.CFnStackListerInput) (*service.CFnStackListerOutput, error)
11+
12+
// CFnStackLister calls the CFnStackListerFunc.
13+
func (m CFnStackLister) CFnStackLister(ctx context.Context, input *service.CFnStackListerInput) (*service.CFnStackListerOutput, error) {
14+
return m(ctx, input)
15+
}

app/interactor/cloudformation.go

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package interactor
2+
3+
import (
4+
"context"
5+
6+
"github.com/google/wire"
7+
"github.com/nao1215/rainbow/app/domain/service"
8+
"github.com/nao1215/rainbow/app/usecase"
9+
)
10+
11+
// CFnStackListerSet is a set of CFnStackLister.
12+
//
13+
//nolint:gochecknoglobals
14+
var CFnStackListerSet = wire.NewSet(
15+
NewCFnStackLister,
16+
wire.Bind(new(usecase.CFnStackLister), new(*CFnStackLister)),
17+
)
18+
19+
var _ usecase.CFnStackLister = (*CFnStackLister)(nil)
20+
21+
// CFnStackLister is an implementation for CFnStackLister.
22+
type CFnStackLister struct {
23+
service.CFnStackLister
24+
}
25+
26+
// NewCFnStackLister returns a new CFnStackLister struct.
27+
func NewCFnStackLister(lister service.CFnStackLister) *CFnStackLister {
28+
return &CFnStackLister{
29+
CFnStackLister: lister,
30+
}
31+
}
32+
33+
// ListCFnStack returns a list of CloudFormation stacks.
34+
func (l *CFnStackLister) ListCFnStack(ctx context.Context, input *usecase.CFnStackListerInput) (*usecase.CFnStackListerOutput, error) {
35+
output, err := l.CFnStackLister.CFnStackLister(ctx, &service.CFnStackListerInput{
36+
Region: input.Region,
37+
})
38+
if err != nil {
39+
return nil, err
40+
}
41+
return &usecase.CFnStackListerOutput{
42+
Stacks: output.Stacks,
43+
}, nil
44+
}

app/interactor/cloudformation_test.go

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package interactor
2+
3+
import (
4+
"context"
5+
"errors"
6+
"testing"
7+
8+
"github.com/aws/aws-sdk-go-v2/aws"
9+
"github.com/google/go-cmp/cmp"
10+
"github.com/nao1215/rainbow/app/domain/model"
11+
"github.com/nao1215/rainbow/app/domain/service"
12+
"github.com/nao1215/rainbow/app/external/mock"
13+
"github.com/nao1215/rainbow/app/usecase"
14+
)
15+
16+
func TestCFnStackLister_ListCFnStack(t *testing.T) {
17+
t.Parallel()
18+
19+
t.Run("success to get cloudformation stack list", func(t *testing.T) {
20+
t.Parallel()
21+
22+
stackLister := mock.CFnStackLister(func(ctx context.Context, input *service.CFnStackListerInput) (*service.CFnStackListerOutput, error) {
23+
want := &service.CFnStackListerInput{
24+
Region: model.RegionAPEast1,
25+
}
26+
if diff := cmp.Diff(want, input); diff != "" {
27+
t.Errorf("differs: (-want +got)\n%s", diff)
28+
}
29+
30+
return &service.CFnStackListerOutput{
31+
Stacks: []*model.Stack{
32+
{
33+
StackName: aws.String("stackName1"),
34+
StackID: aws.String(model.StackStatusCreateComplete.String()),
35+
},
36+
{
37+
StackName: aws.String("stackName2"),
38+
StackID: aws.String(model.StackStatusCreateComplete.String()),
39+
},
40+
},
41+
}, nil
42+
})
43+
44+
lister := NewCFnStackLister(stackLister)
45+
output, err := lister.ListCFnStack(context.Background(), &usecase.CFnStackListerInput{
46+
Region: model.RegionAPEast1,
47+
})
48+
if err != nil {
49+
t.Errorf("unexpected error: %v", err)
50+
}
51+
52+
want := &usecase.CFnStackListerOutput{
53+
Stacks: []*model.Stack{
54+
{
55+
StackName: aws.String("stackName1"),
56+
StackID: aws.String(model.StackStatusCreateComplete.String()),
57+
},
58+
{
59+
StackName: aws.String("stackName2"),
60+
StackID: aws.String(model.StackStatusCreateComplete.String()),
61+
},
62+
},
63+
}
64+
if diff := cmp.Diff(want, output); diff != "" {
65+
t.Errorf("differs: (-want +got)\n%s", diff)
66+
}
67+
})
68+
69+
t.Run("fail to get cloudformation stack list", func(t *testing.T) {
70+
t.Parallel()
71+
72+
stackLister := mock.CFnStackLister(func(ctx context.Context, input *service.CFnStackListerInput) (*service.CFnStackListerOutput, error) {
73+
return nil, errors.New("some error")
74+
})
75+
76+
lister := NewCFnStackLister(stackLister)
77+
_, err := lister.ListCFnStack(context.Background(), &usecase.CFnStackListerInput{
78+
Region: model.RegionAPEast1,
79+
})
80+
if err == nil {
81+
t.Error("expected error, but nil")
82+
}
83+
})
84+
}

0 commit comments

Comments
 (0)