Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 146 additions & 0 deletions internal/config/datasource_base.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package config

import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
"github.com/hashicorp/terraform-plugin-framework/types"
)

type ImplementedDataSource interface {
datasource.DataSourceWithConfigure
GetName() string
SetClient(*MongoDBClient)
}

func AnalyticsDataSourceFunc(iDataSource datasource.DataSource) func() datasource.DataSource {
commonDataSource, ok := iDataSource.(ImplementedDataSource)
if !ok {
panic(fmt.Sprintf("data source %T didn't comply with the ImplementedDataSource interface", iDataSource))
}
return func() datasource.DataSource {
return analyticsDataSource(commonDataSource)
}
}

// DSCommon is used as an embedded struct for all framework data sources. Implements the following plugin-framework defined functions:
// - Metadata
// - Configure
// Client is left empty and populated by the framework when envoking Configure method.
// DataSourceName must be defined when creating an instance of a data source.
//
// When used as a wrapper (ImplementedDataSource is set), it intercepts Read to add analytics tracking.
// When embedded in a data source struct, the data source's own Read method is used.
type DSCommon struct {
ImplementedDataSource // Set when used as a wrapper, nil when embedded
Client *MongoDBClient
DataSourceName string
}

func (d *DSCommon) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
resp.TypeName = fmt.Sprintf("%s_%s", req.ProviderTypeName, d.DataSourceName)
}

func (d *DSCommon) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
if d.ImplementedDataSource != nil {
// When used as a wrapper, delegate to the wrapped data source
d.ImplementedDataSource.Schema(ctx, req, resp)
}
// When embedded, the data source's own Schema method is used
}

func (d *DSCommon) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we have some tests or will be (indirectly) tested with all acc tests?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, tested indirectly.

client, err := configureClient(req.ProviderData)
if err != nil {
resp.Diagnostics.AddError(errorConfigureSummary, err.Error())
return
}
d.Client = client
// If used as a wrapper, set the client on the wrapped data source
if d.ImplementedDataSource != nil {
d.ImplementedDataSource.SetClient(client)
}
}

// Read intercepts the Read operation when DSCommon is used as a wrapper to add analytics tracking.
// When DSCommon is embedded, this method is not used (the data source's own Read method is called).
func (d *DSCommon) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
if d.ImplementedDataSource == nil {
// This shouldn't happen, but if DSCommon is embedded, the data source's Read is used instead
return
}
extra := asUserAgentExtraFromProviderMeta(ctx, d.DataSourceName, UserAgentOperationValueRead, true, req.ProviderMeta)
ctx = AddUserAgentExtra(ctx, extra)
d.ImplementedDataSource.Read(ctx, req, resp)
}

func (d *DSCommon) GetName() string {
return d.DataSourceName
}

func (d *DSCommon) SetClient(client *MongoDBClient) {
d.Client = client
}

func configureClient(providerData any) (*MongoDBClient, error) {
if providerData == nil {
return nil, nil
}

if client, ok := providerData.(*MongoDBClient); ok {
return client, nil
}

return nil, fmt.Errorf(errorConfigure, providerData)
}

// analyticsDataSource wraps an ImplementedDataSource with DSCommon to add analytics tracking.
// We cannot return iDataSource directly because we need to intercept the Read operation
// to inject provider_meta information into the context before calling the actual data source method.
func analyticsDataSource(iDataSource ImplementedDataSource) datasource.DataSource {
return &DSCommon{
DataSourceName: iDataSource.GetName(),
ImplementedDataSource: iDataSource,
}
}

// asUserAgentExtraFromProviderMeta extracts UserAgentExtra from provider_meta.
// This is a shared function used by both resources and data sources.
func asUserAgentExtraFromProviderMeta(ctx context.Context, name, reqOperation string, isDataSource bool, reqProviderMeta tfsdk.Config) UserAgentExtra {
var meta ProviderMeta
var nameValue string
if isDataSource {
nameValue = userAgentNameValueDataSource(name)
} else {
nameValue = userAgentNameValue(name)
}
uaExtra := UserAgentExtra{
Name: nameValue,
Operation: reqOperation,
}
if reqProviderMeta.Raw.IsNull() {
return uaExtra
}
diags := reqProviderMeta.Get(ctx, &meta)
if diags.HasError() {
return uaExtra
}

extrasLen := len(meta.UserAgentExtra.Elements())
userExtras := make(map[string]types.String, extrasLen)
diags.Append(meta.UserAgentExtra.ElementsAs(ctx, &userExtras, false)...)
if diags.HasError() {
return uaExtra
}
userExtrasString := make(map[string]string, extrasLen)
for k, v := range userExtras {
userExtrasString[k] = v.ValueString()
}
return uaExtra.Combine(UserAgentExtra{
Extras: userExtrasString,
ModuleName: meta.ModuleName.ValueString(),
ModuleVersion: meta.ModuleVersion.ValueString(),
})
}
78 changes: 5 additions & 73 deletions internal/config/resource_base.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
"github.com/hashicorp/terraform-plugin-framework/types"
)

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

