Skip to content

Commit 7b2b735

Browse files
committed
feat: Add provider_meta support to all data sources
Adds provider_meta support to all data sources (TPF and SDKv2) for module usage tracking via User-Agent headers. Follows the same pattern as resources (PR #3618). - Created datasource_base.go for data source analytics in TPF - Added SetClient() method pattern matching resources - Shared asUserAgentExtraFromProviderMeta() between resources and data sources
1 parent 8b565e6 commit 7b2b735

File tree

6 files changed

+185
-83
lines changed

6 files changed

+185
-83
lines changed

internal/config/datasource_base.go

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
package config
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/hashicorp/terraform-plugin-framework/datasource"
8+
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
9+
"github.com/hashicorp/terraform-plugin-framework/types"
10+
)
11+
12+
type ImplementedDataSource interface {
13+
datasource.DataSourceWithConfigure
14+
GetName() string
15+
SetClient(*MongoDBClient)
16+
}
17+
18+
func AnalyticsDataSourceFunc(iDataSource datasource.DataSource) func() datasource.DataSource {
19+
commonDataSource, ok := iDataSource.(ImplementedDataSource)
20+
if !ok {
21+
panic(fmt.Sprintf("data source %T didn't comply with the ImplementedDataSource interface", iDataSource))
22+
}
23+
return func() datasource.DataSource {
24+
return analyticsDataSource(commonDataSource)
25+
}
26+
}
27+
28+
// DSCommon is used as an embedded struct for all framework data sources. Implements the following plugin-framework defined functions:
29+
// - Metadata
30+
// - Configure
31+
// Client is left empty and populated by the framework when envoking Configure method.
32+
// DataSourceName must be defined when creating an instance of a data source.
33+
//
34+
// When used as a wrapper (ImplementedDataSource is set), it intercepts Read to add analytics tracking.
35+
// When embedded in a data source struct, the data source's own Read method is used.
36+
type DSCommon struct {
37+
ImplementedDataSource // Set when used as a wrapper, nil when embedded
38+
Client *MongoDBClient
39+
DataSourceName string
40+
}
41+
42+
func (d *DSCommon) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
43+
resp.TypeName = fmt.Sprintf("%s_%s", req.ProviderTypeName, d.DataSourceName)
44+
}
45+
46+
func (d *DSCommon) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
47+
if d.ImplementedDataSource != nil {
48+
// When used as a wrapper, delegate to the wrapped data source
49+
d.ImplementedDataSource.Schema(ctx, req, resp)
50+
}
51+
// When embedded, the data source's own Schema method is used
52+
}
53+
54+
func (d *DSCommon) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
55+
client, err := configureClient(req.ProviderData)
56+
if err != nil {
57+
resp.Diagnostics.AddError(errorConfigureSummary, err.Error())
58+
return
59+
}
60+
d.Client = client
61+
// If used as a wrapper, set the client on the wrapped data source
62+
if d.ImplementedDataSource != nil {
63+
d.ImplementedDataSource.SetClient(client)
64+
}
65+
}
66+
67+
// Read intercepts the Read operation when DSCommon is used as a wrapper to add analytics tracking.
68+
// When DSCommon is embedded, this method is not used (the data source's own Read method is called).
69+
func (d *DSCommon) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
70+
if d.ImplementedDataSource == nil {
71+
// This shouldn't happen, but if DSCommon is embedded, the data source's Read is used instead
72+
return
73+
}
74+
extra := asUserAgentExtraFromProviderMeta(ctx, d.DataSourceName, UserAgentOperationValueRead, true, req.ProviderMeta)
75+
ctx = AddUserAgentExtra(ctx, extra)
76+
d.ImplementedDataSource.Read(ctx, req, resp)
77+
}
78+
79+
func (d *DSCommon) GetName() string {
80+
return d.DataSourceName
81+
}
82+
83+
func (d *DSCommon) SetClient(client *MongoDBClient) {
84+
d.Client = client
85+
}
86+
87+
func configureClient(providerData any) (*MongoDBClient, error) {
88+
if providerData == nil {
89+
return nil, nil
90+
}
91+
92+
if client, ok := providerData.(*MongoDBClient); ok {
93+
return client, nil
94+
}
95+
96+
return nil, fmt.Errorf(errorConfigure, providerData)
97+
}
98+
99+
// analyticsDataSource wraps an ImplementedDataSource with DSCommon to add analytics tracking.
100+
// We cannot return iDataSource directly because we need to intercept the Read operation
101+
// to inject provider_meta information into the context before calling the actual data source method.
102+
func analyticsDataSource(iDataSource ImplementedDataSource) datasource.DataSource {
103+
return &DSCommon{
104+
DataSourceName: iDataSource.GetName(),
105+
ImplementedDataSource: iDataSource,
106+
}
107+
}
108+
109+
// asUserAgentExtraFromProviderMeta extracts UserAgentExtra from provider_meta.
110+
// This is a shared function used by both resources and data sources.
111+
func asUserAgentExtraFromProviderMeta(ctx context.Context, name, reqOperation string, isDataSource bool, reqProviderMeta tfsdk.Config) UserAgentExtra {
112+
var meta ProviderMeta
113+
var nameValue string
114+
if isDataSource {
115+
nameValue = userAgentNameValueDataSource(name)
116+
} else {
117+
nameValue = userAgentNameValue(name)
118+
}
119+
uaExtra := UserAgentExtra{
120+
Name: nameValue,
121+
Operation: reqOperation,
122+
}
123+
if reqProviderMeta.Raw.IsNull() {
124+
return uaExtra
125+
}
126+
diags := reqProviderMeta.Get(ctx, &meta)
127+
if diags.HasError() {
128+
return uaExtra
129+
}
130+
131+
extrasLen := len(meta.UserAgentExtra.Elements())
132+
userExtras := make(map[string]types.String, extrasLen)
133+
diags.Append(meta.UserAgentExtra.ElementsAs(ctx, &userExtras, false)...)
134+
if diags.HasError() {
135+
return uaExtra
136+
}
137+
userExtrasString := make(map[string]string, extrasLen)
138+
for k, v := range userExtras {
139+
userExtrasString[k] = v.ValueString()
140+
}
141+
return uaExtra.Combine(UserAgentExtra{
142+
Extras: userExtrasString,
143+
ModuleName: meta.ModuleName.ValueString(),
144+
ModuleVersion: meta.ModuleVersion.ValueString(),
145+
})
146+
}

