1- import {
2- CheckInLicenseCommand ,
3- CheckoutLicenseCommand ,
4- LicenseManagerClient ,
5- } from '@aws-sdk/client-license-manager'
1+ import { CheckoutLicenseCommand , LicenseManagerClient } from '@aws-sdk/client-license-manager'
62import { createLogger } from '@sim/logger'
73import { getErrorMessage } from '@sim/utils/errors'
8- import { sleep } from '@sim/utils/helpers'
94import { generateId } from '@sim/utils/id'
105import { randomInt } from '@sim/utils/random'
116import { awsRequestHandler } from '@/lib/core/network/proxy-agent.server'
@@ -15,18 +10,12 @@ import { awsRequestHandler } from '@/lib/core/network/proxy-agent.server'
1510 *
1611 * The listing sells an annual contract whose one dimension is `enterprise_license`. AWS Marketplace
1712 * grants the buyer a license in AWS License Manager, and the running product checks it the way AWS
18- * documents for contract-priced containers: a provisional `CheckoutLicense` of the entitlement,
19- * which needs only permissions in AWS's managed `AWSLicenseManagerConsumptionPolicy`. While the
20- * license is active, the Enterprise suite is on (`ENTERPRISE_ENABLED`). Without one, Sim runs as
21- * the open-source core, so a lapsed renewal or an unreachable License Manager never locks a buyer
22- * out of their deployment.
23- *
24- * A contract usually covers the deployment with one unit, which every replica checks out. Each
25- * check returns its unit at once (`CheckInLicense`), but two replicas checking together, or a unit
26- * a crashed replica never returned (License Manager takes it back after an hour), answer exactly
27- * like a license without the entitlement: AWS reports both as `NoEntitlementsAllowedException`. So
28- * that answer switches a running deployment's Enterprise off only after it has held for longer
29- * than a held unit can last.
13+ * documents for a contract that unlocks a tier of features rather than metering a quantity: a
14+ * provisional `CheckoutLicense` of the entitlement with unit `None`. Such a checkout is not counted
15+ * against the contract, so every replica can check at once, and it needs only the permission AWS's
16+ * managed `AWSLicenseManagerConsumptionPolicy` grants. While the license is active, the Enterprise
17+ * suite is on (`ENTERPRISE_ENABLED`). Without one, Sim runs as the open-source core, so a lapsed
18+ * renewal or an unreachable License Manager never locks a buyer out of their deployment.
3019 *
3120 * Opt-in: nothing here runs unless {@link PRODUCT_SKU_ENV} is set, which only the Marketplace
3221 * Helm profile does. Enterprise flags are read once at startup by both the server and the
@@ -57,21 +46,6 @@ const RECHECK_INTERVAL_MS = 15 * 60 * 1000
5746/** Replicas start together; a random part of this added to each re-check keeps them apart. */
5847const RECHECK_JITTER_MS = 2 * 60 * 1000
5948
60- /** License Manager takes back a unit nobody checked in after an hour. */
61- const UNRETURNED_UNIT_MS = 60 * 60 * 1000
62-
63- /**
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.
67- */
68- const NO_ENTITLEMENT_CONFIRMATION_MS = UNRETURNED_UNIT_MS + RECHECK_INTERVAL_MS
69-
70- /** Replicas rolling out together can meet on the unit, so startup retries that answer. */
71- const STARTUP_ATTEMPTS = 3
72- const STARTUP_RETRY_MIN_MS = 1_000
73- const STARTUP_RETRY_MAX_MS = 5_000
74-
7549/**
7650 * Replicas see a change together; each waits a random part of this before restarting, so a renewal
7751 * or lapse never takes every replica down at once.
@@ -80,7 +54,7 @@ const RESTART_SPREAD_MS = 10 * 60 * 1000
8054
8155const ENTERPRISE_FLAGS = [ 'ENTERPRISE_ENABLED' , 'NEXT_PUBLIC_ENTERPRISE_ENABLED' ] as const
8256
83- /** License Manager's answers when the account holds no unit of the entitlement it can use now . */
57+ /** License Manager's answers when the account holds no active license with the entitlement. */
8458const NO_ENTITLEMENT_ERRORS = new Set ( [
8559 'NoEntitlementsAllowedException' ,
8660 'ResourceNotFoundException' ,
@@ -89,11 +63,8 @@ const NO_ENTITLEMENT_ERRORS = new Set([
8963/** The ServiceAccount's IAM role does not allow the check: a setup problem, never a verdict. */
9064const PERMISSION_ERRORS = new Set ( [ 'AccessDeniedException' , 'AuthorizationException' ] )
9165
92- /**
93- * `no-entitlement` is AWS's answer for both an unlicensed account and a unit held elsewhere.
94- * `unknown` is a failed check (network, permissions, throttling), never a verdict on the license.
95- */
96- type LicenseState = 'entitled' | 'no-entitlement' | 'unknown'
66+ /** `unknown` is a failed check (network, permissions, throttling), never a verdict on the license. */
67+ type LicenseState = 'entitled' | 'not-entitled' | 'unknown'
9768
9869function createClient ( ) : LicenseManagerClient {
9970 const region =
@@ -107,25 +78,15 @@ function createClient(): LicenseManagerClient {
10778 } )
10879}
10980
110- /** A unit left out returns to the pool within the hour, so a failed check-in is only logged. */
111- async function checkIn ( client : LicenseManagerClient , licenseConsumptionToken : string ) {
112- try {
113- await client . send (
114- new CheckInLicenseCommand ( { LicenseConsumptionToken : licenseConsumptionToken } )
115- )
116- } catch ( error ) {
117- logger . warn ( 'Could not return the AWS Marketplace license unit; License Manager reclaims it' , {
118- message : getErrorMessage ( error , 'Unknown error' ) ,
119- } )
120- }
121- }
122-
12381function classifyFailure ( error : unknown ) : LicenseState {
12482 const name = error instanceof Error ? error . name : undefined
125- if ( name && NO_ENTITLEMENT_ERRORS . has ( name ) ) return 'no-entitlement'
83+ if ( name && NO_ENTITLEMENT_ERRORS . has ( name ) ) {
84+ logger . info ( 'AWS License Manager reports no active Enterprise entitlement' , { error : name } )
85+ return 'not-entitled'
86+ }
12687 if ( name && PERMISSION_ERRORS . has ( name ) ) {
12788 logger . error (
128- 'The IAM role cannot check the AWS Marketplace license; allow license-manager:CheckoutLicense and license-manager:CheckInLicense ' ,
89+ 'The IAM role cannot check the AWS Marketplace license; allow license-manager:CheckoutLicense' ,
12990 { error : name }
13091 )
13192 return 'unknown'
@@ -147,32 +108,19 @@ async function checkLicense(
147108 ProductSKU : productSku ,
148109 CheckoutType : 'PROVISIONAL' ,
149110 KeyFingerprint : MARKETPLACE_KEY_FINGERPRINT ,
150- Entitlements : [ { Name : ENTERPRISE_ENTITLEMENT , Value : '1' , Unit : 'Count ' } ] ,
111+ Entitlements : [ { Name : ENTERPRISE_ENTITLEMENT , Unit : 'None ' } ] ,
151112 ClientToken : generateId ( ) ,
152113 } )
153114 )
154- if ( checkout . LicenseConsumptionToken ) await checkIn ( client , checkout . LicenseConsumptionToken )
155115 const allowed = ( checkout . EntitlementsAllowed ?? [ ] ) . some (
156116 ( entitlement ) => entitlement . Name === ENTERPRISE_ENTITLEMENT
157117 )
158- return allowed ? 'entitled' : 'no-entitlement '
118+ return allowed ? 'entitled' : 'not-entitled '
159119 } catch ( error ) {
160120 return classifyFailure ( error )
161121 }
162122}
163123
164- async function checkAtStartup (
165- client : LicenseManagerClient ,
166- productSku : string
167- ) : Promise < LicenseState > {
168- let state = await checkLicense ( client , productSku )
169- for ( let attempt = 1 ; state === 'no-entitlement' && attempt < STARTUP_ATTEMPTS ; attempt ++ ) {
170- await sleep ( randomInt ( STARTUP_RETRY_MIN_MS , STARTUP_RETRY_MAX_MS ) )
171- state = await checkLicense ( client , productSku )
172- }
173- return state
174- }
175-
176124function setEnterpriseFlags ( enabled : boolean ) : void {
177125 for ( const flag of ENTERPRISE_FLAGS ) process . env [ flag ] = enabled ? 'true' : 'false'
178126}
@@ -196,7 +144,7 @@ export async function applyAwsMarketplaceContainerLicense(): Promise<void> {
196144 if ( ! productSku ) return
197145
198146 const client = createClient ( )
199- const bootState = await checkAtStartup ( client , productSku )
147+ const bootState = await checkLicense ( client , productSku )
200148 const enterpriseAtBoot = bootState === 'entitled'
201149 setEnterpriseFlags ( enterpriseAtBoot )
202150 logger . info (
@@ -206,21 +154,15 @@ export async function applyAwsMarketplaceContainerLicense(): Promise<void> {
206154 { state : bootState }
207155 )
208156
209- let noEntitlementSince : number | null = null
210157 const scheduleRecheck = ( ) => {
211158 setTimeout ( recheck , RECHECK_INTERVAL_MS + randomInt ( 0 , RECHECK_JITTER_MS ) ) . unref ( )
212159 }
213160 const recheck = async ( ) => {
214161 const state = await checkLicense ( client , productSku )
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 ) {
219- noEntitlementSince ??= Date . now ( )
220- if ( Date . now ( ) - noEntitlementSince >= NO_ENTITLEMENT_CONFIRMATION_MS ) {
221- return restartToApply ( state )
222- }
223- }
162+ // A failed check keeps the current mode; only a definite answer that differs restarts.
163+ const changed =
164+ ( state === 'entitled' && ! enterpriseAtBoot ) || ( state === 'not-entitled' && enterpriseAtBoot )
165+ if ( changed ) return restartToApply ( state )
224166 scheduleRecheck ( )
225167 }
226168 scheduleRecheck ( )
0 commit comments