Skip to content

Commit 8aa4128

Browse files
committed
feat: Extract app-init-no-cleanup-on-error subworkflow for proper cleanup
Calling `app-init-no-cleanup-on-error` as a child execution has the benefit of containing execution failures (traps) and also catching and handling them.
1 parent 7115bf3 commit 8aa4128

4 files changed

Lines changed: 164 additions & 104 deletions

File tree

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

Lines changed: 60 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,25 @@ mod generated {
55
use const_format::formatcp;
66
use generated::{
77
export,
8-
exports::obelisk_flyio::workflow::workflow::{
9-
AppCleanupFailed, AppInitError, AppInitModifyError, Guest, ObeliskConfig, SecretKey,
10-
ServeError,
11-
},
8+
exports::obelisk_flyio::workflow::workflow::Guest,
129
obelisk::{types::time::ScheduleAt, workflow::workflow_support},
13-
obelisk_flyio::activity_fly_http::{
14-
self,
15-
machines::{
16-
CpuKind, GuestConfig, InitConfig, MachineConfig, MachineRestart, MachineState, Mount,
17-
RestartPolicy,
10+
obelisk_flyio::{
11+
activity_fly_http::{
12+
self,
13+
machines::{
14+
CpuKind, GuestConfig, InitConfig, MachineConfig, MachineRestart, MachineState,
15+
Mount, RestartPolicy,
16+
},
17+
regions::Region,
18+
volumes::VolumeCreateRequest,
19+
},
20+
workflow::{
21+
types::{AppCleanupFailed, AppInitModifyError},
22+
workflow::{
23+
self as workflow_import, AppInitError, AppInitNoCleanupError, ObeliskConfig,
24+
SecretKey, ServeError,
25+
},
1826
},
19-
regions::Region,
20-
volumes::VolumeCreateRequest,
2127
},
2228
};
2329

@@ -142,43 +148,66 @@ fn app_modify_without_cleanup(
142148
)));
143149
}
144150
// Attempt to shutdown the temp VM.
145-
let _ = activity_fly_http::machines::stop(app_name, &temp_vm); // Ignore failure to shut down, temp VM will be deleted with force.
151+
// Ignore failure to shut down, temp VM will be deleted with force.
152+
let _ = activity_fly_http::machines::stop(app_name, &temp_vm);
146153
activity_fly_http::machines::delete(app_name, &temp_vm, true)
147154
.map_err(AppInitModifyError::TempVmError)?;
148155

149156
// All OK, return secrets that are needed by the configuration.
150157
Ok(get_secret_keys(&config))
151158
}
152159