func (r *RSCommon) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
extra := r.asUserAgentExtra(ctx, UserAgentOperationValueCreate, req.ProviderMeta)
extra := asUserAgentExtraFromProviderMeta(ctx, r.ResourceName, UserAgentOperationValueCreate, false, req.ProviderMeta)
ctx = AddUserAgentExtra(ctx, extra)
r.ImplementedResource.Create(ctx, req, resp)
}

func (r *RSCommon) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
extra := r.asUserAgentExtra(ctx, UserAgentOperationValueRead, req.ProviderMeta)
extra := asUserAgentExtraFromProviderMeta(ctx, r.ResourceName, UserAgentOperationValueRead, false, req.ProviderMeta)
ctx = AddUserAgentExtra(ctx, extra)
r.ImplementedResource.Read(ctx, req, resp)
}

func (r *RSCommon) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
extra := r.asUserAgentExtra(ctx, UserAgentOperationValueUpdate, req.ProviderMeta)
extra := asUserAgentExtraFromProviderMeta(ctx, r.ResourceName, UserAgentOperationValueUpdate, false, req.ProviderMeta)
ctx = AddUserAgentExtra(ctx, extra)
r.ImplementedResource.Update(ctx, req, resp)
}

func (r *RSCommon) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
extra := r.asUserAgentExtra(ctx, UserAgentOperationValueDelete, req.ProviderMeta)
extra := asUserAgentExtraFromProviderMeta(ctx, r.ResourceName, UserAgentOperationValueDelete, false, req.ProviderMeta)
ctx = AddUserAgentExtra(ctx, extra)
r.ImplementedResource.Delete(ctx, req, resp)
}
Expand All @@ -111,7 +109,7 @@ func (r *RSCommon) ModifyPlan(ctx context.Context, req resource.ModifyPlanReques
if !ok {
return
}
extra := r.asUserAgentExtra(ctx, UserAgentOperationValuePlanModify, req.ProviderMeta)
extra := asUserAgentExtraFromProviderMeta(ctx, r.ResourceName, UserAgentOperationValuePlanModify, false, req.ProviderMeta)
ctx = AddUserAgentExtra(ctx, extra)
resourceWithModifier.ModifyPlan(ctx, req, resp)
}
Expand Down Expand Up @@ -148,69 +146,3 @@ func (r *RSCommon) GetName() string {
func (r *RSCommon) SetClient(client *MongoDBClient) {
r.Client = client
}

func (r *RSCommon) asUserAgentExtra(ctx context.Context, reqOperation string, reqProviderMeta tfsdk.Config) UserAgentExtra {
var meta ProviderMeta
uaExtra := UserAgentExtra{
Name: userAgentNameValue(r.ResourceName),
Operation: reqOperation,
}
if reqProviderMeta.Raw.IsNull() {
return uaExtra
}
diags := reqProviderMeta.Get(ctx, &meta)
if diags.HasError() {
return uaExtra
}

extrasLen := len(meta.UserAgentExtra.Elements())
userExtras := make(map[string]types.String, extrasLen)
diags.Append(meta.UserAgentExtra.ElementsAs(ctx, &userExtras, false)...)
if diags.HasError() {
return uaExtra
}
userExtrasString := make(map[string]string, extrasLen)
for k, v := range userExtras {
userExtrasString[k] = v.ValueString()
}
return uaExtra.Combine(UserAgentExtra{
Extras: userExtrasString,
ModuleName: meta.ModuleName.ValueString(),
ModuleVersion: meta.ModuleVersion.ValueString(),
})
}

// DSCommon is used as an embedded struct for all framework data sources. Implements the following plugin-framework defined functions:
// - Metadata
// - Configure
// Client is left empty and populated by the framework when envoking Configure method.
// DataSourceName must be defined when creating an instance of a data source.
type DSCommon struct {
Client *MongoDBClient
DataSourceName string
}

func (d *DSCommon) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
resp.TypeName = fmt.Sprintf("%s_%s", req.ProviderTypeName, d.DataSourceName)
}

func (d *DSCommon) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
client, err := configureClient(req.ProviderData)
if err != nil {
resp.Diagnostics.AddError(errorConfigureSummary, err.Error())
return
}
d.Client = client
}

func configureClient(providerData any) (*MongoDBClient, error) {
if providerData == nil {
return nil, nil
}

if client, ok := providerData.(*MongoDBClient); ok {
return client, nil
}

return nil, fmt.Errorf(errorConfigure, providerData)
}
27 changes: 20 additions & 7 deletions internal/config/resource_base_sdkv2.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

