Skip to content

Commit 5920d19

Browse files
committed
[cmd/mdatagen] Add basic support for entities to metadata.yaml schema
When entities are defined, mdatagen generates `AssociateWith{EntityType}()` methods on ResourceBuilder that associate resources with entity types using the entity refs API. The entities section is backward compatible - existing metadata.yaml files without entities continue to work as before. This change is fully additive for now. The generated Go API is experimental and will change once #14039 is merged.
1 parent 2646139 commit 5920d19

24 files changed

+682
-0
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Use this changelog template to create an entry for release notes.
2+
3+
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
4+
change_type: enhancement
5+
6+
# The name of the component, or a single word describing the area of concern, (e.g. otlpreceiver)
7+
component: cmd/mdatagen
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: "`metadata.yaml` now supports an optional `entities` section to organize resource attributes into logical entities with identity and description attributes"
11+
12+
# One or more tracking issues or pull requests related to the change
13+
issues: [14051]
14+
15+
# (Optional) One or more lines of additional information to render under the primary note.
16+
# These lines will be padded with 2 spaces and then inserted directly into the document.
17+
# Use pipe (|) for multiline entries.
18+
subtext: |
19+
When entities are defined, mdatagen generates `AssociateWith{EntityType}()` methods on ResourceBuilder
20+
that associate resources with entity types using the entity refs API. The entities section is backward
21+
compatible - existing metadata.yaml files without entities continue to work as before.
22+
23+
# Optional: The change log or logs in which this entry should be included.
24+
# e.g. '[user]' or '[user, api]'
25+
# Include 'user' if the change is relevant to end users.
26+
# Include 'api' if there is a change to a library API.
27+
# Default: '[user]'
28+
change_logs: [user]

