Skip to content
Closed
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
7 changes: 3 additions & 4 deletions internal/config/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ type Config struct {
RealmBaseURL string
TerraformVersion string
PreviewV2AdvancedClusterEnabled bool
AnalyticsEnabled bool
}

type AssumeRole struct {
Expand Down Expand Up @@ -109,10 +110,8 @@ func (c *Config) NewClient(ctx context.Context) (any, error) {
// Don't change logging.NewTransport to NewSubsystemLoggingHTTPTransport until all resources are in TPF.
tfLoggingTransport := logging.NewTransport("Atlas", digestTransport)
// Add UserAgentExtra fields to the User-Agent header, see wrapper_provider_server.go
userAgentTransport := UserAgentTransport{
Transport: tfLoggingTransport,
}
client := &http.Client{Transport: &userAgentTransport}
userAgentTransport := NewUserAgentTransport(tfLoggingTransport, c.AnalyticsEnabled)
client := &http.Client{Transport: userAgentTransport}

optsAtlas := []matlasClient.ClientOpt{matlasClient.SetUserAgent(userAgent(c))}
if c.BaseURL != "" {
Expand Down
73 changes: 73 additions & 0 deletions internal/config/resource_base.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
"fmt"

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

const (
Expand All @@ -18,15 +20,86 @@
// - Configure
// Client is left empty and populated by the framework when envoking Configure method.
// ResourceName must be defined when creating an instance of a resource.

type ProviderMeta struct {
ScriptLocation types.String `tfsdk:"script_location"`
}

func AnalyticsResource(name string, resource resource.ResourceWithImportState) resource.Resource {
return &RSCommon{
ResourceName: name,
Resource: resource,
}
}

type RSCommon struct {
Client *MongoDBClient
ResourceName string
Resource resource.ResourceWithImportState
}

func (r *RSCommon) ReadProviderMetaCreate(ctx context.Context, req *resource.CreateRequest, diags *diag.Diagnostics) ProviderMeta {
var meta ProviderMeta
diags.Append(req.ProviderMeta.Get(ctx, &meta)...)
return meta
}

func (r *RSCommon) ReadProviderMetaUpdate(ctx context.Context, req *resource.UpdateRequest, diags *diag.Diagnostics) ProviderMeta {
var meta ProviderMeta
diags.Append(req.ProviderMeta.Get(ctx, &meta)...)
return meta
}

func (r *RSCommon) AddAnalyticsCreate(ctx context.Context, req *resource.CreateRequest, diags *diag.Diagnostics) context.Context {
meta := r.ReadProviderMetaCreate(ctx, req, diags)
return AddUserAgentExtra(ctx, UserAgentExtra{
ScriptLocation: meta.ScriptLocation.ValueString(),
Name: r.ResourceName,
Operation: "create",
})
}

func (r *RSCommon) AddAnalyticsUpdate(ctx context.Context, req *resource.UpdateRequest, diags *diag.Diagnostics) context.Context {
meta := r.ReadProviderMetaUpdate(ctx, req, diags)
return AddUserAgentExtra(ctx, UserAgentExtra{
ScriptLocation: meta.ScriptLocation.ValueString(),
Name: r.ResourceName,
Operation: "create",
})
}

func (r *RSCommon) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = fmt.Sprintf("%s_%s", req.ProviderTypeName, r.ResourceName)
}

func (r *RSCommon) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
r.Resource.Schema(ctx, req, resp)
}

func (r *RSCommon) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
meta := r.ReadProviderMetaCreate(ctx, &req, &resp.Diagnostics)
ctx = AddUserAgentExtra(ctx, UserAgentExtra{
ModuleName: meta.ModuleName.ValueString(),

Check failure on line 82 in internal/config/resource_base.go

View workflow job for this annotation

GitHub Actions / tf-validate

meta.ModuleName undefined (type ProviderMeta has no field or method ModuleName)

Check failure on line 82 in internal/config/resource_base.go

View workflow job for this annotation

GitHub Actions / tf-validate

unknown field ModuleName in struct literal of type UserAgentExtra

Check failure on line 82 in internal/config/resource_base.go

View workflow job for this annotation

GitHub Actions / build

meta.ModuleName undefined (type ProviderMeta has no field or method ModuleName)

Check failure on line 82 in internal/config/resource_base.go

View workflow job for this annotation

GitHub Actions / build

unknown field ModuleName in struct literal of type UserAgentExtra

Check failure on line 82 in internal/config/resource_base.go

View workflow job for this annotation

GitHub Actions / lint

meta.ModuleName undefined (type ProviderMeta has no field or method ModuleName)

Check failure on line 82 in internal/config/resource_base.go

View workflow job for this annotation

GitHub Actions / lint

unknown field ModuleName in struct literal of type UserAgentExtra

Check failure on line 82 in internal/config/resource_base.go

View workflow job for this annotation

GitHub Actions / lint

meta.ModuleName undefined (type ProviderMeta has no field or method ModuleName)

Check failure on line 82 in internal/config/resource_base.go

View workflow job for this annotation

GitHub Actions / lint

unknown field ModuleName in struct literal of type UserAgentExtra
ModuleVersion: meta.ModuleVersion.ValueString(),

Check failure on line 83 in internal/config/resource_base.go

View workflow job for this annotation

GitHub Actions / tf-validate

meta.ModuleVersion undefined (type ProviderMeta has no field or method ModuleVersion)

Check failure on line 83 in internal/config/resource_base.go

View workflow job for this annotation

GitHub Actions / tf-validate

unknown field ModuleVersion in struct literal of type UserAgentExtra

Check failure on line 83 in internal/config/resource_base.go

View workflow job for this annotation

GitHub Actions / build

meta.ModuleVersion undefined (type ProviderMeta has no field or method ModuleVersion)

Check failure on line 83 in internal/config/resource_base.go

View workflow job for this annotation

GitHub Actions / build

unknown field ModuleVersion in struct literal of type UserAgentExtra

Check failure on line 83 in internal/config/resource_base.go

View workflow job for this annotation

GitHub Actions / lint

meta.ModuleVersion undefined (type ProviderMeta has no field or method ModuleVersion)) (typecheck)

Check failure on line 83 in internal/config/resource_base.go

View workflow job for this annotation

GitHub Actions / lint

unknown field ModuleVersion in struct literal of type UserAgentExtra

Check failure on line 83 in internal/config/resource_base.go

View workflow job for this annotation

GitHub Actions / lint

meta.ModuleVersion undefined (type ProviderMeta has no field or method ModuleVersion) (typecheck)

Check failure on line 83 in internal/config/resource_base.go

View workflow job for this annotation

GitHub Actions / lint

unknown field ModuleVersion in struct literal of type UserAgentExtra
Name: r.ResourceName,
Operation: "create",
})
r.Resource.Create(ctx, req, resp) // Call original create
}

