@@ -12,6 +12,8 @@ export const RailwaySyncFns = {
1212 async getSecrets ( secretSync : TRailwaySyncWithCredentials ) : Promise < TSecretMap > {
1313 try {
1414 const config = secretSync . destinationConfig ;
15+ const { keySchema } = secretSync . syncOptions ;
16+ const { environment } = secretSync ;
1517
1618 const variables = await RailwayPublicAPI . getVariables ( secretSync . connection , {
1719 projectId : config . projectId ,
@@ -26,6 +28,10 @@ export const RailwaySyncFns = {
2628 // eslint-disable-next-line no-continue
2729 if ( key . startsWith ( "RAILWAY_" ) ) continue ;
2830
31+ // Check if key matches the schema
32+ // eslint-disable-next-line no-continue
33+ if ( ! matchesSchema ( key , environment ?. slug || "" , keySchema ) ) continue ;
34+
2935 entries [ key ] = {
3036 value
3137 } ;
@@ -40,85 +46,111 @@ export const RailwaySyncFns = {
4046 }
4147 } ,
4248
49+ /**
50+ * Syncs secrets to Railway and redeploys the service if needed.
51+ *
52+ * Gets existing Railway vars, merges with new secrets (keeping Railway vars if deletion is disabled),
53+ * then replaces every variable with the new values, if variable is not in the secretMap, it is deleted.
54+ * If there's a service, triggers a redeploy to pick up the changes.
55+ */
4356 async syncSecrets ( secretSync : TRailwaySyncWithCredentials , secretMap : TSecretMap ) {
44- const {
45- environment ,
46- syncOptions : { disableSecretDeletion, keySchema }
47- } = secretSync ;
48- const railwaySecrets = await this . getSecrets ( secretSync ) ;
49- const config = secretSync . destinationConfig ;
57+ try {
58+ const {
59+ syncOptions : { disableSecretDeletion }
60+ } = secretSync ;
61+ const railwaySecrets = await this . getSecrets ( secretSync ) ;
62+ const config = secretSync . destinationConfig ;
5063
51- for await ( const key of Object . keys ( secretMap ) ) {
52- try {
53- const existing = railwaySecrets [ key ] ;
54-
55- if ( existing === undefined || existing . value !== secretMap [ key ] . value ) {
56- await RailwayPublicAPI . upsertVariable ( secretSync . connection , {
57- input : {
58- projectId : config . projectId ,
59- environmentId : config . environmentId ,
60- serviceId : config . serviceId || undefined ,
61- name : key ,
62- value : secretMap [ key ] . value ?? ""
63- }
64- } ) ;
64+ const railwaySecretsMap = Object . fromEntries (
65+ Object . entries ( railwaySecrets ) . map ( ( [ key , secret ] ) => [ key , secret . value ] )
66+ ) ;
67+ const secretMapMap = Object . fromEntries ( Object . entries ( secretMap ) . map ( ( [ key , secret ] ) => [ key , secret . value ] ) ) ;
68+
69+ const toReplace = disableSecretDeletion ? { ...railwaySecretsMap , ...secretMapMap } : secretMapMap ;
70+
71+ const upserted = await RailwayPublicAPI . upsertCollection ( secretSync . connection , {
72+ input : {
73+ projectId : config . projectId ,
74+ environmentId : config . environmentId ,
75+ serviceId : config . serviceId || undefined ,
76+ skipDeploys : true ,
77+ variables : toReplace ,
78+ replace : true
6579 }
66- } catch ( error ) {
80+ } ) ;
81+
82+ if ( ! upserted )
6783 throw new SecretSyncError ( {
68- error,
69- secretKey : key
84+ message : "Failed to upsert secrets to Railway"
7085 } ) ;
71- }
72- }
7386
74- if ( disableSecretDeletion ) return ;
87+ if ( ! config . serviceId ) return ;
7588
76- for await ( const key of Object . keys ( railwaySecrets ) ) {
77- try {
78- // eslint-disable-next-line no-continue
79- if ( ! matchesSchema ( key , environment ?. slug || "" , keySchema ) ) continue ;
89+ const latestDeployment = await RailwayPublicAPI . getDeployments ( secretSync . connection , {
90+ input : {
91+ serviceId : config . serviceId ,
92+ environmentId : config . environmentId
93+ } ,
94+ first : 1
95+ } ) ;
8096
81- if ( ! secretMap [ key ] ) {
82- await RailwayPublicAPI . deleteVariable ( secretSync . connection , {
83- input : {
84- projectId : config . projectId ,
85- environmentId : config . environmentId ,
86- serviceId : config . serviceId || undefined ,
87- name : key
88- }
89- } ) ;
90- }
91- } catch ( error ) {
97+ const latestDeploymentId = latestDeployment ?. deployments . edges [ 0 ] . node . id ;
98+
99+ if ( ! latestDeploymentId )
92100 throw new SecretSyncError ( {
93- error,
94- secretKey : key
101+ message : "Failed to get latest deployment from Railway"
95102 } ) ;
96- }
103+
104+ await RailwayPublicAPI . redeployDeployment ( secretSync . connection , {
105+ input : {
106+ deploymentId : latestDeploymentId
107+ }
108+ } ) ;
109+ } catch ( error ) {
110+ if ( error instanceof SecretSyncError ) throw error ;
111+
112+ throw new SecretSyncError ( {
113+ error,
114+ message : "Failed to sync secrets to Railway"
115+ } ) ;
97116 }
98117 } ,
99118
100119 async removeSecrets ( secretSync : TRailwaySyncWithCredentials , secretMap : TSecretMap ) {
101120 const existing = await this . getSecrets ( secretSync ) ;
102121 const config = secretSync . destinationConfig ;
103122
104- for await ( const secret of Object . keys ( existing ) ) {
105- try {
106- if ( secret in secretMap ) {
107- await RailwayPublicAPI . deleteVariable ( secretSync . connection , {
108- input : {
109- projectId : config . projectId ,
110- environmentId : config . environmentId ,
111- serviceId : config . serviceId || undefined ,
112- name : secret
113- }
114- } ) ;
123+ // Create a new variables object excluding secrets that exist in secretMap
124+ const remainingVariables = Object . fromEntries (
125+ Object . entries ( existing )
126+ . filter ( ( [ key ] ) => ! ( key in secretMap ) )
127+ . map ( ( [ key , secret ] ) => [ key , secret . value ] )
128+ ) ;
129+
130+ try {
131+ const upserted = await RailwayPublicAPI . upsertCollection ( secretSync . connection , {
132+ input : {
133+ projectId : config . projectId ,
134+ environmentId : config . environmentId ,
135+ serviceId : config . serviceId || undefined ,
136+ skipDeploys : true ,
137+ variables : remainingVariables ,
138+ replace : true
115139 }
116- } catch ( error ) {
140+ } ) ;
141+
142+ if ( ! upserted ) {
117143 throw new SecretSyncError ( {
118- error,
119- secretKey : secret
144+ message : "Failed to remove secrets from Railway"
120145 } ) ;
121146 }
147+ } catch ( error ) {
148+ if ( error instanceof SecretSyncError ) throw error ;
149+
150+ throw new SecretSyncError ( {
151+ error,
152+ message : "Failed to remove secrets from Railway"
153+ } ) ;
122154 }
123155 }
124156} ;
0 commit comments