160+
fn cleanup(app_name: &str, modify_error: Option<AppInitModifyError>) -> AppInitError {
161+
// Delete the app with force.
162+
match activity_fly_http::apps::delete(app_name, true) {
163+
Ok(()) => AppInitError::CleanupOk,
164+
Err(cleanup_error) => AppInitError::CleanupFailed(AppCleanupFailed {
165+
modify_error,
166+
cleanup_error,
167+
}),
168+
}
169+
}
170+
153171
impl Guest for Component {
154-
fn app_init(
172+
fn app_init_no_cleanup_on_error(
155173
org_slug: String,
156174
app_name: String,
157-
cleanup: bool,
158175
config: ObeliskConfig,
159-
) -> Result<Vec<SecretKey>, AppInitError> {
176+
) -> Result<Vec<SecretKey>, AppInitNoCleanupError> {
160177
// If the app already exists, fail with AppNameConflict
161178
if activity_fly_http::apps::get(&app_name)
162-
.map_err(AppInitError::AppCreateError)?
179+
.map_err(AppInitNoCleanupError::AppCreateError)?
163180
.is_some()
164181
{
165-
return Err(AppInitError::AppNameConflict);
182+
return Err(AppInitNoCleanupError::AppNameConflict);
166183
}
167-
// Create app, clean up on error
168-
activity_fly_http::apps::put(&org_slug, &app_name).map_err(AppInitError::AppCreateError)?;
169-
app_modify_without_cleanup(&app_name, config).map_err(|modify_error| {
170-
if cleanup {
171-
match activity_fly_http::apps::delete(&app_name, true) {
172-
Ok(()) => AppInitError::CleanupOk(modify_error),
173-
Err(cleanup_error) => AppInitError::CleanupFailed(AppCleanupFailed {
174-
modify_error,
175-
cleanup_error,
176-
}),
184+
// Create the app
185+
activity_fly_http::apps::put(&org_slug, &app_name)
186+
.map_err(AppInitNoCleanupError::AppCreateError)?;
187+
app_modify_without_cleanup(&app_name, config)
188+
.map_err(AppInitNoCleanupError::AppInitModifyError)
189+
}
190+
191+
fn app_init(
192+
org_slug: String,
193+
app_name: String,
194+
config: ObeliskConfig,
195+
) -> Result<Vec<SecretKey>, AppInitError> {
196+
// Launch a child workflow by using import
197+
workflow_import::app_init_no_cleanup_on_error(&org_slug, &app_name, &config).map_err(
198+
|err| match err {
199+
AppInitNoCleanupError::AppCreateError(err) => {
200+
// No cleanup needed, app creation failed.
201+
AppInitError::AppCreateError(err)
177202
}
178-
} else {
179-
AppInitError::CleanupSkipped(modify_error)
180-
}
181-
})
203+
AppInitNoCleanupError::AppNameConflict => {
204+
// No cleanup needed, app creation failed on name conflict.
205+
AppInitError::AppNameConflict
206+
}
207+
AppInitNoCleanupError::AppInitModifyError(err) => cleanup(&app_name, Some(err)),
208+
AppInitNoCleanupError::ExecutionFailure => cleanup(&app_name, None),
209+
},
210+
)
182211
}
183212

184213
fn serve(_app_name: String) -> Result<(), ServeError> {
Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
package any:any;
22

33
world any {
4-
include obelisk-flyio:workflow/exports@1.0.0-beta;
4+
//include obelisk-flyio:workflow/exports@1.0.0-beta;
5+
export obelisk-flyio:workflow/workflow@1.0.0-beta;
6+
7+
// Import the same interface for creating child workflows.
8+
import obelisk-flyio:workflow/workflow@1.0.0-beta;
59

610
import obelisk-flyio:activity-fly-http/apps@1.0.0-beta;
711
import obelisk-flyio:activity-fly-http/machines@1.0.0-beta;
812
import obelisk-flyio:activity-fly-http/volumes@1.0.0-beta;
13+
914
import obelisk:workflow/workflow-support@3.0.0;
1015
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package obelisk-flyio:workflow@1.0.0-beta;
2+
3+
interface types {
4+
5+
// Same syntax as in obelisk.toml - "key" or "key=val"
6+
type env-var = string;
7+
8+
record obelisk-config {
9+
activity-wasm-list: option<list<activity-wasm>>,
10+
workflow-list: option<list<workflow>>,
11+
webhook-endpoint-list: option<list<webhok-endpoint>>,
12+
}
13+
14+
record activity-wasm {
15+
name: string,
16+
location-oci: string,
17+
env-vars: option<list<env-var>>,
18+
lock-expiry-seconds: option<u32>,
19+
}
20+
21+
record workflow {
22+
name: string,
23+
location-oci: string,
24+
}
25+
26+
record webhok-endpoint {
27+
name: string,
28+
location-oci: string,
29+
routes: list<route>,
30+
env-vars: option<list<env-var>>,
31+
}
32+
33+
record route {
34+
methods: list<string>,
35+
path: string,
36+
}
37+
38+
record secret-key {
39+
name: string,
40+
/// Set to true if the app already contains this secret.
41+
present: bool,
42+
}
43+
44+
variant app-init-modify-error {
45+
/// Cannot create the volume
46+
volume-create-error(string),
47+
/// Cannot start the temporary VM
48+
temp-vm-error(string),
49+
/// Cannot place files on the volume.
50+
volume-write-error(string),
51+
/// Error running `obelisk server verify -i`
52+
verify-error(string),
53+
}
54+
55+
record app-cleanup-failed {
56+
/// The associated value contains the reason of failure, if available.
57+
/// If the payload is missing a trap in child workflow occurred.
58+
modify-error: option<app-init-modify-error>,
59+
/// Reason why cleanup failed.
60+
cleanup-error: string,
61+
}
62+
63+
variant app-init-no-cleanup-error {
64+
app-name-conflict,
65+
app-create-error(string),
66+
app-init-modify-error(app-init-modify-error),
67+
execution-failure,
68+
}
69+
70+
variant app-init-error {
71+
app-name-conflict,
72+
app-create-error(string),
73+
/// App init failed, cleanup was successful.
74+
cleanup-ok,
75+
/// App init failed, cleanup failed.
76+
/// The associated value contains the reason of failure, if available.
77+
cleanup-failed(app-cleanup-failed),
78+
execution-failure,
79+
}
80+
81+
variant serve-error {
82+
vm-start-error(string),
83+
healthcheck-error(string),
84+
execution-failure,
85+
}
86+
87+
}

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

Lines changed: 11 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,7 @@
11
package obelisk-flyio:workflow@1.0.0-beta;
22

33
interface workflow {
4-
5-
// Same syntax as in obelisk.toml - "key" or "key=val"
6-
type env-var = string;
7-
8-
record obelisk-config {
9-
activity-wasm-list: option<list<activity-wasm>>,
10-
workflow-list: option<list<workflow>>,
11-
webhook-endpoint-list: option<list<webhok-endpoint>>,
12-
}
13-
14-
record activity-wasm {
15-
name: string,
16-
location-oci: string,
17-
env-vars: option<list<env-var>>,
18-
lock-expiry-seconds: option<u32>,
19-
}
20-
21-
record workflow {
22-
name: string,
23-
location-oci: string,
24-
}
25-
26-
record webhok-endpoint {
27-
name: string,
28-
location-oci: string,
29-
routes: list<route>,
30-
env-vars: option<list<env-var>>,
31-
}
32-
33-
record route {
34-
methods: list<string>,
35-
path: string,
36-
}
37-
38-
record secret-key {
39-
name: string,
40-
/// Set to true if the app already contains this secret.
41-
present: bool,
42-
}
43-
44-
variant app-init-modify-error {
45-
/// Cannot create the volume
46-
volume-create-error(string),
47-
/// Cannot start the temporary VM
48-
temp-vm-error(string),
49-
/// Cannot place files on the volume.
50-
volume-write-error(string),
51-
/// Error running `obelisk server verify -i`
52-
verify-error(string),
53-
}
54-
55-
record app-cleanup-failed {
56-
modify-error: app-init-modify-error,
57-
cleanup-error: string,
58-
}
59-
60-
variant app-init-error {
61-
app-name-conflict,
62-
app-create-error(string),
63-
cleanup-ok(app-init-modify-error),
64-
cleanup-failed(app-cleanup-failed),
65-
cleanup-skipped(app-init-modify-error),
66-
execution-failure,
67-
}
68-
69-
variant serve-error {
70-
vm-start-error(string),
71-
healthcheck-error(string),
72-
execution-failure,
73-
}
4+
use types.{obelisk-config, secret-key, app-init-no-cleanup-error, app-init-error, serve-error};
745

756
/// Chcek whether the app_name exists. If it does, return app-name-conflict.
767
/// Create a fly app.
@@ -79,11 +10,19 @@ interface workflow {
7910
/// Store the config file and download WASM components + AOT generated code (`obelisk server verify --ignore-missing-env-vars`).
8011
/// Shutdown and delete the temporary VM.
8112
/// Return list of secret keys the config requires.
82-
/// Any error except for app-name-conflict will trigger the whole app to be deleted, leaving the state as it was before this function was called.
13+
app-init-no-cleanup-on-error: func(
14+
org-slug: string,
15+
app-name: string,
16+
config: obelisk-config,
17+
) -> result<list<secret-key>, app-init-no-cleanup-error>;
18+
19+
/// Same as `app-init-no-cleanup` but performs cleanup on errors.
20+
/// If an error occurs during app configuration
21+
/// the app is deleted, leaving the state as it was before this function was called.
22+
/// If the cleanup fails as well `cleanup-error` is raised.
8323
app-init: func(
8424
org-slug: string,
8525
app-name: string,
86-
cleanup: bool,
8726
config: obelisk-config,
8827
) -> result<list<secret-key>, app-init-error>;
8928

0 commit comments

Comments
 (0)