func (r *RSCommon) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
r.Resource.Read(ctx, req, resp)
}
func (r *RSCommon) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
r.Resource.Update(ctx, req, resp)
}
func (r *RSCommon) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
r.Resource.ImportState(ctx, req, resp)
}
func (r *RSCommon) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
r.Resource.Delete(ctx, req, resp)
}

func (r *RSCommon) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
client, err := configureClient(req.ProviderData)
if err != nil {
Expand Down
48 changes: 48 additions & 0 deletions internal/config/resource_base_sdkv2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package config

import (
"context"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

func NewAnalyticsResource(d *schema.Resource) *schema.Resource {
analyticsResource := AnalyticsResourceSDKv2{
resource: d,
}
return &schema.Resource{
// Original field mapping
Schema: d.Schema,
SchemaFunc: d.SchemaFunc,
// Overriding the CRUDI methods
CreateContext: analyticsResource.CreateContext,
ReadContext: analyticsResource.ReadContext,
UpdateContext: analyticsResource.UpdateContext,
DeleteContext: analyticsResource.DeleteContext,
}
}

type AnalyticsResourceSDKv2 struct {
resource *schema.Resource
}

func (a *AnalyticsResourceSDKv2) CreateContext(ctx context.Context, r *schema.ResourceData, m interface{}) diag.Diagnostics {
// TODO: Add analytics
return a.resource.CreateContext(ctx, r, m)
}

// See Resource documentation.
func (a *AnalyticsResourceSDKv2) ReadContext(ctx context.Context, r *schema.ResourceData, m interface{}) diag.Diagnostics {
return a.resource.ReadContext(ctx, r, m)
}

// See Resource documentation.
func (a *AnalyticsResourceSDKv2) UpdateContext(ctx context.Context, r *schema.ResourceData, m interface{}) diag.Diagnostics {
return a.resource.UpdateContext(ctx, r, m)
}

// See Resource documentation.
func (a *AnalyticsResourceSDKv2) DeleteContext(ctx context.Context, r *schema.ResourceData, m interface{}) diag.Diagnostics {
return a.resource.DeleteContext(ctx, r, m)
}
11 changes: 11 additions & 0 deletions internal/config/transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,20 @@ func AddUserAgentExtra(ctx context.Context, extra UserAgentExtra) context.Contex
// UserAgentTransport wraps an http.RoundTripper to add User-Agent header with additional metadata.
type UserAgentTransport struct {
Transport http.RoundTripper
Enabled bool
}

func NewUserAgentTransport(transport http.RoundTripper, enabled bool) *UserAgentTransport {
return &UserAgentTransport{
Transport: transport,
Enabled: enabled,
}
}

func (t *UserAgentTransport) RoundTrip(req *http.Request) (*http.Response, error) {
if !t.Enabled {
return t.Transport.RoundTrip(req)
}
extra := ReadUserAgentExtra(req.Context())
if extra != nil {
userAgent := req.Header.Get(UserAgentHeader)
Expand Down
18 changes: 18 additions & 0 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/provider"
"github.com/hashicorp/terraform-plugin-framework/provider/metaschema"
"github.com/hashicorp/terraform-plugin-framework/provider/schema"
"github.com/hashicorp/terraform-plugin-framework/providerserver"
"github.com/hashicorp/terraform-plugin-framework/resource"
Expand Down Expand Up @@ -74,6 +75,7 @@ type tfMongodbAtlasProviderModel struct {
AwsSecretAccessKeyID types.String `tfsdk:"aws_secret_access_key"`
AwsSessionToken types.String `tfsdk:"aws_session_token"`
IsMongodbGovCloud types.Bool `tfsdk:"is_mongodbgov_cloud"`
EnableAnalytics types.Bool `tfsdk:"enable_analytics"`
}

type tfAssumeRoleModel struct {
Expand Down Expand Up @@ -105,6 +107,17 @@ func (p *MongodbtlasProvider) Metadata(ctx context.Context, req provider.Metadat
resp.Version = version.ProviderVersion
}

func (p *MongodbtlasProvider) MetaSchema(ctx context.Context, req provider.MetaSchemaRequest, resp *provider.MetaSchemaResponse) {
resp.Schema = metaschema.Schema{
Attributes: map[string]metaschema.Attribute{
"script_location": metaschema.StringAttribute{
Description: "Example metadata field for analytics/usage.",
Optional: true,
},
},
}
}

func (p *MongodbtlasProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) {
resp.Schema = schema.Schema{
Blocks: map[string]schema.Block{
Expand Down Expand Up @@ -156,6 +169,10 @@ func (p *MongodbtlasProvider) Schema(ctx context.Context, req provider.SchemaReq
Optional: true,
Description: "AWS Security Token Service provided session token.",
},
"enable_analytics": schema.BoolAttribute{
Optional: true,
Description: "Allow extra user agent headers such as script_location specified in provider_meta blocks.",
},
},
}
}
Expand Down Expand Up @@ -245,6 +262,7 @@ func (p *MongodbtlasProvider) Configure(ctx context.Context, req provider.Config
RealmBaseURL: data.RealmBaseURL.ValueString(),
TerraformVersion: req.TerraformVersion,
PreviewV2AdvancedClusterEnabled: config.PreviewProviderV2AdvancedCluster(),
AnalyticsEnabled: data.EnableAnalytics.ValueBool(),
}

var assumeRoles []tfAssumeRoleModel
Expand Down
13 changes: 13 additions & 0 deletions internal/provider/provider_sdk2.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,11 +126,23 @@ func NewSdkV2Provider() *schema.Provider {
Optional: true,
Description: "AWS Security Token Service provided session token.",
},
"enable_analytics": {
Type: schema.TypeBool,
Optional: true,
Description: "Allow extra user agent headers such as script_location specified in provider_meta blocks.",
},
},
DataSourcesMap: getDataSourcesMap(),
ResourcesMap: getResourcesMap(),
}
provider.ConfigureContextFunc = providerConfigure(provider)
provider.ProviderMetaSchema = map[string]*schema.Schema{
"script_location": {
Type: schema.TypeString,
Description: "Example metadata field for analytics/usage.",
Optional: true,
},
}
return provider
}

Expand Down Expand Up @@ -287,6 +299,7 @@ func providerConfigure(provider *schema.Provider) func(ctx context.Context, d *s
BaseURL: d.Get("base_url").(string),
RealmBaseURL: d.Get("realm_base_url").(string),
TerraformVersion: provider.TerraformVersion,
AnalyticsEnabled: d.Get("enable_analytics").(bool),
}

assumeRoleValue, ok := d.GetOk("assume_role")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const (
)

func Resource() *schema.Resource {
return &schema.Resource{
return config.NewAnalyticsResource(&schema.Resource{
CreateContext: resourceCreate,
ReadContext: resourceRead,
UpdateContext: resourceUpdate,
Expand Down Expand Up @@ -113,7 +113,7 @@ func Resource() *schema.Resource {
"storage_databases": schemaFederatedDatabaseInstanceDatabases(),
"storage_stores": schemaFederatedDatabaseInstanceStores(),
},
}
})
}

func schemaFederatedDatabaseInstanceDatabases() *schema.Schema {
Expand Down
4 changes: 4 additions & 0 deletions internal/service/organization/resource_organization.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ func resourceCreate(ctx context.Context, d *schema.ResourceData, meta any) diag.
PrivateKey: *organization.ApiKey.PrivateKey,
BaseURL: meta.(*config.MongoDBClient).Config.BaseURL,
TerraformVersion: meta.(*config.MongoDBClient).Config.TerraformVersion,
AnalyticsEnabled: meta.(*config.MongoDBClient).Config.AnalyticsEnabled,
}

clients, _ := cfg.NewClient(ctx)
Expand Down Expand Up @@ -163,6 +164,7 @@ func resourceRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Di
PrivateKey: d.Get("private_key").(string),
BaseURL: meta.(*config.MongoDBClient).Config.BaseURL,
TerraformVersion: meta.(*config.MongoDBClient).Config.TerraformVersion,
AnalyticsEnabled: meta.(*config.MongoDBClient).Config.AnalyticsEnabled,
}

clients, _ := cfg.NewClient(ctx)
Expand Down Expand Up @@ -222,6 +224,7 @@ func resourceUpdate(ctx context.Context, d *schema.ResourceData, meta any) diag.
PrivateKey: d.Get("private_key").(string),
BaseURL: meta.(*config.MongoDBClient).Config.BaseURL,
TerraformVersion: meta.(*config.MongoDBClient).Config.TerraformVersion,
AnalyticsEnabled: meta.(*config.MongoDBClient).Config.AnalyticsEnabled,
}