cmd/mdatagen/go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ require (
1616
go.opentelemetry.io/collector/consumer/consumertest v0.138.0
1717
go.opentelemetry.io/collector/filter v0.138.0
1818
go.opentelemetry.io/collector/pdata v1.44.0
19+
go.opentelemetry.io/collector/pdata/xpdata v0.138.0
1920
go.opentelemetry.io/collector/pipeline v1.44.0
2021
go.opentelemetry.io/collector/processor v1.44.0
2122
go.opentelemetry.io/collector/processor/processortest v0.138.0

cmd/mdatagen/internal/metadata.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ type Metadata struct {
3030
SemConvVersion string `mapstructure:"sem_conv_version"`
3131
// ResourceAttributes that can be emitted by the component.
3232
ResourceAttributes map[AttributeName]Attribute `mapstructure:"resource_attributes"`
33+
// Entities organizes resource attributes into logical entities.
34+
Entities map[string]Entity `mapstructure:"entities"`
3335
// Attributes emitted by one or more metrics.
3436
Attributes map[AttributeName]Attribute `mapstructure:"attributes"`
3537
// Metrics that can be emitted by the component.
@@ -56,6 +58,10 @@ func (md Metadata) GetCodeCovComponentID() string {
5658
return strings.ReplaceAll(md.Status.Class+"_"+md.Type, "/", "_")
5759
}
5860

61+
func (md Metadata) HasEntities() bool {
62+
return len(md.Entities) > 0
63+
}
64+
5965
func (md *Metadata) Validate() error {
6066
var errs error
6167
if err := md.validateType(); err != nil {
@@ -75,6 +81,10 @@ func (md *Metadata) Validate() error {
7581
errs = errors.Join(errs, err)
7682
}
7783

84+
if err := md.validateEntities(); err != nil {
85+
errs = errors.Join(errs, err)
86+
}
87+
7888
if err := md.validateMetricsAndEvents(); err != nil {
7989
errs = errors.Join(errs, err)
8090
}
@@ -122,6 +132,41 @@ func (md *Metadata) validateResourceAttributes() error {
122132
return errs
123133
}
124134

135+
func (md *Metadata) validateEntities() error {
136+
var errs error
137+
usedAttrs := make(map[AttributeName]string)
138+
139+
for entityType, entity := range md.Entities {
140+
if entityType == "" {
141+
errs = errors.Join(errs, fmt.Errorf("entity type cannot be empty"))
142+
}
143+
if len(entity.IdAttributes) == 0 {
144+
errs = errors.Join(errs, fmt.Errorf(`entity "%v": id_attributes is required`, entityType))
145+
}
146+
for _, attrName := range entity.IdAttributes {
147+
if _, ok := md.ResourceAttributes[attrName]; !ok {
148+
errs = errors.Join(errs, fmt.Errorf(`entity "%v": id_attributes refers to undefined resource attribute: %v`, entityType, attrName))
149+
}
150+
if otherEntity, used := usedAttrs[attrName]; used {
151+
errs = errors.Join(errs, fmt.Errorf(`entity "%v": attribute %v is already used by entity "%v"`, entityType, attrName, otherEntity))
152+
} else {
153+
usedAttrs[attrName] = entityType
154+
}
155+
}
156+
for _, attrName := range entity.DescriptionAttributes {
157+
if _, ok := md.ResourceAttributes[attrName]; !ok {
158+
errs = errors.Join(errs, fmt.Errorf(`entity "%v": description_attributes refers to undefined resource attribute: %v`, entityType, attrName))
159+
}
160+
if otherEntity, used := usedAttrs[attrName]; used {
161+
errs = errors.Join(errs, fmt.Errorf(`entity "%v": attribute %v is already used by entity "%v"`, entityType, attrName, otherEntity))
162+
} else {
163+
usedAttrs[attrName] = entityType
164+
}
165+
}
166+
}
167+
return errs
168+
}
169+
125170
func (md *Metadata) validateMetricsAndEvents() error {
126171
var errs error
127172
usedAttrs := map[AttributeName]bool{}
@@ -388,3 +433,10 @@ func (s Signal) HasOptionalAttribute(attrs map[AttributeName]Attribute) bool {
388433
}
389434
return false
390435
}
436+
437+
type Entity struct {
438+
// IdAttributes are the resource attributes that uniquely identify the entity.
439+
IdAttributes []AttributeName `mapstructure:"id_attributes"`
440+
// DescriptionAttributes are the resource attributes that describe the entity.
441+
DescriptionAttributes []AttributeName `mapstructure:"description_attributes"`
442+
}

cmd/mdatagen/internal/metadata_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,22 @@ func TestValidate(t *testing.T) {
120120
name: "testdata/no_type_attr.yaml",
121121
wantErr: "empty type for attribute: used_attr",
122122
},
123+
{
124+
name: "testdata/entity_undefined_id_attribute.yaml",
125+
wantErr: `entity "host": id_attributes refers to undefined resource attribute: host.missing`,
126+
},
127+
{
128+
name: "testdata/entity_undefined_description_attribute.yaml",
129+
wantErr: `entity "host": description_attributes refers to undefined resource attribute: host.missing`,
130+
},
131+
{
132+
name: "testdata/entity_empty_id_attributes.yaml",
133+
wantErr: `entity "host": id_attributes is required`,
134+
},
135+
{
136+
name: "testdata/entity_duplicate_attributes.yaml",
137+
wantErr: `entity "process": attribute host.name is already used by entity "host"`,
138+
},
123139
}
124140
for _, tt := range tests {
125141
t.Run(tt.name, func(t *testing.T) {

cmd/mdatagen/internal/sampleconnector/documentation.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,3 +113,15 @@ metrics:
113113
| string.resource.attr_disable_warning | Resource attribute with any string value. | Any Str | true |
114114
| string.resource.attr_remove_warning | Resource attribute with any string value. | Any Str | false |
115115
| string.resource.attr_to_be_removed | Resource attribute with any string value. | Any Str | true |
116+
117+
## Entities
118+
119+
The following entities are defined for this component:
120+
121+
### test.entity
122+
123+
**ID Attributes:**
124+
- `string.resource.attr`
125+
126+
**Description Attributes:**
127+
- `map.resource.attr`

cmd/mdatagen/internal/sampleconnector/internal/metadata/generated_resource.go

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cmd/mdatagen/internal/sampleconnector/metadata.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,13 @@ resource_attributes:
6565
warnings:
6666
if_enabled: This resource_attribute is deprecated and will be removed soon.
6767

68+
entities:
69+
test.entity:
70+
id_attributes:
71+
- string.resource.attr
72+
description_attributes:
73+
- map.resource.attr
74+
6875
attributes:
6976
boolean_attr:
7077
description: Attribute with a boolean value.

cmd/mdatagen/internal/templates/documentation.md.tmpl

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,36 @@ events:
212212

213213
{{- end }}
214214

215+
{{- if .Entities }}
216+
217+
## Entities
218+
219+
The following entities are defined for this component:
220+
221+
{{- range $entityType, $entity := .Entities }}
222+
223+
### {{ $entityType }}
224+
225+
{{- if $entity.IdAttributes }}
226+
227+
**ID Attributes:**
228+
{{- range $entity.IdAttributes }}
229+
- `{{ . }}`
230+
{{- end }}
231+
{{- end }}
232+
233+
{{- if $entity.DescriptionAttributes }}
234+
235+
**Description Attributes:**
236+
{{- range $entity.DescriptionAttributes }}
237+
- `{{ . }}`
238+
{{- end }}
239+
{{- end }}
240+
241+
{{- end }}
242+
243+
{{- end }}
244+
215245
{{- if .Telemetry.Metrics }}
216246

217247
## Internal Telemetry

cmd/mdatagen/internal/templates/resource.go.tmpl

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ package {{ .Package }}
44

55
import (
66
"go.opentelemetry.io/collector/pdata/pcommon"
7+
{{- if .HasEntities }}
8+
"go.opentelemetry.io/collector/pdata/xpdata/entity"
9+
{{- end }}
710
)
811

912
// ResourceBuilder is a helper struct to build resources predefined in metadata.yaml.
@@ -43,6 +46,29 @@ func (rb *ResourceBuilder) Set{{ $name.Render }}(val {{ $attr.Type.Primitive }})
4346
{{- end }}
4447
{{ end }}
4548

49+
{{- if .HasEntities }}
50+
51+
{{ range $entityType, $entity := .Entities }}
52+
// AssociateWith{{ $entityType | publicVar }} associates the resource with entity type "{{ $entityType }}".
53+
func (rb *ResourceBuilder) AssociateWith{{ $entityType | publicVar }}() {
54+
entityRef := entity.ResourceEntityRefs(rb.res).AppendEmpty()
55+
entityRef.SetType("{{ $entityType }}")
56+
{{- if $entity.IdAttributes }}
57+
idKeys := entityRef.IdKeys()
58+
{{- range $entity.IdAttributes }}
59+
idKeys.Append("{{ . }}")
60+
{{- end }}
61+
{{- end }}
62+
{{- if $entity.DescriptionAttributes }}
63+
descKeys := entityRef.DescriptionKeys()
64+
{{- range $entity.DescriptionAttributes }}
65+
descKeys.Append("{{ . }}")
66+
{{- end }}
67+
{{- end }}
68+
}
69+
{{ end }}
70+
{{- end }}
71+
4672
// Emit returns the built resource and resets the internal builder state.
4773
func (rb *ResourceBuilder) Emit() pcommon.Resource {
4874
r := rb.res
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[comment]: <> (Code generated by mdatagen. DO NOT EDIT.)
2+
3+
# sample
4+
5+
## Resource Attributes
6+
7+
| Name | Description | Values | Enabled |
8+
| ---- | ----------- | ------ | ------- |
9+
| host.id | The unique host identifier | Any Str | true |
10+
| host.name | The hostname | Any Str | true |
11+
| process.pid | The process identifier | Any Int | true |

0 commit comments

Comments
 (0)