Skip to content

Commit 580ec83

Browse files
committed
refactor: Start minio VM during app-init
1 parent 6ce99cc commit 580ec83

4 files changed

Lines changed: 158 additions & 47 deletions

File tree

scripts/json-app-init-stargazers.sh

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
# Prints JSON containing arguments to `app-init` function.
44

5+
SKIP_CLEANUP=${SKIP_CLEANUP:-false}
6+
57
cat <<EOF
68
[
79
"$FLY_ORG_SLUG",
@@ -42,6 +44,7 @@ cat <<EOF
4244
}
4345
]
4446
},
45-
60
47+
60,
48+
$SKIP_CLEANUP
4649
]
4750
EOF

workflow/deployer-workflow/impl-flyio/src/lib.rs

Lines changed: 140 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,16 @@ export!(Component with_types_in generated);
3838

3939
const VOLUME_NAME: &str = "db";
4040
const VM_NAME_TEMP: &str = "temp";
41+
42+
const MAX_VM_FAILURE_RETRIES: u32 = 5;
43+
44+
const MINIO_VM_NAME: &str = "minio";
45+
const MINIO_IMAGE: &str = "minio/minio:RELEASE.2025-09-07T16-13-09Z-cpuv1";
46+
const MINIO_BUCKET_NAME: &str = "litestream-bucket";
47+
4148
const VM_NAME_FINAL: &str = "obelisk";
4249
const VOLUME_MOUNT_PATH: &str = "/volume";
43-
const IMAGE: &str = "getobelisk/obelisk:0.25.3-ubuntu";
50+
const FINAL_IMAGE: &str = "getobelisk/obelisk:0.25.3-ubuntu";
4451
const OBELISK_TOML_PATH: &str = formatcp!("{VOLUME_MOUNT_PATH}/obelisk.toml");
4552
const OBELISK_BIN_PATH: &str = "/obelisk/obelisk";
4653
const REGION: Region = Region::Ams;
@@ -69,6 +76,27 @@ fn allocate_ip(app_name: &str) -> Result<(), AppInitModifyError> {
6976
Ok(())
7077
}
7178