clients, _ := cfg.NewClient(ctx)
Expand Down Expand Up @@ -262,6 +265,7 @@ func resourceDelete(ctx context.Context, d *schema.ResourceData, meta any) diag.
PrivateKey: d.Get("private_key").(string),
BaseURL: meta.(*config.MongoDBClient).Config.BaseURL,
TerraformVersion: meta.(*config.MongoDBClient).Config.TerraformVersion,
AnalyticsEnabled: meta.(*config.MongoDBClient).Config.AnalyticsEnabled,
}

clients, _ := cfg.NewClient(ctx)
Expand Down
7 changes: 4 additions & 3 deletions internal/service/organization/resource_organization_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -353,9 +353,10 @@ func getTestClientWithNewOrgCreds(rs *terraform.ResourceState) (*admin.APIClient
}

cfg := config.Config{
PublicKey: rs.Primary.Attributes["public_key"],
PrivateKey: rs.Primary.Attributes["private_key"],
BaseURL: acc.MongoDBClient.Config.BaseURL,
PublicKey: rs.Primary.Attributes["public_key"],
PrivateKey: rs.Primary.Attributes["private_key"],
BaseURL: acc.MongoDBClient.Config.BaseURL,
AnalyticsEnabled: acc.MongoDBClient.Config.AnalyticsEnabled,
}

