Skip to content

Commit 4e32d78

Browse files
authored
chore(deploy): enhance performance for API (#1170)
<!-- Provide summary of changes --> Fixes #1138. Enhance performance for `ListEnvironmentsDeployedTo` by running `GetResourcesByTags` in parallel. <!-- Issue number, if available. E.g. "Fixes #31", "Addresses #42, 77" --> By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.
1 parent 280d129 commit 4e32d78

2 files changed

Lines changed: 76 additions & 59 deletions

File tree

internal/pkg/deploy/deploy.go

Lines changed: 53 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -53,47 +53,44 @@ type ConfigStoreClient interface {
5353

5454
// Store fetches information on deployed services.
5555
type Store struct {
56-
rgClient resourceGetter
5756
configStore ConfigStoreClient
58-
newRgClientFromIDs func(string, string) error
59-
newRgClientFromRole func(string, string) error
57+
newRgClientFromIDs func(string, string) (resourceGetter, error)
58+
newRgClientFromRole func(string, string) (resourceGetter, error)
6059
}
6160

6261
// NewStore returns a new store.
6362
func NewStore(store ConfigStoreClient) (*Store, error) {
6463
s := &Store{
6564
configStore: store,
6665
}
67-
s.newRgClientFromIDs = func(appName, envName string) error {
66+
s.newRgClientFromIDs = func(appName, envName string) (resourceGetter, error) {
6867
env, err := s.configStore.GetEnvironment(appName, envName)
6968
if err != nil {
70-
return fmt.Errorf("get environment config %s: %w", envName, err)
69+
return nil, fmt.Errorf("get environment config %s: %w", envName, err)
7170
}
7271
sess, err := session.NewProvider().FromRole(env.ManagerRoleARN, env.Region)
7372
if err != nil {
74-
return fmt.Errorf("create new session from env role: %w", err)
73+
return nil, fmt.Errorf("create new session from env role: %w", err)
7574
}
76-
s.rgClient = rg.New(sess)
77-
return nil
75+
return rg.New(sess), nil
7876
}
79-
s.newRgClientFromRole = func(roleARN, region string) error {
77+
s.newRgClientFromRole = func(roleARN, region string) (resourceGetter, error) {
8078
sess, err := session.NewProvider().FromRole(roleARN, region)
8179
if err != nil {
82-
return fmt.Errorf("create new session from env role: %w", err)
80+
return nil, fmt.Errorf("create new session from env role: %w", err)
8381
}
84-
s.rgClient = rg.New(sess)
85-
return nil
82+
return rg.New(sess), nil
8683
}
8784
return s, nil
8885
}
8986

9087
// ListDeployedServices returns the names of deployed services in an environment part of an application.
9188
func (s *Store) ListDeployedServices(appName string, envName string) ([]string, error) {
92-
err := s.newRgClientFromIDs(appName, envName)
89+
rgClient, err := s.newRgClientFromIDs(appName, envName)
9390
if err != nil {
9491
return nil, err
9592
}
96-
resources, err := s.rgClient.GetResourcesByTags(ecsServiceResourceType, map[string]string{
93+
resources, err := rgClient.GetResourcesByTags(ecsServiceResourceType, map[string]string{
9794
AppTagKey: appName,
9895
EnvTagKey: envName,
9996
})
@@ -115,41 +112,66 @@ func (s *Store) ListDeployedServices(appName string, envName string) ([]string,
115112
return svcs, nil
116113
}
117114

115+
type result struct {
116+
name string
117+
err error
118+
}
119+
120+
func (s *Store) deployedServices(rgClient resourceGetter, app, env, svc string) result {
121+
resources, err := rgClient.GetResourcesByTags(ecsServiceResourceType, map[string]string{
122+
AppTagKey: app,
123+
EnvTagKey: env,
124+
ServiceTagKey: svc,
125+
})
126+
if err != nil {
127+
return result{err: fmt.Errorf("get resources by Copilot tags: %w", err)}
128+
}
129+
// If no resources found, the resp length is 0.
130+
var res result
131+
if len(resources) != 0 {
132+
res.name = env
133+
}
134+
return res
135+
}
136+
118137
// ListEnvironmentsDeployedTo returns all the environment that a service is deployed in.
119138
func (s *Store) ListEnvironmentsDeployedTo(appName string, svcName string) ([]string, error) {
120139
envs, err := s.configStore.ListEnvironments(appName)
121140
if err != nil {
122141
return nil, fmt.Errorf("list environment for app %s: %w", appName, err)
123142
}
124-
var envsWithDeployment []string
143+
deployedEnv := make(chan result, len(envs))
144+
defer close(deployedEnv)
125145
for _, env := range envs {
126-
err := s.newRgClientFromRole(env.ManagerRoleARN, env.Region)
127-
if err != nil {
128-
return nil, err
129-
}
130-
resources, err := s.rgClient.GetResourcesByTags(ecsServiceResourceType, map[string]string{
131-
AppTagKey: appName,
132-
EnvTagKey: env.Name,
133-
ServiceTagKey: svcName,
134-
})
135-
if err != nil {
136-
return nil, fmt.Errorf("get resources by Copilot tags: %w", err)
146+
go func(env *config.Environment) {
147+
rgClient, err := s.newRgClientFromRole(env.ManagerRoleARN, env.Region)
148+
if err != nil {
149+
deployedEnv <- result{err: err}
150+
return
151+
}
152+
deployedEnv <- s.deployedServices(rgClient, appName, env.Name, svcName)
153+
}(env)
154+
}
155+
var envsWithDeployment []string
156+
for i := 0; i < len(envs); i++ {
157+
env := <-deployedEnv
158+
if env.err != nil {
159+
return nil, env.err
137160
}
138-
// If no resources found, the resp length is 0.
139-
if len(resources) != 0 {
140-
envsWithDeployment = append(envsWithDeployment, env.Name)
161+
if env.name != "" {
162+
envsWithDeployment = append(envsWithDeployment, env.name)
141163
}
142164
}
143165
return envsWithDeployment, nil
144166
}
145167

146168
// IsServiceDeployed returns whether a service is deployed in an environment or not.
147169
func (s *Store) IsServiceDeployed(appName string, envName string, svcName string) (bool, error) {
148-
err := s.newRgClientFromIDs(appName, envName)
170+
rgClient, err := s.newRgClientFromIDs(appName, envName)
149171
if err != nil {
150172
return false, err
151173
}
152-
svcARNs, err := s.rgClient.GetResourcesByTags(ecsServiceResourceType, map[string]string{
174+
svcARNs, err := rgClient.GetResourcesByTags(ecsServiceResourceType, map[string]string{
153175
AppTagKey: appName,
154176
EnvTagKey: envName,
155177
ServiceTagKey: svcName,

internal/pkg/deploy/deploy_test.go

Lines changed: 23 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -117,9 +117,8 @@ func TestStore_ListDeployedServices(t *testing.T) {
117117
tc.setupMocks(mocks)
118118

119119
store := &Store{
120-
rgClient: mockRgGetter,
121120
configStore: mockConfigStore,
122-
newRgClientFromIDs: func(string, string) error { return nil },
121+
newRgClientFromIDs: func(string, string) (resourceGetter, error) { return mockRgGetter, nil },
123122
}
124123

125124
// WHEN
@@ -184,28 +183,26 @@ func TestStore_ListEnvironmentsDeployedTo(t *testing.T) {
184183
inputSvc: "mockSvc",
185184

186185
setupMocks: func(m storeMock) {
187-
gomock.InOrder(
188-
m.configStore.EXPECT().ListEnvironments("mockApp").Return([]*config.Environment{
189-
{
190-
App: "mockApp",
191-
Name: "mockEnv1",
192-
},
193-
{
194-
App: "mockApp",
195-
Name: "mockEnv2",
196-
},
197-
}, nil),
198-
m.rgGetter.EXPECT().GetResourcesByTags(ecsServiceResourceType, map[string]string{
199-
AppTagKey: "mockApp",
200-
EnvTagKey: "mockEnv1",
201-
ServiceTagKey: "mockSvc",
202-
}).Return([]*rg.Resource{{ARN: "mockSvcARN"}}, nil),
203-
m.rgGetter.EXPECT().GetResourcesByTags(ecsServiceResourceType, map[string]string{
204-
AppTagKey: "mockApp",
205-
EnvTagKey: "mockEnv2",
206-
ServiceTagKey: "mockSvc",
207-
}).Return([]*rg.Resource{}, nil),
208-
)
186+
m.configStore.EXPECT().ListEnvironments("mockApp").Return([]*config.Environment{
187+
{
188+
App: "mockApp",
189+
Name: "mockEnv1",
190+
},
191+
{
192+
App: "mockApp",
193+
Name: "mockEnv2",
194+
},
195+
}, nil)
196+
m.rgGetter.EXPECT().GetResourcesByTags(ecsServiceResourceType, map[string]string{
197+
AppTagKey: "mockApp",
198+
EnvTagKey: "mockEnv1",
199+
ServiceTagKey: "mockSvc",
200+
}).Return([]*rg.Resource{{ARN: "mockSvcARN"}}, nil)
201+
m.rgGetter.EXPECT().GetResourcesByTags(ecsServiceResourceType, map[string]string{
202+
AppTagKey: "mockApp",
203+
EnvTagKey: "mockEnv2",
204+
ServiceTagKey: "mockSvc",
205+
}).Return([]*rg.Resource{}, nil)
209206
},
210207

211208
wantedEnvs: []string{"mockEnv1"},
@@ -228,9 +225,8 @@ func TestStore_ListEnvironmentsDeployedTo(t *testing.T) {
228225
tc.setupMocks(mocks)
229226

230227
store := &Store{
231-
rgClient: mockRgGetter,
232228
configStore: mockConfigStore,
233-
newRgClientFromRole: func(string, string) error { return nil },
229+
newRgClientFromRole: func(string, string) (resourceGetter, error) { return mockRgGetter, nil },
234230
}
235231

236232
// WHEN
@@ -326,9 +322,8 @@ func TestStore_IsDeployed(t *testing.T) {
326322
tc.setupMocks(mocks)
327323

328324
store := &Store{
329-
rgClient: mockRgGetter,
330325
configStore: mockConfigStore,
331-
newRgClientFromIDs: func(string, string) error { return nil },
326+
newRgClientFromIDs: func(string, string) (resourceGetter, error) { return mockRgGetter, nil },
332327
}
333328

334329
// WHEN

0 commit comments

Comments
 (0)