79+
fn wait_until_started(app_name: &str, machine_id: &str) -> Result<(), AppInitModifyError> {
80+
for _ in 0..10 {
81+
let machine = activity_fly_http::machines::get(app_name, machine_id)
82+
.map_err(AppInitModifyError::TempVmError)?;
83+
let state = machine
84+
.ok_or_else(|| {
85+
AppInitModifyError::TempVmError(
86+
"cannot find temp VM that was created successfuly".to_string(),
87+
)
88+
})?
89+
.state;
90+
if state == MachineState::Started {
91+
break;
92+
}
93+
workflow_support::sleep(ScheduleAt::In(SchedulingDuration::Seconds(
94+
SLEEP_BETWEEN_RETRIES.as_secs(),
95+
)));
96+
}
97+
Ok(())
98+
}
99+
72100
fn setup_volume(app_name: &str, obelisk_toml: &str) -> Result<(), AppInitModifyError> {
73101
// Create a volume
74102
activity_fly_http::volumes::create(
@@ -83,11 +111,11 @@ fn setup_volume(app_name: &str, obelisk_toml: &str) -> Result<(), AppInitModifyE
83111
.map_err(AppInitModifyError::VolumeCreateError)?;
84112

85113
// Launch a temporary VM
86-
let temp_vm = activity_fly_http::machines::create(
114+
let temp_vm_id = activity_fly_http::machines::create(
87115
app_name,
88116
VM_NAME_TEMP,
89117
&MachineConfig {
90-
image: IMAGE.to_string(),
118+
image: FINAL_IMAGE.to_string(),
91119
guest: Some(GuestConfig {
92120
cpu_kind: Some(CpuKind::Shared),
93121
cpus: Some(1),
@@ -119,29 +147,12 @@ fn setup_volume(app_name: &str, obelisk_toml: &str) -> Result<(), AppInitModifyE
119147
)
120148
.map_err(AppInitModifyError::TempVmError)?;
121149

122-
// Wait until its state is "started"
123-
for _ in 0..10 {
124-
let machine = activity_fly_http::machines::get(app_name, &temp_vm)
125-
.map_err(AppInitModifyError::TempVmError)?;
126-
let state = machine
127-
.ok_or_else(|| {
128-
AppInitModifyError::TempVmError(
129-
"cannot find temp VM that was created successfuly".to_string(),
130-
)
131-
})?
132-
.state;
133-
if state == MachineState::Started {
134-
break;
135-
}
136-
workflow_support::sleep(ScheduleAt::In(SchedulingDuration::Seconds(
137-
SLEEP_BETWEEN_RETRIES.as_secs(),
138-
)));
139-
}
150+
wait_until_started(app_name, &temp_vm_id)?;
140151

141152
// Write obelisk.toml
142153
let exec_response = activity_fly_http::machines::exec(
143154
app_name,
144-
&temp_vm,
155+
&temp_vm_id,
145156
&[
146157
"sh".to_string(),
147158
"-c".to_string(),
@@ -157,7 +168,7 @@ fn setup_volume(app_name: &str, obelisk_toml: &str) -> Result<(), AppInitModifyE
157168
// Download WASM Components, verify configuration.
158169
let exec_response = activity_fly_http::machines::exec(
159170
app_name,
160-
&temp_vm,
171+
&temp_vm_id,
161172
&[
162173
OBELISK_BIN_PATH.to_string(),
163174
"server".to_string(),
@@ -175,13 +186,13 @@ fn setup_volume(app_name: &str, obelisk_toml: &str) -> Result<(), AppInitModifyE
175186
}
176187
// Attempt to shutdown the temp VM.
177188
// Ignore failure to shut down, temp VM will be deleted with force.
178-
let _ = activity_fly_http::machines::stop(app_name, &temp_vm);
189+
let _ = activity_fly_http::machines::stop(app_name, &temp_vm_id);
179190
// Wait a bit for clean shutdown
180191
workflow_support::sleep(ScheduleAt::In(SchedulingDuration::Seconds(
181192
SLEEP_AFTER_TEMP_VM_SHUTDOWN.as_secs(),
182193
)));
183194
// Destroy the VM with force.
184-
activity_fly_http::machines::delete(app_name, &temp_vm, true)
195+
activity_fly_http::machines::delete(app_name, &temp_vm_id, true)
185196
.map_err(AppInitModifyError::TempVmError)?;
186197

187198
Ok(())
@@ -220,12 +231,74 @@ fn wait_for_secrets(
220231
Ok(())
221232
}
222233

223-
fn launch_final_vm(app_name: &str) -> Result<(), AppInitModifyError> {
224-
activity_fly_http::machines::create(
234+
fn minio_start(app_name: &str) -> Result<String, AppInitModifyError> {
235+
let machine_id = activity_fly_http::machines::create(
236+
app_name,
237+
MINIO_VM_NAME,
238+
&MachineConfig {
239+
image: MINIO_IMAGE.to_string(),
240+
guest: Some(GuestConfig {
241+
cpu_kind: Some(CpuKind::Shared),
242+
cpus: Some(1),
243+
memory_mb: Some(256),
244+
kernel_args: None,
245+
}),
246+
auto_destroy: None,
247+
init: Some(InitConfig {
248+
cmd: Some(
249+
"server /data --console-address :9001"
250+
.split(' ')
251+
.map(ToString::to_string)
252+
.collect(),
253+
),
254+
entrypoint: None,
255+
exec: None,
256+
kernel_args: None,
257+
swap_size_mb: None,
258+
tty: None,
259+
}),
260+
env: None,
261+
restart: Some(MachineRestart {
262+
max_retries: Some(MAX_VM_FAILURE_RETRIES),
263+
policy: RestartPolicy::OnFailure,
264+
}),
265+
stop_config: None,
266+
mounts: None,
267+
services: None,
268+
},
269+
Some(REGION),
270+
)
271+
.map_err(AppInitModifyError::MinioVmError)?;
272+
wait_until_started(app_name, &machine_id)?;
273+
Ok(machine_id)
274+
}
275+
276+
fn minio_configure(app_name: &str, machine_id: &str) -> Result<(), AppInitModifyError> {
277+
let exec = |command: &str| {
278+
let exec_response = activity_fly_http::machines::exec(
279+
app_name,
280+
machine_id,
281+
&command
282+
.split(' ')
283+
.map(ToString::to_string)
284+
.collect::<Vec<_>>(),
285+
)
286+
.map_err(AppInitModifyError::MinioVmError)?;
287+
assert_eq!(0, exec_response.exit_code.unwrap());
288+
Ok(())
289+
};
290+
exec("mc alias set myminio http://127.0.0.1:9000 minioadmin minioadmin")?;
291+
exec(&format!("mc mb myminio/{MINIO_BUCKET_NAME}"))?;
292+
exec("mc ls myminio --json")?;
293+
Ok(())
294+
}
295+
296+
fn start_final_vm(app_name: &str) -> Result<(), AppInitModifyError> {
297+
let machine_id = activity_fly_http::machines::create(
225298
app_name,
226299
VM_NAME_FINAL,
227300
&MachineConfig {
228-
image: IMAGE.to_string(),
301+
image: FINAL_IMAGE.to_string(),
229302
guest: Some(GuestConfig {
230303
cpu_kind: Some(CpuKind::Shared),
231304
cpus: Some(1),
@@ -240,16 +313,16 @@ fn launch_final_vm(app_name: &str) -> Result<(), AppInitModifyError> {
240313
.map(ToString::to_string)
241314
.collect(),
242315
),
243-
entrypoint: None, // defaults to /obelisk/obelisk
316+
entrypoint: None,
244317
exec: None,
245318
kernel_args: None,
246319
swap_size_mb: Some(256),
247320
tty: None,
248321
}),
249322
env: None,
250323
restart: Some(MachineRestart {
251-
max_retries: None,
252-
policy: RestartPolicy::No,
324+
max_retries: Some(MAX_VM_FAILURE_RETRIES),
325+
policy: RestartPolicy::OnFailure,
253326
}),
254327
stop_config: None,
255328
mounts: Some(vec![Mount {
@@ -279,8 +352,9 @@ fn launch_final_vm(app_name: &str) -> Result<(), AppInitModifyError> {
279352
},
280353
Some(REGION),
281354
)
282-
.map(|_| ())
283-
.map_err(AppInitModifyError::FinalVmError)
355+
.map_err(AppInitModifyError::FinalVmError)?;
356+
wait_until_started(app_name, &machine_id)?;
357+
Ok(())
284358
}
285359

286360
/// Sleep until the health check passes, observing the deadline, or the app is deleted.
@@ -307,13 +381,19 @@ fn check_health(app_name: &str, health_check_deadline_secs: u16) -> Result<(), A
307381
}
308382
}
309383

310-
fn cleanup(app_name: &str, modify_error: AppInitModifyError) -> AppInitError {
311-
if matches!(
312-
modify_error,
313-
AppInitModifyError::AppNameGetError
314-
| AppInitModifyError::AppNameConflict
315-
| AppInitModifyError::AppDeleted
316-
) {
384+
fn cleanup(
385+
app_name: &str,
386+
modify_error: AppInitModifyError,
387+
skip_cleanup_on_error: bool,
388+
) -> AppInitError {
389+
if skip_cleanup_on_error
390+
|| matches!(
391+
modify_error,
392+
AppInitModifyError::AppNameGetError
393+
| AppInitModifyError::AppNameConflict
394+
| AppInitModifyError::AppDeleted
395+
)
396+
{
317397
return AppInitError::CleanupNotRequired;
318398
}
319399
// Delete the app with force.
@@ -363,9 +443,16 @@ impl Guest for Component {
363443
Ok(())
364444
}
365445

446+
fn minio_start(app_name: String) -> Result<String, AppInitModifyError> {
447+
minio_start(&app_name)
448+
}
449+
450+
fn minio_configure(app_name: String, machine_id: String) -> Result<(), AppInitModifyError> {
451+
minio_configure(&app_name, &machine_id)
452+
}
453+
366454
fn start_final_vm(app_name: String) -> Result<(), AppInitModifyError> {
367-
launch_final_vm(&app_name)?;
368-
Ok(())
455+
start_final_vm(&app_name)
369456
}
370457

371458
fn wait_for_health_check(
@@ -381,19 +468,26 @@ impl Guest for Component {
381468
app_name: String,
382469
config: ObeliskConfig,
383470
health_check_deadline_secs: u16,
471+
skip_cleanup_on_error: bool,
384472
) -> Result<(), AppInitError> {
385473
// Launch sub-workflows by using import.
386474
// In case of any error including a trap (panic), delete the whole app.
387475
workflow_import::prepare(&org_slug, &app_name, &config)
388-
.map_err(|err| cleanup(&app_name, err))?;
476+
.map_err(|err| cleanup(&app_name, err, skip_cleanup_on_error))?;
389477

390478
workflow_import::wait_for_secrets(&app_name, &config)
391-
.map_err(|err| cleanup(&app_name, err))?;
479+
.map_err(|err| cleanup(&app_name, err, skip_cleanup_on_error))?;
480+
481+
let minio_vm_id = workflow_import::minio_start(&app_name)
482+
.map_err(|err| cleanup(&app_name, err, skip_cleanup_on_error))?;
483+
workflow_import::minio_configure(&app_name, &minio_vm_id)
484+
.map_err(|err| cleanup(&app_name, err, skip_cleanup_on_error))?;
392485

393-
workflow_import::start_final_vm(&app_name).map_err(|err| cleanup(&app_name, err))?;
486+
workflow_import::start_final_vm(&app_name)
487+
.map_err(|err| cleanup(&app_name, err, skip_cleanup_on_error))?;
394488

395489
workflow_import::wait_for_health_check(&app_name, health_check_deadline_secs)
396-
.map_err(|err| cleanup(&app_name, err))?;
490+
.map_err(|err| cleanup(&app_name, err, skip_cleanup_on_error))?;
397491

398492
Ok(())
399493
}

workflow/deployer-workflow/wit/obelisk-flyio_workflow@1.0.0-beta/types.wit

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ interface types {
5454
verify-error(string),
5555
/// Waiting for secrets was interrupted by deleting the app.
5656
app-deleted,
57+
/// Cannot start or configure MinIO VM
58+
minio-vm-error(string),
5759
/// Cannot start the final VM
5860
final-vm-error(string),
5961
health-check-failed,

workflow/deployer-workflow/wit/obelisk-flyio_workflow@1.0.0-beta/workflow.wit

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,17 @@ interface workflow {
1818
config: obelisk-config,
1919
) -> result<_, app-init-modify-error>;
2020

21+
/// Start the MinIO VM.
22+
minio-start: func(
23+
app-name: string,
24+
) -> result<string, app-init-modify-error>;
25+
26+
/// Initialize the litestream bucket.
27+
minio-configure: func(
28+
app-name: string,
29+
machine-id: string,
30+
) -> result<_, app-init-modify-error>;
31+
2132
/// Start the final VM.
2233
start-final-vm: func(
2334
app-name: string,
@@ -38,6 +49,7 @@ interface workflow {
3849
app-name: string,
3950
config: obelisk-config,
4051
health-check-deadline-secs: u16,
52+
skip-cleanup-on-error: bool,
4153
) -> result<_, app-init-error>;
4254
}
4355

0 commit comments

Comments
 (0)