-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathexecutor.go
More file actions
140 lines (131 loc) · 4.25 KB
/
Copy pathexecutor.go
File metadata and controls
140 lines (131 loc) · 4.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
package rollout
import (
"context"
"github.com/aws/aws-sdk-go-v2/service/ecs"
ecstypes "github.com/aws/aws-sdk-go-v2/service/ecs/types"
"github.com/loilo-inc/canarycage/v5/awsiface"
"github.com/loilo-inc/canarycage/v5/env"
"github.com/loilo-inc/canarycage/v5/key"
"github.com/loilo-inc/canarycage/v5/logger"
"github.com/loilo-inc/canarycage/v5/task"
"github.com/loilo-inc/canarycage/v5/taskset"
"github.com/loilo-inc/canarycage/v5/types"
"github.com/loilo-inc/logos/v2/di"
)
type Executor interface {
RollOut(ctx context.Context, input *types.RollOutInput) error
ServiceUpdated() bool
}
type executor struct {
di *di.D
td *ecstypes.TaskDefinition
serviceUpdated bool
}
func NewExecutor(di *di.D, td *ecstypes.TaskDefinition) Executor {
return &executor{di: di, td: td}
}
func (c *executor) RollOut(ctx context.Context, input *types.RollOutInput) (lastErr error) {
env := c.di.Get(key.Env).(*env.Envars)
ecsCli := c.di.Get(key.EcsCli).(awsiface.EcsClient)
l := c.logger()
if input.UpdateService {
l.Infof("--updateService flag is set. use provided service configurations for canary test instead of current service")
}
canaryTasks, startCanaryTaskErr := c.startCanaryTasks(ctx, input)
// ensure canary task stopped after rolling out either success or failure
defer func() {
_ = recover()
if canaryTasks == nil {
return
} else if err := canaryTasks.Cleanup(ctx); err != nil {
l.Errorf("failed to cleanup canary tasks due to: %s", err)
lastErr = err
}
}()
if startCanaryTaskErr != nil {
l.Errorf("😨 failed to start canary task due to: %w", startCanaryTaskErr)
return startCanaryTaskErr
}
l.Infof("executing canary tasks...")
if err := canaryTasks.Exec(ctx); err != nil {
l.Errorf("😨 failed to exec canary tasks: %s", err)
return err
}
l.Infof("canary tasks have been executed successfully!")
l.Infof(
"updating the task definition of '%s' into '%s:%d'...",
env.Service, *c.td.Family, c.td.Revision,
)
updateInput := &ecs.UpdateServiceInput{
Cluster: &env.Cluster,
Service: &env.Service,
TaskDefinition: c.td.TaskDefinitionArn,
}
if input.UpdateService {
applyServiceDefinitionToUpdateInput(updateInput, env.ServiceDefinitionInput)
}
if _, err := ecsCli.UpdateService(ctx, updateInput); err != nil {
l.Errorf("😨 failed to update service: %s", err)
return err
}
c.serviceUpdated = true
l.Infof("waiting for service '%s' to be stable...", env.Service)
if err := ecs.NewServicesStableWaiter(ecsCli).Wait(ctx, &ecs.DescribeServicesInput{
Cluster: &env.Cluster,
Services: []string{env.Service},
}, env.GetServiceStableWait()); err != nil {
l.Errorf("😨 failed to wait for service to be stable: %s", err)
return err
}
l.Infof("🥴 service '%s' has become to be stable!", env.Service)
l.Infof(
"🐥 service '%s' successfully rolled out to '%s:%d'!",
env.Service, *c.td.Family, c.td.Revision,
)
return nil
}
func (c *executor) ServiceUpdated() bool {
return c.serviceUpdated
}
func (c *executor) logger() logger.Logger {
return c.di.Get(key.Logger).(logger.Logger)
}
func (c *executor) startCanaryTasks(
ctx context.Context,
input *types.RollOutInput,
) (taskset.Set, error) {
var networkConfiguration *ecstypes.NetworkConfiguration
var platformVersion *string
var loadBalancers []ecstypes.LoadBalancer
env := c.di.Get(key.Env).(*env.Envars)
factory := c.di.Get(key.TaskFactory).(task.Factory)
ecsCli := c.di.Get(key.EcsCli).(awsiface.EcsClient)
if input.UpdateService {
networkConfiguration = env.ServiceDefinitionInput.NetworkConfiguration
platformVersion = env.ServiceDefinitionInput.PlatformVersion
loadBalancers = env.ServiceDefinitionInput.LoadBalancers
} else {
if o, err := ecsCli.DescribeServices(ctx, &ecs.DescribeServicesInput{
Cluster: &env.Cluster,
Services: []string{env.Service},
}); err != nil {
return nil, err
} else {
service := o.Services[0]
networkConfiguration = service.NetworkConfiguration
platformVersion = service.PlatformVersion
loadBalancers = service.LoadBalancers
}
}
return taskset.NewSet(
factory,
&taskset.Input{
Input: &task.Input{
NetworkConfiguration: networkConfiguration,
TaskDefinition: c.td,
PlatformVersion: platformVersion,
},
LoadBalancers: loadBalancers,
},
), nil
}