ctx := context.Background()
Expand Down
26 changes: 11 additions & 15 deletions internal/service/project/resource_project.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,7 @@ var _ resource.ResourceWithConfigure = &projectRS{}
var _ resource.ResourceWithImportState = &projectRS{}

func Resource() resource.Resource {
return &projectRS{
RSCommon: config.RSCommon{
ResourceName: projectResourceName,
},
}
return config.AnalyticsResource(projectResourceName, &projectRS{})
}

type projectRS struct {
Expand All @@ -59,10 +55,10 @@ func (r *projectRS) Create(ctx context.Context, req resource.CreateRequest, resp
var limits []TFLimitModel

connV2 := r.Client.AtlasV2

diags := req.Plan.Get(ctx, &projectPlan)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
diags := &resp.Diagnostics
ctx = r.AddAnalyticsCreate(ctx, &req, diags)
diags.Append(req.Plan.Get(ctx, &projectPlan)...)
if diags.HasError() {
return
}
projectGroup := &admin.Group{
Expand Down Expand Up @@ -174,17 +170,16 @@ func (r *projectRS) Create(ctx context.Context, req resource.CreateRequest, resp
filteredLimits := FilterUserDefinedLimits(projectProps.Limits, limits)
projectProps.Limits = filteredLimits

projectPlanNew, diags := NewTFProjectResourceModel(ctx, projectRes, *projectProps)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
projectPlanNew, localDiags := NewTFProjectResourceModel(ctx, projectRes, *projectProps)
diags.Append(localDiags...)
if diags.HasError() {
return
}
updatePlanFromConfig(projectPlanNew, &projectPlan)

// set state to fully populated data
diags = resp.State.Set(ctx, projectPlanNew)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
diags.Append(resp.State.Set(ctx, projectPlanNew)...)
if diags.HasError() {
return
}
}
Expand Down Expand Up @@ -244,6 +239,7 @@ func (r *projectRS) Update(ctx context.Context, req resource.UpdateRequest, resp
var projectState TFProjectRSModel
var projectPlan TFProjectRSModel
connV2 := r.Client.AtlasV2
ctx = r.AddAnalyticsUpdate(ctx, &req, &resp.Diagnostics)

// get current state
resp.Diagnostics.Append(req.State.Get(ctx, &projectState)...)
Expand Down
Loading
Loading