internal/config/resource_base.go

Lines changed: 5 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@ import (
44
"context"
55
"fmt"
66

7-
"github.com/hashicorp/terraform-plugin-framework/datasource"
87
"github.com/hashicorp/terraform-plugin-framework/resource"
9-
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
108
"github.com/hashicorp/terraform-plugin-framework/types"
119
)
1210

@@ -73,25 +71,25 @@ func (r *RSCommon) Configure(ctx context.Context, req resource.ConfigureRequest,
7371
}
7472

7573
func (r *RSCommon) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
76-
extra := r.asUserAgentExtra(ctx, UserAgentOperationValueCreate, req.ProviderMeta)
74+
extra := asUserAgentExtraFromProviderMeta(ctx, r.ResourceName, UserAgentOperationValueCreate, false, req.ProviderMeta)
7775
ctx = AddUserAgentExtra(ctx, extra)
7876
r.ImplementedResource.Create(ctx, req, resp)
7977
}
8078

8179
func (r *RSCommon) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
82-
extra := r.asUserAgentExtra(ctx, UserAgentOperationValueRead, req.ProviderMeta)
80+
extra := asUserAgentExtraFromProviderMeta(ctx, r.ResourceName, UserAgentOperationValueRead, false, req.ProviderMeta)
8381
ctx = AddUserAgentExtra(ctx, extra)
8482
r.ImplementedResource.Read(ctx, req, resp)
8583
}
8684

8785
func (r *RSCommon) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
88-
extra := r.asUserAgentExtra(ctx, UserAgentOperationValueUpdate, req.ProviderMeta)
86+
extra := asUserAgentExtraFromProviderMeta(ctx, r.ResourceName, UserAgentOperationValueUpdate, false, req.ProviderMeta)
8987
ctx = AddUserAgentExtra(ctx, extra)
9088
r.ImplementedResource.Update(ctx, req, resp)
9189
}
9290

