Skip to content

Commit 8ee094b

Browse files
committed
fix(providers): serialize profile update validation
Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>
1 parent 4cc163e commit 8ee094b

4 files changed

Lines changed: 44 additions & 39 deletions

File tree

architecture/gateway.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,12 @@ responses copy the stored version onto the profile payload so exported YAML can
253253
carry the expected version for safe single-profile updates. Database migrations
254254
backfill existing rows with version 1.
255255

256+
Provider profile updates also hold the sandbox synchronization guard while
257+
checking attached-sandbox dynamic token grant ambiguity and writing the updated
258+
profile. Sandbox provider attach/detach uses the same guard, so one gateway
259+
process cannot interleave an attach with a profile update that would leave an
260+
ambiguous final dynamic-token state.
261+
256262
Policy and runtime settings are delivered together through the effective sandbox
257263
config path. A gateway-global policy can override sandbox-scoped policy. The
258264
sandbox supervisor polls for config revisions and hot-reloads dynamic policy

crates/openshell-cli/src/run.rs

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4955,6 +4955,21 @@ pub async fn provider_profile_export(
49554955
output: &str,
49564956
tls: &TlsOptions,
49574957
) -> Result<()> {
4958+
let rendered = provider_profile_export_text(server, id, output, tls).await?;
4959+
if output == "json" {
4960+
println!("{rendered}");
4961+
} else {
4962+
print!("{rendered}");
4963+
}
4964+
Ok(())
4965+
}
4966+
4967+
pub async fn provider_profile_export_text(
4968+
server: &str,
4969+
id: &str,
4970+
output: &str,
4971+
tls: &TlsOptions,
4972+
) -> Result<String> {
49584973
let mut client = grpc_client(server, tls).await?;
49594974
let response = client
49604975
.get_provider_profile(GetProviderProfileRequest { id: id.to_string() })
@@ -4966,16 +4981,14 @@ pub async fn provider_profile_export(
49664981
.ok_or_else(|| miette!("provider profile '{id}' not found"))?;
49674982
let profile = ProviderTypeProfile::from_proto(&profile);
49684983

4969-
if !crate::output::print_output_direct(
4970-
output,
4971-
|| profile_to_json(&profile).into_diagnostic(),
4972-
|| profile_to_yaml(&profile).into_diagnostic(),
4973-
)? {
4974-
return Err(miette!(
4984+
match output {
4985+
"json" => profile_to_json(&profile).into_diagnostic(),
4986+
"yaml" => profile_to_yaml(&profile).into_diagnostic(),
4987+
"table" => Err(miette!(
49754988
"profile export supports '-o yaml' and '-o json'; table output is not supported"
4976-
));
4989+
)),
4990+
_ => Err(miette!("unsupported output format: {output}")),
49774991
}
4978-
Ok(())
49794992
}
49804993

49814994
pub async fn provider_profile_import(

crates/openshell-cli/tests/provider_commands_integration.rs

Lines changed: 12 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1417,37 +1417,18 @@ binaries: [/usr/bin/custom]
14171417
run::provider_profile_import(&ts.endpoint, Some(&profile_path), None, &ts.tls)
14181418
.await
14191419
.expect("profile import");
1420-
let resource_version = ts
1421-
.state
1422-
.profiles
1423-
.lock()
1424-
.await
1425-
.get("custom-api")
1426-
.expect("custom profile")
1427-
.resource_version;
1428-
std::fs::write(
1429-
&profile_path,
1430-
format!(
1431-
r"
1432-
id: custom-api
1433-
resource_version: {resource_version}
1434-
display_name: Custom API Updated
1435-
category: other
1436-
credentials:
1437-
- name: api_key
1438-
env_vars: [CUSTOM_API_KEY]
1439-
auth_style: bearer
1440-
header_name: authorization
1441-
discovery:
1442-
credentials: [api_key]
1443-
endpoints:
1444-
- host: api.updated.example
1445-
port: 443
1446-
binaries: [/usr/bin/custom]
1447-
"
1448-
),
1449-
)
1450-
.unwrap();
1420+
let exported_yaml =
1421+
run::provider_profile_export_text(&ts.endpoint, "custom-api", "yaml", &ts.tls)
1422+
.await
1423+
.expect("profile export text");
1424+
assert!(exported_yaml.contains("resource_version: 1"));
1425+
let updated_yaml = exported_yaml
1426+
.replace(
1427+
"display_name: Custom API",
1428+
"display_name: Custom API Updated",
1429+
)
1430+
.replace("host: api.custom.example", "host: api.updated.example");
1431+
std::fs::write(&profile_path, updated_yaml).unwrap();
14511432
run::provider_profile_update(&ts.endpoint, &profile_path, &ts.tls)
14521433
.await
14531434
.expect("profile update");

crates/openshell-server/src/grpc/provider.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1375,6 +1375,11 @@ pub(super) async fn handle_update_provider_profiles(
13751375
severity: "error".to_string(),
13761376
});
13771377
}
1378+
let _sandbox_sync_guard = if has_errors(&diagnostics) {
1379+
None
1380+
} else {
1381+
Some(state.compute.sandbox_sync_guard().await)
1382+
};
13781383
if !has_errors(&diagnostics) {
13791384
diagnostics.extend(
13801385
profile_attached_sandbox_diagnostics(state.store.as_ref(), &profiles, "update").await?,

0 commit comments

Comments
 (0)