Skip to content

Commit 9e13682

Browse files
committed
fix(aws-marketplace): count only an unbroken run of no-entitlement answers, assert the checkout request, bump the chart
1 parent e5fa7c0 commit 9e13682

3 files changed

Lines changed: 38 additions & 10 deletions

File tree

‎apps/sim/lib/billing/aws-marketplace/container-license.test.ts‎

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,17 @@ import { applyAwsMarketplaceContainerLicense } from '@/lib/billing/aws-marketpla
2222
const MINUTE = 60 * 1000
2323
const TOKEN = 'consumption-token-placeholder'
2424

25-
type Answer = 'entitled' | 'NoEntitlementsAllowedException' | 'AccessDeniedException'
25+
type Answer =
26+
| 'entitled'
27+
| 'NoEntitlementsAllowedException'
28+
| 'AccessDeniedException'
29+
| 'ServerInternalException'
2630

2731
/** License Manager's checkout answers in order; the last one repeats once the list runs out. */
2832
let answers: Answer[] = []
2933
let checkInFails = false
30-
const checkIns: string[] = []
34+
const checkoutRequests: unknown[] = []
35+
const checkIns: Array<string | undefined> = []
3136

3237
function awsError(name: string): Error {
3338
return Object.assign(new Error(name), { name })
@@ -51,6 +56,7 @@ describe('applyAwsMarketplaceContainerLicense', () => {
5156
answers = []
5257
checkInFails = false
5358
checkIns.length = 0
59+
checkoutRequests.length = 0
5460
vi.stubEnv('AWS_MARKETPLACE_LICENSE_PRODUCT_SKU', 'product-placeholder')
5561
kill = vi.spyOn(process, 'kill').mockImplementation(() => true)
5662
mockSend.mockReset()
@@ -60,6 +66,7 @@ describe('applyAwsMarketplaceContainerLicense', () => {
6066
if (checkInFails) throw awsError('ServerInternalException')
6167
return {}
6268
}
69+
checkoutRequests.push((command as { input: unknown }).input)
6370
const answer = answers.length > 1 ? answers.shift() : answers[0]
6471
if (answer !== 'entitled') throw awsError(answer ?? 'ServerInternalException')
6572
return {
@@ -73,7 +80,6 @@ describe('applyAwsMarketplaceContainerLicense', () => {
7380
vi.clearAllTimers()
7481
vi.useRealTimers()
7582
kill.mockRestore()
76-
vi.unstubAllEnvs()
7783
Reflect.deleteProperty(process.env, 'ENTERPRISE_ENABLED')
7884
Reflect.deleteProperty(process.env, 'NEXT_PUBLIC_ENTERPRISE_ENABLED')
7985
})
@@ -88,6 +94,14 @@ describe('applyAwsMarketplaceContainerLicense', () => {
8894
answers = ['entitled']
8995
await boot()
9096
expect(enterpriseEnabled()).toBe(true)
97+
expect(checkoutRequests).toEqual([
98+
expect.objectContaining({
99+
ProductSKU: 'product-placeholder',
100+
CheckoutType: 'PROVISIONAL',
101+
KeyFingerprint: 'aws:294406891311:AWS/Marketplace:issuer-fingerprint',
102+
Entitlements: [{ Name: 'enterprise_license', Value: '1', Unit: 'Count' }],
103+
}),
104+
])
91105
expect(checkIns).toEqual([TOKEN])
92106
})
93107

@@ -132,6 +146,19 @@ describe('applyAwsMarketplaceContainerLicense', () => {
132146
expect(kill).toHaveBeenCalledWith(process.pid, 'SIGTERM')
133147
})
134148

149+
it('does not count failed checks toward switching Enterprise off', async () => {
150+
answers = [
151+
'entitled',
152+
'NoEntitlementsAllowedException',
153+
...Array<Answer>(6).fill('ServerInternalException'),
154+
'NoEntitlementsAllowedException',
155+
'entitled',
156+
]
157+
await boot()
158+
await vi.advanceTimersByTimeAsync(130 * MINUTE)
159+
expect(kill).not.toHaveBeenCalled()
160+
})
161+
135162
it('never restarts a running Enterprise deployment over a permission failure', async () => {
136163
answers = ['entitled', 'AccessDeniedException']
137164
await boot()

‎apps/sim/lib/billing/aws-marketplace/container-license.ts‎

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,9 @@ const RECHECK_JITTER_MS = 2 * 60 * 1000
6161
const UNRETURNED_UNIT_MS = 60 * 60 * 1000
6262

6363
/**
64-
* How long "no entitlement" must hold before a running Enterprise deployment believes it: longer
65-
* than an unreturned unit lasts, plus one re-check, so a unit held elsewhere can never cause it.
64+
* How long "no entitlement" must hold, with no other answer in between, before a running Enterprise
65+
* deployment believes it: longer than an unreturned unit lasts, plus one re-check, so a unit held
66+
* elsewhere can never cause it.
6667
*/
6768
const NO_ENTITLEMENT_CONFIRMATION_MS = UNRETURNED_UNIT_MS + RECHECK_INTERVAL_MS
6869

@@ -211,10 +212,10 @@ export async function applyAwsMarketplaceContainerLicense(): Promise<void> {
211212
}
212213
const recheck = async () => {
213214
const state = await checkLicense(client, productSku)
214-
if (state === 'entitled') {
215-
noEntitlementSince = null
216-
if (!enterpriseAtBoot) return restartToApply(state)
217-
} else if (state === 'no-entitlement' && enterpriseAtBoot) {
215+
// Only an unbroken run of "no entitlement" answers counts toward switching Enterprise off.
216+
if (state !== 'no-entitlement') noEntitlementSince = null
217+
if (state === 'entitled' && !enterpriseAtBoot) return restartToApply(state)
218+
if (state === 'no-entitlement' && enterpriseAtBoot) {
218219
noEntitlementSince ??= Date.now()
219220
if (Date.now() - noEntitlementSince >= NO_ENTITLEMENT_CONFIRMATION_MS) {
220221
return restartToApply(state)

‎helm/sim/Chart.yaml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ apiVersion: v2
22
name: sim
33
description: A Helm chart for Sim - the open-source AI workspace where teams build, deploy, and manage AI agents
44
type: application
5-
version: 1.11.16
5+
version: 1.11.17
66
appVersion: "v0.8.26"
77
kubeVersion: ">=1.25.0-0"
88
home: https://sim.ai

0 commit comments

Comments
 (0)