@stacksjs/ts-cloud-core 0.1.7 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +7 -6
- package/src/advanced-features.test.ts +465 -0
- package/src/aws/cloudformation.ts +421 -0
- package/src/aws/cloudfront.ts +158 -0
- package/src/aws/credentials.test.ts +132 -0
- package/src/aws/credentials.ts +545 -0
- package/src/aws/index.ts +87 -0
- package/src/aws/s3.test.ts +188 -0
- package/src/aws/s3.ts +1088 -0
- package/src/aws/signature.test.ts +670 -0
- package/src/aws/signature.ts +1155 -0
- package/src/backup/disaster-recovery.test.ts +726 -0
- package/src/backup/disaster-recovery.ts +500 -0
- package/src/backup/index.ts +34 -0
- package/src/backup/manager.test.ts +498 -0
- package/src/backup/manager.ts +432 -0
- package/src/cicd/circleci.ts +430 -0
- package/src/cicd/github-actions.ts +424 -0
- package/src/cicd/gitlab-ci.ts +255 -0
- package/src/cicd/index.ts +8 -0
- package/src/cli/history.ts +396 -0
- package/src/cli/index.ts +10 -0
- package/src/cli/progress.ts +458 -0
- package/src/cli/repl.ts +454 -0
- package/src/cli/suggestions.ts +327 -0
- package/src/cli/table.test.ts +319 -0
- package/src/cli/table.ts +332 -0
- package/src/cloudformation/builder.test.ts +327 -0
- package/src/cloudformation/builder.ts +378 -0
- package/src/cloudformation/builders/api-gateway.ts +449 -0
- package/src/cloudformation/builders/cache.ts +334 -0
- package/src/cloudformation/builders/cdn.ts +278 -0
- package/src/cloudformation/builders/compute.ts +485 -0
- package/src/cloudformation/builders/database.ts +392 -0
- package/src/cloudformation/builders/functions.ts +343 -0
- package/src/cloudformation/builders/messaging.ts +140 -0
- package/src/cloudformation/builders/monitoring.ts +300 -0
- package/src/cloudformation/builders/network.ts +264 -0
- package/src/cloudformation/builders/queue.ts +147 -0
- package/src/cloudformation/builders/security.ts +399 -0
- package/src/cloudformation/builders/storage.ts +285 -0
- package/src/cloudformation/index.ts +30 -0
- package/src/cloudformation/types.ts +173 -0
- package/src/compliance/aws-config.ts +543 -0
- package/src/compliance/cloudtrail.ts +376 -0
- package/src/compliance/compliance.test.ts +423 -0
- package/src/compliance/guardduty.ts +446 -0
- package/src/compliance/index.ts +66 -0
- package/src/compliance/security-hub.ts +456 -0
- package/src/containers/build-optimization.ts +416 -0
- package/src/containers/containers.test.ts +508 -0
- package/src/containers/image-scanning.ts +360 -0
- package/src/containers/index.ts +9 -0
- package/src/containers/registry.ts +293 -0
- package/src/containers/service-mesh.ts +520 -0
- package/src/database/database.test.ts +762 -0
- package/src/database/index.ts +9 -0
- package/src/database/migrations.ts +444 -0
- package/src/database/performance.ts +528 -0
- package/src/database/replicas.ts +534 -0
- package/src/database/users.ts +494 -0
- package/src/dependency-graph.ts +143 -0
- package/src/deployment/ab-testing.ts +582 -0
- package/src/deployment/blue-green.ts +452 -0
- package/src/deployment/canary.ts +500 -0
- package/src/deployment/deployment.test.ts +526 -0
- package/src/deployment/index.ts +61 -0
- package/src/deployment/progressive.ts +62 -0
- package/src/dns/dns.test.ts +641 -0
- package/src/dns/dnssec.ts +315 -0
- package/src/dns/index.ts +8 -0
- package/src/dns/resolver.ts +496 -0
- package/src/dns/routing.ts +593 -0
- package/src/email/advanced/analytics.ts +445 -0
- package/src/email/advanced/index.ts +11 -0
- package/src/email/advanced/rules.ts +465 -0
- package/src/email/advanced/scheduling.ts +352 -0
- package/src/email/advanced/search.ts +412 -0
- package/src/email/advanced/shared-mailboxes.ts +404 -0
- package/src/email/advanced/templates.ts +455 -0
- package/src/email/advanced/threading.ts +281 -0
- package/src/email/analytics.ts +467 -0
- package/src/email/bounce-handling.ts +425 -0
- package/src/email/email.test.ts +431 -0
- package/src/email/handlers/__tests__/inbound.test.ts +38 -0
- package/src/email/handlers/__tests__/outbound.test.ts +37 -0
- package/src/email/handlers/converter.ts +227 -0
- package/src/email/handlers/feedback.ts +228 -0
- package/src/email/handlers/inbound.ts +169 -0
- package/src/email/handlers/outbound.ts +178 -0
- package/src/email/index.ts +15 -0
- package/src/email/reputation.ts +303 -0
- package/src/email/templates.ts +352 -0
- package/src/errors/index.test.ts +434 -0
- package/src/errors/index.ts +416 -0
- package/src/health-checks/index.ts +40 -0
- package/src/index.ts +360 -0
- package/src/intrinsic-functions.ts +118 -0
- package/src/lambda/concurrency.ts +330 -0
- package/src/lambda/destinations.ts +345 -0
- package/src/lambda/dlq.ts +425 -0
- package/src/lambda/index.ts +11 -0
- package/src/lambda/lambda.test.ts +840 -0
- package/src/lambda/layers.ts +263 -0
- package/src/lambda/versions.ts +376 -0
- package/src/lambda/vpc.ts +399 -0
- package/src/local/config.ts +114 -0
- package/src/local/index.ts +6 -0
- package/src/local/mock-aws.ts +351 -0
- package/src/modules/ai.ts +340 -0
- package/src/modules/api.ts +478 -0
- package/src/modules/auth.ts +805 -0
- package/src/modules/cache.ts +417 -0
- package/src/modules/cdn.ts +1062 -0
- package/src/modules/communication.ts +1094 -0
- package/src/modules/compute.ts +3348 -0
- package/src/modules/database.ts +554 -0
- package/src/modules/deployment.ts +1079 -0
- package/src/modules/dns.ts +337 -0
- package/src/modules/email.ts +1538 -0
- package/src/modules/filesystem.ts +515 -0
- package/src/modules/index.ts +32 -0
- package/src/modules/messaging.ts +486 -0
- package/src/modules/monitoring.ts +2086 -0
- package/src/modules/network.ts +664 -0
- package/src/modules/parameter-store.ts +325 -0
- package/src/modules/permissions.ts +1081 -0
- package/src/modules/phone.ts +494 -0
- package/src/modules/queue.ts +1260 -0
- package/src/modules/redirects.ts +464 -0
- package/src/modules/registry.ts +699 -0
- package/src/modules/search.ts +401 -0
- package/src/modules/secrets.ts +416 -0
- package/src/modules/security.ts +731 -0
- package/src/modules/sms.ts +389 -0
- package/src/modules/storage.ts +1120 -0
- package/src/modules/workflow.ts +680 -0
- package/src/multi-account/config.ts +521 -0
- package/src/multi-account/index.ts +7 -0
- package/src/multi-account/manager.ts +427 -0
- package/src/multi-region/cross-region.ts +410 -0
- package/src/multi-region/index.ts +8 -0
- package/src/multi-region/manager.ts +483 -0
- package/src/multi-region/regions.ts +435 -0
- package/src/network-security/index.ts +48 -0
- package/src/observability/index.ts +9 -0
- package/src/observability/logs.ts +522 -0
- package/src/observability/metrics.ts +460 -0
- package/src/observability/observability.test.ts +782 -0
- package/src/observability/synthetics.ts +568 -0
- package/src/observability/xray.ts +358 -0
- package/src/phone/advanced/analytics.ts +349 -0
- package/src/phone/advanced/callbacks.ts +428 -0
- package/src/phone/advanced/index.ts +8 -0
- package/src/phone/advanced/ivr-builder.ts +504 -0
- package/src/phone/advanced/recording.ts +310 -0
- package/src/phone/handlers/__tests__/incoming-call.test.ts +40 -0
- package/src/phone/handlers/incoming-call.ts +117 -0
- package/src/phone/handlers/missed-call.ts +116 -0
- package/src/phone/handlers/voicemail.ts +179 -0
- package/src/phone/index.ts +9 -0
- package/src/presets/api-backend.ts +134 -0
- package/src/presets/data-pipeline.ts +204 -0
- package/src/presets/extend.test.ts +295 -0
- package/src/presets/extend.ts +297 -0
- package/src/presets/fullstack-app.ts +144 -0
- package/src/presets/index.ts +27 -0
- package/src/presets/jamstack.ts +135 -0
- package/src/presets/microservices.ts +167 -0
- package/src/presets/ml-api.ts +208 -0
- package/src/presets/nodejs-server.ts +104 -0
- package/src/presets/nodejs-serverless.ts +114 -0
- package/src/presets/realtime-app.ts +184 -0
- package/src/presets/static-site.ts +64 -0
- package/src/presets/traditional-web-app.ts +339 -0
- package/src/presets/wordpress.ts +138 -0
- package/src/preview/github.test.ts +249 -0
- package/src/preview/github.ts +297 -0
- package/src/preview/index.ts +37 -0
- package/src/preview/manager.test.ts +440 -0
- package/src/preview/manager.ts +326 -0
- package/src/preview/notifications.test.ts +582 -0
- package/src/preview/notifications.ts +341 -0
- package/src/queue/batch-processing.ts +402 -0
- package/src/queue/dlq-monitoring.ts +402 -0
- package/src/queue/fifo.ts +342 -0
- package/src/queue/index.ts +9 -0
- package/src/queue/management.ts +428 -0
- package/src/queue/queue.test.ts +429 -0
- package/src/resource-mgmt/index.ts +39 -0
- package/src/resource-naming.ts +62 -0
- package/src/s3/index.ts +523 -0
- package/src/schema/cloud-config.schema.json +554 -0
- package/src/schema/index.ts +68 -0
- package/src/security/certificate-manager.ts +492 -0
- package/src/security/index.ts +9 -0
- package/src/security/scanning.ts +545 -0
- package/src/security/secrets-manager.ts +476 -0
- package/src/security/secrets-rotation.ts +456 -0
- package/src/security/security.test.ts +738 -0
- package/src/sms/advanced/ab-testing.ts +389 -0
- package/src/sms/advanced/analytics.ts +336 -0
- package/src/sms/advanced/campaigns.ts +523 -0
- package/src/sms/advanced/chatbot.ts +224 -0
- package/src/sms/advanced/index.ts +10 -0
- package/src/sms/advanced/link-tracking.ts +248 -0
- package/src/sms/advanced/mms.ts +308 -0
- package/src/sms/handlers/__tests__/send.test.ts +40 -0
- package/src/sms/handlers/delivery-status.ts +133 -0
- package/src/sms/handlers/receive.ts +162 -0
- package/src/sms/handlers/send.ts +174 -0
- package/src/sms/index.ts +9 -0
- package/src/stack-diff.ts +389 -0
- package/src/static-site/index.ts +85 -0
- package/src/template-builder.ts +110 -0
- package/src/template-validator.ts +574 -0
- package/src/utils/cache.ts +291 -0
- package/src/utils/diff.ts +269 -0
- package/src/utils/hash.ts +227 -0
- package/src/utils/index.ts +8 -0
- package/src/utils/parallel.ts +294 -0
- package/src/validators/credentials.test.ts +274 -0
- package/src/validators/credentials.ts +233 -0
- package/src/validators/quotas.test.ts +434 -0
- package/src/validators/quotas.ts +217 -0
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canary Deployment Strategy
|
|
3
|
+
* Gradual rollout with automatic rollback based on metrics
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface CanaryDeployment {
|
|
7
|
+
id: string
|
|
8
|
+
name: string
|
|
9
|
+
baselineVersion: DeploymentVersion
|
|
10
|
+
canaryVersion: DeploymentVersion
|
|
11
|
+
stages: CanaryStage[]
|
|
12
|
+
currentStage: number
|
|
13
|
+
status: 'pending' | 'in_progress' | 'completed' | 'rolled_back' | 'failed'
|
|
14
|
+
metrics?: CanaryMetrics
|
|
15
|
+
autoPromote?: boolean
|
|
16
|
+
autoRollback?: boolean
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface DeploymentVersion {
|
|
20
|
+
version: string
|
|
21
|
+
targetGroupArn?: string
|
|
22
|
+
taskDefinitionArn?: string
|
|
23
|
+
functionVersionArn?: string
|
|
24
|
+
weight: number
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface CanaryStage {
|
|
28
|
+
name: string
|
|
29
|
+
trafficPercentage: number
|
|
30
|
+
durationMinutes: number
|
|
31
|
+
alarmThresholds?: AlarmThresholds
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface AlarmThresholds {
|
|
35
|
+
errorRate?: number // Percentage (e.g., 1 = 1%)
|
|
36
|
+
latencyP99?: number // Milliseconds
|
|
37
|
+
latencyP95?: number // Milliseconds
|
|
38
|
+
httpErrorRate?: number // Percentage
|
|
39
|
+
customMetrics?: CustomMetric[]
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface CustomMetric {
|
|
43
|
+
name: string
|
|
44
|
+
namespace: string
|
|
45
|
+
threshold: number
|
|
46
|
+
comparisonOperator: 'GreaterThanThreshold' | 'LessThanThreshold' | 'GreaterThanOrEqualToThreshold' | 'LessThanOrEqualToThreshold'
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface CanaryMetrics {
|
|
50
|
+
baselineErrorRate: number
|
|
51
|
+
canaryErrorRate: number
|
|
52
|
+
baselineLatencyP99: number
|
|
53
|
+
canaryLatencyP99: number
|
|
54
|
+
baselineRequestCount: number
|
|
55
|
+
canaryRequestCount: number
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface CanaryResult {
|
|
59
|
+
success: boolean
|
|
60
|
+
deploymentId: string
|
|
61
|
+
startTime: Date
|
|
62
|
+
endTime?: Date
|
|
63
|
+
completedStages: number
|
|
64
|
+
rolledBack: boolean
|
|
65
|
+
reason?: string
|
|
66
|
+
metricsAtCompletion?: CanaryMetrics
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Canary deployment manager
|
|
71
|
+
*/
|
|
72
|
+
export class CanaryManager {
|
|
73
|
+
private deployments: Map<string, CanaryDeployment> = new Map()
|
|
74
|
+
private deploymentHistory: Map<string, CanaryResult[]> = new Map()
|
|
75
|
+
private deploymentCounter = 0
|
|
76
|
+
private resultCounter = 0
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Predefined canary strategies
|
|
80
|
+
*/
|
|
81
|
+
static readonly Strategies = {
|
|
82
|
+
/**
|
|
83
|
+
* Conservative: 10% -> 25% -> 50% -> 100%
|
|
84
|
+
*/
|
|
85
|
+
CONSERVATIVE: [
|
|
86
|
+
{ name: 'Initial Canary', trafficPercentage: 10, durationMinutes: 10 },
|
|
87
|
+
{ name: 'Quarter Traffic', trafficPercentage: 25, durationMinutes: 10 },
|
|
88
|
+
{ name: 'Half Traffic', trafficPercentage: 50, durationMinutes: 15 },
|
|
89
|
+
{ name: 'Full Traffic', trafficPercentage: 100, durationMinutes: 5 },
|
|
90
|
+
] as CanaryStage[],
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Balanced: 20% -> 50% -> 100%
|
|
94
|
+
*/
|
|
95
|
+
BALANCED: [
|
|
96
|
+
{ name: 'Initial Canary', trafficPercentage: 20, durationMinutes: 5 },
|
|
97
|
+
{ name: 'Half Traffic', trafficPercentage: 50, durationMinutes: 10 },
|
|
98
|
+
{ name: 'Full Traffic', trafficPercentage: 100, durationMinutes: 5 },
|
|
99
|
+
] as CanaryStage[],
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Aggressive: 50% -> 100%
|
|
103
|
+
*/
|
|
104
|
+
AGGRESSIVE: [
|
|
105
|
+
{ name: 'Half Traffic', trafficPercentage: 50, durationMinutes: 5 },
|
|
106
|
+
{ name: 'Full Traffic', trafficPercentage: 100, durationMinutes: 5 },
|
|
107
|
+
] as CanaryStage[],
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Linear 10%: Incremental 10% steps
|
|
111
|
+
*/
|
|
112
|
+
LINEAR_10: [
|
|
113
|
+
{ name: 'Canary 10%', trafficPercentage: 10, durationMinutes: 5 },
|
|
114
|
+
{ name: 'Canary 20%', trafficPercentage: 20, durationMinutes: 5 },
|
|
115
|
+
{ name: 'Canary 30%', trafficPercentage: 30, durationMinutes: 5 },
|
|
116
|
+
{ name: 'Canary 40%', trafficPercentage: 40, durationMinutes: 5 },
|
|
117
|
+
{ name: 'Canary 50%', trafficPercentage: 50, durationMinutes: 5 },
|
|
118
|
+
{ name: 'Canary 60%', trafficPercentage: 60, durationMinutes: 5 },
|
|
119
|
+
{ name: 'Canary 70%', trafficPercentage: 70, durationMinutes: 5 },
|
|
120
|
+
{ name: 'Canary 80%', trafficPercentage: 80, durationMinutes: 5 },
|
|
121
|
+
{ name: 'Canary 90%', trafficPercentage: 90, durationMinutes: 5 },
|
|
122
|
+
{ name: 'Full Traffic', trafficPercentage: 100, durationMinutes: 5 },
|
|
123
|
+
] as CanaryStage[],
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Create canary deployment
|
|
128
|
+
*/
|
|
129
|
+
createDeployment(deployment: Omit<CanaryDeployment, 'id'>): CanaryDeployment {
|
|
130
|
+
const id = `canary-${Date.now()}-${this.deploymentCounter++}`
|
|
131
|
+
|
|
132
|
+
const canaryDeployment: CanaryDeployment = {
|
|
133
|
+
id,
|
|
134
|
+
...deployment,
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
this.deployments.set(id, canaryDeployment)
|
|
138
|
+
|
|
139
|
+
return canaryDeployment
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Create Lambda canary deployment
|
|
144
|
+
*/
|
|
145
|
+
createLambdaCanaryDeployment(options: {
|
|
146
|
+
name: string
|
|
147
|
+
baselineVersionArn: string
|
|
148
|
+
canaryVersionArn: string
|
|
149
|
+
strategy?: keyof typeof CanaryManager.Strategies
|
|
150
|
+
autoPromote?: boolean
|
|
151
|
+
errorRateThreshold?: number
|
|
152
|
+
latencyThreshold?: number
|
|
153
|
+
}): CanaryDeployment {
|
|
154
|
+
const strategy = options.strategy || 'BALANCED'
|
|
155
|
+
const stages = CanaryManager.Strategies[strategy].map(stage => ({
|
|
156
|
+
...stage,
|
|
157
|
+
alarmThresholds: {
|
|
158
|
+
errorRate: options.errorRateThreshold || 1,
|
|
159
|
+
latencyP99: options.latencyThreshold || 1000,
|
|
160
|
+
},
|
|
161
|
+
}))
|
|
162
|
+
|
|
163
|
+
return this.createDeployment({
|
|
164
|
+
name: options.name,
|
|
165
|
+
baselineVersion: {
|
|
166
|
+
version: 'baseline',
|
|
167
|
+
functionVersionArn: options.baselineVersionArn,
|
|
168
|
+
weight: 100,
|
|
169
|
+
},
|
|
170
|
+
canaryVersion: {
|
|
171
|
+
version: 'canary',
|
|
172
|
+
functionVersionArn: options.canaryVersionArn,
|
|
173
|
+
weight: 0,
|
|
174
|
+
},
|
|
175
|
+
stages,
|
|
176
|
+
currentStage: 0,
|
|
177
|
+
status: 'pending',
|
|
178
|
+
autoPromote: options.autoPromote ?? true,
|
|
179
|
+
autoRollback: true,
|
|
180
|
+
})
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Create ECS canary deployment
|
|
185
|
+
*/
|
|
186
|
+
createECSCanaryDeployment(options: {
|
|
187
|
+
name: string
|
|
188
|
+
baselineTaskDefinitionArn: string
|
|
189
|
+
canaryTaskDefinitionArn: string
|
|
190
|
+
baselineTargetGroupArn: string
|
|
191
|
+
canaryTargetGroupArn: string
|
|
192
|
+
strategy?: keyof typeof CanaryManager.Strategies
|
|
193
|
+
}): CanaryDeployment {
|
|
194
|
+
const strategy = options.strategy || 'CONSERVATIVE'
|
|
195
|
+
const stages = CanaryManager.Strategies[strategy]
|
|
196
|
+
|
|
197
|
+
return this.createDeployment({
|
|
198
|
+
name: options.name,
|
|
199
|
+
baselineVersion: {
|
|
200
|
+
version: 'baseline',
|
|
201
|
+
taskDefinitionArn: options.baselineTaskDefinitionArn,
|
|
202
|
+
targetGroupArn: options.baselineTargetGroupArn,
|
|
203
|
+
weight: 100,
|
|
204
|
+
},
|
|
205
|
+
canaryVersion: {
|
|
206
|
+
version: 'canary',
|
|
207
|
+
taskDefinitionArn: options.canaryTaskDefinitionArn,
|
|
208
|
+
targetGroupArn: options.canaryTargetGroupArn,
|
|
209
|
+
weight: 0,
|
|
210
|
+
},
|
|
211
|
+
stages,
|
|
212
|
+
currentStage: 0,
|
|
213
|
+
status: 'pending',
|
|
214
|
+
autoRollback: true,
|
|
215
|
+
})
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Execute canary deployment
|
|
220
|
+
*/
|
|
221
|
+
async executeDeployment(deploymentId: string, dryRun: boolean = false): Promise<CanaryResult> {
|
|
222
|
+
const deployment = this.deployments.get(deploymentId)
|
|
223
|
+
|
|
224
|
+
if (!deployment) {
|
|
225
|
+
throw new Error(`Deployment not found: ${deploymentId}`)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const result: CanaryResult = {
|
|
229
|
+
success: false,
|
|
230
|
+
deploymentId: `result-${Date.now()}-${this.resultCounter++}`,
|
|
231
|
+
startTime: new Date(),
|
|
232
|
+
completedStages: 0,
|
|
233
|
+
rolledBack: false,
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
console.log(`${dryRun ? '[DRY RUN] ' : ''}Starting canary deployment: ${deployment.name}`)
|
|
237
|
+
console.log(` Strategy: ${deployment.stages.length} stages`)
|
|
238
|
+
console.log(` Auto-promote: ${deployment.autoPromote}`)
|
|
239
|
+
console.log(` Auto-rollback: ${deployment.autoRollback}`)
|
|
240
|
+
console.log('')
|
|
241
|
+
|
|
242
|
+
deployment.status = 'in_progress'
|
|
243
|
+
|
|
244
|
+
// Execute each canary stage
|
|
245
|
+
for (let i = 0; i < deployment.stages.length; i++) {
|
|
246
|
+
const stage = deployment.stages[i]
|
|
247
|
+
deployment.currentStage = i
|
|
248
|
+
|
|
249
|
+
console.log(`Stage ${i + 1}/${deployment.stages.length}: ${stage.name}`)
|
|
250
|
+
console.log(` Traffic: ${stage.trafficPercentage}% canary, ${100 - stage.trafficPercentage}% baseline`)
|
|
251
|
+
console.log(` Duration: ${stage.durationMinutes} minutes`)
|
|
252
|
+
|
|
253
|
+
// Update traffic weights
|
|
254
|
+
if (!dryRun) {
|
|
255
|
+
deployment.baselineVersion.weight = 100 - stage.trafficPercentage
|
|
256
|
+
deployment.canaryVersion.weight = stage.trafficPercentage
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Monitor stage
|
|
260
|
+
const stageSuccessful = await this.monitorStage(deployment, stage, dryRun)
|
|
261
|
+
|
|
262
|
+
if (!stageSuccessful) {
|
|
263
|
+
console.log(` ✗ Stage failed - metrics exceeded thresholds`)
|
|
264
|
+
|
|
265
|
+
if (deployment.autoRollback) {
|
|
266
|
+
console.log(`\\n Rolling back deployment...`)
|
|
267
|
+
await this.rollback(deploymentId, dryRun)
|
|
268
|
+
result.rolledBack = true
|
|
269
|
+
result.reason = 'Metrics exceeded thresholds'
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
result.endTime = new Date()
|
|
273
|
+
deployment.status = 'rolled_back'
|
|
274
|
+
|
|
275
|
+
this.recordDeployment(deploymentId, result)
|
|
276
|
+
return result
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
console.log(` ✓ Stage completed successfully`)
|
|
280
|
+
result.completedStages++
|
|
281
|
+
|
|
282
|
+
console.log('')
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// All stages completed
|
|
286
|
+
deployment.status = 'completed'
|
|
287
|
+
result.success = true
|
|
288
|
+
result.endTime = new Date()
|
|
289
|
+
|
|
290
|
+
console.log(`✓ Canary deployment completed successfully`)
|
|
291
|
+
|
|
292
|
+
this.recordDeployment(deploymentId, result)
|
|
293
|
+
|
|
294
|
+
return result
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Monitor canary stage
|
|
299
|
+
*/
|
|
300
|
+
private async monitorStage(
|
|
301
|
+
deployment: CanaryDeployment,
|
|
302
|
+
stage: CanaryStage,
|
|
303
|
+
dryRun: boolean,
|
|
304
|
+
): Promise<boolean> {
|
|
305
|
+
console.log(` Monitoring metrics...`)
|
|
306
|
+
|
|
307
|
+
if (dryRun) {
|
|
308
|
+
console.log(` [SKIPPED - DRY RUN]`)
|
|
309
|
+
return true
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Simulate metric collection
|
|
313
|
+
await new Promise(resolve => setTimeout(resolve, 100))
|
|
314
|
+
|
|
315
|
+
// Simulate metrics (in real implementation, would query CloudWatch)
|
|
316
|
+
const metrics: CanaryMetrics = {
|
|
317
|
+
baselineErrorRate: Math.random() * 0.5, // 0-0.5%
|
|
318
|
+
canaryErrorRate: Math.random() * 0.8, // 0-0.8%
|
|
319
|
+
baselineLatencyP99: 200 + Math.random() * 100,
|
|
320
|
+
canaryLatencyP99: 180 + Math.random() * 150,
|
|
321
|
+
baselineRequestCount: Math.floor(Math.random() * 1000) + 500,
|
|
322
|
+
canaryRequestCount: Math.floor((Math.random() * 1000 + 500) * stage.trafficPercentage / 100),
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
deployment.metrics = metrics
|
|
326
|
+
|
|
327
|
+
console.log(` Baseline: ${metrics.baselineErrorRate.toFixed(2)}% errors, ${metrics.baselineLatencyP99.toFixed(0)}ms P99`)
|
|
328
|
+
console.log(` Canary: ${metrics.canaryErrorRate.toFixed(2)}% errors, ${metrics.canaryLatencyP99.toFixed(0)}ms P99`)
|
|
329
|
+
|
|
330
|
+
// Check alarm thresholds
|
|
331
|
+
if (stage.alarmThresholds) {
|
|
332
|
+
const { errorRate, latencyP99 } = stage.alarmThresholds
|
|
333
|
+
|
|
334
|
+
if (errorRate && metrics.canaryErrorRate > errorRate) {
|
|
335
|
+
console.log(` ⚠ Error rate exceeded threshold: ${metrics.canaryErrorRate.toFixed(2)}% > ${errorRate}%`)
|
|
336
|
+
return false
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (latencyP99 && metrics.canaryLatencyP99 > latencyP99) {
|
|
340
|
+
console.log(` ⚠ Latency P99 exceeded threshold: ${metrics.canaryLatencyP99.toFixed(0)}ms > ${latencyP99}ms`)
|
|
341
|
+
return false
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return true
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Rollback canary deployment
|
|
350
|
+
*/
|
|
351
|
+
async rollback(deploymentId: string, dryRun: boolean = false): Promise<void> {
|
|
352
|
+
const deployment = this.deployments.get(deploymentId)
|
|
353
|
+
|
|
354
|
+
if (!deployment) {
|
|
355
|
+
throw new Error(`Deployment not found: ${deploymentId}`)
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
console.log(`${dryRun ? '[DRY RUN] ' : ''}Rolling back canary deployment: ${deployment.name}`)
|
|
359
|
+
|
|
360
|
+
if (!dryRun) {
|
|
361
|
+
deployment.baselineVersion.weight = 100
|
|
362
|
+
deployment.canaryVersion.weight = 0
|
|
363
|
+
deployment.status = 'rolled_back'
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
console.log(` Traffic restored to baseline: 100%`)
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Promote canary to baseline
|
|
371
|
+
*/
|
|
372
|
+
promoteCanary(deploymentId: string): void {
|
|
373
|
+
const deployment = this.deployments.get(deploymentId)
|
|
374
|
+
|
|
375
|
+
if (!deployment) {
|
|
376
|
+
throw new Error(`Deployment not found: ${deploymentId}`)
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
console.log(`Promoting canary to baseline: ${deployment.name}`)
|
|
380
|
+
|
|
381
|
+
// Swap versions
|
|
382
|
+
const temp = deployment.baselineVersion
|
|
383
|
+
deployment.baselineVersion = deployment.canaryVersion
|
|
384
|
+
deployment.canaryVersion = temp
|
|
385
|
+
|
|
386
|
+
deployment.baselineVersion.weight = 100
|
|
387
|
+
deployment.canaryVersion.weight = 0
|
|
388
|
+
deployment.status = 'completed'
|
|
389
|
+
|
|
390
|
+
console.log(` Canary promoted successfully`)
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Record deployment result
|
|
395
|
+
*/
|
|
396
|
+
private recordDeployment(deploymentId: string, result: CanaryResult): void {
|
|
397
|
+
if (!this.deploymentHistory.has(deploymentId)) {
|
|
398
|
+
this.deploymentHistory.set(deploymentId, [])
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
this.deploymentHistory.get(deploymentId)!.push(result)
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Get deployment
|
|
406
|
+
*/
|
|
407
|
+
getDeployment(id: string): CanaryDeployment | undefined {
|
|
408
|
+
return this.deployments.get(id)
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* List deployments
|
|
413
|
+
*/
|
|
414
|
+
listDeployments(): CanaryDeployment[] {
|
|
415
|
+
return Array.from(this.deployments.values())
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Get deployment history
|
|
420
|
+
*/
|
|
421
|
+
getDeploymentHistory(deploymentId: string): CanaryResult[] {
|
|
422
|
+
return this.deploymentHistory.get(deploymentId) || []
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Generate CloudFormation for Lambda canary
|
|
427
|
+
*/
|
|
428
|
+
generateLambdaAliasCF(deployment: CanaryDeployment, aliasName: string): any {
|
|
429
|
+
return {
|
|
430
|
+
Type: 'AWS::Lambda::Alias',
|
|
431
|
+
Properties: {
|
|
432
|
+
FunctionName: { Ref: 'LambdaFunction' },
|
|
433
|
+
Name: aliasName,
|
|
434
|
+
FunctionVersion: deployment.canaryVersion.version,
|
|
435
|
+
RoutingConfig: {
|
|
436
|
+
AdditionalVersionWeights: [
|
|
437
|
+
{
|
|
438
|
+
FunctionVersion: deployment.baselineVersion.version,
|
|
439
|
+
FunctionWeight: deployment.baselineVersion.weight / 100,
|
|
440
|
+
},
|
|
441
|
+
],
|
|
442
|
+
},
|
|
443
|
+
},
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Generate CloudFormation for ALB weighted target groups
|
|
449
|
+
*/
|
|
450
|
+
generateALBListenerRuleCF(deployment: CanaryDeployment): any {
|
|
451
|
+
return {
|
|
452
|
+
Type: 'AWS::ElasticLoadBalancingV2::ListenerRule',
|
|
453
|
+
Properties: {
|
|
454
|
+
ListenerArn: { Ref: 'LoadBalancerListener' },
|
|
455
|
+
Priority: 1,
|
|
456
|
+
Conditions: [
|
|
457
|
+
{
|
|
458
|
+
Field: 'path-pattern',
|
|
459
|
+
Values: ['/*'],
|
|
460
|
+
},
|
|
461
|
+
],
|
|
462
|
+
Actions: [
|
|
463
|
+
{
|
|
464
|
+
Type: 'forward',
|
|
465
|
+
ForwardConfig: {
|
|
466
|
+
TargetGroups: [
|
|
467
|
+
{
|
|
468
|
+
TargetGroupArn: deployment.baselineVersion.targetGroupArn,
|
|
469
|
+
Weight: deployment.baselineVersion.weight,
|
|
470
|
+
},
|
|
471
|
+
{
|
|
472
|
+
TargetGroupArn: deployment.canaryVersion.targetGroupArn,
|
|
473
|
+
Weight: deployment.canaryVersion.weight,
|
|
474
|
+
},
|
|
475
|
+
],
|
|
476
|
+
TargetGroupStickinessConfig: {
|
|
477
|
+
Enabled: false,
|
|
478
|
+
},
|
|
479
|
+
},
|
|
480
|
+
},
|
|
481
|
+
],
|
|
482
|
+
},
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Clear all data
|
|
488
|
+
*/
|
|
489
|
+
clear(): void {
|
|
490
|
+
this.deployments.clear()
|
|
491
|
+
this.deploymentHistory.clear()
|
|
492
|
+
this.deploymentCounter = 0
|
|
493
|
+
this.resultCounter = 0
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Global canary manager instance
|
|
499
|
+
*/
|
|
500
|
+
export const canaryManager: CanaryManager = new CanaryManager()
|