@@ -44,19 +44,18 @@ const OBELISK_BIN_PATH: &str = "/obelisk/obelisk";
4444const REGION : Region = Region :: Ams ;
4545const WEBHOOK_PORT : u16 = 9090 ;
4646
47- fn app_modify_without_cleanup (
48- app_name : & str ,
49- config : ObeliskConfig ,
50- ) -> Result < Vec < String > , AppInitModifyError > {
51- // Allocate an IPv6 address first.
47+ fn allocate_ip ( app_name : & str ) -> Result < ( ) , AppInitModifyError > {
5248 activity_fly_http:: ips:: allocate (
5349 app_name,
5450 IpRequest {
5551 config : IpVariant :: Ipv6 ( Ipv6Config { region : None } ) ,
5652 } ,
5753 )
58- . map_err ( AppInitModifyError :: IpAllocateError ) ?;
54+ . map ( |_ip| ( ) )
55+ . map_err ( AppInitModifyError :: IpAllocateError )
56+ }
5957
58+ fn setup_volume ( app_name : & str , config : & ObeliskConfig ) -> Result < ( ) , AppInitModifyError > {
6059 // Create a volume
6160 activity_fly_http:: volumes:: create (
6261 app_name,
@@ -124,7 +123,7 @@ fn app_modify_without_cleanup(
124123 }
125124
126125 // Write obelisk.toml
127- let obelisk_toml = serialize_obelisk_toml ( & config) ;
126+ let obelisk_toml = serialize_obelisk_toml ( config) ;
128127 let exec_response = activity_fly_http:: machines:: exec (
129128 app_name,
130129 & temp_vm,
@@ -168,11 +167,94 @@ fn app_modify_without_cleanup(
168167 activity_fly_http:: machines:: delete ( app_name, & temp_vm, true )
169168 . map_err ( AppInitModifyError :: TempVmError ) ?;
170169
171- // All OK, return secrets that are needed by the configuration.
172- Ok ( get_secret_keys ( config) )
170+ Ok ( ( ) )
171+ }
172+
173+ // Sleep until all requested secrets are stored in the app.
174+ fn wait_for_secrets (
175+ app_name : & str ,
176+ required_secrets : HashSet < String > ,
177+ sleep_between_retries_seconds : u32 ,
178+ ) -> Result < ( ) , AppInitModifyError > {
179+ while !required_secrets. is_empty ( ) {
180+ let actual_secrets = match activity_fly_http:: secrets:: list ( app_name) {
181+ Ok ( actual_secrets) => actual_secrets
182+ . into_iter ( )
183+ . map ( |secret| secret. name )
184+ . collect ( ) ,
185+ Err ( _) => {
186+ // has the app been deleted?
187+ match activity_fly_http:: apps:: get ( app_name) {
188+ Ok ( None ) => return Err ( AppInitModifyError :: AppDeleted ) ,
189+ Ok ( Some ( _) ) | Err ( _) => HashSet :: new ( ) , // app exists or unknown, keep looping.
190+ }
191+ }
192+ } ;
193+ if required_secrets. is_subset ( & actual_secrets) {
194+ break ;
195+ }
196+ workflow_support:: sleep ( ScheduleAt :: In ( SchedulingDuration :: Seconds (
197+ sleep_between_retries_seconds as u64 ,
198+ ) ) ) ;
199+ }
200+ Ok ( ( ) )
201+ }
202+
203+ fn launch_final_vm ( app_name : & str ) -> Result < ( ) , AppInitModifyError > {
204+ activity_fly_http:: machines:: create (
205+ app_name,
206+ VM_NAME_FINAL ,
207+ & MachineConfig {
208+ image : IMAGE . to_string ( ) ,
209+ guest : Some ( GuestConfig {
210+ cpu_kind : Some ( CpuKind :: Shared ) ,
211+ cpus : Some ( 1 ) ,
212+ memory_mb : Some ( 256 ) ,
213+ kernel_args : None ,
214+ } ) ,
215+ auto_destroy : None ,
216+ init : Some ( InitConfig {
217+ cmd : Some (
218+ vec ! [ "server" , "run" , "--config" , "/volume/obelisk.toml" ]
219+ . into_iter ( )
220+ . map ( ToString :: to_string)
221+ . collect ( ) ,
222+ ) ,
223+ entrypoint : None , // defaults to /obelisk/obelisk
224+ exec : None ,
225+ kernel_args : None ,
226+ swap_size_mb : Some ( 256 ) ,
227+ tty : None ,
228+ } ) ,
229+ env : None ,
230+ restart : Some ( MachineRestart {
231+ max_retries : None ,
232+ policy : RestartPolicy :: No ,
233+ } ) ,
234+ stop_config : None ,
235+ mounts : Some ( vec ! [ Mount {
236+ volume: VOLUME_NAME . to_string( ) ,
237+ path: VOLUME_MOUNT_PATH . to_string( ) ,
238+ } ] ) ,
239+ services : Some ( vec ! [ ServiceConfig {
240+ internal_port: WEBHOOK_PORT ,
241+ protocol: ServiceProtocol :: Tcp ,
242+ ports: vec![ PortConfig {
243+ port: 443 ,
244+ handlers: vec![ PortHandler :: Tls ] ,
245+ } ] ,
246+ } ] ) ,
247+ } ,
248+ Some ( REGION ) ,
249+ )
250+ . map ( |_| ( ) )
251+ . map_err ( AppInitModifyError :: FinalVmError )
173252}
174253
175254fn cleanup ( app_name : & str , modify_error : AppInitModifyError ) -> AppInitError {
255+ if matches ! ( modify_error, AppInitModifyError :: AppDeleted ) {
256+ return AppInitError :: CleanupOk ;
257+ }
176258 // Delete the app with force.
177259 match activity_fly_http:: apps:: delete ( app_name, true ) {
178260 Ok ( ( ) ) => AppInitError :: CleanupOk ,
@@ -201,8 +283,19 @@ impl Guest for Component {
201283 fn app_modify_no_cleanup_on_error (
202284 app_name : String ,
203285 config : ObeliskConfig ,
204- ) -> Result < Vec < String > , AppInitModifyError > {
205- app_modify_without_cleanup ( & app_name, config)
286+ sleep_between_retries_seconds : u32 ,
287+ ) -> Result < ( ) , AppInitModifyError > {
288+ // Allocate an IPv6 address first.
289+ allocate_ip ( & app_name) ?;
290+ // Put `obelisk.toml`, downloaded WASM files and codegen cache on a new volume.
291+ setup_volume ( & app_name, & config) ?;
292+ // Sleep until all requested secrets are stored in the app.
293+ let required_secrets = get_secret_keys ( config) ;
294+ wait_for_secrets ( & app_name, required_secrets, sleep_between_retries_seconds) ?;
295+ // All preparation is done, start the final VM.
296+ launch_final_vm ( & app_name) ?;
297+ // TODO Add a healthcheck to the exposed server and loop until success is reached, with configurable max retries. Cleanup on failure.
298+ Ok ( ( ) )
206299 }
207300
208301 fn app_init (
@@ -212,89 +305,18 @@ impl Guest for Component {
212305 sleep_between_retries_seconds : u32 ,
213306 ) -> Result < ( ) , AppInitError > {
214307 app_create ( & org_slug, & app_name) ?;
215- // Launch a child workflow by using import
216- let required_secrets = workflow_import:: app_modify_no_cleanup_on_error ( & app_name, & config)
217- . map_err ( |err| cleanup ( & app_name, err) ) ?;
218- // Sleep until all requested secrets are stored in the app.
219- let required_secrets: HashSet < _ > = required_secrets. into_iter ( ) . collect ( ) ;
220- while !required_secrets. is_empty ( ) {
221- let actual_secrets = match activity_fly_http:: secrets:: list ( & app_name) {
222- Ok ( actual_secrets) => actual_secrets
223- . into_iter ( )
224- . map ( |secret| secret. name )
225- . collect ( ) ,
226- Err ( _) => {
227- // has the app been deleted?
228- match activity_fly_http:: apps:: get ( & app_name) {
229- Ok ( None ) => return Err ( AppInitError :: AppDeleted ) ,
230- Ok ( Some ( _) ) | Err ( _) => HashSet :: new ( ) , // app exists or unknown, keep looping.
231- }
232- }
233- } ;
234- if required_secrets. is_subset ( & actual_secrets) {
235- break ;
236- }
237- workflow_support:: sleep ( ScheduleAt :: In ( SchedulingDuration :: Seconds (
238- sleep_between_retries_seconds as u64 ,
239- ) ) ) ;
240- }
241-
242- // Launch the final VM
243- activity_fly_http:: machines:: create (
308+ // Launch a child workflow by using import.
309+ // In case of any error including a trap (panic), delete the whole app.
310+ workflow_import:: app_modify_no_cleanup_on_error (
244311 & app_name,
245- VM_NAME_FINAL ,
246- & MachineConfig {
247- image : IMAGE . to_string ( ) ,
248- guest : Some ( GuestConfig {
249- cpu_kind : Some ( CpuKind :: Shared ) ,
250- cpus : Some ( 1 ) ,
251- memory_mb : Some ( 256 ) ,
252- kernel_args : None ,
253- } ) ,
254- auto_destroy : None ,
255- init : Some ( InitConfig {
256- cmd : Some (
257- vec ! [ "server" , "run" , "--config" , "/volume/obelisk.toml" ]
258- . into_iter ( )
259- . map ( ToString :: to_string)
260- . collect ( ) ,
261- ) ,
262- entrypoint : None , // defaults to /obelisk/obelisk
263- exec : None ,
264- kernel_args : None ,
265- swap_size_mb : Some ( 256 ) ,
266- tty : None ,
267- } ) ,
268- env : None ,
269- restart : Some ( MachineRestart {
270- max_retries : None ,
271- policy : RestartPolicy :: No ,
272- } ) ,
273- stop_config : None ,
274- mounts : Some ( vec ! [ Mount {
275- volume: VOLUME_NAME . to_string( ) ,
276- path: VOLUME_MOUNT_PATH . to_string( ) ,
277- } ] ) ,
278- services : Some ( vec ! [ ServiceConfig {
279- internal_port: WEBHOOK_PORT ,
280- protocol: ServiceProtocol :: Tcp ,
281- ports: vec![ PortConfig {
282- port: 443 ,
283- handlers: vec![ PortHandler :: Tls ] ,
284- } ] ,
285- } ] ) ,
286- } ,
287- Some ( REGION ) ,
312+ & config,
313+ sleep_between_retries_seconds,
288314 )
289- . map_err ( AppInitError :: FinalVmError ) ?;
290-
291- // TODO Add a healthcheck to the exposed server and loop until success is reached, with configurable max retries. Cleanup on failure.
292-
293- Ok ( ( ) )
315+ . map_err ( |err| cleanup ( & app_name, err) )
294316 }
295317}
296318
297- fn get_secret_keys ( config : ObeliskConfig ) -> Vec < String > {
319+ fn get_secret_keys ( config : ObeliskConfig ) -> HashSet < String > {
298320 let a_iter = config
299321 . activity_wasm_list
300322 . into_iter ( )
@@ -309,8 +331,7 @@ fn get_secret_keys(config: ObeliskConfig) -> Vec<String> {
309331 . flat_map ( |component| component. env_vars )
310332 . flatten ( )
311333 . filter ( |env_var| !env_var. contains ( "=" ) ) ;
312- let unique_keys: hashbrown:: HashSet < _ > = a_iter. chain ( w_iter) . collect ( ) ;
313- unique_keys. into_iter ( ) . collect ( )
334+ a_iter. chain ( w_iter) . collect ( )
314335}
315336
316337// FIXME: Insecure, use proper TOML serializer.
0 commit comments