func NewAnalyticsResourceSDKv2(d *schema.Resource, name string) *schema.Resource {
func NewAnalyticsResourceSDKv2(d *schema.Resource, name string, isDataSource bool) *schema.Resource {
analyticsResource := &AnalyticsResourceSDKv2{
resource: d,
name: name,
resource: d,
name: name,
isDataSource: isDataSource,
}
/*
We are not initializing deprecated fields, for example Update to avoid the message:
Expand Down Expand Up @@ -83,8 +84,9 @@ type ProviderMetaSDKv2 struct {
}

type AnalyticsResourceSDKv2 struct {
resource *schema.Resource
name string
resource *schema.Resource
name string
isDataSource bool
}

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

func (a *AnalyticsResourceSDKv2) updateContextWithProviderMeta(ctx context.Context, meta ProviderMetaSDKv2, operationName string) context.Context {
// updateContextWithProviderMetaSDKv2 is a shared function to update context with provider meta for SDKv2 resources and data sources.
func updateContextWithProviderMetaSDKv2(ctx context.Context, name string, isDataSource bool, meta ProviderMetaSDKv2, operationName string) context.Context {
moduleName := ""
if meta.ModuleName != nil {
moduleName = *meta.ModuleName
Expand All @@ -176,9 +179,15 @@ func (a *AnalyticsResourceSDKv2) updateContextWithProviderMeta(ctx context.Conte
if meta.ModuleVersion != nil {
moduleVersion = *meta.ModuleVersion
}
var nameValue string
if isDataSource {
nameValue = userAgentNameValueDataSource(name)
} else {
nameValue = userAgentNameValue(name)
}

uaExtra := UserAgentExtra{
Name: userAgentNameValue(a.name),
Name: nameValue,
Operation: operationName,
Extras: meta.UserAgentExtra,
ModuleName: moduleName,
Expand All @@ -188,6 +197,10 @@ func (a *AnalyticsResourceSDKv2) updateContextWithProviderMeta(ctx context.Conte
return ctx
}

func (a *AnalyticsResourceSDKv2) updateContextWithProviderMeta(ctx context.Context, meta ProviderMetaSDKv2, operationName string) context.Context {
return updateContextWithProviderMetaSDKv2(ctx, a.name, a.isDataSource, meta, operationName)
}

func parseProviderMeta(r *schema.ResourceData) (ProviderMetaSDKv2, error) {
meta := ProviderMetaSDKv2{}
err := r.GetProviderMeta(&meta)
Expand Down
3 changes: 3 additions & 0 deletions internal/config/user_agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ type UserAgentExtra struct {
ModuleVersion string
}

func userAgentNameValueDataSource(name string) string {
return "data." + userAgentNameValue(name)
}
func userAgentNameValue(name string) string {
return strings.TrimPrefix(name, "mongodbatlas_")
}
Expand Down
6 changes: 5 additions & 1 deletion internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,11 @@ func (p *MongodbtlasProvider) DataSources(context.Context) []func() datasource.D
advancedcluster.DataSource,
advancedcluster.PluralDataSource,
}
return dataSources
analyticsDataSources := []func() datasource.DataSource{}
for _, dataSourceFunc := range dataSources {
analyticsDataSources = append(analyticsDataSources, config.AnalyticsDataSourceFunc(dataSourceFunc()))
}
return analyticsDataSources
}

func (p *MongodbtlasProvider) Resources(context.Context) []func() resource.Resource {
Expand Down
8 changes: 6 additions & 2 deletions internal/provider/provider_sdk2.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,11 @@ func getDataSourcesMap() map[string]*schema.Resource {
"mongodbatlas_shared_tier_snapshot": sharedtier.DataSourceSnapshot(),
"mongodbatlas_shared_tier_snapshots": sharedtier.PluralDataSourceSnapshot(),
}
return dataSourcesMap
analyticsMap := map[string]*schema.Resource{}
for name, dataSource := range dataSourcesMap {
analyticsMap[name] = config.NewAnalyticsResourceSDKv2(dataSource, name, true)
}
return analyticsMap
}

func getResourcesMap() map[string]*schema.Resource {
Expand Down Expand Up @@ -292,7 +296,7 @@ func getResourcesMap() map[string]*schema.Resource {
}
analyticsMap := map[string]*schema.Resource{}
for name, resource := range resourcesMap {
analyticsMap[name] = config.NewAnalyticsResourceSDKv2(resource, name)
analyticsMap[name] = config.NewAnalyticsResourceSDKv2(resource, name, false)
}
return analyticsMap
}
Expand Down