@@ -9,6 +9,7 @@ import type {
9
9
WaitOptions ,
10
10
CancellationOptions ,
11
11
StartAndWaitForPortsOptions ,
12
+ SecretsStoreBinding ,
12
13
} from '../types' ;
13
14
import { generateId , parseTimeExpression } from './helpers' ;
14
15
import { DurableObject } from 'cloudflare:workers' ;
@@ -232,6 +233,7 @@ export class Container<Env = unknown> extends DurableObject<Env> {
232
233
envVars : ContainerStartOptions [ 'env' ] = { } ;
233
234
entrypoint : ContainerStartOptions [ 'entrypoint' ] ;
234
235
enableInternet : ContainerStartOptions [ 'enableInternet' ] = true ;
236
+ secretsStoreBindings : SecretsStoreBinding [ ] = [ ] ;
235
237
236
238
// =========================
237
239
// PUBLIC INTERFACE
@@ -261,6 +263,12 @@ export class Container<Env = unknown> extends DurableObject<Env> {
261
263
if ( options ) {
262
264
if ( options . defaultPort !== undefined ) this . defaultPort = options . defaultPort ;
263
265
if ( options . sleepAfter !== undefined ) this . sleepAfter = options . sleepAfter ;
266
+ if ( options . secretsStoreBindings ) this . secretsStoreBindings = options . secretsStoreBindings ;
267
+ }
268
+
269
+ // Auto-detect Secrets Store bindings if not explicitly provided
270
+ if ( this . secretsStoreBindings . length === 0 ) {
271
+ this . secretsStoreBindings = this . autoDetectSecretsStoreBindings ( env ) ;
264
272
}
265
273
266
274
// Create schedules table if it doesn't exist
@@ -912,7 +920,11 @@ export class Container<Env = unknown> extends DurableObject<Env> {
912
920
enableInternet,
913
921
} ;
914
922
915
- if ( envVars && Object . keys ( envVars ) . length > 0 ) startConfig . env = envVars ;
923
+ // Merge Secrets Store environment variables with existing environment variables
924
+ const secretsStoreEnvVars = this . setupSecretsStoreBindingEnvironment ( ) ;
925
+ const mergedEnvVars = { ...secretsStoreEnvVars , ...envVars } ;
926
+
927
+ if ( Object . keys ( mergedEnvVars ) . length > 0 ) startConfig . env = mergedEnvVars ;
916
928
if ( entrypoint ) startConfig . entrypoint = entrypoint ;
917
929
918
930
this . renewActivityTimeout ( ) ;
@@ -1289,6 +1301,155 @@ export class Container<Env = unknown> extends DurableObject<Env> {
1289
1301
return this . toSchedule ( schedule ) ;
1290
1302
}
1291
1303
1304
+ // ==========================
1305
+ // SECRETS STORE BINDING HELPERS
1306
+ // ==========================
1307
+
1308
+ /**
1309
+ * Generate environment variables for Secrets Store bindings
1310
+ * Creates SECRETS_{BINDING_NAME}_BINDING, SECRETS_{BINDING_NAME}_STORE_ID, and SECRETS_{BINDING_NAME}_SECRET_NAME environment variables
1311
+ * @private
1312
+ */
1313
+ private setupSecretsStoreBindingEnvironment ( ) : Record < string , string > {
1314
+ const secretsEnvVars : Record < string , string > = { } ;
1315
+ for ( const binding of this . secretsStoreBindings ) {
1316
+ const envPrefix = binding . binding . toUpperCase ( ) . replace ( / [ ^ A - Z 0 - 9 ] / g, '_' ) ;
1317
+ secretsEnvVars [ `SECRETS_${ envPrefix } _BINDING` ] = binding . binding ;
1318
+ secretsEnvVars [ `SECRETS_${ envPrefix } _STORE_ID` ] = binding . storeId ;
1319
+ secretsEnvVars [ `SECRETS_${ envPrefix } _SECRET_NAME` ] = binding . secretName ;
1320
+ }
1321
+ return secretsEnvVars ;
1322
+ }
1323
+
1324
+ /**
1325
+ * Get detailed information about configured Secrets Store bindings
1326
+ * @public
1327
+ */
1328
+ public getSecretsStoreBindingInfo ( ) : Record < string , { binding : string ; storeId : string ; secretName : string ; envVars : Record < string , string > } > {
1329
+ const bindingInfo : Record < string , any > = { } ;
1330
+ for ( const binding of this . secretsStoreBindings ) {
1331
+ const envPrefix = binding . binding . toUpperCase ( ) . replace ( / [ ^ A - Z 0 - 9 ] / g, '_' ) ;
1332
+ const envVars : Record < string , string > = {
1333
+ [ `SECRETS_${ envPrefix } _BINDING` ] : binding . binding ,
1334
+ [ `SECRETS_${ envPrefix } _STORE_ID` ] : binding . storeId ,
1335
+ [ `SECRETS_${ envPrefix } _SECRET_NAME` ] : binding . secretName
1336
+ } ;
1337
+
1338
+ bindingInfo [ binding . binding ] = {
1339
+ binding : binding . binding ,
1340
+ storeId : binding . storeId ,
1341
+ secretName : binding . secretName ,
1342
+ envVars
1343
+ } ;
1344
+ }
1345
+ return bindingInfo ;
1346
+ }
1347
+
1348
+ /**
1349
+ * Validate Secrets Store binding environment variables are properly set
1350
+ * @public
1351
+ */
1352
+ public validateSecretsStoreBindingEnvironment ( ) : { valid : boolean ; bindings : Record < string , any > ; errors : string [ ] } {
1353
+ const errors : string [ ] = [ ] ;
1354
+ const bindings : Record < string , any > = { } ;
1355
+
1356
+ // Get all Secrets Store environment variables from process.env
1357
+ const secretsVars : Record < string , string > = { } ;
1358
+ for ( const [ key , value ] of Object . entries ( process . env ) ) {
1359
+ if ( key . startsWith ( 'SECRETS_' ) && value ) {
1360
+ secretsVars [ key ] = value ;
1361
+ }
1362
+ }
1363
+
1364
+ // Group Secrets Store variables by binding name
1365
+ const groupedBindings : Record < string , Record < string , string > > = { } ;
1366
+ for ( const [ key , value ] of Object . entries ( secretsVars ) ) {
1367
+ const match = key . match ( / ^ S E C R E T S _ ( [ A - Z 0 - 9 _ ] + ) _ ( B I N D I N G | S T O R E _ I D | S E C R E T _ N A M E ) $ / ) ;
1368
+ if ( match ) {
1369
+ const bindingName = match [ 1 ] ;
1370
+ const varType = match [ 2 ] ;
1371
+ if ( ! groupedBindings [ bindingName ] ) {
1372
+ groupedBindings [ bindingName ] = { } ;
1373
+ }
1374
+ groupedBindings [ bindingName ] [ varType ] = value ;
1375
+ }
1376
+ }
1377
+
1378
+ // Check each configured binding
1379
+ for ( const binding of this . secretsStoreBindings ) {
1380
+ const envPrefix = binding . binding . toUpperCase ( ) . replace ( / [ ^ A - Z 0 - 9 ] / g, '_' ) ;
1381
+ const foundBinding = groupedBindings [ envPrefix ] ;
1382
+
1383
+ if ( ! foundBinding ) {
1384
+ errors . push ( `Secrets Store binding '${ binding . binding } ' not found in environment variables` ) ;
1385
+ continue ;
1386
+ }
1387
+
1388
+ if ( ! foundBinding . BINDING ) {
1389
+ errors . push ( `SECRETS_${ envPrefix } _BINDING environment variable missing` ) ;
1390
+ }
1391
+
1392
+ if ( ! foundBinding . STORE_ID ) {
1393
+ errors . push ( `SECRETS_${ envPrefix } _STORE_ID environment variable missing` ) ;
1394
+ }
1395
+
1396
+ if ( ! foundBinding . SECRET_NAME ) {
1397
+ errors . push ( `SECRETS_${ envPrefix } _SECRET_NAME environment variable missing` ) ;
1398
+ }
1399
+
1400
+ bindings [ binding . binding ] = {
1401
+ configured : binding ,
1402
+ environment : foundBinding ,
1403
+ valid : foundBinding . BINDING && foundBinding . STORE_ID && foundBinding . SECRET_NAME
1404
+ } ;
1405
+ }
1406
+
1407
+ return {
1408
+ valid : errors . length === 0 ,
1409
+ bindings,
1410
+ errors
1411
+ } ;
1412
+ }
1413
+
1414
+ /**
1415
+ * Get a summary of Secrets Store bindings for logging and debugging
1416
+ * @public
1417
+ */
1418
+ public getSecretsStoreBindingSummary ( ) : { configured : number ; bindings : Array < { name : string ; storeId : string ; secretName : string } > } {
1419
+ return {
1420
+ configured : this . secretsStoreBindings . length ,
1421
+ bindings : this . secretsStoreBindings . map ( binding => ( {
1422
+ name : binding . binding ,
1423
+ storeId : binding . storeId ,
1424
+ secretName : binding . secretName
1425
+ } ) )
1426
+ } ;
1427
+ }
1428
+
1429
+ /**
1430
+ * Auto-detect Secrets Store bindings from the environment
1431
+ * @private
1432
+ */
1433
+ private autoDetectSecretsStoreBindings ( env : any ) : SecretsStoreBinding [ ] {
1434
+ const secretsStoreBindings : SecretsStoreBinding [ ] = [ ] ;
1435
+
1436
+ // Look for Secrets Store binding properties in the environment
1437
+ for ( const [ key , value ] of Object . entries ( env ) ) {
1438
+ // Check if this property looks like a Secrets Store binding
1439
+ if ( value && typeof value === 'object' && 'get' in value && typeof value . get === 'function' ) {
1440
+ // This looks like a Secrets Store binding - we need store_id and secret_name
1441
+ // For auto-detection, we'll use sensible defaults based on the binding name
1442
+ secretsStoreBindings . push ( {
1443
+ binding : key ,
1444
+ storeId : `auto-detected-store-${ key . toLowerCase ( ) } ` ,
1445
+ secretName : key . toLowerCase ( ) . replace ( / _ / g, '-' )
1446
+ } ) ;
1447
+ }
1448
+ }
1449
+
1450
+ return secretsStoreBindings ;
1451
+ }
1452
+
1292
1453
private isActivityExpired ( ) : boolean {
1293
1454
return this . sleepAfterMs <= Date . now ( ) ;
1294
1455
}
0 commit comments