@geekmidas/cli 10.0.0-alpha.123 → 10.0.0-alpha.125
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/dist/{EndpointGenerator-BDa6pPgE.cjs → EndpointGenerator-BGxf9jWE.cjs} +8 -4
- package/dist/{EndpointGenerator-BDa6pPgE.cjs.map → EndpointGenerator-BGxf9jWE.cjs.map} +1 -1
- package/dist/{EndpointGenerator-CP_z_E3e.mjs → EndpointGenerator-errMeldx.mjs} +8 -4
- package/dist/{EndpointGenerator-CP_z_E3e.mjs.map → EndpointGenerator-errMeldx.mjs.map} +1 -1
- package/dist/{GoDaddyProvider-BlB5nJky.mjs → GoDaddyProvider-BN1dODm9.mjs} +3 -3
- package/dist/{GoDaddyProvider-BlB5nJky.mjs.map → GoDaddyProvider-BN1dODm9.mjs.map} +1 -1
- package/dist/{GoDaddyProvider-Dubq-pCc.cjs → GoDaddyProvider-BTdtqhI_.cjs} +3 -3
- package/dist/{GoDaddyProvider-Dubq-pCc.cjs.map → GoDaddyProvider-BTdtqhI_.cjs.map} +1 -1
- package/dist/{HostingerProvider-BNpS9aRJ.mjs → HostingerProvider-Cjh_2Ml6.mjs} +2 -2
- package/dist/{HostingerProvider-BNpS9aRJ.mjs.map → HostingerProvider-Cjh_2Ml6.mjs.map} +1 -1
- package/dist/{HostingerProvider-DR1iws1P.cjs → HostingerProvider-OdQmKXE3.cjs} +2 -2
- package/dist/{HostingerProvider-DR1iws1P.cjs.map → HostingerProvider-OdQmKXE3.cjs.map} +1 -1
- package/dist/{LegacyStateStore-ClvUC8qy.cjs → LegacyStateStore-C2S8TGwx.cjs} +3 -3
- package/dist/{LegacyStateStore-ClvUC8qy.cjs.map → LegacyStateStore-C2S8TGwx.cjs.map} +1 -1
- package/dist/{LegacyStateStore-DGALQGZu.mjs → LegacyStateStore-DgXmuG87.mjs} +3 -3
- package/dist/{LegacyStateStore-DGALQGZu.mjs.map → LegacyStateStore-DgXmuG87.mjs.map} +1 -1
- package/dist/{LocalStateStore-43kSYNpw.cjs → LocalStateStore-CaJw9H-3.cjs} +2 -2
- package/dist/{LocalStateStore-43kSYNpw.cjs.map → LocalStateStore-CaJw9H-3.cjs.map} +1 -1
- package/dist/{LocalStateStore-CBYUzVR5.mjs → LocalStateStore-ojY_BiMK.mjs} +2 -2
- package/dist/{LocalStateStore-CBYUzVR5.mjs.map → LocalStateStore-ojY_BiMK.mjs.map} +1 -1
- package/dist/{OpenApiTsGenerator-CsrukCE-.mjs → OpenApiTsGenerator-W_HRXH1X.mjs} +2 -2
- package/dist/{OpenApiTsGenerator-CsrukCE-.mjs.map → OpenApiTsGenerator-W_HRXH1X.mjs.map} +1 -1
- package/dist/{OpenApiTsGenerator-FNhY7Ssz.cjs → OpenApiTsGenerator-cQgtxWQo.cjs} +2 -2
- package/dist/{OpenApiTsGenerator-FNhY7Ssz.cjs.map → OpenApiTsGenerator-cQgtxWQo.cjs.map} +1 -1
- package/dist/{S3StateStore-DLHjDYYP.mjs → S3StateStore-BSPkxhrx.mjs} +2 -2
- package/dist/{S3StateStore-DLHjDYYP.mjs.map → S3StateStore-BSPkxhrx.mjs.map} +1 -1
- package/dist/{S3StateStore-ahE7hSNK.cjs → S3StateStore-BqugXIcb.cjs} +2 -2
- package/dist/{S3StateStore-ahE7hSNK.cjs.map → S3StateStore-BqugXIcb.cjs.map} +1 -1
- package/dist/{SSMStateStore-qIzIuazA.cjs → SSMStateStore-B0OsuT_k.cjs} +2 -2
- package/dist/{SSMStateStore-qIzIuazA.cjs.map → SSMStateStore-B0OsuT_k.cjs.map} +1 -1
- package/dist/{SSMStateStore-j5RuMkch.mjs → SSMStateStore-DM85OiH0.mjs} +2 -2
- package/dist/{SSMStateStore-j5RuMkch.mjs.map → SSMStateStore-DM85OiH0.mjs.map} +1 -1
- package/dist/{SubscriberGenerator-C8CPxrCK.cjs → SubscriberGenerator-BDUp7OeY.cjs} +237 -54
- package/dist/SubscriberGenerator-BDUp7OeY.cjs.map +1 -0
- package/dist/{SubscriberGenerator-y3pncae_.mjs → SubscriberGenerator-ocQ2yj0K.mjs} +227 -56
- package/dist/SubscriberGenerator-ocQ2yj0K.mjs.map +1 -0
- package/dist/{add-Bm-C56lH.mjs → add-C7ZBXIzM.mjs} +6 -6
- package/dist/{add-Bm-C56lH.mjs.map → add-C7ZBXIzM.mjs.map} +1 -1
- package/dist/{add-C2CvXrYw.cjs → add-CrxvmSpq.cjs} +7 -7
- package/dist/{add-C2CvXrYw.cjs.map → add-CrxvmSpq.cjs.map} +1 -1
- package/dist/{bundler-Dk7tAvAX.cjs → bundler-DVUXW95p.cjs} +5 -5
- package/dist/{bundler-Dk7tAvAX.cjs.map → bundler-DVUXW95p.cjs.map} +1 -1
- package/dist/{bundler-DnkFt4mn.mjs → bundler-DpZ9uBGN.mjs} +5 -5
- package/dist/{bundler-DnkFt4mn.mjs.map → bundler-DpZ9uBGN.mjs.map} +1 -1
- package/dist/{cli-Di464j1O.cjs → cli-BMRhE6cZ.cjs} +4 -4
- package/dist/{cli-Di464j1O.cjs.map → cli-BMRhE6cZ.cjs.map} +1 -1
- package/dist/{cli-CVeHFXNE.mjs → cli-BWfYyy1k.mjs} +4 -4
- package/dist/{cli-CVeHFXNE.mjs.map → cli-BWfYyy1k.mjs.map} +1 -1
- package/dist/{compose-cHkF---f.mjs → compose-BFmdLig3.mjs} +525 -55
- package/dist/compose-BFmdLig3.mjs.map +1 -0
- package/dist/{compose-ChVvb3xM.cjs → compose-Bqcy0lqo.cjs} +554 -84
- package/dist/compose-Bqcy0lqo.cjs.map +1 -0
- package/dist/{config-DrOX29sT.d.mts → config-CdytFgSR.d.cts} +3 -4
- package/dist/config-CdytFgSR.d.cts.map +1 -0
- package/dist/{config-B3iz9BtK.d.cts → config-_y1FGBWR.d.mts} +3 -4
- package/dist/config-_y1FGBWR.d.mts.map +1 -0
- package/dist/config.cjs +2 -2
- package/dist/config.d.cts +3 -3
- package/dist/config.d.mts +3 -3
- package/dist/config.mjs +2 -2
- package/dist/{credentialSchemas-B67WJOOm.cjs → credentialSchemas-DFxvGEwa.cjs} +2 -2
- package/dist/{credentialSchemas-B67WJOOm.cjs.map → credentialSchemas-DFxvGEwa.cjs.map} +1 -1
- package/dist/{credentialSchemas-DDOLRCLF.mjs → credentialSchemas-DaMpt-Dx.mjs} +2 -2
- package/dist/{credentialSchemas-DDOLRCLF.mjs.map → credentialSchemas-DaMpt-Dx.mjs.map} +1 -1
- package/dist/{credentials-Zqr3QQYB.cjs → credentials-D7XJypNd.cjs} +173 -3
- package/dist/credentials-D7XJypNd.cjs.map +1 -0
- package/dist/{credentials-B5N5yKk6.mjs → credentials-DpfnUvIR.mjs} +132 -4
- package/dist/credentials-DpfnUvIR.mjs.map +1 -0
- package/dist/deploy/public.cjs +16 -17
- package/dist/deploy/public.cjs.map +1 -1
- package/dist/deploy/public.d.cts +3 -5
- package/dist/deploy/public.d.cts.map +1 -1
- package/dist/deploy/public.d.mts +3 -5
- package/dist/deploy/public.d.mts.map +1 -1
- package/dist/deploy/public.mjs +11 -12
- package/dist/deploy/public.mjs.map +1 -1
- package/dist/{deploy-DO4djCHb.mjs → deploy-D8pUA9Y0.mjs} +1240 -130
- package/dist/deploy-D8pUA9Y0.mjs.map +1 -0
- package/dist/{deploy-BffR_uEN.cjs → deploy-DVrNjX1X.cjs} +1261 -169
- package/dist/deploy-DVrNjX1X.cjs.map +1 -0
- package/dist/{discover-C5roJuc9.mjs → discover-CzGM5P3p.mjs} +136 -14
- package/dist/discover-CzGM5P3p.mjs.map +1 -0
- package/dist/{discover-DozUKs8f.cjs → discover-DL6h-J97.cjs} +158 -12
- package/dist/discover-DL6h-J97.cjs.map +1 -0
- package/dist/{dokploy-BcZm93Cc.mjs → dokploy-CzDGNB6A.mjs} +2 -2
- package/dist/{dokploy-BcZm93Cc.mjs.map → dokploy-CzDGNB6A.mjs.map} +1 -1
- package/dist/{dokploy-CsdvfH3t.cjs → dokploy-TtK6Zyl3.cjs} +2 -2
- package/dist/{dokploy-CsdvfH3t.cjs.map → dokploy-TtK6Zyl3.cjs.map} +1 -1
- package/dist/{drivers-BjGDYfnC.mjs → drivers-C-VqMtld.mjs} +2 -2
- package/dist/{drivers-BjGDYfnC.mjs.map → drivers-C-VqMtld.mjs.map} +1 -1
- package/dist/{drivers-DbjiYpW6.cjs → drivers-L29WvE95.cjs} +2 -2
- package/dist/{drivers-DbjiYpW6.cjs.map → drivers-L29WvE95.cjs.map} +1 -1
- package/dist/{eas-CN_cUXth.cjs → eas-D3clzkni.cjs} +2 -2
- package/dist/{eas-CN_cUXth.cjs.map → eas-D3clzkni.cjs.map} +1 -1
- package/dist/{eas-CBShP20v.mjs → eas-DaobzDks.mjs} +2 -2
- package/dist/{eas-CBShP20v.mjs.map → eas-DaobzDks.mjs.map} +1 -1
- package/dist/{emit-Dmc1nvdS.cjs → emit-CYloptRS.cjs} +34 -1
- package/dist/emit-CYloptRS.cjs.map +1 -0
- package/dist/{emit-C1e8qD08.mjs → emit-CiJGdy_f.mjs} +34 -1
- package/dist/emit-CiJGdy_f.mjs.map +1 -0
- package/dist/{encryption-CPDM49Hi.cjs → encryption-DBkIFiR2.cjs} +3 -3
- package/dist/{encryption-CPDM49Hi.cjs.map → encryption-DBkIFiR2.cjs.map} +1 -1
- package/dist/{encryption-CITa6dXL.mjs → encryption-DV31gEY8.mjs} +3 -3
- package/dist/{encryption-CITa6dXL.mjs.map → encryption-DV31gEY8.mjs.map} +1 -1
- package/dist/{engine-CTnHmHjz.mjs → engine-CFOR9h6X.mjs} +140 -16
- package/dist/engine-CFOR9h6X.mjs.map +1 -0
- package/dist/{engine-D-jfMVj-.cjs → engine-CuZUGbEh.cjs} +143 -19
- package/dist/engine-CuZUGbEh.cjs.map +1 -0
- package/dist/{github-D56PSK39.cjs → github-CsvtWSBW.cjs} +115 -47
- package/dist/github-CsvtWSBW.cjs.map +1 -0
- package/dist/{github-9RlA7EjN.mjs → github-Nep8pI4_.mjs} +113 -45
- package/dist/github-Nep8pI4_.mjs.map +1 -0
- package/dist/{googlePlay-SX_jQeJf.cjs → googlePlay-C-UfBXJU.cjs} +5 -5
- package/dist/{googlePlay-SX_jQeJf.cjs.map → googlePlay-C-UfBXJU.cjs.map} +1 -1
- package/dist/{googlePlay-COi5LXON.mjs → googlePlay-lF7Atm_N.mjs} +4 -4
- package/dist/{googlePlay-COi5LXON.mjs.map → googlePlay-lF7Atm_N.mjs.map} +1 -1
- package/dist/{index-DcyRA5Rf.d.mts → index-DrkV7N5w.d.mts} +2 -2
- package/dist/{index-DcyRA5Rf.d.mts.map → index-DrkV7N5w.d.mts.map} +1 -1
- package/dist/{index-DjLSMgAL.d.cts → index-yaXZ9m-J.d.cts} +2 -2
- package/dist/{index-DjLSMgAL.d.cts.map → index-yaXZ9m-J.d.cts.map} +1 -1
- package/dist/index.cjs +166 -85
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +149 -68
- package/dist/index.mjs.map +1 -1
- package/dist/{local-BwC2v2Y8.mjs → local-B7Zv1NoA.mjs} +2 -2
- package/dist/{local-BwC2v2Y8.mjs.map → local-B7Zv1NoA.mjs.map} +1 -1
- package/dist/{local-DNhnkugT.cjs → local-OPC9BCZt.cjs} +3 -3
- package/dist/{local-DNhnkugT.cjs.map → local-OPC9BCZt.cjs.map} +1 -1
- package/dist/{migrate-CrtxXeGY.cjs → migrate-Dp3GhFnf.cjs} +4 -4
- package/dist/{migrate-CrtxXeGY.cjs.map → migrate-Dp3GhFnf.cjs.map} +1 -1
- package/dist/{migrate-0J6gsfrQ.mjs → migrate-NfbPVhi9.mjs} +4 -4
- package/dist/{migrate-0J6gsfrQ.mjs.map → migrate-NfbPVhi9.mjs.map} +1 -1
- package/dist/{migrate-DJa_Dlsl.mjs → migrate-_sBX0pve.mjs} +3 -3
- package/dist/{migrate-DJa_Dlsl.mjs.map → migrate-_sBX0pve.mjs.map} +1 -1
- package/dist/{migrate-BCz1lkh_.cjs → migrate-hlNKNWmg.cjs} +3 -3
- package/dist/{migrate-BCz1lkh_.cjs.map → migrate-hlNKNWmg.cjs.map} +1 -1
- package/dist/openapi.cjs +3 -3
- package/dist/openapi.d.cts +1 -1
- package/dist/openapi.d.mts +1 -1
- package/dist/openapi.mjs +3 -3
- package/dist/{publish-CuQlOW5p.mjs → publish-BuUVaZVy.mjs} +2 -2
- package/dist/{publish-CuQlOW5p.mjs.map → publish-BuUVaZVy.mjs.map} +1 -1
- package/dist/{publish-qSpnvmdX.cjs → publish-ZOo4xEuZ.cjs} +2 -2
- package/dist/{publish-qSpnvmdX.cjs.map → publish-ZOo4xEuZ.cjs.map} +1 -1
- package/dist/{ready-BOfogqzr.mjs → ready-Ca883OvY.mjs} +6 -6
- package/dist/{ready-BOfogqzr.mjs.map → ready-Ca883OvY.mjs.map} +1 -1
- package/dist/{ready-BFFP838V.cjs → ready-qnqp6JAY.cjs} +6 -6
- package/dist/{ready-BFFP838V.cjs.map → ready-qnqp6JAY.cjs.map} +1 -1
- package/dist/reconcile/public.cjs +2 -2
- package/dist/reconcile/public.d.cts +1 -2
- package/dist/reconcile/public.d.cts.map +1 -1
- package/dist/reconcile/public.d.mts +1 -2
- package/dist/reconcile/public.d.mts.map +1 -1
- package/dist/reconcile/public.mjs +2 -2
- package/dist/{resolve-j8hUcbGz.mjs → resolve-BCU9pYDx.mjs} +4 -4
- package/dist/{resolve-j8hUcbGz.mjs.map → resolve-BCU9pYDx.mjs.map} +1 -1
- package/dist/{resolve-AsfohXd1.cjs → resolve-C9QqOl5t.cjs} +4 -4
- package/dist/{resolve-AsfohXd1.cjs.map → resolve-C9QqOl5t.cjs.map} +1 -1
- package/dist/{rollback-Bxq_URgL.cjs → rollback-CTWdDBgB.cjs} +7 -7
- package/dist/{rollback-Bxq_URgL.cjs.map → rollback-CTWdDBgB.cjs.map} +1 -1
- package/dist/{rollback-JgbAY43C.mjs → rollback-CWyyTQ0B.mjs} +7 -7
- package/dist/{rollback-JgbAY43C.mjs.map → rollback-CWyyTQ0B.mjs.map} +1 -1
- package/dist/{runtime-DIZTeGJV.d.cts → runtime-DCkfI2E7.d.mts} +2 -2
- package/dist/{runtime-DIZTeGJV.d.cts.map → runtime-DCkfI2E7.d.mts.map} +1 -1
- package/dist/{runtime-WMLJY-db.d.mts → runtime-Ie4znWIB.d.cts} +2 -2
- package/dist/{runtime-WMLJY-db.d.mts.map → runtime-Ie4znWIB.d.cts.map} +1 -1
- package/dist/{s3-B4FTQQkV.mjs → s3-BZzSnker.mjs} +2 -2
- package/dist/{s3-B4FTQQkV.mjs.map → s3-BZzSnker.mjs.map} +1 -1
- package/dist/{s3-DwKLXwUT.cjs → s3-pqgmCTOq.cjs} +2 -2
- package/dist/{s3-DwKLXwUT.cjs.map → s3-pqgmCTOq.cjs.map} +1 -1
- package/dist/sandbox/credentials-worker.cjs +2 -2
- package/dist/sandbox/credentials-worker.mjs +2 -2
- package/dist/sandbox/discover-worker.cjs +1 -1
- package/dist/sandbox/discover-worker.mjs +1 -1
- package/dist/sandbox/migrate-worker.cjs +1 -1
- package/dist/sandbox/migrate-worker.mjs +1 -1
- package/dist/{seeds-Dv78P9LS.mjs → seeds-CK7w79VC.mjs} +848 -61
- package/dist/seeds-CK7w79VC.mjs.map +1 -0
- package/dist/{seeds-BWC-qjSI.cjs → seeds-CPEcmoPn.cjs} +953 -64
- package/dist/seeds-CPEcmoPn.cjs.map +1 -0
- package/dist/{serverSetup-DB6sKwAy.mjs → serverSetup-BAPNuNEX.mjs} +6 -6
- package/dist/{serverSetup-DB6sKwAy.mjs.map → serverSetup-BAPNuNEX.mjs.map} +1 -1
- package/dist/{serverSetup-CtV80a5M.cjs → serverSetup-C6t6Vtt7.cjs} +6 -6
- package/dist/{serverSetup-CtV80a5M.cjs.map → serverSetup-C6t6Vtt7.cjs.map} +1 -1
- package/dist/{sniffer-ctpwBBkT.cjs → sniffer-DdX_WN_-.cjs} +9 -9
- package/dist/{sniffer-ctpwBBkT.cjs.map → sniffer-DdX_WN_-.cjs.map} +1 -1
- package/dist/{sniffer-qDFUQmWP.mjs → sniffer-g9v7cqDN.mjs} +9 -9
- package/dist/{sniffer-qDFUQmWP.mjs.map → sniffer-g9v7cqDN.mjs.map} +1 -1
- package/dist/{dns-Bmnuttja.cjs → snsSubscriptions-5Cqww8S7.cjs} +351 -3
- package/dist/snsSubscriptions-5Cqww8S7.cjs.map +1 -0
- package/dist/{dns-DFvO5a53.mjs → snsSubscriptions-BmYeeTH8.mjs} +280 -4
- package/dist/snsSubscriptions-BmYeeTH8.mjs.map +1 -0
- package/dist/{sst-C1pxbFEd.mjs → sst-CTqZZl1_.mjs} +3 -4
- package/dist/{sst-C1pxbFEd.mjs.map → sst-CTqZZl1_.mjs.map} +1 -1
- package/dist/{sst-B-Uz4cvm.cjs → sst-lEkBwzGc.cjs} +4 -5
- package/dist/{sst-B-Uz4cvm.cjs.map → sst-lEkBwzGc.cjs.map} +1 -1
- package/dist/{stage-C7lf36az.mjs → stage-C-I2dVw1.mjs} +6 -6
- package/dist/{stage-C7lf36az.mjs.map → stage-C-I2dVw1.mjs.map} +1 -1
- package/dist/{stage-DzALYUwY.cjs → stage-CUE5pMmw.cjs} +7 -7
- package/dist/{stage-DzALYUwY.cjs.map → stage-CUE5pMmw.cjs.map} +1 -1
- package/dist/{stages-B7mCWI_R.cjs → stages-3yUZaZeA.cjs} +2 -2
- package/dist/{stages-B7mCWI_R.cjs.map → stages-3yUZaZeA.cjs.map} +1 -1
- package/dist/{stages-DSltTW2d.mjs → stages-DTTv0NZW.mjs} +2 -2
- package/dist/{stages-DSltTW2d.mjs.map → stages-DTTv0NZW.mjs.map} +1 -1
- package/dist/target/public.cjs +1 -1
- package/dist/target/public.d.cts +2 -2
- package/dist/target/public.d.mts +2 -2
- package/dist/target/public.mjs +1 -1
- package/dist/{terminal-DJlqcW_U.cjs → terminal-PKhPAajN.cjs} +3 -3
- package/dist/{terminal-DJlqcW_U.cjs.map → terminal-PKhPAajN.cjs.map} +1 -1
- package/dist/{terminal-CW_dWeTe.mjs → terminal-bqY33lpN.mjs} +3 -3
- package/dist/{terminal-CW_dWeTe.mjs.map → terminal-bqY33lpN.mjs.map} +1 -1
- package/dist/{types-Q2SC_ly2.d.cts → types-Bmjs4jZV.d.mts} +248 -4
- package/dist/types-Bmjs4jZV.d.mts.map +1 -0
- package/dist/{types-C8gXOLWL.d.mts → types-D0IsQxES.d.cts} +249 -3
- package/dist/types-D0IsQxES.d.cts.map +1 -0
- package/dist/vitest.cjs +1 -1
- package/dist/vitest.mjs +1 -1
- package/dist/workspace/index.cjs +1 -1
- package/dist/workspace/index.d.cts +2 -2
- package/dist/workspace/index.d.mts +2 -2
- package/dist/workspace/index.mjs +1 -1
- package/dist/{workspace-DUnnVjqC.mjs → workspace-DBHF9Gba.mjs} +5 -3
- package/dist/workspace-DBHF9Gba.mjs.map +1 -0
- package/dist/{workspace-C82GLPnf.cjs → workspace-Du4Afb03.cjs} +5 -3
- package/dist/workspace-Du4Afb03.cjs.map +1 -0
- package/package.json +17 -15
- package/dist/SubscriberGenerator-C8CPxrCK.cjs.map +0 -1
- package/dist/SubscriberGenerator-y3pncae_.mjs.map +0 -1
- package/dist/compose-ChVvb3xM.cjs.map +0 -1
- package/dist/compose-cHkF---f.mjs.map +0 -1
- package/dist/config-B3iz9BtK.d.cts.map +0 -1
- package/dist/config-DrOX29sT.d.mts.map +0 -1
- package/dist/credentials-B5N5yKk6.mjs.map +0 -1
- package/dist/credentials-Zqr3QQYB.cjs.map +0 -1
- package/dist/deploy-BffR_uEN.cjs.map +0 -1
- package/dist/deploy-DO4djCHb.mjs.map +0 -1
- package/dist/discover-C08meHhu.d.mts +0 -125
- package/dist/discover-C08meHhu.d.mts.map +0 -1
- package/dist/discover-C5roJuc9.mjs.map +0 -1
- package/dist/discover-DipvfWpK.d.cts +0 -125
- package/dist/discover-DipvfWpK.d.cts.map +0 -1
- package/dist/discover-DozUKs8f.cjs.map +0 -1
- package/dist/dns-Bmnuttja.cjs.map +0 -1
- package/dist/dns-DFvO5a53.mjs.map +0 -1
- package/dist/emit-C1e8qD08.mjs.map +0 -1
- package/dist/emit-Dmc1nvdS.cjs.map +0 -1
- package/dist/engine-CTnHmHjz.mjs.map +0 -1
- package/dist/engine-D-jfMVj-.cjs.map +0 -1
- package/dist/github-9RlA7EjN.mjs.map +0 -1
- package/dist/github-D56PSK39.cjs.map +0 -1
- package/dist/run-BJLIerMM.mjs +0 -133
- package/dist/run-BJLIerMM.mjs.map +0 -1
- package/dist/run-fVZ-rLnc.cjs +0 -174
- package/dist/run-fVZ-rLnc.cjs.map +0 -1
- package/dist/sandbox-Co1y2CmF.d.mts +0 -104
- package/dist/sandbox-Co1y2CmF.d.mts.map +0 -1
- package/dist/sandbox-DvNqr6od.d.cts +0 -104
- package/dist/sandbox-DvNqr6od.d.cts.map +0 -1
- package/dist/seeds-BWC-qjSI.cjs.map +0 -1
- package/dist/seeds-Dv78P9LS.mjs.map +0 -1
- package/dist/types-C8gXOLWL.d.mts.map +0 -1
- package/dist/types-Q2SC_ly2.d.cts.map +0 -1
- package/dist/workspace-C82GLPnf.cjs.map +0 -1
- package/dist/workspace-DUnnVjqC.mjs.map +0 -1
|
@@ -1,99 +1,26 @@
|
|
|
1
1
|
const require_rolldown_runtime = require("./rolldown-runtime-C0BPl7ul.cjs");
|
|
2
2
|
const require_errors = require("./errors-DMC4Umsb.cjs");
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const require_dns = require("./dns-Bmnuttja.cjs");
|
|
3
|
+
const require_credentials = require("./credentials-D7XJypNd.cjs");
|
|
4
|
+
const require_workspace = require("./workspace-Du4Afb03.cjs");
|
|
5
|
+
const require_snsSubscriptions = require("./snsSubscriptions-5Cqww8S7.cjs");
|
|
7
6
|
const require_define = require("./define-crmMcXFS.cjs");
|
|
8
|
-
const require_seeds = require("./seeds-
|
|
7
|
+
const require_seeds = require("./seeds-CPEcmoPn.cjs");
|
|
9
8
|
const require_derive = require("./derive-DAbo49sm.cjs");
|
|
9
|
+
const require_SubscriberGenerator = require("./SubscriberGenerator-BDUp7OeY.cjs");
|
|
10
10
|
const require_identity = require("./identity-CbLfgQNT.cjs");
|
|
11
|
-
const require_discover = require("./discover-
|
|
11
|
+
const require_discover = require("./discover-DL6h-J97.cjs");
|
|
12
12
|
const require_sandbox = require("./sandbox-CHv3OYXg.cjs");
|
|
13
13
|
const require_config = require("./config.cjs");
|
|
14
|
-
const require_resolve = require("./resolve-
|
|
15
|
-
const require_local = require("./local-
|
|
16
|
-
const require_sniffer = require("./sniffer-
|
|
14
|
+
const require_resolve = require("./resolve-C9QqOl5t.cjs");
|
|
15
|
+
const require_local = require("./local-OPC9BCZt.cjs");
|
|
16
|
+
const require_sniffer = require("./sniffer-DdX_WN_-.cjs");
|
|
17
17
|
const require_projectBucket = require("./projectBucket-BBbQ1lHF.cjs");
|
|
18
|
-
const require_credentialSchemas = require("./credentialSchemas-
|
|
18
|
+
const require_credentialSchemas = require("./credentialSchemas-DFxvGEwa.cjs");
|
|
19
19
|
let node_path = require("node:path");
|
|
20
20
|
let node_crypto = require("node:crypto");
|
|
21
21
|
let _geekmidas_manifest = require("@geekmidas/manifest");
|
|
22
22
|
let _geekmidas_storage_s3_url = require("@geekmidas/storage/s3-url");
|
|
23
23
|
_geekmidas_storage_s3_url = require_rolldown_runtime.__toESM(_geekmidas_storage_s3_url, 1);
|
|
24
|
-
//#region src/providers/aws.ts
|
|
25
|
-
/**
|
|
26
|
-
* The AWS account a provider provisions in — the stage's, found the way the
|
|
27
|
-
* SSM and Secrets Manager stores find it.
|
|
28
|
-
*
|
|
29
|
-
* 1. `--profile`, resolved on its own: exported keys left over from another
|
|
30
|
-
* account never win over a profile someone named.
|
|
31
|
-
* 2. `AWS_PROFILE`, else `AWS_ACCESS_KEY_ID`/`AWS_SECRET_ACCESS_KEY` — the
|
|
32
|
-
* deploy's own `aws` credential kind (`storedCredentials`).
|
|
33
|
-
* 3. The SDK's default chain: `~/.aws` default profile, SSO, a role. In CI,
|
|
34
|
-
* whatever `aws-actions/configure-aws-credentials` exported.
|
|
35
|
-
*
|
|
36
|
-
* None of them: the provider has no provisioning credentials, and the stage
|
|
37
|
-
* is treated as `external`.
|
|
38
|
-
*/
|
|
39
|
-
async function awsProvisioningCredentials(lookup) {
|
|
40
|
-
const region = lookup.env.AWS_REGION ?? lookup.env.AWS_DEFAULT_REGION;
|
|
41
|
-
const where = region ? { region } : {};
|
|
42
|
-
if (lookup.profile) return {
|
|
43
|
-
profile: lookup.profile,
|
|
44
|
-
...where
|
|
45
|
-
};
|
|
46
|
-
const stored = await require_credentials.storedCredentials({
|
|
47
|
-
env: lookup.env,
|
|
48
|
-
...lookup.home ? { home: lookup.home } : {}
|
|
49
|
-
}).get({
|
|
50
|
-
kind: "aws",
|
|
51
|
-
stage: lookup.stage
|
|
52
|
-
});
|
|
53
|
-
if (stored) return stored;
|
|
54
|
-
try {
|
|
55
|
-
const { fromNodeProviderChain } = await import("@aws-sdk/credential-providers");
|
|
56
|
-
await fromNodeProviderChain({
|
|
57
|
-
timeout: 1e3,
|
|
58
|
-
maxRetries: 0
|
|
59
|
-
})();
|
|
60
|
-
return {
|
|
61
|
-
chain: true,
|
|
62
|
-
...where
|
|
63
|
-
};
|
|
64
|
-
} catch {
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* An S3-compatible endpoint the environment points the SDK at — the AWS
|
|
70
|
-
* emulator in tests. The SDK reads it itself; it is read here too, because
|
|
71
|
-
* an emulator takes path-style requests, and a key written for one has to
|
|
72
|
-
* say where it is.
|
|
73
|
-
*/
|
|
74
|
-
function awsEndpoint(env, service) {
|
|
75
|
-
return env[`AWS_ENDPOINT_URL_${service}`] ?? env.AWS_ENDPOINT_URL;
|
|
76
|
-
}
|
|
77
|
-
/** An SDK client's config for a provisioning credential. */
|
|
78
|
-
async function awsClientConfig(credential, region) {
|
|
79
|
-
if ("profile" in credential) {
|
|
80
|
-
const { fromIni } = await import("@aws-sdk/credential-providers");
|
|
81
|
-
return {
|
|
82
|
-
region,
|
|
83
|
-
credentials: fromIni({ profile: credential.profile })
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
if ("accessKeyId" in credential) return {
|
|
87
|
-
region,
|
|
88
|
-
credentials: {
|
|
89
|
-
accessKeyId: credential.accessKeyId,
|
|
90
|
-
secretAccessKey: credential.secretAccessKey,
|
|
91
|
-
...credential.sessionToken ? { sessionToken: credential.sessionToken } : {}
|
|
92
|
-
}
|
|
93
|
-
};
|
|
94
|
-
return { region };
|
|
95
|
-
}
|
|
96
|
-
//#endregion
|
|
97
24
|
//#region src/target/secrets.ts
|
|
98
25
|
/**
|
|
99
26
|
* A stage's secrets as a target sees them: read and written through the
|
|
@@ -249,28 +176,37 @@ const bucketArn = (bucket) => `arn:aws:s3:::${bucket}`;
|
|
|
249
176
|
function userPolicy(bucket, options = {}) {
|
|
250
177
|
return {
|
|
251
178
|
Version: "2012-10-17",
|
|
252
|
-
Statement: [
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
179
|
+
Statement: [
|
|
180
|
+
{
|
|
181
|
+
Sid: "GkmObjects",
|
|
182
|
+
Effect: "Allow",
|
|
183
|
+
Action: [
|
|
184
|
+
"s3:GetObject",
|
|
185
|
+
"s3:PutObject",
|
|
186
|
+
"s3:DeleteObject",
|
|
187
|
+
"s3:AbortMultipartUpload",
|
|
188
|
+
"s3:ListMultipartUploadParts",
|
|
189
|
+
...options.versioning ? ["s3:GetObjectVersion"] : []
|
|
190
|
+
],
|
|
191
|
+
Resource: `${bucketArn(bucket)}/*`
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
Sid: "GkmBucket",
|
|
195
|
+
Effect: "Allow",
|
|
196
|
+
Action: [
|
|
197
|
+
"s3:ListBucket",
|
|
198
|
+
"s3:GetBucketLocation",
|
|
199
|
+
"s3:ListBucketMultipartUploads"
|
|
200
|
+
],
|
|
201
|
+
Resource: bucketArn(bucket)
|
|
202
|
+
},
|
|
203
|
+
...options.eventsTopicArn ? [{
|
|
204
|
+
Sid: "GkmEvents",
|
|
205
|
+
Effect: "Allow",
|
|
206
|
+
Action: "sns:ConfirmSubscription",
|
|
207
|
+
Resource: options.eventsTopicArn
|
|
208
|
+
}] : []
|
|
209
|
+
]
|
|
274
210
|
};
|
|
275
211
|
}
|
|
276
212
|
/**
|
|
@@ -285,10 +221,13 @@ function openResource(bucket, pattern) {
|
|
|
285
221
|
}
|
|
286
222
|
/**
|
|
287
223
|
* The bucket's own policy: every request refused unless it came over TLS,
|
|
288
|
-
* and — only when a file server opens paths on it —
|
|
289
|
-
*
|
|
224
|
+
* and — only when a file server opens paths on it — `s3:GetObject` on
|
|
225
|
+
* exactly those prefixes: to anyone, or, when a CDN serves them, to
|
|
226
|
+
* CloudFront alone, for exactly the stage's distributions. Behind a CDN the
|
|
227
|
+
* bucket is never public: a key outside the open prefixes is refused to
|
|
228
|
+
* CloudFront too, so the edge answers it 403.
|
|
290
229
|
*/
|
|
291
|
-
function bucketPolicy(bucket, open = []) {
|
|
230
|
+
function bucketPolicy(bucket, open = [], options = {}) {
|
|
292
231
|
const statements = [{
|
|
293
232
|
Sid: "GkmDenyInsecureTransport",
|
|
294
233
|
Effect: "Deny",
|
|
@@ -297,12 +236,21 @@ function bucketPolicy(bucket, open = []) {
|
|
|
297
236
|
Resource: [bucketArn(bucket), `${bucketArn(bucket)}/*`],
|
|
298
237
|
Condition: { Bool: { "aws:SecureTransport": "false" } }
|
|
299
238
|
}];
|
|
300
|
-
|
|
239
|
+
const resources = [...new Set(open.map((p) => openResource(bucket, p)))].sort();
|
|
240
|
+
if (open.length > 0 && options.cdn) statements.push({
|
|
241
|
+
Sid: "GkmCdnRead",
|
|
242
|
+
Effect: "Allow",
|
|
243
|
+
Principal: { Service: "cloudfront.amazonaws.com" },
|
|
244
|
+
Action: "s3:GetObject",
|
|
245
|
+
Resource: resources,
|
|
246
|
+
Condition: { StringEquals: { "AWS:SourceArn": [...new Set(options.cdn.distributionArns)].sort() } }
|
|
247
|
+
});
|
|
248
|
+
else if (open.length > 0) statements.push({
|
|
301
249
|
Sid: "GkmOpenPaths",
|
|
302
250
|
Effect: "Allow",
|
|
303
251
|
Principal: "*",
|
|
304
252
|
Action: "s3:GetObject",
|
|
305
|
-
Resource:
|
|
253
|
+
Resource: resources
|
|
306
254
|
});
|
|
307
255
|
return {
|
|
308
256
|
Version: "2012-10-17",
|
|
@@ -351,7 +299,7 @@ function canonical(value, key) {
|
|
|
351
299
|
}
|
|
352
300
|
if (value && typeof value === "object") return Object.fromEntries(Object.keys(value).sort().map((k) => {
|
|
353
301
|
const v = value[k];
|
|
354
|
-
return [k, canonical(k === "Action" || k === "Resource" || k === "AWS" ? Array.isArray(v) ? v : [v] : v, k)];
|
|
302
|
+
return [k, canonical(k === "Action" || k === "Resource" || k === "AWS" || k === "AWS:SourceArn" ? Array.isArray(v) ? v : [v] : v, k)];
|
|
355
303
|
}));
|
|
356
304
|
return value;
|
|
357
305
|
}
|
|
@@ -397,7 +345,7 @@ function identityTags(ctx, id) {
|
|
|
397
345
|
];
|
|
398
346
|
}
|
|
399
347
|
/** A read whose "there is none" answer is one of `missing`. */
|
|
400
|
-
async function readOr$
|
|
348
|
+
async function readOr$3(read, missing) {
|
|
401
349
|
try {
|
|
402
350
|
return await read();
|
|
403
351
|
} catch (error) {
|
|
@@ -427,7 +375,7 @@ async function ensureIamUser(ctx, clients, input) {
|
|
|
427
375
|
key: `iam-user:${id}`,
|
|
428
376
|
type: "iam-user"
|
|
429
377
|
};
|
|
430
|
-
const found = await readOr$
|
|
378
|
+
const found = await readOr$3(() => iam.send(new IAM.GetUserCommand({ UserName: user })), ["NoSuchEntity", "NoSuchEntityException"]);
|
|
431
379
|
if (found?.User) {
|
|
432
380
|
const tags = await iam.send(new IAM.ListUserTagsCommand({ UserName: user }));
|
|
433
381
|
const tag = (key) => tags.Tags?.find((t) => t.Key === key)?.Value;
|
|
@@ -447,7 +395,7 @@ async function ensureIamUser(ctx, clients, input) {
|
|
|
447
395
|
await ctx.state.ready(entry, user);
|
|
448
396
|
});
|
|
449
397
|
const exists = Boolean(found?.User) || !ctx.dryRun;
|
|
450
|
-
const current = exists ? await readOr$
|
|
398
|
+
const current = exists ? await readOr$3(() => iam.send(new IAM.GetUserPolicyCommand({
|
|
451
399
|
UserName: user,
|
|
452
400
|
PolicyName: input.policyName
|
|
453
401
|
})), ["NoSuchEntity", "NoSuchEntityException"]) : void 0;
|
|
@@ -692,7 +640,7 @@ var BackupsKeyRefused = class extends require_errors.GkmError {
|
|
|
692
640
|
key;
|
|
693
641
|
said;
|
|
694
642
|
constructor(stage, bucket, key, said) {
|
|
695
|
-
super(`The ${
|
|
643
|
+
super(`The ${require_snsSubscriptions.BACKUPS_URL_KEY} key of '${stage}' could not put s3://${bucket}/${key} (${said}), so its backups would fail the same way. Run the deploy with the stage's AWS credentials to put its user and policy back, or --rotate-keys to issue it a new key.`);
|
|
696
644
|
this.stage = stage;
|
|
697
645
|
this.bucket = bucket;
|
|
698
646
|
this.key = key;
|
|
@@ -721,7 +669,7 @@ function backupsRegion(workspace, stage, credential, env = process.env) {
|
|
|
721
669
|
async function ensureStageBackups(input) {
|
|
722
670
|
const { workspace, manifest, stage, ctx } = input;
|
|
723
671
|
const env = input.env ?? process.env;
|
|
724
|
-
const choice =
|
|
672
|
+
const choice = require_snsSubscriptions.stageBackups(workspace, manifest, stage);
|
|
725
673
|
if (choice.mode === "disabled") {
|
|
726
674
|
ctx.log(`💾 backups: off — deploy.backups.${stage} is false. Nothing is backed up, and nothing already backed up is deleted.`);
|
|
727
675
|
return choice;
|
|
@@ -731,7 +679,7 @@ async function ensureStageBackups(input) {
|
|
|
731
679
|
const clash = Object.entries(manifest).find(([id, d]) => d.kind === "objects" && require_derive.appKey(id) === "backups");
|
|
732
680
|
if (clash) throw new BackupsUserNameTaken(stage, clash[0]);
|
|
733
681
|
}
|
|
734
|
-
const credential = await awsProvisioningCredentials({
|
|
682
|
+
const credential = await require_snsSubscriptions.awsProvisioningCredentials({
|
|
735
683
|
stage,
|
|
736
684
|
env,
|
|
737
685
|
...input.profile ? { profile: input.profile } : {},
|
|
@@ -741,11 +689,11 @@ async function ensureStageBackups(input) {
|
|
|
741
689
|
const region = backupsRegion(workspace, stage, credential, env);
|
|
742
690
|
const clients = await backupsClients(credential, region, env);
|
|
743
691
|
const bucket = require_projectBucket.projectBucketName(ctx.identity.scope, await require_projectBucket.callerAccountId(clients.sts));
|
|
744
|
-
const prefix =
|
|
745
|
-
const expiries =
|
|
692
|
+
const prefix = require_snsSubscriptions.backupsPrefix(workspace, stage);
|
|
693
|
+
const expiries = require_snsSubscriptions.backupExpiries(workspace, manifest);
|
|
746
694
|
ctx.log(`💾 backups: ${choice.backups.describe}, to s3://${bucket}/${prefix}/${ctx.dryRun ? " (dry run)" : ""}`);
|
|
747
695
|
if (await require_projectBucket.findProjectBucket(clients.s3, bucket) === "none") await ctx.change({
|
|
748
|
-
construct:
|
|
696
|
+
construct: require_snsSubscriptions.BACKUPS_SERVICE,
|
|
749
697
|
resource: `bucket ${bucket}`,
|
|
750
698
|
change: `create in ${region}, expiring each stage's backups`
|
|
751
699
|
}, async () => {
|
|
@@ -757,7 +705,7 @@ async function ensureStageBackups(input) {
|
|
|
757
705
|
});
|
|
758
706
|
});
|
|
759
707
|
else if (await require_projectBucket.reconcileProjectBucketLifecycle(clients.s3, bucket, expiries, { dryRun: true })) await ctx.change({
|
|
760
|
-
construct:
|
|
708
|
+
construct: require_snsSubscriptions.BACKUPS_SERVICE,
|
|
761
709
|
resource: `bucket ${bucket}`,
|
|
762
710
|
change: `lifecycle: ${expiries.map((e) => `${e.prefix} after ${e.days} days`).join(", ")}`
|
|
763
711
|
}, async () => {
|
|
@@ -773,7 +721,7 @@ async function ensureStageBackups(input) {
|
|
|
773
721
|
await ensureAccessKey(ctx, clients, {
|
|
774
722
|
id: BACKUPS_ID,
|
|
775
723
|
bucket,
|
|
776
|
-
urlKey:
|
|
724
|
+
urlKey: require_snsSubscriptions.BACKUPS_URL_KEY,
|
|
777
725
|
...user
|
|
778
726
|
});
|
|
779
727
|
return choice;
|
|
@@ -783,8 +731,8 @@ async function backupsClients(credential, region, env) {
|
|
|
783
731
|
const S3 = await import("@aws-sdk/client-s3");
|
|
784
732
|
const IAM = await import("@aws-sdk/client-iam");
|
|
785
733
|
const { STSClient } = await import("@aws-sdk/client-sts");
|
|
786
|
-
const config = await awsClientConfig(credential, region);
|
|
787
|
-
const endpoint = awsEndpoint(env, "S3");
|
|
734
|
+
const config = await require_snsSubscriptions.awsClientConfig(credential, region);
|
|
735
|
+
const endpoint = require_snsSubscriptions.awsEndpoint(env, "S3");
|
|
788
736
|
return {
|
|
789
737
|
IAM,
|
|
790
738
|
iam: new IAM.IAMClient(config),
|
|
@@ -809,8 +757,8 @@ async function backupsClients(credential, region, env) {
|
|
|
809
757
|
*/
|
|
810
758
|
async function verifyStageBackups(input) {
|
|
811
759
|
const { stage } = input;
|
|
812
|
-
if (
|
|
813
|
-
const url = input.secrets[
|
|
760
|
+
if (require_snsSubscriptions.stageBackups(input.workspace, input.manifest, stage).mode !== "on") return false;
|
|
761
|
+
const url = input.secrets[require_snsSubscriptions.BACKUPS_URL_KEY];
|
|
814
762
|
if (!url) return false;
|
|
815
763
|
const address = (await import("@geekmidas/storage/s3-url")).parse(url);
|
|
816
764
|
if (!address.accessKeyId || !address.secretAccessKey) return false;
|
|
@@ -824,7 +772,7 @@ async function verifyStageBackups(input) {
|
|
|
824
772
|
...address.endpoint ? { endpoint: address.endpoint } : {},
|
|
825
773
|
...address.forcePathStyle ? { forcePathStyle: true } : {}
|
|
826
774
|
});
|
|
827
|
-
const Key = `${
|
|
775
|
+
const Key = `${require_snsSubscriptions.backupsPrefix(input.workspace, stage)}/${VERIFY_MARKER}`;
|
|
828
776
|
const put = async () => {
|
|
829
777
|
try {
|
|
830
778
|
await client.send(new S3.PutObjectCommand({
|
|
@@ -861,6 +809,1108 @@ async function verifyStageBackups(input) {
|
|
|
861
809
|
}
|
|
862
810
|
}
|
|
863
811
|
//#endregion
|
|
812
|
+
//#region src/providers/dnsWriter.ts
|
|
813
|
+
/**
|
|
814
|
+
* The DNS records a provider asks for — a certificate's validation record, a
|
|
815
|
+
* CDN's name — written through the stage's own DNS providers.
|
|
816
|
+
*
|
|
817
|
+
* The same machinery the deploy's DNS step uses for the stack's hosts
|
|
818
|
+
* (`settleStackDns`), handed a plan of CNAMEs instead of the server's
|
|
819
|
+
* addresses: a record holding its value is left alone, one with another value
|
|
820
|
+
* is replaced and printed, and what is written is read back. A host under no
|
|
821
|
+
* domain in `dns`, or under a `manual` one, is printed for the person to
|
|
822
|
+
* create and reported back, so the provider can say what it is waiting on.
|
|
823
|
+
*/
|
|
824
|
+
async function writeProviderDns(records, options) {
|
|
825
|
+
const { log } = options;
|
|
826
|
+
const dns = options.workspace.dns ? require_seeds.normalizeDnsConfig(options.workspace.dns) : {};
|
|
827
|
+
const manual = [];
|
|
828
|
+
const domains = /* @__PURE__ */ new Map();
|
|
829
|
+
for (const record of records) {
|
|
830
|
+
const host = record.host.toLowerCase().replace(/\.$/, "");
|
|
831
|
+
const value = record.value.replace(/\.$/, "");
|
|
832
|
+
const domain = require_seeds.findRootDomain(host, dns);
|
|
833
|
+
if (!domain) {
|
|
834
|
+
log(` create at its DNS host: ${host} CNAME ${value}`);
|
|
835
|
+
manual.push(host);
|
|
836
|
+
continue;
|
|
837
|
+
}
|
|
838
|
+
const config = dns[domain];
|
|
839
|
+
const plan = domains.get(domain) ?? {
|
|
840
|
+
domain,
|
|
841
|
+
provider: typeof config.provider === "string" ? config.provider : "custom",
|
|
842
|
+
config,
|
|
843
|
+
records: []
|
|
844
|
+
};
|
|
845
|
+
const planned = {
|
|
846
|
+
host,
|
|
847
|
+
domain,
|
|
848
|
+
name: require_seeds.extractSubdomain(host, domain),
|
|
849
|
+
type: record.type,
|
|
850
|
+
value,
|
|
851
|
+
ttl: require_snsSubscriptions.recordTtl(config)
|
|
852
|
+
};
|
|
853
|
+
plan.records.push(planned);
|
|
854
|
+
domains.set(domain, plan);
|
|
855
|
+
}
|
|
856
|
+
if (domains.size === 0) return { manual };
|
|
857
|
+
const plans = [...domains.values()];
|
|
858
|
+
const tokens = await require_snsSubscriptions.dnsCredentials(options.stage, plans.map(({ domain, config }) => ({
|
|
859
|
+
domain,
|
|
860
|
+
config
|
|
861
|
+
})), options.credentials);
|
|
862
|
+
const applied = await require_snsSubscriptions.settleStackDns({
|
|
863
|
+
stage: options.stage,
|
|
864
|
+
server: { ipv4: "" },
|
|
865
|
+
domains: plans,
|
|
866
|
+
uncovered: []
|
|
867
|
+
}, {
|
|
868
|
+
...options.dryRun ? { dryRun: true } : { confirm: true },
|
|
869
|
+
log,
|
|
870
|
+
credentials: {
|
|
871
|
+
...tokens.godaddy ? { credential: tokens.godaddy } : {},
|
|
872
|
+
log
|
|
873
|
+
},
|
|
874
|
+
...tokens.hostinger ? { hostinger: tokens.hostinger } : {},
|
|
875
|
+
...options.providerFor ? { providerFor: options.providerFor } : {}
|
|
876
|
+
});
|
|
877
|
+
for (const change of applied.changes) {
|
|
878
|
+
if (change.action !== "manual") continue;
|
|
879
|
+
const host = plans.find((p) => p.domain === change.domain)?.records.find((r) => r.name === change.name)?.host;
|
|
880
|
+
if (host) manual.push(host);
|
|
881
|
+
}
|
|
882
|
+
return { manual: [...new Set(manual)].sort() };
|
|
883
|
+
}
|
|
884
|
+
//#endregion
|
|
885
|
+
//#region src/providers/s3/cdn.ts
|
|
886
|
+
/** AWS's managed `CachingOptimized` cache policy: path-keyed, gzip and brotli. */
|
|
887
|
+
const CACHING_OPTIMIZED = "658327ea-f89d-4fab-a63d-7e88639e58f6";
|
|
888
|
+
/** What an immutable path is served with. */
|
|
889
|
+
const IMMUTABLE_CACHE_CONTROL = "public, max-age=31536000, immutable";
|
|
890
|
+
/** The only region CloudFront reads a certificate from. */
|
|
891
|
+
const CDN_REGION = "us-east-1";
|
|
892
|
+
/** The distribution's bucket origin. */
|
|
893
|
+
const ORIGIN_ID = "bucket";
|
|
894
|
+
/** Its processors' origin: the stack's processor unit, at a host of its own. */
|
|
895
|
+
const PROCESSORS_ORIGIN_ID = "processors";
|
|
896
|
+
/**
|
|
897
|
+
* AWS's managed `CachingOptimizedForUncompressedObjects`: keyed on the path
|
|
898
|
+
* alone, and nothing re-compressed — a derivative is an image, already
|
|
899
|
+
* compressed, and its width is in the path, so nothing else may fragment it.
|
|
900
|
+
*/
|
|
901
|
+
const CACHING_OPTIMIZED_UNCOMPRESSED = "b2884449-e4de-46a7-ac36-70bc7f1ddd6d";
|
|
902
|
+
/** A CDN is a name on the stage's domain, and the stage has none. */
|
|
903
|
+
var FileServerCdnNeedsDomain = class extends require_errors.GkmError {
|
|
904
|
+
server;
|
|
905
|
+
stage;
|
|
906
|
+
constructor(server, stage) {
|
|
907
|
+
super(`'${server}' is served through a CDN on '${stage}', which answers on <fileserver>.<domain> — and '${stage}' has no domain. Set domains.${stage} in gkm.config.ts, or set deploy.objects.${stage}.cdn to false to serve it from the bucket's own endpoint.`);
|
|
908
|
+
this.server = server;
|
|
909
|
+
this.stage = stage;
|
|
910
|
+
this.name = "FileServerCdnNeedsDomain";
|
|
911
|
+
}
|
|
912
|
+
};
|
|
913
|
+
/**
|
|
914
|
+
* The certificate is waiting on its validation record: one the person has to
|
|
915
|
+
* create, or one that has not propagated yet. Everything up to it is
|
|
916
|
+
* recorded, so the next deploy carries on from here.
|
|
917
|
+
*/
|
|
918
|
+
var CdnCertificatePending = class extends require_errors.GkmError {
|
|
919
|
+
server;
|
|
920
|
+
host;
|
|
921
|
+
record;
|
|
922
|
+
reason;
|
|
923
|
+
constructor(server, host, record, reason) {
|
|
924
|
+
super((reason === "manual" ? `The certificate for ${host} ('${server}') is validated by a DNS record no DNS provider in gkm.config.ts can write. Create it at the domain's DNS host:
|
|
925
|
+
` : `The certificate for ${host} ('${server}') is not issued yet: ACM has not seen its validation record. It is written; DNS can take a while to answer with it. Check it resolves:
|
|
926
|
+
`) + ` ${record.name} CNAME ${record.value}\nthen deploy again — the certificate and everything before it are recorded, and the deploy carries on from there.`);
|
|
927
|
+
this.server = server;
|
|
928
|
+
this.host = host;
|
|
929
|
+
this.record = record;
|
|
930
|
+
this.reason = reason;
|
|
931
|
+
this.name = "CdnCertificatePending";
|
|
932
|
+
}
|
|
933
|
+
};
|
|
934
|
+
/** ACM gave up on the certificate. */
|
|
935
|
+
var CdnCertificateFailed = class extends require_errors.GkmError {
|
|
936
|
+
server;
|
|
937
|
+
host;
|
|
938
|
+
status;
|
|
939
|
+
reason;
|
|
940
|
+
constructor(server, host, status, reason) {
|
|
941
|
+
super(`ACM did not issue the certificate for ${host} ('${server}'): ${status}` + (reason ? ` (${reason})` : "") + ". A CAA record on the domain that does not allow amazon.com, or a validation record removed before it was checked, are the usual causes. Fix it and deploy again: a new certificate is requested.");
|
|
942
|
+
this.server = server;
|
|
943
|
+
this.host = host;
|
|
944
|
+
this.status = status;
|
|
945
|
+
this.reason = reason;
|
|
946
|
+
this.name = "CdnCertificateFailed";
|
|
947
|
+
}
|
|
948
|
+
};
|
|
949
|
+
/** Where a file server answers: `<fileserver>.<domain>`. */
|
|
950
|
+
function cdnHost(serverId, domain) {
|
|
951
|
+
return `${require_derive.appKey(serverId)}.${domain}`.toLowerCase();
|
|
952
|
+
}
|
|
953
|
+
/**
|
|
954
|
+
* The name the stage's CDN resources go by, within CloudFront's limits:
|
|
955
|
+
* letters, digits, `-` and `_`, and at most 64 characters (an origin access
|
|
956
|
+
* control's). Kept whole where it fits; otherwise shortened with a hash of
|
|
957
|
+
* the whole, so two long names never meet.
|
|
958
|
+
*/
|
|
959
|
+
function cdnName(scope, stage, serverId) {
|
|
960
|
+
const whole = `gkm-${scope}-${stage}-${require_derive.appKey(serverId)}`.replace(/[^A-Za-z0-9_-]/g, "-");
|
|
961
|
+
if (whole.length <= 56) return whole;
|
|
962
|
+
return `${whole.slice(0, 47)}-${hash(whole)}`;
|
|
963
|
+
}
|
|
964
|
+
function hash(value) {
|
|
965
|
+
let h = 2166136261;
|
|
966
|
+
for (let i = 0; i < value.length; i++) {
|
|
967
|
+
h ^= value.charCodeAt(i);
|
|
968
|
+
h = Math.imul(h, 16777619);
|
|
969
|
+
}
|
|
970
|
+
return (h >>> 0).toString(36).padStart(8, "0").slice(0, 8);
|
|
971
|
+
}
|
|
972
|
+
/**
|
|
973
|
+
* An open or immutable glob as a CloudFront path pattern. CloudFront's `*`
|
|
974
|
+
* crosses `/`, so a single star is as wide as a double one — the bucket
|
|
975
|
+
* policy reads it the same way, and the construct's own check is the
|
|
976
|
+
* stricter of the three.
|
|
977
|
+
*/
|
|
978
|
+
function pathPattern(glob) {
|
|
979
|
+
return glob.replace(/^\/+/, "").replace(/\*\*/g, "*");
|
|
980
|
+
}
|
|
981
|
+
/** A CloudFront list: its `Quantity` and, when it has some, its `Items`. */
|
|
982
|
+
function list(items) {
|
|
983
|
+
return items.length > 0 ? {
|
|
984
|
+
Quantity: items.length,
|
|
985
|
+
Items: [...items]
|
|
986
|
+
} : { Quantity: 0 };
|
|
987
|
+
}
|
|
988
|
+
/** One of the stage's response headers policies. */
|
|
989
|
+
function headersPolicyConfig(input) {
|
|
990
|
+
return {
|
|
991
|
+
Name: input.name,
|
|
992
|
+
Comment: input.comment,
|
|
993
|
+
...input.origins.length > 0 ? { CorsConfig: {
|
|
994
|
+
AccessControlAllowOrigins: list([...input.origins].sort()),
|
|
995
|
+
AccessControlAllowHeaders: {
|
|
996
|
+
Quantity: 1,
|
|
997
|
+
Items: ["*"]
|
|
998
|
+
},
|
|
999
|
+
AccessControlAllowMethods: {
|
|
1000
|
+
Quantity: 3,
|
|
1001
|
+
Items: [
|
|
1002
|
+
"GET",
|
|
1003
|
+
"HEAD",
|
|
1004
|
+
"OPTIONS"
|
|
1005
|
+
]
|
|
1006
|
+
},
|
|
1007
|
+
AccessControlAllowCredentials: false,
|
|
1008
|
+
AccessControlMaxAgeSec: 86400,
|
|
1009
|
+
OriginOverride: false
|
|
1010
|
+
} } : {},
|
|
1011
|
+
...input.immutable ? { CustomHeadersConfig: {
|
|
1012
|
+
Quantity: 1,
|
|
1013
|
+
Items: [{
|
|
1014
|
+
Header: "Cache-Control",
|
|
1015
|
+
Value: IMMUTABLE_CACHE_CONTROL,
|
|
1016
|
+
Override: true
|
|
1017
|
+
}]
|
|
1018
|
+
} } : {}
|
|
1019
|
+
};
|
|
1020
|
+
}
|
|
1021
|
+
/** The parts of a distribution's config this provider owns. */
|
|
1022
|
+
function distributionConfig(input) {
|
|
1023
|
+
const behaviour = (policy) => ({
|
|
1024
|
+
TargetOriginId: ORIGIN_ID,
|
|
1025
|
+
ViewerProtocolPolicy: "redirect-to-https",
|
|
1026
|
+
AllowedMethods: {
|
|
1027
|
+
Quantity: 3,
|
|
1028
|
+
Items: [
|
|
1029
|
+
"GET",
|
|
1030
|
+
"HEAD",
|
|
1031
|
+
"OPTIONS"
|
|
1032
|
+
],
|
|
1033
|
+
CachedMethods: {
|
|
1034
|
+
Quantity: 2,
|
|
1035
|
+
Items: ["GET", "HEAD"]
|
|
1036
|
+
}
|
|
1037
|
+
},
|
|
1038
|
+
Compress: true,
|
|
1039
|
+
CachePolicyId: CACHING_OPTIMIZED,
|
|
1040
|
+
...policy ? { ResponseHeadersPolicyId: policy } : {}
|
|
1041
|
+
});
|
|
1042
|
+
const patterns = [...new Set(input.immutable.map(pathPattern))].sort();
|
|
1043
|
+
const processors = input.processors;
|
|
1044
|
+
const processorPatterns = processors ? [...new Set(processors.prefixes.map((p) => `${pathPattern(p)}*`))].sort() : [];
|
|
1045
|
+
return {
|
|
1046
|
+
CallerReference: input.callerReference,
|
|
1047
|
+
Comment: input.comment,
|
|
1048
|
+
Enabled: true,
|
|
1049
|
+
Aliases: {
|
|
1050
|
+
Quantity: 1,
|
|
1051
|
+
Items: [input.host]
|
|
1052
|
+
},
|
|
1053
|
+
Origins: list([{
|
|
1054
|
+
Id: ORIGIN_ID,
|
|
1055
|
+
DomainName: input.bucketDomain,
|
|
1056
|
+
OriginPath: "",
|
|
1057
|
+
S3OriginConfig: { OriginAccessIdentity: "" },
|
|
1058
|
+
OriginAccessControlId: input.originAccessControlId
|
|
1059
|
+
}, ...processors ? [{
|
|
1060
|
+
Id: PROCESSORS_ORIGIN_ID,
|
|
1061
|
+
DomainName: processors.host,
|
|
1062
|
+
OriginPath: "",
|
|
1063
|
+
CustomOriginConfig: {
|
|
1064
|
+
HTTPPort: 80,
|
|
1065
|
+
HTTPSPort: 443,
|
|
1066
|
+
OriginProtocolPolicy: "https-only",
|
|
1067
|
+
OriginSslProtocols: {
|
|
1068
|
+
Quantity: 1,
|
|
1069
|
+
Items: ["TLSv1.2"]
|
|
1070
|
+
},
|
|
1071
|
+
OriginReadTimeout: 30,
|
|
1072
|
+
OriginKeepaliveTimeout: 5
|
|
1073
|
+
},
|
|
1074
|
+
CustomHeaders: {
|
|
1075
|
+
Quantity: 1,
|
|
1076
|
+
Items: [{
|
|
1077
|
+
HeaderName: require_seeds.ORIGIN_VERIFY_HEADER,
|
|
1078
|
+
HeaderValue: processors.secret
|
|
1079
|
+
}]
|
|
1080
|
+
}
|
|
1081
|
+
}] : []]),
|
|
1082
|
+
DefaultCacheBehavior: behaviour(input.corsPolicyId),
|
|
1083
|
+
CacheBehaviors: list([...processorPatterns.map((PathPattern) => ({
|
|
1084
|
+
...behaviour(input.corsPolicyId),
|
|
1085
|
+
TargetOriginId: PROCESSORS_ORIGIN_ID,
|
|
1086
|
+
Compress: false,
|
|
1087
|
+
CachePolicyId: CACHING_OPTIMIZED_UNCOMPRESSED,
|
|
1088
|
+
PathPattern
|
|
1089
|
+
})), ...patterns.map((PathPattern) => ({
|
|
1090
|
+
...behaviour(input.immutablePolicyId),
|
|
1091
|
+
PathPattern
|
|
1092
|
+
}))]),
|
|
1093
|
+
ViewerCertificate: {
|
|
1094
|
+
ACMCertificateArn: input.certificateArn,
|
|
1095
|
+
SSLSupportMethod: "sni-only",
|
|
1096
|
+
MinimumProtocolVersion: "TLSv1.2_2021",
|
|
1097
|
+
CloudFrontDefaultCertificate: false
|
|
1098
|
+
},
|
|
1099
|
+
HttpVersion: "http2and3",
|
|
1100
|
+
IsIPV6Enabled: true,
|
|
1101
|
+
DefaultRootObject: ""
|
|
1102
|
+
};
|
|
1103
|
+
}
|
|
1104
|
+
/**
|
|
1105
|
+
* Whether `current` already holds everything in `wanted`: each key `wanted`
|
|
1106
|
+
* names, recursively, with lists compared as sets. What else `current` holds
|
|
1107
|
+
* — logging, a WAF, geo restrictions set by hand — is left alone, and a
|
|
1108
|
+
* `CallerReference`, fixed at creation, is never compared.
|
|
1109
|
+
*/
|
|
1110
|
+
function holds(current, wanted, key) {
|
|
1111
|
+
if (key === "CallerReference") return true;
|
|
1112
|
+
if (Array.isArray(wanted)) return Array.isArray(current) && current.length === wanted.length && wanted.every((w) => current.some((c) => holds(c, w)));
|
|
1113
|
+
if (wanted && typeof wanted === "object") {
|
|
1114
|
+
if (!current || typeof current !== "object") return false;
|
|
1115
|
+
return Object.entries(wanted).every(([k, v]) => holds(current[k], v, k));
|
|
1116
|
+
}
|
|
1117
|
+
return current === wanted;
|
|
1118
|
+
}
|
|
1119
|
+
/** Header configs a policy may hold that this provider never sets. */
|
|
1120
|
+
const FOREIGN_HEADER_CONFIGS = [
|
|
1121
|
+
"CorsConfig",
|
|
1122
|
+
"CustomHeadersConfig",
|
|
1123
|
+
"SecurityHeadersConfig",
|
|
1124
|
+
"RemoveHeadersConfig",
|
|
1125
|
+
"ServerTimingHeadersConfig"
|
|
1126
|
+
];
|
|
1127
|
+
/** Whether a policy is exactly what is wanted: nothing missing, nothing more. */
|
|
1128
|
+
function sameHeadersPolicy(current, wanted) {
|
|
1129
|
+
if (!current || !holds(current, wanted)) return false;
|
|
1130
|
+
return FOREIGN_HEADER_CONFIGS.every((k) => {
|
|
1131
|
+
if (k in wanted) return true;
|
|
1132
|
+
const extra = current[k];
|
|
1133
|
+
return !extra || extra.Quantity === 0;
|
|
1134
|
+
});
|
|
1135
|
+
}
|
|
1136
|
+
const defaultCdnClients = async (credential) => {
|
|
1137
|
+
const CF = await import("@aws-sdk/client-cloudfront");
|
|
1138
|
+
const ACM = await import("@aws-sdk/client-acm");
|
|
1139
|
+
const config = await require_snsSubscriptions.awsClientConfig(credential, CDN_REGION);
|
|
1140
|
+
const cloudfront = require_snsSubscriptions.awsEndpoint(process.env, "CLOUDFRONT");
|
|
1141
|
+
const acm = require_snsSubscriptions.awsEndpoint(process.env, "ACM");
|
|
1142
|
+
return {
|
|
1143
|
+
CF,
|
|
1144
|
+
ACM,
|
|
1145
|
+
cloudfront: new CF.CloudFrontClient({
|
|
1146
|
+
...config,
|
|
1147
|
+
...cloudfront ? { endpoint: cloudfront } : {}
|
|
1148
|
+
}),
|
|
1149
|
+
acm: new ACM.ACMClient({
|
|
1150
|
+
...config,
|
|
1151
|
+
...acm ? { endpoint: acm } : {}
|
|
1152
|
+
}),
|
|
1153
|
+
wait: {
|
|
1154
|
+
timeoutMs: 9e5,
|
|
1155
|
+
intervalMs: 1e4
|
|
1156
|
+
}
|
|
1157
|
+
};
|
|
1158
|
+
};
|
|
1159
|
+
let makeCdnClients = defaultCdnClients;
|
|
1160
|
+
/** What an AWS SDK error is called, whichever field carries it. */
|
|
1161
|
+
function errorName$2(error) {
|
|
1162
|
+
const e = error;
|
|
1163
|
+
return e?.Code ?? e?.name ?? "";
|
|
1164
|
+
}
|
|
1165
|
+
async function readOr$2(read, missing) {
|
|
1166
|
+
try {
|
|
1167
|
+
return await read();
|
|
1168
|
+
} catch (error) {
|
|
1169
|
+
if (missing.includes(errorName$2(error))) return void 0;
|
|
1170
|
+
throw error;
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
1174
|
+
/** What stands in for an id a dry run would create. */
|
|
1175
|
+
const PLANNED = "(created by this deploy)";
|
|
1176
|
+
/** Each file server over a bucket, behind its own distribution. */
|
|
1177
|
+
async function ensureBucketCdn(ctx, input) {
|
|
1178
|
+
const domain = ctx.workspace.domains?.[ctx.stage];
|
|
1179
|
+
if (!domain) throw new FileServerCdnNeedsDomain(input.servers[0].id, ctx.stage);
|
|
1180
|
+
const clients = await makeCdnClients(ctx.credential);
|
|
1181
|
+
const out = {
|
|
1182
|
+
distributionArns: [],
|
|
1183
|
+
urls: {}
|
|
1184
|
+
};
|
|
1185
|
+
for (const server of input.servers) {
|
|
1186
|
+
const { arn, url } = await ensureServerCdn(ctx, clients, {
|
|
1187
|
+
...input,
|
|
1188
|
+
server,
|
|
1189
|
+
host: cdnHost(server.id, domain),
|
|
1190
|
+
processorHost: require_seeds.processorOriginHost(server.id, domain)
|
|
1191
|
+
});
|
|
1192
|
+
out.distributionArns.push(arn);
|
|
1193
|
+
out.urls[server.id] = url;
|
|
1194
|
+
}
|
|
1195
|
+
return out;
|
|
1196
|
+
}
|
|
1197
|
+
async function ensureServerCdn(ctx, clients, input) {
|
|
1198
|
+
const { server, host } = input;
|
|
1199
|
+
const name = cdnName(ctx.identity.scope, ctx.stage, server.id);
|
|
1200
|
+
const comment = `gkm ${ctx.identity.key} ${ctx.stage} ${server.id}`.slice(0, 128);
|
|
1201
|
+
const certificateArn = await ensureCertificate(ctx, clients, {
|
|
1202
|
+
id: server.id,
|
|
1203
|
+
host,
|
|
1204
|
+
name
|
|
1205
|
+
});
|
|
1206
|
+
const originAccessControlId = await ensureOriginAccessControl(ctx, clients, {
|
|
1207
|
+
id: server.id,
|
|
1208
|
+
name,
|
|
1209
|
+
bucket: input.bucket
|
|
1210
|
+
});
|
|
1211
|
+
const corsPolicyId = input.origins.length > 0 ? await ensureHeadersPolicy(ctx, clients, {
|
|
1212
|
+
id: server.id,
|
|
1213
|
+
kind: "cors",
|
|
1214
|
+
config: headersPolicyConfig({
|
|
1215
|
+
name: `${name}-cors`,
|
|
1216
|
+
comment: `${comment}: CORS`,
|
|
1217
|
+
origins: input.origins,
|
|
1218
|
+
immutable: false
|
|
1219
|
+
})
|
|
1220
|
+
}) : void 0;
|
|
1221
|
+
const immutablePolicyId = server.immutable.length > 0 ? await ensureHeadersPolicy(ctx, clients, {
|
|
1222
|
+
id: server.id,
|
|
1223
|
+
kind: "immutable",
|
|
1224
|
+
config: headersPolicyConfig({
|
|
1225
|
+
name: `${name}-immutable`,
|
|
1226
|
+
comment: `${comment}: CORS and Cache-Control immutable`,
|
|
1227
|
+
origins: input.origins,
|
|
1228
|
+
immutable: true
|
|
1229
|
+
})
|
|
1230
|
+
}) : void 0;
|
|
1231
|
+
const processors = server.processors?.length ? {
|
|
1232
|
+
prefixes: server.processors,
|
|
1233
|
+
host: input.processorHost,
|
|
1234
|
+
secret: await ensureOriginSecret(ctx, server.id)
|
|
1235
|
+
} : void 0;
|
|
1236
|
+
const distribution = await ensureDistribution(ctx, clients, {
|
|
1237
|
+
id: server.id,
|
|
1238
|
+
comment,
|
|
1239
|
+
config: distributionConfig({
|
|
1240
|
+
comment,
|
|
1241
|
+
callerReference: name,
|
|
1242
|
+
host,
|
|
1243
|
+
certificateArn,
|
|
1244
|
+
bucketDomain: `${input.bucket}.s3.${input.region}.amazonaws.com`,
|
|
1245
|
+
originAccessControlId,
|
|
1246
|
+
...corsPolicyId ? { corsPolicyId } : {},
|
|
1247
|
+
...immutablePolicyId ? { immutablePolicyId } : {},
|
|
1248
|
+
immutable: server.immutable,
|
|
1249
|
+
...processors ? { processors } : {}
|
|
1250
|
+
})
|
|
1251
|
+
});
|
|
1252
|
+
if (distribution.domainName !== PLANNED) {
|
|
1253
|
+
if ((await ctx.writeDns([{
|
|
1254
|
+
host,
|
|
1255
|
+
type: "CNAME",
|
|
1256
|
+
value: distribution.domainName
|
|
1257
|
+
}])).manual.includes(host)) ctx.log(` ${host} answers once that record exists — ${server.id}'s URL is https://${host}`);
|
|
1258
|
+
}
|
|
1259
|
+
return {
|
|
1260
|
+
arn: distribution.arn,
|
|
1261
|
+
url: `https://${host}`
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
/**
|
|
1265
|
+
* The secret the CDN sends a file server's processors: the one in the stage's
|
|
1266
|
+
* secrets, or a new one written there — read by the stack, whose edge checks
|
|
1267
|
+
* it, when it composes.
|
|
1268
|
+
*/
|
|
1269
|
+
async function ensureOriginSecret(ctx, serverId) {
|
|
1270
|
+
const key = require_seeds.originSecretKey(serverId);
|
|
1271
|
+
const kept = ctx.secrets[key];
|
|
1272
|
+
if (kept) return kept;
|
|
1273
|
+
const secret = require_seeds.newOriginSecret();
|
|
1274
|
+
ctx.log(` ${serverId}: a new processor origin secret, kept as ${key}`);
|
|
1275
|
+
await ctx.writeSecrets({ [key]: secret });
|
|
1276
|
+
return secret;
|
|
1277
|
+
}
|
|
1278
|
+
/** The certificate for `host`: recorded, adopted, or requested — then issued. */
|
|
1279
|
+
async function ensureCertificate(ctx, clients, input) {
|
|
1280
|
+
const { ACM, acm } = clients;
|
|
1281
|
+
const { id, host } = input;
|
|
1282
|
+
const entry = {
|
|
1283
|
+
key: `acm-certificate:${id}`,
|
|
1284
|
+
type: "acm-certificate",
|
|
1285
|
+
data: { host }
|
|
1286
|
+
};
|
|
1287
|
+
const describe = (arn) => readOr$2(() => acm.send(new ACM.DescribeCertificateCommand({ CertificateArn: arn })), ["ResourceNotFoundException"]).then((out) => out?.Certificate);
|
|
1288
|
+
let arn;
|
|
1289
|
+
const recorded = ctx.state.record(entry.key);
|
|
1290
|
+
if (recorded?.id && recorded.data?.host === host) {
|
|
1291
|
+
const found = await describe(recorded.id);
|
|
1292
|
+
if (found && found.Status !== "FAILED" && found.Status !== "VALIDATION_TIMED_OUT" && found.Status !== "REVOKED" && found.Status !== "EXPIRED") arn = recorded.id;
|
|
1293
|
+
}
|
|
1294
|
+
if (!arn) {
|
|
1295
|
+
arn = await findCertificate(clients, host);
|
|
1296
|
+
if (arn) ctx.log(` adopting the certificate for ${host} — ${arn}`);
|
|
1297
|
+
}
|
|
1298
|
+
if (!arn) {
|
|
1299
|
+
if (ctx.dryRun) {
|
|
1300
|
+
await ctx.change({
|
|
1301
|
+
construct: id,
|
|
1302
|
+
resource: `certificate ${host}`,
|
|
1303
|
+
change: "request (ACM, us-east-1, DNS-validated)"
|
|
1304
|
+
}, async () => {});
|
|
1305
|
+
return PLANNED;
|
|
1306
|
+
}
|
|
1307
|
+
await ctx.state.pending(entry);
|
|
1308
|
+
arn = (await acm.send(new ACM.RequestCertificateCommand({
|
|
1309
|
+
DomainName: host,
|
|
1310
|
+
ValidationMethod: "DNS",
|
|
1311
|
+
IdempotencyToken: input.name.replace(/[^A-Za-z0-9]/g, "").slice(0, 32),
|
|
1312
|
+
Tags: identityTags(ctx, id).map((t) => ({
|
|
1313
|
+
Key: t.Key,
|
|
1314
|
+
Value: t.Value
|
|
1315
|
+
}))
|
|
1316
|
+
}))).CertificateArn;
|
|
1317
|
+
await ctx.change({
|
|
1318
|
+
construct: id,
|
|
1319
|
+
resource: `certificate ${host}`,
|
|
1320
|
+
change: "request (ACM, us-east-1, DNS-validated)"
|
|
1321
|
+
}, async () => {});
|
|
1322
|
+
}
|
|
1323
|
+
if (!ctx.dryRun) await ctx.state.ready(entry, arn);
|
|
1324
|
+
let certificate = await describe(arn);
|
|
1325
|
+
let record = certificate?.DomainValidationOptions?.[0]?.ResourceRecord;
|
|
1326
|
+
for (let i = 0; !record && i < 15; i++) {
|
|
1327
|
+
await sleep(Math.min(clients.wait.intervalMs, 2e3));
|
|
1328
|
+
certificate = await describe(arn);
|
|
1329
|
+
record = certificate?.DomainValidationOptions?.[0]?.ResourceRecord;
|
|
1330
|
+
}
|
|
1331
|
+
const validation = record ? {
|
|
1332
|
+
name: record.Name.replace(/\.$/, ""),
|
|
1333
|
+
value: record.Value.replace(/\.$/, "")
|
|
1334
|
+
} : void 0;
|
|
1335
|
+
const written = validation ? await ctx.writeDns([{
|
|
1336
|
+
host: validation.name,
|
|
1337
|
+
type: "CNAME",
|
|
1338
|
+
value: validation.value
|
|
1339
|
+
}]) : { manual: [] };
|
|
1340
|
+
if (certificate?.Status === "ISSUED" || ctx.dryRun) return arn;
|
|
1341
|
+
if (validation && written.manual.includes(validation.name.toLowerCase())) throw new CdnCertificatePending(id, host, validation, "manual");
|
|
1342
|
+
ctx.log(` waiting for ACM to issue the certificate for ${host}…`);
|
|
1343
|
+
const deadline = Date.now() + clients.wait.timeoutMs;
|
|
1344
|
+
while (certificate?.Status === "PENDING_VALIDATION" && Date.now() < deadline) {
|
|
1345
|
+
await sleep(clients.wait.intervalMs);
|
|
1346
|
+
certificate = await describe(arn);
|
|
1347
|
+
}
|
|
1348
|
+
if (certificate?.Status === "ISSUED") return arn;
|
|
1349
|
+
if (certificate?.Status === "PENDING_VALIDATION" || !certificate) throw new CdnCertificatePending(id, host, validation ?? {
|
|
1350
|
+
name: `(not published yet) ${host}`,
|
|
1351
|
+
value: ""
|
|
1352
|
+
}, "timeout");
|
|
1353
|
+
throw new CdnCertificateFailed(id, host, certificate.Status ?? "unknown", certificate.FailureReason);
|
|
1354
|
+
}
|
|
1355
|
+
/** An issued or pending certificate for exactly `host`, if the account has one. */
|
|
1356
|
+
async function findCertificate(clients, host) {
|
|
1357
|
+
const { ACM, acm } = clients;
|
|
1358
|
+
let NextToken;
|
|
1359
|
+
do {
|
|
1360
|
+
const page = await acm.send(new ACM.ListCertificatesCommand({
|
|
1361
|
+
CertificateStatuses: ["ISSUED", "PENDING_VALIDATION"],
|
|
1362
|
+
...NextToken ? { NextToken } : {}
|
|
1363
|
+
}));
|
|
1364
|
+
const match = page.CertificateSummaryList?.find((c) => c.DomainName?.toLowerCase() === host);
|
|
1365
|
+
if (match?.CertificateArn) return match.CertificateArn;
|
|
1366
|
+
NextToken = page.NextToken;
|
|
1367
|
+
} while (NextToken);
|
|
1368
|
+
}
|
|
1369
|
+
/** The origin access control CloudFront signs its bucket requests with. */
|
|
1370
|
+
async function ensureOriginAccessControl(ctx, clients, input) {
|
|
1371
|
+
const { CF, cloudfront } = clients;
|
|
1372
|
+
const entry = {
|
|
1373
|
+
key: `cloudfront-oac:${input.id}`,
|
|
1374
|
+
type: "cloudfront-oac",
|
|
1375
|
+
data: { name: input.name }
|
|
1376
|
+
};
|
|
1377
|
+
const recorded = ctx.state.record(entry.key)?.id;
|
|
1378
|
+
if (recorded) {
|
|
1379
|
+
if (await readOr$2(() => cloudfront.send(new CF.GetOriginAccessControlCommand({ Id: recorded })), ["NoSuchOriginAccessControl"])) return recorded;
|
|
1380
|
+
}
|
|
1381
|
+
let Marker;
|
|
1382
|
+
do {
|
|
1383
|
+
const page = await cloudfront.send(new CF.ListOriginAccessControlsCommand({ ...Marker ? { Marker } : {} }));
|
|
1384
|
+
const match = page.OriginAccessControlList?.Items?.find((o) => o.Name === input.name);
|
|
1385
|
+
if (match?.Id) {
|
|
1386
|
+
if (!ctx.dryRun) await ctx.state.ready(entry, match.Id);
|
|
1387
|
+
return match.Id;
|
|
1388
|
+
}
|
|
1389
|
+
Marker = page.OriginAccessControlList?.NextMarker;
|
|
1390
|
+
} while (Marker);
|
|
1391
|
+
let id = PLANNED;
|
|
1392
|
+
await ctx.change({
|
|
1393
|
+
construct: input.id,
|
|
1394
|
+
resource: `origin access control ${input.name}`,
|
|
1395
|
+
change: `create (CloudFront signs its reads of ${input.bucket})`
|
|
1396
|
+
}, async () => {
|
|
1397
|
+
await ctx.state.pending(entry);
|
|
1398
|
+
id = (await cloudfront.send(new CF.CreateOriginAccessControlCommand({ OriginAccessControlConfig: {
|
|
1399
|
+
Name: input.name,
|
|
1400
|
+
Description: `gkm: CloudFront's reads of ${input.bucket}`,
|
|
1401
|
+
OriginAccessControlOriginType: "s3",
|
|
1402
|
+
SigningBehavior: "always",
|
|
1403
|
+
SigningProtocol: "sigv4"
|
|
1404
|
+
} }))).OriginAccessControl.Id;
|
|
1405
|
+
await ctx.state.ready(entry, id);
|
|
1406
|
+
});
|
|
1407
|
+
return id;
|
|
1408
|
+
}
|
|
1409
|
+
/** One response headers policy, as wanted. */
|
|
1410
|
+
async function ensureHeadersPolicy(ctx, clients, input) {
|
|
1411
|
+
const { CF, cloudfront } = clients;
|
|
1412
|
+
const { config } = input;
|
|
1413
|
+
const entry = {
|
|
1414
|
+
key: `cloudfront-headers:${input.id}:${input.kind}`,
|
|
1415
|
+
type: "cloudfront-headers",
|
|
1416
|
+
data: { name: config.Name }
|
|
1417
|
+
};
|
|
1418
|
+
const read = (Id) => readOr$2(() => cloudfront.send(new CF.GetResponseHeadersPolicyConfigCommand({ Id })), ["NoSuchResponseHeadersPolicy"]);
|
|
1419
|
+
let id = ctx.state.record(entry.key)?.id;
|
|
1420
|
+
let current = id ? await read(id) : void 0;
|
|
1421
|
+
if (!current) {
|
|
1422
|
+
id = void 0;
|
|
1423
|
+
let Marker;
|
|
1424
|
+
do {
|
|
1425
|
+
const page = await cloudfront.send(new CF.ListResponseHeadersPoliciesCommand({
|
|
1426
|
+
Type: "custom",
|
|
1427
|
+
...Marker ? { Marker } : {}
|
|
1428
|
+
}));
|
|
1429
|
+
const match = page.ResponseHeadersPolicyList?.Items?.find((p) => p.ResponseHeadersPolicy?.ResponseHeadersPolicyConfig?.Name === config.Name);
|
|
1430
|
+
if (match?.ResponseHeadersPolicy?.Id) {
|
|
1431
|
+
id = match.ResponseHeadersPolicy.Id;
|
|
1432
|
+
current = await read(id);
|
|
1433
|
+
}
|
|
1434
|
+
Marker = page.ResponseHeadersPolicyList?.NextMarker;
|
|
1435
|
+
} while (!id && Marker);
|
|
1436
|
+
}
|
|
1437
|
+
if (id && current) {
|
|
1438
|
+
if (!ctx.dryRun) await ctx.state.ready(entry, id);
|
|
1439
|
+
if (sameHeadersPolicy(current.ResponseHeadersPolicyConfig, config)) return id;
|
|
1440
|
+
const existing = id;
|
|
1441
|
+
await ctx.change({
|
|
1442
|
+
construct: input.id,
|
|
1443
|
+
resource: `response headers policy ${config.Name}`,
|
|
1444
|
+
change: "put back what drifted"
|
|
1445
|
+
}, async () => {
|
|
1446
|
+
await cloudfront.send(new CF.UpdateResponseHeadersPolicyCommand({
|
|
1447
|
+
Id: existing,
|
|
1448
|
+
IfMatch: current.ETag,
|
|
1449
|
+
ResponseHeadersPolicyConfig: config
|
|
1450
|
+
}));
|
|
1451
|
+
});
|
|
1452
|
+
return existing;
|
|
1453
|
+
}
|
|
1454
|
+
let created = PLANNED;
|
|
1455
|
+
await ctx.change({
|
|
1456
|
+
construct: input.id,
|
|
1457
|
+
resource: `response headers policy ${config.Name}`,
|
|
1458
|
+
change: "create"
|
|
1459
|
+
}, async () => {
|
|
1460
|
+
await ctx.state.pending(entry);
|
|
1461
|
+
created = (await cloudfront.send(new CF.CreateResponseHeadersPolicyCommand({ ResponseHeadersPolicyConfig: config }))).ResponseHeadersPolicy.Id;
|
|
1462
|
+
await ctx.state.ready(entry, created);
|
|
1463
|
+
});
|
|
1464
|
+
return created;
|
|
1465
|
+
}
|
|
1466
|
+
/** The distribution: recorded, found by its comment, or created — as wanted. */
|
|
1467
|
+
async function ensureDistribution(ctx, clients, input) {
|
|
1468
|
+
const { CF, cloudfront } = clients;
|
|
1469
|
+
const entry = {
|
|
1470
|
+
key: `cloudfront-distribution:${input.id}`,
|
|
1471
|
+
type: "cloudfront-distribution"
|
|
1472
|
+
};
|
|
1473
|
+
const get = (Id) => readOr$2(() => cloudfront.send(new CF.GetDistributionCommand({ Id })), ["NoSuchDistribution"]);
|
|
1474
|
+
let found = ctx.state.record(entry.key)?.id ? await get(ctx.state.record(entry.key).id) : void 0;
|
|
1475
|
+
if (!found) {
|
|
1476
|
+
let Marker;
|
|
1477
|
+
do {
|
|
1478
|
+
const page = await cloudfront.send(new CF.ListDistributionsCommand({ ...Marker ? { Marker } : {} }));
|
|
1479
|
+
const match = page.DistributionList?.Items?.find((d) => d.Comment === input.comment);
|
|
1480
|
+
if (match?.Id) found = await get(match.Id);
|
|
1481
|
+
Marker = page.DistributionList?.NextMarker;
|
|
1482
|
+
} while (!found && Marker);
|
|
1483
|
+
}
|
|
1484
|
+
if (found?.Distribution) {
|
|
1485
|
+
const distribution = found.Distribution;
|
|
1486
|
+
const id = distribution.Id;
|
|
1487
|
+
const located = {
|
|
1488
|
+
arn: distribution.ARN,
|
|
1489
|
+
domainName: distribution.DomainName
|
|
1490
|
+
};
|
|
1491
|
+
if (!ctx.dryRun) await ctx.state.ready({
|
|
1492
|
+
...entry,
|
|
1493
|
+
data: located
|
|
1494
|
+
}, id);
|
|
1495
|
+
const current = await cloudfront.send(new CF.GetDistributionConfigCommand({ Id: id }));
|
|
1496
|
+
if (holds(current.DistributionConfig, input.config)) return located;
|
|
1497
|
+
await ctx.change({
|
|
1498
|
+
construct: input.id,
|
|
1499
|
+
resource: `distribution ${id}`,
|
|
1500
|
+
change: "put back what drifted"
|
|
1501
|
+
}, async () => {
|
|
1502
|
+
await cloudfront.send(new CF.UpdateDistributionCommand({
|
|
1503
|
+
Id: id,
|
|
1504
|
+
IfMatch: current.ETag,
|
|
1505
|
+
DistributionConfig: {
|
|
1506
|
+
...current.DistributionConfig,
|
|
1507
|
+
...input.config,
|
|
1508
|
+
CallerReference: current.DistributionConfig.CallerReference
|
|
1509
|
+
}
|
|
1510
|
+
}));
|
|
1511
|
+
});
|
|
1512
|
+
return located;
|
|
1513
|
+
}
|
|
1514
|
+
let created = {
|
|
1515
|
+
arn: `arn:aws:cloudfront::${PLANNED}`,
|
|
1516
|
+
domainName: PLANNED
|
|
1517
|
+
};
|
|
1518
|
+
const host = input.config.Aliases?.Items?.[0] ?? input.id;
|
|
1519
|
+
await ctx.change({
|
|
1520
|
+
construct: input.id,
|
|
1521
|
+
resource: `distribution ${host}`,
|
|
1522
|
+
change: "create (CloudFront, takes minutes to reach every edge)"
|
|
1523
|
+
}, async () => {
|
|
1524
|
+
await ctx.state.pending(entry);
|
|
1525
|
+
const out = await cloudfront.send(new CF.CreateDistributionWithTagsCommand({ DistributionConfigWithTags: {
|
|
1526
|
+
DistributionConfig: input.config,
|
|
1527
|
+
Tags: { Items: identityTags(ctx, input.id).map((t) => ({
|
|
1528
|
+
Key: t.Key,
|
|
1529
|
+
Value: t.Value
|
|
1530
|
+
})) }
|
|
1531
|
+
} }));
|
|
1532
|
+
created = {
|
|
1533
|
+
arn: out.Distribution.ARN,
|
|
1534
|
+
domainName: out.Distribution.DomainName
|
|
1535
|
+
};
|
|
1536
|
+
await ctx.state.ready({
|
|
1537
|
+
...entry,
|
|
1538
|
+
data: created
|
|
1539
|
+
}, out.Distribution.Id);
|
|
1540
|
+
});
|
|
1541
|
+
return created;
|
|
1542
|
+
}
|
|
1543
|
+
//#endregion
|
|
1544
|
+
//#region src/providers/s3/events.ts
|
|
1545
|
+
/** What S3 tells the topic about: every object written, and every removed. */
|
|
1546
|
+
const BUCKET_EVENTS = ["s3:ObjectCreated:*", "s3:ObjectRemoved:*"];
|
|
1547
|
+
/**
|
|
1548
|
+
* Each bucket a subscriber watches, with the subscribers discovery could name.
|
|
1549
|
+
*
|
|
1550
|
+
* A bucket is watched when a process consumes its events — discovery records
|
|
1551
|
+
* the edge, and the subscriber by its export — or when a build put a
|
|
1552
|
+
* subscriber in its declaration. Only a named subscriber can be subscribed: a
|
|
1553
|
+
* route is named after its export.
|
|
1554
|
+
*/
|
|
1555
|
+
function bucketWatchers(manifest, runnables, subscribers = {}) {
|
|
1556
|
+
const watched = /* @__PURE__ */ new Map();
|
|
1557
|
+
for (const [id, declaration] of Object.entries(manifest)) {
|
|
1558
|
+
if (declaration.kind !== "objects") continue;
|
|
1559
|
+
const key = (0, _geekmidas_manifest.provideKey)(id, "eventsConnectionString");
|
|
1560
|
+
const named = Object.entries(subscribers).flatMap(([worker, bound]) => bound.filter((s) => s.connectionKey === key).map((s) => ({
|
|
1561
|
+
worker,
|
|
1562
|
+
name: s.name
|
|
1563
|
+
}))).sort((a, b) => `${a.worker}/${a.name}`.localeCompare(`${b.worker}/${b.name}`));
|
|
1564
|
+
const edged = Object.values(runnables).some((edges) => edges.includes(require_discover.eventsEdge(id)));
|
|
1565
|
+
const declared = (declaration.subscribers?.length ?? 0) > 0;
|
|
1566
|
+
if (named.length > 0 || edged || declared) watched.set(id, named);
|
|
1567
|
+
}
|
|
1568
|
+
return watched;
|
|
1569
|
+
}
|
|
1570
|
+
/** The topic a bucket's changes are published to. */
|
|
1571
|
+
function eventsTopicName(bucket) {
|
|
1572
|
+
return `${bucket.replaceAll(".", "-")}-events`;
|
|
1573
|
+
}
|
|
1574
|
+
/** What SNS gives up delivering, kept. */
|
|
1575
|
+
function eventsDeadLetterName(bucket) {
|
|
1576
|
+
return `${eventsTopicName(bucket)}-dlq`;
|
|
1577
|
+
}
|
|
1578
|
+
/** Only this bucket, in this account, may publish to its topic. */
|
|
1579
|
+
function eventsTopicPolicy(topicArn, bucket, account) {
|
|
1580
|
+
return {
|
|
1581
|
+
Version: "2012-10-17",
|
|
1582
|
+
Statement: [{
|
|
1583
|
+
Sid: "GkmBucketPublishes",
|
|
1584
|
+
Effect: "Allow",
|
|
1585
|
+
Principal: { Service: "s3.amazonaws.com" },
|
|
1586
|
+
Action: "sns:Publish",
|
|
1587
|
+
Resource: topicArn,
|
|
1588
|
+
Condition: {
|
|
1589
|
+
ArnLike: { "aws:SourceArn": `arn:aws:s3:::${bucket}` },
|
|
1590
|
+
StringEquals: { "aws:SourceAccount": account }
|
|
1591
|
+
}
|
|
1592
|
+
}]
|
|
1593
|
+
};
|
|
1594
|
+
}
|
|
1595
|
+
/** Only the bucket's topic may send to its dead-letter queue. */
|
|
1596
|
+
function eventsDeadLetterPolicy(queueArn, topicArn) {
|
|
1597
|
+
return {
|
|
1598
|
+
Version: "2012-10-17",
|
|
1599
|
+
Statement: [{
|
|
1600
|
+
Sid: "GkmTopicDeadLetters",
|
|
1601
|
+
Effect: "Allow",
|
|
1602
|
+
Principal: { Service: "sns.amazonaws.com" },
|
|
1603
|
+
Action: "sqs:SendMessage",
|
|
1604
|
+
Resource: queueArn,
|
|
1605
|
+
Condition: { ArnEquals: { "aws:SourceArn": topicArn } }
|
|
1606
|
+
}]
|
|
1607
|
+
};
|
|
1608
|
+
}
|
|
1609
|
+
/** The id this provider's notification entry goes under. */
|
|
1610
|
+
const notificationId = (id) => `gkm-${id}`;
|
|
1611
|
+
/**
|
|
1612
|
+
* The bucket's notifications with this provider's entry put back, and whether
|
|
1613
|
+
* that changed anything. Every other entry — a queue, a function, a topic
|
|
1614
|
+
* somebody added by hand — is kept exactly as it was.
|
|
1615
|
+
*/
|
|
1616
|
+
function mergedNotifications(current, id, topicArn) {
|
|
1617
|
+
const own = notificationId(id);
|
|
1618
|
+
const wanted = {
|
|
1619
|
+
Id: own,
|
|
1620
|
+
TopicArn: topicArn,
|
|
1621
|
+
Events: [...BUCKET_EVENTS]
|
|
1622
|
+
};
|
|
1623
|
+
const topics = current.TopicConfigurations ?? [];
|
|
1624
|
+
const existing = topics.find((t) => t.Id === own);
|
|
1625
|
+
const same = existing !== void 0 && existing.TopicArn === topicArn && JSON.stringify([...existing.Events ?? []].sort()) === JSON.stringify([...BUCKET_EVENTS].sort()) && existing.Filter === void 0;
|
|
1626
|
+
const { ResponseMetadata: _, ...rest } = current;
|
|
1627
|
+
return {
|
|
1628
|
+
config: {
|
|
1629
|
+
...rest,
|
|
1630
|
+
TopicConfigurations: [...topics.filter((t) => t.Id !== own), wanted]
|
|
1631
|
+
},
|
|
1632
|
+
changed: !same
|
|
1633
|
+
};
|
|
1634
|
+
}
|
|
1635
|
+
/** S3 refused the entry because another one already covers these events. */
|
|
1636
|
+
var BucketNotificationsOverlap = class extends require_errors.GkmError {
|
|
1637
|
+
bucket;
|
|
1638
|
+
detail;
|
|
1639
|
+
constructor(bucket, detail) {
|
|
1640
|
+
super(`'${bucket}' already sends object creates or deletes somewhere else, and S3 sends each event to one place only, so the deploy cannot add its own entry. Remove the other notification from the bucket, or give it a prefix, and deploy again. S3 said: ${detail}`);
|
|
1641
|
+
this.bucket = bucket;
|
|
1642
|
+
this.detail = detail;
|
|
1643
|
+
this.name = "BucketNotificationsOverlap";
|
|
1644
|
+
}
|
|
1645
|
+
};
|
|
1646
|
+
const defaultEventsClients = async (credential, region) => {
|
|
1647
|
+
const SNS = await import("@aws-sdk/client-sns");
|
|
1648
|
+
const SQS = await import("@aws-sdk/client-sqs");
|
|
1649
|
+
const { STSClient, GetCallerIdentityCommand } = await import("@aws-sdk/client-sts");
|
|
1650
|
+
const config = await require_snsSubscriptions.awsClientConfig(credential, region);
|
|
1651
|
+
const endpoint = require_snsSubscriptions.awsEndpoint(process.env, "SNS");
|
|
1652
|
+
const sts = new STSClient({
|
|
1653
|
+
...config,
|
|
1654
|
+
...require_snsSubscriptions.awsEndpoint(process.env, "STS") ? { endpoint: require_snsSubscriptions.awsEndpoint(process.env, "STS") } : {}
|
|
1655
|
+
});
|
|
1656
|
+
const { Account } = await sts.send(new GetCallerIdentityCommand({}));
|
|
1657
|
+
sts.destroy();
|
|
1658
|
+
return {
|
|
1659
|
+
SNS,
|
|
1660
|
+
SQS,
|
|
1661
|
+
sns: new SNS.SNSClient({
|
|
1662
|
+
...config,
|
|
1663
|
+
...endpoint ? { endpoint } : {}
|
|
1664
|
+
}),
|
|
1665
|
+
sqs: new SQS.SQSClient({
|
|
1666
|
+
...config,
|
|
1667
|
+
...require_snsSubscriptions.awsEndpoint(process.env, "SQS") ? { endpoint: require_snsSubscriptions.awsEndpoint(process.env, "SQS") } : {}
|
|
1668
|
+
}),
|
|
1669
|
+
account: Account,
|
|
1670
|
+
region,
|
|
1671
|
+
...endpoint ? { endpoint } : {}
|
|
1672
|
+
};
|
|
1673
|
+
};
|
|
1674
|
+
let makeEventsClients = defaultEventsClients;
|
|
1675
|
+
function errorName$1(error) {
|
|
1676
|
+
const e = error;
|
|
1677
|
+
return e?.name ?? e?.Code ?? e?.code ?? "";
|
|
1678
|
+
}
|
|
1679
|
+
async function readOr$1(read, missing) {
|
|
1680
|
+
try {
|
|
1681
|
+
return await read();
|
|
1682
|
+
} catch (error) {
|
|
1683
|
+
if (missing.includes(errorName$1(error))) return void 0;
|
|
1684
|
+
throw error;
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
/**
|
|
1688
|
+
* The topic, its policy, the dead-letter queue and the bucket's notification
|
|
1689
|
+
* entry for one watched bucket — created where missing, put back where they
|
|
1690
|
+
* drifted.
|
|
1691
|
+
*/
|
|
1692
|
+
async function ensureBucketEvents(ctx, s3, input) {
|
|
1693
|
+
const { id, bucket, region } = input;
|
|
1694
|
+
const { SNS, SQS, sns, sqs, account } = await makeEventsClients(ctx.credential, region);
|
|
1695
|
+
const topicName = eventsTopicName(bucket);
|
|
1696
|
+
const topicArn = `arn:aws:sns:${region}:${account}:${topicName}`;
|
|
1697
|
+
const topicEntry = {
|
|
1698
|
+
key: `sns-topic:${id}`,
|
|
1699
|
+
type: "sns-topic"
|
|
1700
|
+
};
|
|
1701
|
+
const topic = await readOr$1(() => sns.send(new SNS.GetTopicAttributesCommand({ TopicArn: topicArn })), ["NotFound", "NotFoundException"]);
|
|
1702
|
+
if (topic) await ctx.state.ready(topicEntry, topicArn);
|
|
1703
|
+
else await ctx.change({
|
|
1704
|
+
construct: id,
|
|
1705
|
+
resource: `SNS topic ${topicName}`,
|
|
1706
|
+
change: "create"
|
|
1707
|
+
}, async () => {
|
|
1708
|
+
await ctx.state.pending(topicEntry);
|
|
1709
|
+
await sns.send(new SNS.CreateTopicCommand({
|
|
1710
|
+
Name: topicName,
|
|
1711
|
+
Tags: identityTags(ctx, id)
|
|
1712
|
+
}));
|
|
1713
|
+
await ctx.state.ready(topicEntry, topicArn);
|
|
1714
|
+
});
|
|
1715
|
+
const topicPolicy = eventsTopicPolicy(topicArn, bucket, account);
|
|
1716
|
+
const currentTopicPolicy = topic?.Attributes?.Policy;
|
|
1717
|
+
if (!currentTopicPolicy || canonicalPolicy(JSON.parse(currentTopicPolicy)) !== canonicalPolicy(topicPolicy)) await ctx.change({
|
|
1718
|
+
construct: id,
|
|
1719
|
+
resource: `SNS topic ${topicName}`,
|
|
1720
|
+
change: `publishable by '${bucket}' only`
|
|
1721
|
+
}, async () => {
|
|
1722
|
+
await sns.send(new SNS.SetTopicAttributesCommand({
|
|
1723
|
+
TopicArn: topicArn,
|
|
1724
|
+
AttributeName: "Policy",
|
|
1725
|
+
AttributeValue: JSON.stringify(topicPolicy)
|
|
1726
|
+
}));
|
|
1727
|
+
});
|
|
1728
|
+
const queueName = eventsDeadLetterName(bucket);
|
|
1729
|
+
const deadLetterArn = `arn:aws:sqs:${region}:${account}:${queueName}`;
|
|
1730
|
+
const queueEntry = {
|
|
1731
|
+
key: `sqs-queue:${id}-events-dlq`,
|
|
1732
|
+
type: "sqs-queue"
|
|
1733
|
+
};
|
|
1734
|
+
const queue = await readOr$1(() => sqs.send(new SQS.GetQueueUrlCommand({ QueueName: queueName })), ["QueueDoesNotExist", "AWS.SimpleQueueService.NonExistentQueue"]);
|
|
1735
|
+
let queueUrl = queue?.QueueUrl;
|
|
1736
|
+
if (queueUrl) await ctx.state.ready(queueEntry, deadLetterArn);
|
|
1737
|
+
else await ctx.change({
|
|
1738
|
+
construct: id,
|
|
1739
|
+
resource: `SQS queue ${queueName}`,
|
|
1740
|
+
change: "create"
|
|
1741
|
+
}, async () => {
|
|
1742
|
+
await ctx.state.pending(queueEntry);
|
|
1743
|
+
queueUrl = (await sqs.send(new SQS.CreateQueueCommand({
|
|
1744
|
+
QueueName: queueName,
|
|
1745
|
+
Attributes: { MessageRetentionPeriod: "1209600" },
|
|
1746
|
+
tags: Object.fromEntries(identityTags(ctx, id).map((t) => [t.Key, t.Value]))
|
|
1747
|
+
}))).QueueUrl;
|
|
1748
|
+
await ctx.state.ready(queueEntry, deadLetterArn);
|
|
1749
|
+
});
|
|
1750
|
+
const queuePolicy = eventsDeadLetterPolicy(deadLetterArn, topicArn);
|
|
1751
|
+
const currentQueuePolicy = queue?.QueueUrl ? (await sqs.send(new SQS.GetQueueAttributesCommand({
|
|
1752
|
+
QueueUrl: queue.QueueUrl,
|
|
1753
|
+
AttributeNames: ["Policy"]
|
|
1754
|
+
}))).Attributes?.Policy : void 0;
|
|
1755
|
+
if (!currentQueuePolicy || canonicalPolicy(JSON.parse(currentQueuePolicy)) !== canonicalPolicy(queuePolicy)) await ctx.change({
|
|
1756
|
+
construct: id,
|
|
1757
|
+
resource: `SQS queue ${queueName}`,
|
|
1758
|
+
change: `receives what ${topicName} cannot deliver`
|
|
1759
|
+
}, async () => {
|
|
1760
|
+
await sqs.send(new SQS.SetQueueAttributesCommand({
|
|
1761
|
+
QueueUrl: queueUrl,
|
|
1762
|
+
Attributes: { Policy: JSON.stringify(queuePolicy) }
|
|
1763
|
+
}));
|
|
1764
|
+
});
|
|
1765
|
+
const merged = mergedNotifications(await readOr$1(() => s3.s3.send(new s3.S3.GetBucketNotificationConfigurationCommand({ Bucket: bucket })), ["NoSuchBucket"]) ?? {}, id, topicArn);
|
|
1766
|
+
if (merged.changed) await ctx.change({
|
|
1767
|
+
construct: id,
|
|
1768
|
+
resource: `bucket ${bucket}`,
|
|
1769
|
+
change: `send object creates and deletes to ${topicName}`
|
|
1770
|
+
}, async () => {
|
|
1771
|
+
try {
|
|
1772
|
+
await s3.s3.send(new s3.S3.PutBucketNotificationConfigurationCommand({
|
|
1773
|
+
Bucket: bucket,
|
|
1774
|
+
NotificationConfiguration: merged.config
|
|
1775
|
+
}));
|
|
1776
|
+
} catch (error) {
|
|
1777
|
+
const message = error.message ?? "";
|
|
1778
|
+
if (/overlap/i.test(message)) throw new BucketNotificationsOverlap(bucket, message);
|
|
1779
|
+
throw error;
|
|
1780
|
+
}
|
|
1781
|
+
});
|
|
1782
|
+
sns.destroy();
|
|
1783
|
+
sqs.destroy();
|
|
1784
|
+
return {
|
|
1785
|
+
topicArn,
|
|
1786
|
+
deadLetterArn
|
|
1787
|
+
};
|
|
1788
|
+
}
|
|
1789
|
+
/**
|
|
1790
|
+
* The events key: the topic's address, signed by the bucket's own access key
|
|
1791
|
+
* — read off the bucket's URL, so a rotated key rotates this one with it.
|
|
1792
|
+
*/
|
|
1793
|
+
async function ensureEventsKey(ctx, input) {
|
|
1794
|
+
const { id, topicArn, region } = input;
|
|
1795
|
+
const key = (0, _geekmidas_manifest.provideKey)(id, "eventsConnectionString");
|
|
1796
|
+
const url = ctx.secrets[(0, _geekmidas_manifest.provideKey)(id, "url")];
|
|
1797
|
+
let parsed;
|
|
1798
|
+
try {
|
|
1799
|
+
parsed = url ? _geekmidas_storage_s3_url.parse(url) : void 0;
|
|
1800
|
+
} catch {
|
|
1801
|
+
parsed = void 0;
|
|
1802
|
+
}
|
|
1803
|
+
if (!parsed?.accessKeyId || !parsed.secretAccessKey) {
|
|
1804
|
+
await ctx.change({
|
|
1805
|
+
construct: id,
|
|
1806
|
+
resource: key,
|
|
1807
|
+
change: "set to the topic’s address"
|
|
1808
|
+
}, async () => {});
|
|
1809
|
+
return;
|
|
1810
|
+
}
|
|
1811
|
+
const { snsUrl } = await import("@geekmidas/events/sns");
|
|
1812
|
+
const endpoint = require_snsSubscriptions.awsEndpoint(process.env, "SNS");
|
|
1813
|
+
const address = new URL(snsUrl.build({
|
|
1814
|
+
topicArn,
|
|
1815
|
+
region,
|
|
1816
|
+
...endpoint ? { endpoint } : {}
|
|
1817
|
+
}));
|
|
1818
|
+
address.searchParams.set("accessKeyId", parsed.accessKeyId);
|
|
1819
|
+
address.searchParams.set("secretAccessKey", parsed.secretAccessKey);
|
|
1820
|
+
const value = address.toString();
|
|
1821
|
+
const entry = {
|
|
1822
|
+
key: `events-key:${id}`,
|
|
1823
|
+
type: "url"
|
|
1824
|
+
};
|
|
1825
|
+
if (ctx.secrets[key] === value) {
|
|
1826
|
+
await ctx.state.ready(entry, topicArn);
|
|
1827
|
+
return;
|
|
1828
|
+
}
|
|
1829
|
+
await ctx.change({
|
|
1830
|
+
construct: id,
|
|
1831
|
+
resource: key,
|
|
1832
|
+
change: `set to ${topicArn}`
|
|
1833
|
+
}, async () => {
|
|
1834
|
+
await ctx.writeSecrets({ [key]: value });
|
|
1835
|
+
await ctx.state.ready(entry, topicArn);
|
|
1836
|
+
});
|
|
1837
|
+
}
|
|
1838
|
+
/** Where SNS pushes to a worker: its own host on the stage's domain. */
|
|
1839
|
+
function workerPushHost(worker, domain) {
|
|
1840
|
+
return `${require_derive.appKey(worker)}.${domain}`;
|
|
1841
|
+
}
|
|
1842
|
+
/**
|
|
1843
|
+
* One HTTP subscription per subscriber to the bucket's topic, at its route on
|
|
1844
|
+
* its worker's host, with the bucket's dead-letter queue — and each ARN
|
|
1845
|
+
* recorded, which is what the worker accepts deliveries under.
|
|
1846
|
+
*
|
|
1847
|
+
* Unfiltered: a bucket's notifications carry no `type` attribute, and the
|
|
1848
|
+
* subscriber's own filter — events, prefix, suffix — is what decides. Only a
|
|
1849
|
+
* compose stage pushes to a worker; any other is told so and left alone.
|
|
1850
|
+
*/
|
|
1851
|
+
async function ensurePushSubscriptions(ctx, input) {
|
|
1852
|
+
const { id, events, region, watchers } = input;
|
|
1853
|
+
if (!require_snsSubscriptions.deploysWithCompose(ctx.workspace)) {
|
|
1854
|
+
ctx.log(` '${id}' is watched, and only a compose stage pushes a bucket's changes to a worker: its subscribers receive nothing on '${ctx.stage}'`);
|
|
1855
|
+
return;
|
|
1856
|
+
}
|
|
1857
|
+
if (watchers.length === 0) return;
|
|
1858
|
+
const domain = ctx.workspace.domains?.[ctx.stage];
|
|
1859
|
+
if (!domain) throw new require_seeds.NoDomainForStage(ctx.stage);
|
|
1860
|
+
const { SNSConnection, reconcileHttpSubscriptions } = await import("@geekmidas/events/sns");
|
|
1861
|
+
const { credentials } = await require_snsSubscriptions.awsClientConfig(ctx.credential, region);
|
|
1862
|
+
const endpoint = require_snsSubscriptions.awsEndpoint(process.env, "SNS");
|
|
1863
|
+
const byWorker = /* @__PURE__ */ new Map();
|
|
1864
|
+
for (const watcher of watchers) byWorker.set(watcher.worker, [...byWorker.get(watcher.worker) ?? [], watcher]);
|
|
1865
|
+
for (const [worker, own] of byWorker) {
|
|
1866
|
+
const host = workerPushHost(worker, domain);
|
|
1867
|
+
const endpoints = own.map(({ name }) => ({
|
|
1868
|
+
name,
|
|
1869
|
+
endpoint: `https://${host}${require_SubscriberGenerator.subscriberRoute(name)}`,
|
|
1870
|
+
deadLetterQueueArn: events.deadLetterArn
|
|
1871
|
+
}));
|
|
1872
|
+
const unrecorded = own.filter(({ name }) => ctx.state.record(require_snsSubscriptions.subscriptionKey(worker, name))?.status !== "ready");
|
|
1873
|
+
if (ctx.dryRun) {
|
|
1874
|
+
if (unrecorded.length > 0) await ctx.change({
|
|
1875
|
+
construct: id,
|
|
1876
|
+
resource: `SNS subscriptions on ${host}`,
|
|
1877
|
+
change: `subscribe ${unrecorded.map((w) => w.name).join(", ")}`
|
|
1878
|
+
}, async () => {});
|
|
1879
|
+
continue;
|
|
1880
|
+
}
|
|
1881
|
+
const connection = new SNSConnection({
|
|
1882
|
+
topicArn: events.topicArn,
|
|
1883
|
+
region,
|
|
1884
|
+
...endpoint ? { endpoint } : {},
|
|
1885
|
+
...credentials ? { credentials } : {}
|
|
1886
|
+
});
|
|
1887
|
+
const result = await reconcileHttpSubscriptions(connection, {
|
|
1888
|
+
endpoints,
|
|
1889
|
+
ownedHost: host
|
|
1890
|
+
});
|
|
1891
|
+
await connection.close();
|
|
1892
|
+
for (const spec of endpoints) {
|
|
1893
|
+
const arn = result.subscriptions[spec.name];
|
|
1894
|
+
const pending = result.pending.includes(spec.name);
|
|
1895
|
+
if (ctx.state.record(require_snsSubscriptions.subscriptionKey(worker, spec.name))?.id !== arn) ctx.log(` ✓ '${spec.name}' subscribed to ${events.topicArn.split(":").pop()} at ${spec.endpoint}`);
|
|
1896
|
+
const { key, type, data } = require_snsSubscriptions.subscriptionResource({
|
|
1897
|
+
worker,
|
|
1898
|
+
name: spec.name,
|
|
1899
|
+
topicArn: events.topicArn,
|
|
1900
|
+
endpoint: spec.endpoint,
|
|
1901
|
+
deadLetterQueueArn: events.deadLetterArn,
|
|
1902
|
+
pending
|
|
1903
|
+
}, arn);
|
|
1904
|
+
await ctx.state.ready({
|
|
1905
|
+
key,
|
|
1906
|
+
type,
|
|
1907
|
+
...data ? { data } : {}
|
|
1908
|
+
}, arn);
|
|
1909
|
+
}
|
|
1910
|
+
for (const arn of result.removed) ctx.log(` – unsubscribed ${arn}: no subscriber wants it any more`);
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
//#endregion
|
|
864
1914
|
//#region src/providers/s3/origins.ts
|
|
865
1915
|
/**
|
|
866
1916
|
* Which browser origins may call a bucket: the stage's sites that depend on
|
|
@@ -919,10 +1969,12 @@ function bucketConstructs(manifest) {
|
|
|
919
1969
|
return Object.entries(manifest).filter(([, d]) => d.kind === "objects").map(([id, d]) => ({
|
|
920
1970
|
id,
|
|
921
1971
|
versioned: d.versioned === true,
|
|
922
|
-
...
|
|
1972
|
+
...(0, _geekmidas_manifest.bucketExpiry)(manifest, id) ? { expire: (0, _geekmidas_manifest.bucketExpiry)(manifest, id) } : {},
|
|
923
1973
|
servers: Object.entries(manifest).filter(([, s]) => s.kind === "file-server" && s.of === id).map(([sid, s]) => ({
|
|
924
1974
|
id: sid,
|
|
925
|
-
open: s.open ?? []
|
|
1975
|
+
open: s.open ?? [],
|
|
1976
|
+
immutable: s.immutable ?? [],
|
|
1977
|
+
...s.processors?.length ? { processors: [...new Set(s.processors.map((p) => p.prefix))].sort() } : {}
|
|
926
1978
|
})).sort((a, b) => a.id.localeCompare(b.id))
|
|
927
1979
|
})).sort((a, b) => a.id.localeCompare(b.id));
|
|
928
1980
|
}
|
|
@@ -1032,8 +2084,8 @@ async function readOr(read, missing) {
|
|
|
1032
2084
|
const defaultClients = async (credential, region) => {
|
|
1033
2085
|
const S3 = await import("@aws-sdk/client-s3");
|
|
1034
2086
|
const IAM = await import("@aws-sdk/client-iam");
|
|
1035
|
-
const config = await awsClientConfig(credential, region);
|
|
1036
|
-
const endpoint = awsEndpoint(process.env, "S3");
|
|
2087
|
+
const config = await require_snsSubscriptions.awsClientConfig(credential, region);
|
|
2088
|
+
const endpoint = require_snsSubscriptions.awsEndpoint(process.env, "S3");
|
|
1037
2089
|
return {
|
|
1038
2090
|
S3,
|
|
1039
2091
|
IAM,
|
|
@@ -1054,9 +2106,10 @@ let makeClients = defaultClients;
|
|
|
1054
2106
|
async function ensureS3(ctx) {
|
|
1055
2107
|
const region = s3Region(ctx);
|
|
1056
2108
|
const clients = await makeClients(ctx.credential, region);
|
|
1057
|
-
|
|
2109
|
+
const watched = bucketWatchers(ctx.manifest, ctx.runnables, ctx.subscribers);
|
|
2110
|
+
for (const construct of bucketConstructs(ctx.manifest)) await ensureBucket(ctx, clients, construct, watched.get(construct.id));
|
|
1058
2111
|
}
|
|
1059
|
-
async function ensureBucket(ctx, stageClients, construct) {
|
|
2112
|
+
async function ensureBucket(ctx, stageClients, construct, watchers) {
|
|
1060
2113
|
const { id } = construct;
|
|
1061
2114
|
const nameInput = {
|
|
1062
2115
|
scope: ctx.identity.scope,
|
|
@@ -1072,19 +2125,40 @@ async function ensureBucket(ctx, stageClients, construct) {
|
|
|
1072
2125
|
clients = await makeClients(ctx.credential, located.region);
|
|
1073
2126
|
ctx.log(` '${bucket}' is in ${located.region}, not ${stageClients.region}: '${id}' is reached and addressed there`);
|
|
1074
2127
|
}
|
|
2128
|
+
const cdn = ctx.config.cdn !== false && construct.servers.length > 0 ? await ensureBucketCdn(ctx, {
|
|
2129
|
+
bucket,
|
|
2130
|
+
region: clients.region,
|
|
2131
|
+
servers: construct.servers,
|
|
2132
|
+
origins: bucketOrigins({
|
|
2133
|
+
workspace: ctx.workspace,
|
|
2134
|
+
manifest: ctx.manifest,
|
|
2135
|
+
runnables: ctx.runnables,
|
|
2136
|
+
stage: ctx.stage,
|
|
2137
|
+
bucket: id
|
|
2138
|
+
}).origins
|
|
2139
|
+
}) : void 0;
|
|
1075
2140
|
await ensureBucketSettings(ctx, clients, {
|
|
1076
2141
|
id,
|
|
1077
2142
|
bucket,
|
|
1078
2143
|
exists,
|
|
1079
2144
|
versioning,
|
|
1080
2145
|
open,
|
|
2146
|
+
...cdn ? { cdn } : {},
|
|
1081
2147
|
...construct.expire ? { expire: construct.expire } : {}
|
|
1082
2148
|
});
|
|
2149
|
+
const events = watchers ? await ensureBucketEvents(ctx, clients, {
|
|
2150
|
+
id,
|
|
2151
|
+
bucket,
|
|
2152
|
+
region: clients.region
|
|
2153
|
+
}) : void 0;
|
|
1083
2154
|
const user = await ensureIamUser(ctx, clients, {
|
|
1084
2155
|
id,
|
|
1085
2156
|
user: iamUserName(nameInput),
|
|
1086
2157
|
policyName: IAM_POLICY_NAME,
|
|
1087
|
-
policy: userPolicy(bucket, {
|
|
2158
|
+
policy: userPolicy(bucket, {
|
|
2159
|
+
versioning,
|
|
2160
|
+
...events ? { eventsTopicArn: events.topicArn } : {}
|
|
2161
|
+
}),
|
|
1088
2162
|
describe: "this bucket only"
|
|
1089
2163
|
});
|
|
1090
2164
|
await ensureAccessKey(ctx, clients, {
|
|
@@ -1093,8 +2167,22 @@ async function ensureBucket(ctx, stageClients, construct) {
|
|
|
1093
2167
|
urlKey: (0, _geekmidas_manifest.provideKey)(id, "url"),
|
|
1094
2168
|
...user
|
|
1095
2169
|
});
|
|
1096
|
-
|
|
2170
|
+
if (events && watchers) {
|
|
2171
|
+
await ensureEventsKey(ctx, {
|
|
2172
|
+
id,
|
|
2173
|
+
topicArn: events.topicArn,
|
|
2174
|
+
region: clients.region
|
|
2175
|
+
});
|
|
2176
|
+
await ensurePushSubscriptions(ctx, {
|
|
2177
|
+
id,
|
|
2178
|
+
events,
|
|
2179
|
+
region: clients.region,
|
|
2180
|
+
watchers
|
|
2181
|
+
});
|
|
2182
|
+
}
|
|
2183
|
+
const bucketUrl = bucketPublicUrl(bucket, clients.region, clients.endpoint);
|
|
1097
2184
|
for (const server of construct.servers) {
|
|
2185
|
+
const publicUrl = cdn?.urls[server.id] ?? bucketUrl;
|
|
1098
2186
|
const key = (0, _geekmidas_manifest.provideKey)(server.id, "url");
|
|
1099
2187
|
const entry = {
|
|
1100
2188
|
key: `file-server:${server.id}`,
|
|
@@ -1252,7 +2340,7 @@ async function ensureBucketSettings(ctx, clients, input) {
|
|
|
1252
2340
|
Value: t.Value
|
|
1253
2341
|
})) }
|
|
1254
2342
|
})));
|
|
1255
|
-
const isOpen = input.open.length > 0;
|
|
2343
|
+
const isOpen = input.open.length > 0 && !input.cdn;
|
|
1256
2344
|
const block = publicAccessBlock(isOpen);
|
|
1257
2345
|
const ensureBlock = async () => {
|
|
1258
2346
|
const now = (await read(() => s3.send(new S3.GetPublicAccessBlockCommand({ Bucket })), ["NoSuchPublicAccessBlockConfiguration"]))?.PublicAccessBlockConfiguration;
|
|
@@ -1261,10 +2349,10 @@ async function ensureBucketSettings(ctx, clients, input) {
|
|
|
1261
2349
|
PublicAccessBlockConfiguration: block
|
|
1262
2350
|
})));
|
|
1263
2351
|
};
|
|
1264
|
-
const policy = bucketPolicy(bucket, input.open);
|
|
2352
|
+
const policy = bucketPolicy(bucket, input.open, input.cdn ? { cdn: input.cdn } : {});
|
|
1265
2353
|
const ensurePolicy = async () => {
|
|
1266
2354
|
const current = await read(() => s3.send(new S3.GetBucketPolicyCommand({ Bucket })), ["NoSuchBucketPolicy"]);
|
|
1267
|
-
if (!(current?.Policy !== void 0 && canonicalPolicy(JSON.parse(current.Policy)) === canonicalPolicy(policy))) await set(isOpen ? `policy: TLS only, public read on ${input.open.join(", ")}` : "policy: TLS only", () => s3.send(new S3.PutBucketPolicyCommand({
|
|
2355
|
+
if (!(current?.Policy !== void 0 && canonicalPolicy(JSON.parse(current.Policy)) === canonicalPolicy(policy))) await set(isOpen ? `policy: TLS only, public read on ${input.open.join(", ")}` : input.cdn && input.open.length > 0 ? `policy: TLS only, CloudFront reads ${input.open.join(", ")}` : "policy: TLS only", () => s3.send(new S3.PutBucketPolicyCommand({
|
|
1268
2356
|
Bucket,
|
|
1269
2357
|
Policy: JSON.stringify(policy)
|
|
1270
2358
|
})));
|
|
@@ -1394,9 +2482,14 @@ const BUILTIN_PROVIDERS = { objects: { s3: {
|
|
|
1394
2482
|
name: "s3",
|
|
1395
2483
|
check: require_workspace.checkS3Config,
|
|
1396
2484
|
provisioning: require_workspace.S3_PROVISIONING,
|
|
1397
|
-
credentials: awsProvisioningCredentials,
|
|
1398
|
-
runtimeKeys(manifest) {
|
|
1399
|
-
|
|
2485
|
+
credentials: require_snsSubscriptions.awsProvisioningCredentials,
|
|
2486
|
+
runtimeKeys(manifest, runnables = {}) {
|
|
2487
|
+
const watched = bucketWatchers(manifest, runnables);
|
|
2488
|
+
return bucketConstructs(manifest).flatMap((b) => [
|
|
2489
|
+
(0, _geekmidas_manifest.provideKey)(b.id, "url"),
|
|
2490
|
+
...watched.has(b.id) ? [(0, _geekmidas_manifest.provideKey)(b.id, "eventsConnectionString")] : [],
|
|
2491
|
+
...b.servers.map((s) => (0, _geekmidas_manifest.provideKey)(s.id, "url"))
|
|
2492
|
+
]);
|
|
1400
2493
|
},
|
|
1401
2494
|
ensure: ensureS3,
|
|
1402
2495
|
verify: verifyS3
|
|
@@ -1514,15 +2607,15 @@ async function provisionStage(options) {
|
|
|
1514
2607
|
report
|
|
1515
2608
|
});
|
|
1516
2609
|
}
|
|
1517
|
-
const backups =
|
|
2610
|
+
const backups = require_snsSubscriptions.stageBackups(workspace, manifest, stage);
|
|
1518
2611
|
if (configured.length === 0 && backups.mode !== "on") {
|
|
1519
2612
|
if (backups.mode === "disabled") log(`💾 backups: off — deploy.backups.${stage} is false. Nothing is backed up, and nothing already backed up is deleted.`);
|
|
1520
2613
|
return reports;
|
|
1521
2614
|
}
|
|
1522
2615
|
const identity = require_identity.deployIdentity(workspace, stage);
|
|
1523
2616
|
let secrets = (await options.secrets.read())?.custom ?? {};
|
|
1524
|
-
for (const entry of configured) entry.report.planned = entry.provider.runtimeKeys(manifest).filter((key) => secrets[key] === void 0);
|
|
1525
|
-
const state = dryRun ? await readOnlyState(options.state, stage) : journalState(await require_seeds.DeployJournal.open(options.state, stage, () =>
|
|
2617
|
+
for (const entry of configured) entry.report.planned = entry.provider.runtimeKeys(manifest, runnables).filter((key) => secrets[key] === void 0);
|
|
2618
|
+
const state = dryRun ? await readOnlyState(options.state, stage) : journalState(await require_seeds.DeployJournal.open(options.state, stage, () => require_snsSubscriptions.deploysWithCompose(workspace) ? require_seeds.createComposeState(stage) : require_seeds.createEmptyState(stage, "", "")));
|
|
1526
2619
|
const writeSecrets = async (values) => {
|
|
1527
2620
|
if (dryRun) return;
|
|
1528
2621
|
const stored = await options.secrets.read() ?? require_seeds.initStageSecrets(stage);
|
|
@@ -1543,6 +2636,7 @@ async function provisionStage(options) {
|
|
|
1543
2636
|
workspace,
|
|
1544
2637
|
manifest,
|
|
1545
2638
|
runnables,
|
|
2639
|
+
...options.subscribers ? { subscribers: options.subscribers } : {},
|
|
1546
2640
|
stage,
|
|
1547
2641
|
identity,
|
|
1548
2642
|
config: entry.config,
|
|
@@ -1560,6 +2654,17 @@ async function provisionStage(options) {
|
|
|
1560
2654
|
log(` ${dryRun ? "would " : ""}${action.change} — ${action.resource}`);
|
|
1561
2655
|
},
|
|
1562
2656
|
writeSecrets,
|
|
2657
|
+
writeDns: (records) => writeProviderDns(records, {
|
|
2658
|
+
workspace,
|
|
2659
|
+
stage,
|
|
2660
|
+
dryRun,
|
|
2661
|
+
credentials: options.credentials ?? require_credentials.storedCredentials({
|
|
2662
|
+
env,
|
|
2663
|
+
...options.home ? { home: options.home } : {}
|
|
2664
|
+
}),
|
|
2665
|
+
...options.dnsProviderFor ? { providerFor: options.dnsProviderFor } : {},
|
|
2666
|
+
log
|
|
2667
|
+
}),
|
|
1563
2668
|
log
|
|
1564
2669
|
});
|
|
1565
2670
|
if (entry.report.actions.length === 0) log(" up to date");
|
|
@@ -1569,7 +2674,7 @@ async function provisionStage(options) {
|
|
|
1569
2674
|
kind: "backups",
|
|
1570
2675
|
mode: backups.mode === "on" ? "provider" : "none",
|
|
1571
2676
|
actions: [],
|
|
1572
|
-
planned: backups.mode === "on" && secrets["BACKUPS_URL"] === void 0 ? [
|
|
2677
|
+
planned: backups.mode === "on" && secrets["BACKUPS_URL"] === void 0 ? [require_snsSubscriptions.BACKUPS_URL_KEY] : []
|
|
1573
2678
|
};
|
|
1574
2679
|
reports.push(report);
|
|
1575
2680
|
await ensureStageBackups({
|
|
@@ -1910,7 +3015,7 @@ async function runDeploy(source, request, ctx) {
|
|
|
1910
3015
|
const resources = !ctx.buildOnly && phaseCtx.stage !== phaseCtx.workspace.stages.local;
|
|
1911
3016
|
const provision = async () => {
|
|
1912
3017
|
if (!request.skipDns && resolved.target.runtime === "server") try {
|
|
1913
|
-
await
|
|
3018
|
+
await require_snsSubscriptions.dnsCredentials(phaseCtx.stage, require_snsSubscriptions.stageDnsDomains(phaseCtx.stage, phaseCtx.workspace.domains, phaseCtx.workspace.dns), ctx.credentials);
|
|
1914
3019
|
} catch (error) {
|
|
1915
3020
|
emit({
|
|
1916
3021
|
type: "phase.failed",
|
|
@@ -1995,12 +3100,14 @@ async function provisionResources(phaseCtx, request, ctx, store, emit) {
|
|
|
1995
3100
|
stage: phaseCtx.stage,
|
|
1996
3101
|
manifest: phaseCtx.manifest,
|
|
1997
3102
|
runnables: phaseCtx.runnables ?? {},
|
|
3103
|
+
...phaseCtx.subscribers ? { subscribers: phaseCtx.subscribers } : {},
|
|
1998
3104
|
secrets: phaseCtx.secrets,
|
|
1999
3105
|
state: store,
|
|
2000
3106
|
dryRun: ctx.dryRun,
|
|
2001
3107
|
...request.rotateKeys ? { rotateKeys: true } : {},
|
|
2002
3108
|
...request.retireOldKeys ? { retireOldKeys: true } : {},
|
|
2003
3109
|
...ctx.home ? { home: ctx.home } : {},
|
|
3110
|
+
credentials: ctx.credentials,
|
|
2004
3111
|
log: (line) => phaseCtx.logger.info(line)
|
|
2005
3112
|
});
|
|
2006
3113
|
if (!ctx.dryRun) return;
|
|
@@ -2086,14 +3193,16 @@ async function prepare(source, request, ctx, emit, redactor) {
|
|
|
2086
3193
|
const options = await require_resolve.parseTargetOptions(resolved);
|
|
2087
3194
|
const allowDevServices = request.allowDevServices === true;
|
|
2088
3195
|
if (allowDevServices && resolved.target.runtime === "aws") throw new require_seeds.DevServicesNeedServerTarget(targetName);
|
|
2089
|
-
|
|
3196
|
+
require_snsSubscriptions.assertSitePasswordTarget(configured, stage, resolved.target.name);
|
|
2090
3197
|
const runnables = {};
|
|
2091
3198
|
const background = {};
|
|
3199
|
+
const subscribers = {};
|
|
2092
3200
|
const manifest = await require_discover.discover({
|
|
2093
3201
|
patterns: require_seeds.constructGlobs(configured),
|
|
2094
3202
|
cwd: configured.root,
|
|
2095
3203
|
runnables,
|
|
2096
|
-
background
|
|
3204
|
+
background,
|
|
3205
|
+
subscribers
|
|
2097
3206
|
});
|
|
2098
3207
|
require_workspace.resolveStageTelemetry({
|
|
2099
3208
|
...configured.deploy?.telemetry ? { telemetry: configured.deploy.telemetry } : {},
|
|
@@ -2174,6 +3283,7 @@ async function prepare(source, request, ctx, emit, redactor) {
|
|
|
2174
3283
|
manifest,
|
|
2175
3284
|
runnables,
|
|
2176
3285
|
background,
|
|
3286
|
+
subscribers,
|
|
2177
3287
|
options,
|
|
2178
3288
|
dryRun: ctx.dryRun,
|
|
2179
3289
|
atomic: request.atomic ?? false,
|
|
@@ -2268,10 +3378,10 @@ function deploy(input) {
|
|
|
2268
3378
|
};
|
|
2269
3379
|
const ctx = {
|
|
2270
3380
|
emit,
|
|
2271
|
-
credentials: input.credentials ?? require_credentials.storedCredentials({ home: input.home ??
|
|
3381
|
+
credentials: input.credentials ?? require_credentials.storedCredentials({ home: input.home ?? require_credentials.gkmHome() }),
|
|
2272
3382
|
dryRun: input.dryRun ?? false,
|
|
2273
3383
|
...input.buildOnly ? { buildOnly: true } : {},
|
|
2274
|
-
home: input.home ??
|
|
3384
|
+
home: input.home ?? require_credentials.gkmHome(),
|
|
2275
3385
|
redactor,
|
|
2276
3386
|
...input.signal ? { signal: input.signal } : {},
|
|
2277
3387
|
...input.childOutput ? { childOutput: input.childOutput } : {},
|
|
@@ -2356,24 +3466,6 @@ Object.defineProperty(exports, "UnknownDeployApps", {
|
|
|
2356
3466
|
return UnknownDeployApps;
|
|
2357
3467
|
}
|
|
2358
3468
|
});
|
|
2359
|
-
Object.defineProperty(exports, "awsClientConfig", {
|
|
2360
|
-
enumerable: true,
|
|
2361
|
-
get: function() {
|
|
2362
|
-
return awsClientConfig;
|
|
2363
|
-
}
|
|
2364
|
-
});
|
|
2365
|
-
Object.defineProperty(exports, "awsEndpoint", {
|
|
2366
|
-
enumerable: true,
|
|
2367
|
-
get: function() {
|
|
2368
|
-
return awsEndpoint;
|
|
2369
|
-
}
|
|
2370
|
-
});
|
|
2371
|
-
Object.defineProperty(exports, "awsProvisioningCredentials", {
|
|
2372
|
-
enumerable: true,
|
|
2373
|
-
get: function() {
|
|
2374
|
-
return awsProvisioningCredentials;
|
|
2375
|
-
}
|
|
2376
|
-
});
|
|
2377
3469
|
Object.defineProperty(exports, "backupsUserName", {
|
|
2378
3470
|
enumerable: true,
|
|
2379
3471
|
get: function() {
|
|
@@ -2399,4 +3491,4 @@ Object.defineProperty(exports, "iamUserName", {
|
|
|
2399
3491
|
}
|
|
2400
3492
|
});
|
|
2401
3493
|
|
|
2402
|
-
//# sourceMappingURL=deploy-
|
|
3494
|
+
//# sourceMappingURL=deploy-DVrNjX1X.cjs.map
|