Skip to content

Commit 1779545

Browse files
authored
v2: Support requirement levels on public v2 groups (#1584)
1 parent 2d53b1a commit 1779545

14 files changed

Lines changed: 319 additions & 35 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ All notable changes to this project will be documented in this file.
44

55
# Unreleased
66

7+
- 💥 BREAKING CHANGE 💥 Preserve per-attribute `requirement_level` on attribute refs of public attribute groups in the v2 resolved and materialized schemas. Each entry in an attribute group's `attributes` is now an object (`{ base, requirement_level }`) instead of a bare `attribute_catalog` index. ([#1584](https://github.com/open-telemetry/weaver/pull/1584) by @lmolkova)
78
- Use the OS-native certificate store (via ureq's `platform-verifier` feature) to validate TLS connections for remote registry downloads, instead of a fixed bundled root CA list. ([#1583](https://github.com/open-telemetry/weaver/pull/1583) by @jerbly)
89
- Fix panic when a registry, policy, or template path uses a commit SHA. ([#1414](https://github.com/open-telemetry/weaver/pull/1414))
910
- Add a stats dashboard with charts to the `serve` UI. ([#1570](https://github.com/open-telemetry/weaver/pull/1570) by @jerbly)

crates/weaver_forge/src/v2/attribute_group.rs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
use crate::v2::provenance::Provenance;
44
use schemars::JsonSchema;
55
use serde::{Deserialize, Serialize};
6-
use weaver_semconv::v2::{signal_id::SignalId, CommonFields};
6+
use weaver_semconv::{
7+
attribute::RequirementLevel,
8+
v2::{signal_id::SignalId, CommonFields},
9+
};
710

811
use crate::v2::attribute::Attribute;
912

@@ -14,7 +17,7 @@ pub struct AttributeGroup {
1417
/// The name of the attribute group, must be unique.
1518
pub id: SignalId,
1619
/// List of attributes.
17-
pub attributes: Vec<Attribute>,
20+
pub attributes: Vec<AttributeGroupAttribute>,
1821
/// Common fields (like brief, note, annotations).
1922
#[serde(flatten)]
2023
pub common: CommonFields,
@@ -23,3 +26,20 @@ pub struct AttributeGroup {
2326
#[serde(skip_serializing_if = "Provenance::is_empty")]
2427
pub provenance: Provenance,
2528
}
29+
30+
/// An attribute belonging to a public attribute group, carrying the
31+
/// group-specific requirement level.
32+
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema)]
33+
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
34+
#[serde(deny_unknown_fields)]
35+
pub struct AttributeGroupAttribute {
36+
/// Base attribute definition.
37+
#[serde(flatten)]
38+
pub base: Attribute,
39+
/// The requirement level of the attribute within this group. One of
40+
/// "required", "conditionally_required", "recommended" or "opt_in".
41+
/// When set to "conditionally_required", the string provided as
42+
/// `condition` MUST specify the conditions under which the attribute
43+
/// is required.
44+
pub requirement_level: RequirementLevel,
45+
}

crates/weaver_forge/src/v2/registry.rs

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use crate::{
1010
error::Error,
1111
v2::{
1212
attribute::Attribute,
13-
attribute_group::AttributeGroup,
13+
attribute_group::{AttributeGroup, AttributeGroupAttribute},
1414
entity::{Entity, EntityAttribute},
1515
event::{Event, EventAttribute, EventRefinement},
1616
metric::{Metric, MetricAttribute, MetricRefinement},
@@ -422,17 +422,20 @@ impl ForgeResolvedRegistry {
422422
.attributes
423423
.iter()
424424
.filter_map(|ar| {
425-
let attr = attribute_lookup(ar).map(|a| Attribute {
426-
key: a.key.clone(),
427-
r#type: a.r#type.clone(),
428-
examples: a.examples.clone(),
429-
common: a.common.clone(),
430-
provenance: resolve_provenance(&a.provenance),
425+
let attr = attribute_lookup(&ar.base).map(|a| AttributeGroupAttribute {
426+
base: Attribute {
427+
key: a.key.clone(),
428+
r#type: a.r#type.clone(),
429+
examples: a.examples.clone(),
430+
common: a.common.clone(),
431+
provenance: resolve_provenance(&a.provenance),
432+
},
433+
requirement_level: ar.requirement_level.clone(),
431434
});
432435
if attr.is_none() {
433436
errors.push(Error::AttributeNotFound {
434437
group_id: format!("attribute_group.{}", &ag.id),
435-
attr_ref: AttributeRef(ar.0),
438+
attr_ref: AttributeRef(ar.base.0),
436439
});
437440
}
438441
attr
@@ -567,7 +570,17 @@ mod tests {
567570
common: CommonFields::default(),
568571
provenance: Default::default(),
569572
}],
570-
attribute_groups: vec![],
573+
attribute_groups: vec![v2::attribute_group::AttributeGroup {
574+
id: SignalId::from("my-group".to_owned()),
575+
attributes: vec![v2::attribute_group::AttributeGroupAttributeRef {
576+
base: attribute::AttributeRef(0),
577+
requirement_level: weaver_semconv::attribute::RequirementLevel::Basic(
578+
weaver_semconv::attribute::BasicRequirementLevelSpec::Required,
579+
),
580+
}],
581+
common: CommonFields::default(),
582+
provenance: Default::default(),
583+
}],
571584
},
572585
refinements: v2::refinements::Refinements {
573586
spans: vec![span::SpanRefinement {
@@ -636,6 +649,18 @@ mod tests {
636649
assert_eq!(forge_registry.registry.metrics.len(), 1);
637650
assert_eq!(forge_registry.registry.events.len(), 1);
638651
assert_eq!(forge_registry.registry.entities.len(), 1);
652+
assert_eq!(forge_registry.registry.attribute_groups.len(), 1);
653+
654+
let group = &forge_registry.registry.attribute_groups[0];
655+
assert_eq!(group.id, "my-group".to_owned().into());
656+
assert_eq!(group.attributes.len(), 1);
657+
assert_eq!(group.attributes[0].base.key, "test.attr");
658+
assert_eq!(
659+
group.attributes[0].requirement_level,
660+
weaver_semconv::attribute::RequirementLevel::Basic(
661+
weaver_semconv::attribute::BasicRequirementLevelSpec::Required,
662+
)
663+
);
639664
assert_eq!(forge_registry.refinements.spans.len(), 1);
640665
assert_eq!(forge_registry.refinements.metrics.len(), 1);
641666
assert_eq!(forge_registry.refinements.events.len(), 1);

crates/weaver_resolved_schema/src/v2/attribute_group.rs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44
55
use schemars::JsonSchema;
66
use serde::{Deserialize, Serialize};
7-
use weaver_semconv::v2::{signal_id::SignalId, CommonFields};
7+
use weaver_semconv::{
8+
attribute::RequirementLevel,
9+
v2::{signal_id::SignalId, CommonFields},
10+
};
811

912
use crate::v2::{attribute::AttributeRef, provenance::Provenance, Signal};
1013

@@ -23,7 +26,7 @@ pub struct AttributeGroup {
2326
pub id: SignalId,
2427

2528
/// List of attributes and group references that belong to this group
26-
pub attributes: Vec<AttributeRef>,
29+
pub attributes: Vec<AttributeGroupAttributeRef>,
2730

2831
/// Common fields (like brief, note, annotations).
2932
#[serde(flatten)]
@@ -35,6 +38,22 @@ pub struct AttributeGroup {
3538
pub provenance: Provenance,
3639
}
3740

41+
/// A reference to an attribute in a public attribute group that remembers the
42+
/// group-specific requirement level refinement.
43+
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema)]
44+
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
45+
#[serde(deny_unknown_fields)]
46+
pub struct AttributeGroupAttributeRef {
47+
/// Reference, by index, to the attribute catalog.
48+
pub base: AttributeRef,
49+
/// The requirement level of the attribute within this group. One of
50+
/// "required", "conditionally_required", "recommended" or "opt_in".
51+
/// When set to "conditionally_required", the string provided as
52+
/// `condition` MUST specify the conditions under which the attribute
53+
/// is required.
54+
pub requirement_level: RequirementLevel,
55+
}
56+
3857
impl Signal for AttributeGroup {
3958
fn id(&self) -> &str {
4059
&self.id

crates/weaver_resolved_schema/src/v2/mod.rs

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -529,7 +529,10 @@ pub fn convert_v1_to_v2(
529529
// TODO - we need to check lineage and remove parent groups.
530530
for attr in g.attributes.iter().filter_map(|a| c.attribute(a)) {
531531
if let Some(a) = v2_catalog.convert_ref(attr) {
532-
attributes.push(a);
532+
attributes.push(attribute_group::AttributeGroupAttributeRef {
533+
base: a,
534+
requirement_level: attr.requirement_level.clone(),
535+
});
533536
} else {
534537
// TODO logic error!
535538
}
@@ -1190,6 +1193,79 @@ mod tests {
11901193
}
11911194
}
11921195

1196+
#[test]
1197+
fn test_convert_public_attribute_group_carries_requirement_level() {
1198+
use weaver_semconv::attribute::{BasicRequirementLevelSpec, RequirementLevel};
1199+
1200+
let mut builder = crate::catalog::test_utils::CatalogBuilder::default();
1201+
// The catalog attribute carries the requirement level that was
1202+
// resolved from the attribute ref refinement in the public group.
1203+
let ref0 = builder.add(
1204+
Attribute {
1205+
name: "test.key".to_owned(),
1206+
r#type: weaver_semconv::attribute::AttributeType::PrimitiveOrArray(
1207+
weaver_semconv::attribute::PrimitiveOrArrayTypeSpec::String,
1208+
),
1209+
brief: "".to_owned(),
1210+
examples: None,
1211+
tag: None,
1212+
requirement_level: RequirementLevel::Basic(BasicRequirementLevelSpec::Required),
1213+
sampling_relevant: None,
1214+
note: "".to_owned(),
1215+
stability: Some(Stability::Stable),
1216+
deprecated: None,
1217+
prefix: false,
1218+
tags: None,
1219+
annotations: None,
1220+
value: None,
1221+
role: None,
1222+
},
1223+
None,
1224+
);
1225+
let v1_catalog = builder.build();
1226+
let v1_registry = crate::registry::Registry {
1227+
registry_url: "my.schema.url".to_owned(),
1228+
groups: vec![Group {
1229+
id: "test.group".to_owned(),
1230+
r#type: GroupType::AttributeGroup,
1231+
brief: "a public group".to_owned(),
1232+
note: "".to_owned(),
1233+
prefix: "".to_owned(),
1234+
extends: None,
1235+
stability: Some(Stability::Stable),
1236+
deprecated: None,
1237+
attributes: vec![ref0],
1238+
span_kind: None,
1239+
events: vec![],
1240+
metric_name: None,
1241+
instrument: None,
1242+
unit: None,
1243+
requirement_level: None,
1244+
name: None,
1245+
lineage: None,
1246+
display_name: None,
1247+
body: None,
1248+
annotations: None,
1249+
entity_associations: vec![],
1250+
visibility: Some(AttributeGroupVisibilitySpec::Public),
1251+
is_v2: true,
1252+
span_name: None,
1253+
}],
1254+
};
1255+
let (_, v2_registry, _, _) = convert_v1_to_v2(v1_catalog, v1_registry, BTreeSet::new())
1256+
.expect("Failed to convert v1 to v2");
1257+
// The public attribute group is emitted...
1258+
assert_eq!(v2_registry.attribute_groups.len(), 1);
1259+
let group = &v2_registry.attribute_groups[0];
1260+
assert_eq!(group.id, "test.group".to_owned().into());
1261+
// ...and its attribute ref carries the group-specific requirement level.
1262+
assert_eq!(group.attributes.len(), 1);
1263+
assert_eq!(
1264+
group.attributes[0].requirement_level,
1265+
RequirementLevel::Basic(BasicRequirementLevelSpec::Required)
1266+
);
1267+
}
1268+
11931269
#[test]
11941270
fn test_try_from_v1_to_v2() {
11951271
let mut dependencies = BTreeSet::new();

crates/weaver_resolver/data/registry-test-published-2/published/resolved_schema.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ registry:
1616
attribute_groups:
1717
- id: imported.group.a
1818
attributes:
19-
- 1
19+
- base: 1
20+
requirement_level: recommended
2021
brief: a fun group
2122
note: a group note
2223
stability: stable

crates/weaver_resolver/data/registry-test-v2-5-published/expected-schema.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ registry:
2323
attribute_groups:
2424
- id: local.group
2525
attributes:
26-
- 1
26+
- base: 1
27+
requirement_level: required
2728
brief: local group
2829
stability: stable
2930
spans:

crates/weaver_resolver/data/registry-test-v2-5-published/published/resolved_schema.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ registry:
1616
attribute_groups:
1717
- id: imported.group.a
1818
attributes:
19-
- 1
19+
- base: 1
20+
requirement_level: recommended
2021
brief: a fun group
2122
note: a group note
2223
stability: stable

crates/weaver_resolver/data/registry-test-v2-5-published/registry/main.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ attribute_groups:
2626
stability: stable
2727
attributes:
2828
- ref: local.group.attr
29+
requirement_level: required
2930
imports:
3031
spans:
3132
- imported.*

crates/weaver_resolver/src/dependency.rs

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -935,15 +935,15 @@ impl ImportableDependency for V2Schema {
935935
}
936936
let mut attributes = vec![];
937937
for ar in ag.attributes.iter() {
938-
let attr = self.attribute_catalog.attribute(ar).ok_or(
938+
let attr = self.attribute_catalog.attribute(&ar.base).ok_or(
939939
Error::InvalidRegistryAttributeRef {
940940
registry_name: self.schema_url.name().to_owned(),
941-
attribute_ref: ar.0,
941+
attribute_ref: ar.base.0,
942942
},
943943
)?;
944944
let source = get_attribute_source(attr);
945945
attributes.push(attribute_catalog.attribute_ref_with_provenance(
946-
convert_v2_attribute(attr, RequirementLevel::default(), None),
946+
convert_v2_attribute(attr, ar.requirement_level.clone(), None),
947947
source,
948948
cache_lookup,
949949
)?);
@@ -1394,7 +1394,18 @@ mod tests {
13941394
attribute_groups: vec![
13951395
weaver_resolved_schema::v2::attribute_group::AttributeGroup {
13961396
id: "attribute_group.e".to_owned().into(),
1397-
attributes: vec![],
1397+
// A public group whose attribute ref carries a
1398+
// non-default requirement level; importing the group
1399+
// must preserve it.
1400+
attributes: vec![
1401+
weaver_resolved_schema::v2::attribute_group::AttributeGroupAttributeRef {
1402+
base: weaver_resolved_schema::v2::attribute::AttributeRef(0),
1403+
requirement_level:
1404+
weaver_semconv::attribute::RequirementLevel::Basic(
1405+
weaver_semconv::attribute::BasicRequirementLevelSpec::Required,
1406+
),
1407+
},
1408+
],
13981409
common: Default::default(),
13991410
provenance: Default::default(),
14001411
},
@@ -1439,7 +1450,15 @@ mod tests {
14391450
}],
14401451
attributes: vec![],
14411452
},
1442-
attribute_catalog: vec![],
1453+
attribute_catalog: vec![weaver_resolved_schema::v2::attribute::Attribute {
1454+
key: "attr.in.group".to_owned(),
1455+
r#type: weaver_semconv::attribute::AttributeType::PrimitiveOrArray(
1456+
weaver_semconv::attribute::PrimitiveOrArrayTypeSpec::String,
1457+
),
1458+
examples: None,
1459+
common: Default::default(),
1460+
provenance: Default::default(),
1461+
}],
14431462
refinements: weaver_resolved_schema::v2::refinements::Refinements {
14441463
spans: vec![],
14451464
metrics: vec![],
@@ -1570,6 +1589,26 @@ mod tests {
15701589
"Should import metric, event, entity, span and attribute_group"
15711590
);
15721591

1592+
// The imported public attribute group must preserve the per-attribute
1593+
// requirement level authored on its ref (rather than resetting it to
1594+
// the default).
1595+
let group = result
1596+
.iter()
1597+
.find(|g| g.group.id == "attribute_group.e")
1598+
.expect("attribute_group.e should be imported")
1599+
.group
1600+
.clone();
1601+
assert_eq!(group.attributes.len(), 1);
1602+
let attr = catalog
1603+
.attribute(&group.attributes[0])
1604+
.expect("imported attribute should exist in the catalog");
1605+
assert_eq!(
1606+
attr.requirement_level,
1607+
weaver_semconv::attribute::RequirementLevel::Basic(
1608+
weaver_semconv::attribute::BasicRequirementLevelSpec::Required,
1609+
)
1610+
);
1611+
15731612
Ok(())
15741613
}
15751614

0 commit comments

Comments
 (0)