Skip to content

Commit 40bcafb

Browse files
fix(workspace): honor profile_workspace in runtime profile resolution
Runtime profile lookups now consult provider.profile_workspace via get_type_profile_for_scope. Providers created with --global-profile (profile_workspace="") resolve to the platform profile even when a workspace profile shadows the same ID. All 6 runtime call sites updated; type-only call sites remain scope-agnostic. Signed-off-by: Derek Carr <decarr@redhat.com>
1 parent f8ba6df commit 40bcafb

3 files changed

Lines changed: 340 additions & 10 deletions

File tree

‎crates/openshell-server/src/grpc/policy.rs‎

Lines changed: 83 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -542,9 +542,11 @@ async fn build_credential_set_for_sandbox_with_catalog(
542542

543543
let provider_type = provider.r#type.trim();
544544
let profile_id = normalize_provider_type(provider_type).unwrap_or(provider_type);
545-
let Some(profile) =
546-
super::provider::get_provider_type_profile_with_catalog(catalog, profile_id)
547-
else {
545+
let Some(profile) = super::provider::get_provider_type_profile_for_scope(
546+
catalog,
547+
profile_id,
548+
&provider.profile_workspace,
549+
) else {
548550
warn!(
549551
provider_name = %name,
550552
provider_type,
@@ -1502,9 +1504,11 @@ async fn profile_provider_policy_layers_with_catalog(
15021504

15031505
let provider_type = provider.r#type.trim();
15041506
let profile_id = normalize_provider_type(provider_type).unwrap_or(provider_type);
1505-
let Some(profile) =
1506-
super::provider::get_provider_type_profile_with_catalog(catalog, profile_id)
1507-
else {
1507+
let Some(profile) = super::provider::get_provider_type_profile_for_scope(
1508+
catalog,
1509+
profile_id,
1510+
&provider.profile_workspace,
1511+
) else {
15081512
warn!(
15091513
provider_name = %name,
15101514
provider_type,
@@ -5431,6 +5435,79 @@ mod tests {
54315435
);
54325436
}
54335437

5438+
#[tokio::test]
5439+
async fn provider_policy_layers_respect_profile_workspace_scope() {
5440+
let store = test_store().await;
5441+
5442+
let make_stored_profile =
5443+
|id: &str, workspace: &str, host: &str| openshell_core::proto::StoredProviderProfile {
5444+
metadata: Some(openshell_core::proto::datamodel::v1::ObjectMeta {
5445+
id: format!("profile-{id}-{workspace}"),
5446+
name: id.to_string(),
5447+
created_at_ms: 1_000_000,
5448+
labels: HashMap::new(),
5449+
resource_version: 0,
5450+
annotations: HashMap::new(),
5451+
workspace: workspace.to_string(),
5452+
deletion_timestamp_ms: 0,
5453+
}),
5454+
profile: Some(openshell_core::proto::ProviderProfile {
5455+
id: id.to_string(),
5456+
display_name: format!("{host} profile"),
5457+
endpoints: vec![NetworkEndpoint {
5458+
host: host.to_string(),
5459+
port: 443,
5460+
..Default::default()
5461+
}],
5462+
..Default::default()
5463+
}),
5464+
};
5465+
5466+
store
5467+
.put_message(&make_stored_profile(
5468+
"scope-api",
5469+
"",
5470+
"platform.example.com",
5471+
))
5472+
.await
5473+
.unwrap();
5474+
store
5475+
.put_message(&make_stored_profile(
5476+
"scope-api",
5477+
"default",
5478+
"workspace.example.com",
5479+
))
5480+
.await
5481+
.unwrap();
5482+
5483+
let mut global_provider = test_provider("global-prov", "scope-api");
5484+
global_provider.profile_workspace = String::new();
5485+
store.put_message(&global_provider).await.unwrap();
5486+
5487+
let normal_provider = test_provider("normal-prov", "scope-api");
5488+
store.put_message(&normal_provider).await.unwrap();
5489+
5490+
let global_layers =
5491+
profile_provider_policy_layers(&store, "default", &["global-prov".to_string()])
5492+
.await
5493+
.unwrap();
5494+
assert_eq!(global_layers.len(), 1);
5495+
assert_eq!(
5496+
global_layers[0].rule.endpoints[0].host, "platform.example.com",
5497+
"provider with profile_workspace='' should resolve the platform profile"
5498+
);
5499+
5500+
let normal_layers =
5501+
profile_provider_policy_layers(&store, "default", &["normal-prov".to_string()])
5502+
.await
5503+
.unwrap();
5504+
assert_eq!(normal_layers.len(), 1);
5505+
assert_eq!(
5506+
normal_layers[0].rule.endpoints[0].host, "workspace.example.com",
5507+
"provider with profile_workspace='default' should resolve the workspace profile"
5508+
);
5509+
}
5510+
54345511
#[test]
54355512
fn providers_v2_enabled_defaults_false_when_unset() {
54365513
assert!(

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

Lines changed: 120 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -650,7 +650,9 @@ pub(super) async fn resolve_dynamic_credentials_with_catalog(
650650

651651
let profile_id =
652652
normalize_provider_type(&provider.r#type).unwrap_or(provider.r#type.as_str());
653-
let Some(profile) = get_provider_type_profile_with_catalog(catalog, profile_id) else {
653+
let Some(profile) =
654+
get_provider_type_profile_for_scope(catalog, profile_id, &provider.profile_workspace)
655+
else {
654656
continue;
655657
};
656658

@@ -1092,7 +1094,9 @@ fn dynamic_token_grant_bindings_for_provider_with_catalog(
10921094
) -> Vec<DynamicTokenGrantBinding> {
10931095
let provider_name = provider.object_name().to_string();
10941096
let profile_id = normalize_provider_type(&provider.r#type).unwrap_or(provider.r#type.as_str());
1095-
let Some(profile) = get_provider_type_profile_with_catalog(catalog, profile_id) else {
1097+
let Some(profile) =
1098+
get_provider_type_profile_for_scope(catalog, profile_id, &provider.profile_workspace)
1099+
else {
10961100
return Vec::new();
10971101
};
10981102
dynamic_token_grant_bindings_for_profile(&provider_name, &profile.to_proto())
@@ -1759,6 +1763,14 @@ pub(super) fn get_provider_type_profile_with_catalog(
17591763
catalog.get_type_profile(id)
17601764
}
17611765

1766+
pub(super) fn get_provider_type_profile_for_scope(
1767+
catalog: &EffectiveProviderProfileCatalog,
1768+
id: &str,
1769+
profile_workspace: &str,
1770+
) -> Option<ProviderTypeProfile> {
1771+
catalog.get_type_profile_for_scope(id, profile_workspace)
1772+
}
1773+
17621774
#[cfg(test)]
17631775
pub(super) async fn get_provider_type_profile(
17641776
store: &Store,
@@ -1796,7 +1808,11 @@ fn provider_refresh_defaults(
17961808
provider: &Provider,
17971809
credential_key: &str,
17981810
) -> Option<CredentialRefreshProfile> {
1799-
let profile = get_provider_type_profile_with_catalog(catalog, &provider.r#type)?;
1811+
let profile = get_provider_type_profile_for_scope(
1812+
catalog,
1813+
&provider.r#type,
1814+
&provider.profile_workspace,
1815+
)?;
18001816
profile
18011817
.credentials
18021818
.iter()
@@ -1819,7 +1835,9 @@ fn resolved_additional_output_keys(
18191835
provider: &Provider,
18201836
credential_key: &str,
18211837
) -> HashMap<String, String> {
1822-
let Some(profile) = get_provider_type_profile_with_catalog(catalog, &provider.r#type) else {
1838+
let Some(profile) =
1839+
get_provider_type_profile_for_scope(catalog, &provider.r#type, &provider.profile_workspace)
1840+
else {
18231841
return HashMap::new();
18241842
};
18251843
profile.resolved_additional_output_keys(credential_key)
@@ -9282,4 +9300,102 @@ mod tests {
92829300
"workspace profile should NOT appear in global list"
92839301
);
92849302
}
9303+
9304+
#[tokio::test]
9305+
async fn global_profile_provider_resolves_platform_profile_at_runtime() {
9306+
let state = test_server_state().await;
9307+
9308+
let mut platform_profile = custom_profile("scope-test");
9309+
platform_profile.display_name = "Platform Version".to_string();
9310+
handle_import_provider_profiles(
9311+
&state,
9312+
Request::new(ImportProviderProfilesRequest {
9313+
profiles: vec![ProviderProfileImportItem {
9314+
profile: Some(platform_profile),
9315+
source: "scope-test.yaml".to_string(),
9316+
}],
9317+
workspace: String::new(),
9318+
}),
9319+
)
9320+
.await
9321+
.unwrap();
9322+
9323+
let mut ws_profile = custom_profile("scope-test");
9324+
ws_profile.display_name = "Workspace Version".to_string();
9325+
handle_import_provider_profiles(
9326+
&state,
9327+
Request::new(ImportProviderProfilesRequest {
9328+
profiles: vec![ProviderProfileImportItem {
9329+
profile: Some(ws_profile),
9330+
source: "scope-test.yaml".to_string(),
9331+
}],
9332+
workspace: "default".to_string(),
9333+
}),
9334+
)
9335+
.await
9336+
.unwrap();
9337+
9338+
let catalog = state
9339+
.provider_profile_sources
9340+
.snapshot_catalog(state.store.as_ref(), "default")
9341+
.await
9342+
.unwrap();
9343+
9344+
let global_result = catalog.get_type_profile_for_scope("scope-test", "");
9345+
assert!(global_result.is_some());
9346+
assert_eq!(
9347+
global_result.unwrap().display_name,
9348+
"Platform Version",
9349+
"provider with profile_workspace='' should resolve platform profile"
9350+
);
9351+
}
9352+
9353+
#[tokio::test]
9354+
async fn normal_provider_resolves_workspace_shadow_at_runtime() {
9355+
let state = test_server_state().await;
9356+
9357+
let mut platform_profile = custom_profile("scope-test-ws");
9358+
platform_profile.display_name = "Platform Version".to_string();
9359+
handle_import_provider_profiles(
9360+
&state,
9361+
Request::new(ImportProviderProfilesRequest {
9362+
profiles: vec![ProviderProfileImportItem {
9363+
profile: Some(platform_profile),
9364+
source: "scope-test-ws.yaml".to_string(),
9365+
}],
9366+
workspace: String::new(),
9367+
}),
9368+
)
9369+
.await
9370+
.unwrap();
9371+
9372+
let mut ws_profile = custom_profile("scope-test-ws");
9373+
ws_profile.display_name = "Workspace Version".to_string();
9374+
handle_import_provider_profiles(
9375+
&state,
9376+
Request::new(ImportProviderProfilesRequest {
9377+
profiles: vec![ProviderProfileImportItem {
9378+
profile: Some(ws_profile),
9379+
source: "scope-test-ws.yaml".to_string(),
9380+
}],
9381+
workspace: "default".to_string(),
9382+
}),
9383+
)
9384+
.await
9385+
.unwrap();
9386+
9387+
let catalog = state
9388+
.provider_profile_sources
9389+
.snapshot_catalog(state.store.as_ref(), "default")
9390+
.await
9391+
.unwrap();
9392+
9393+
let ws_result = catalog.get_type_profile_for_scope("scope-test-ws", "default");
9394+
assert!(ws_result.is_some());
9395+
assert_eq!(
9396+
ws_result.unwrap().display_name,
9397+
"Workspace Version",
9398+
"provider with profile_workspace='default' should resolve workspace profile"
9399+
);
9400+
}
92859401
}

0 commit comments

Comments
 (0)