9391
func (r *RSCommon) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
94-
extra := r.asUserAgentExtra(ctx, UserAgentOperationValueDelete, req.ProviderMeta)
92+
extra := asUserAgentExtraFromProviderMeta(ctx, r.ResourceName, UserAgentOperationValueDelete, false, req.ProviderMeta)
9593
ctx = AddUserAgentExtra(ctx, extra)
9694
r.ImplementedResource.Delete(ctx, req, resp)
9795
}
@@ -111,7 +109,7 @@ func (r *RSCommon) ModifyPlan(ctx context.Context, req resource.ModifyPlanReques
111109
if !ok {
112110
return
113111
}
114-
extra := r.asUserAgentExtra(ctx, UserAgentOperationValuePlanModify, req.ProviderMeta)
112+
extra := asUserAgentExtraFromProviderMeta(ctx, r.ResourceName, UserAgentOperationValuePlanModify, false, req.ProviderMeta)
115113
ctx = AddUserAgentExtra(ctx, extra)
116114
resourceWithModifier.ModifyPlan(ctx, req, resp)
117115
}
@@ -148,69 +146,3 @@ func (r *RSCommon) GetName() string {
148146
func (r *RSCommon) SetClient(client *MongoDBClient) {
149147
r.Client = client
150148
}
151-
152-
func (r *RSCommon) asUserAgentExtra(ctx context.Context, reqOperation string, reqProviderMeta tfsdk.Config) UserAgentExtra {
153-
var meta ProviderMeta
154-
uaExtra := UserAgentExtra{
155-
Name: userAgentNameValue(r.ResourceName),
156-
Operation: reqOperation,
157-
}
158-
if reqProviderMeta.Raw.IsNull() {
159-
return uaExtra
160-
}
161-
diags := reqProviderMeta.Get(ctx, &meta)
162-
if diags.HasError() {
163-
return uaExtra
164-
}
165-
166-
extrasLen := len(meta.UserAgentExtra.Elements())
167-
userExtras := make(map[string]types.String, extrasLen)
168-
diags.Append(meta.UserAgentExtra.ElementsAs(ctx, &userExtras, false)...)
169-
if diags.HasError() {
170-
return uaExtra
171-
}
172-
userExtrasString := make(map[string]string, extrasLen)
173-
for k, v := range userExtras {
174-
userExtrasString[k] = v.ValueString()
175-
}
176-
return uaExtra.Combine(UserAgentExtra{
177-
Extras: userExtrasString,
178-
ModuleName: meta.ModuleName.ValueString(),
179-
ModuleVersion: meta.ModuleVersion.ValueString(),
180-
})
181-
}
182-
183-
// DSCommon is used as an embedded struct for all framework data sources. Implements the following plugin-framework defined functions:
184-
// - Metadata
185-
// - Configure
186-
// Client is left empty and populated by the framework when envoking Configure method.
187-
// DataSourceName must be defined when creating an instance of a data source.
188-
type DSCommon struct {
189-
Client *MongoDBClient
190-
DataSourceName string
191-
}
192-
193-
func (d *DSCommon) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
194-
resp.TypeName = fmt.Sprintf("%s_%s", req.ProviderTypeName, d.DataSourceName)
195-
}
196-
197-
func (d *DSCommon) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
198-
client, err := configureClient(req.ProviderData)
199-
if err != nil {
200-
resp.Diagnostics.AddError(errorConfigureSummary, err.Error())
201-
return
202-
}
203-
d.Client = client
204-
}
205-
206-
func configureClient(providerData any) (*MongoDBClient, error) {
207-
if providerData == nil {
208-
return nil, nil
209-
}
210-
211-
if client, ok := providerData.(*MongoDBClient); ok {
212-
return client, nil
213-
}
214-
215-
return nil, fmt.Errorf(errorConfigure, providerData)
216-
}

internal/config/resource_base_sdkv2.go

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@ import (
88
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
99
)
1010

11-
func NewAnalyticsResourceSDKv2(d *schema.Resource, name string) *schema.Resource {
11+
func NewAnalyticsResourceSDKv2(d *schema.Resource, name string, isDataSource bool) *schema.Resource {
1212
analyticsResource := &AnalyticsResourceSDKv2{
13-
resource: d,
14-
name: name,
13+
resource: d,
14+
name: name,
15+
isDataSource: isDataSource,
1516
}
1617
/*
1718
We are not initializing deprecated fields, for example Update to avoid the message:
@@ -83,8 +84,9 @@ type ProviderMetaSDKv2 struct {
8384
}
8485

8586
type AnalyticsResourceSDKv2 struct {
86-
resource *schema.Resource
87-
name string
87+
resource *schema.Resource
88+
name string
89+
isDataSource bool
8890
}
8991

9092
func (a *AnalyticsResourceSDKv2) CreateContext(ctx context.Context, r *schema.ResourceData, m interface{}) diag.Diagnostics {
@@ -167,7 +169,8 @@ func (a *AnalyticsResourceSDKv2) resourceImport(ctx context.Context, d *schema.R
167169
return a.resource.Importer.StateContext(ctx, d, meta)
168170
}
169171

170-
func (a *AnalyticsResourceSDKv2) updateContextWithProviderMeta(ctx context.Context, meta ProviderMetaSDKv2, operationName string) context.Context {
172+
// updateContextWithProviderMetaSDKv2 is a shared function to update context with provider meta for SDKv2 resources and data sources.
173+
func updateContextWithProviderMetaSDKv2(ctx context.Context, name string, isDataSource bool, meta ProviderMetaSDKv2, operationName string) context.Context {
171174
moduleName := ""
172175
if meta.ModuleName != nil {
173176
moduleName = *meta.ModuleName
@@ -176,9 +179,15 @@ func (a *AnalyticsResourceSDKv2) updateContextWithProviderMeta(ctx context.Conte
176179
if meta.ModuleVersion != nil {
177180
moduleVersion = *meta.ModuleVersion
178181
}
182+
var nameValue string
183+
if isDataSource {
184+
nameValue = userAgentNameValueDataSource(name)
185+
} else {
186+
nameValue = userAgentNameValue(name)
187+
}
179188

180189
uaExtra := UserAgentExtra{
181-
Name: userAgentNameValue(a.name),
190+
Name: nameValue,
182191
Operation: operationName,
183192
Extras: meta.UserAgentExtra,
184193
ModuleName: moduleName,
@@ -188,6 +197,10 @@ func (a *AnalyticsResourceSDKv2) updateContextWithProviderMeta(ctx context.Conte
188197
return ctx
189198
}
190199

200+
func (a *AnalyticsResourceSDKv2) updateContextWithProviderMeta(ctx context.Context, meta ProviderMetaSDKv2, operationName string) context.Context {
201+
return updateContextWithProviderMetaSDKv2(ctx, a.name, a.isDataSource, meta, operationName)
202+
}
203+
191204
func parseProviderMeta(r *schema.ResourceData) (ProviderMetaSDKv2, error) {
192205
meta := ProviderMetaSDKv2{}
193206
err := r.GetProviderMeta(&meta)

internal/config/user_agent.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ type UserAgentExtra struct {
3333
ModuleVersion string
3434
}
3535

36+
func userAgentNameValueDataSource(name string) string {
37+
return "data." + userAgentNameValue(name)
38+
}
3639
func userAgentNameValue(name string) string {
3740
return strings.TrimPrefix(name, "mongodbatlas_")
3841
}

internal/provider/provider.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,11 @@ func (p *MongodbtlasProvider) DataSources(context.Context) []func() datasource.D
299299
advancedcluster.DataSource,
300300
advancedcluster.PluralDataSource,
301301
}
302-
return dataSources
302+
analyticsDataSources := []func() datasource.DataSource{}
303+
for _, dataSourceFunc := range dataSources {
304+
analyticsDataSources = append(analyticsDataSources, config.AnalyticsDataSourceFunc(dataSourceFunc()))
305+
}
306+
return analyticsDataSources
303307
}
304308

305309
func (p *MongodbtlasProvider) Resources(context.Context) []func() resource.Resource {

internal/provider/provider_sdk2.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,11 @@ func getDataSourcesMap() map[string]*schema.Resource {
243243
"mongodbatlas_shared_tier_snapshot": sharedtier.DataSourceSnapshot(),
244244
"mongodbatlas_shared_tier_snapshots": sharedtier.PluralDataSourceSnapshot(),
245245
}
246-
return dataSourcesMap
246+
analyticsMap := map[string]*schema.Resource{}
247+
for name, dataSource := range dataSourcesMap {
248+
analyticsMap[name] = config.NewAnalyticsResourceSDKv2(dataSource, name, true)
249+
}
250+
return analyticsMap
247251
}
248252

249253
func getResourcesMap() map[string]*schema.Resource {
@@ -292,7 +296,7 @@ func getResourcesMap() map[string]*schema.Resource {
292296
}
293297
analyticsMap := map[string]*schema.Resource{}
294298
for name, resource := range resourcesMap {
295-
analyticsMap[name] = config.NewAnalyticsResourceSDKv2(resource, name)
299+
analyticsMap[name] = config.NewAnalyticsResourceSDKv2(resource, name, false)
296300
}
297301
return analyticsMap
298302
}

0 commit comments

Comments
 (0)