From 03bf044604db8e1e953037601c583732f4c9e9f6 Mon Sep 17 00:00:00 2001 From: drewmullen Date: Mon, 25 Apr 2022 13:18:59 -0400 Subject: [PATCH 01/24] build provider_meta feturea into provider --- internal/provider/provider.go | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/internal/provider/provider.go b/internal/provider/provider.go index a7d89a452b..de3cfa3bc8 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -462,6 +462,38 @@ func (p *AwsCloudControlApiProvider) GetDataSources(ctx context.Context) (map[st return dataSources, diags } +func (p *AwsCloudControlApiProvider) GetMetaSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) { + return tfsdk.Schema{ + Version: 1, + Attributes: map[string]tfsdk.Attribute{ + "user_agent": { + Attributes: tfsdk.ListNestedAttributes( + map[string]tfsdk.Attribute{ + "product_name": { + Type: types.StringType, + Description: "Product name. At least one of `product_name` or `comment` must be set.", + Required: true, + }, + "product_version": { + Type: types.StringType, + Description: "Product version. Optional, and should only be set when `product_name` is set.", + Optional: true, + }, + "comment": { + Type: types.StringType, + Description: "User-Agent comment. At least one of `comment` or `product_name` must be set.", + Optional: true, + }, + }, + tfsdk.ListNestedAttributesOptions{}, + ), + Description: "Product details to append to User-Agent string in all AWS API calls.", + Optional: true, + }, + }, + }, nil +} + func (p *AwsCloudControlApiProvider) CloudControlApiClient(_ context.Context) *cloudcontrol.Client { return p.ccClient } From 809e5671a2d666574ac844ba282eaa31282f811f Mon Sep 17 00:00:00 2001 From: drewmullen Date: Mon, 23 May 2022 16:00:57 -0400 Subject: [PATCH 02/24] add provider_meta block for experimental testing --- internal/acctest/data.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/internal/acctest/data.go b/internal/acctest/data.go index b7f0e5a453..c4617f4a35 100644 --- a/internal/acctest/data.go +++ b/internal/acctest/data.go @@ -39,6 +39,16 @@ provider "awscc" { role_arn = %[1]q } } + +terraform { + provider_meta "awscc" { + user_agent = [{ + name = "my-test-module" + version = "0.0.1" + comment = "testing user-agent comment" + }] + } +} `, role) + config } return config From 9c7563fdb70eb41a2cdd52780c34425cf7cb3e8e Mon Sep 17 00:00:00 2001 From: drewmullen Date: Fri, 3 Jun 2022 15:09:57 -0400 Subject: [PATCH 03/24] structs for useragent --- .gitignore | 1 + internal/acctest/data.go | 37 ++++++++++++++++++++++- internal/aws/ec2/vpc_resource_gen_test.go | 19 ++++++++++++ internal/generic/resource.go | 25 ++++++++++++++- internal/provider/provider.go | 6 ++++ 5 files changed, 86 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 5fcfaf88af..b7e34d77d7 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ website/node_modules .idea *.iml *.test +*__debug_bin website/vendor diff --git a/internal/acctest/data.go b/internal/acctest/data.go index c4617f4a35..51a5c9f7bf 100644 --- a/internal/acctest/data.go +++ b/internal/acctest/data.go @@ -46,7 +46,13 @@ terraform { name = "my-test-module" version = "0.0.1" comment = "testing user-agent comment" - }] + }, + { + name = "2nd-user-agent" + version = "0.0.1" + comment = "2nd user agent" + } + ] } } `, role) + config @@ -54,6 +60,35 @@ terraform { return config } +func (td *TestData) MetadataConfig() string { + config := fmt.Sprintf(` +resource %[1]q %[2]q {} +`, td.TerraformResourceType, td.ResourceLabel) + + config = fmt.Sprintf(` +resource awscc_ec2_vpc drew { + cidr_block = "10.0.0.0/16" +} + +terraform { + provider_meta "awscc" { + user_agent = [{ + product_name = "my-test-module" + product_version = "0.0.1" + comment = "testing user-agent comment" + }, + { + product_name = "2nd-user-agent" + product_version = "0.0.1" + comment = "2nd user agent" + } + ] + } +} +` + config) + return config +} + // DataSourceWithEmptyResourceConfig returns a Terraform configuration for the data source and its respective resource. func (td *TestData) DataSourceWithEmptyResourceConfig() string { return td.EmptyConfig() + fmt.Sprintf(` diff --git a/internal/aws/ec2/vpc_resource_gen_test.go b/internal/aws/ec2/vpc_resource_gen_test.go index 8e9b715e5d..5fec7362f9 100644 --- a/internal/aws/ec2/vpc_resource_gen_test.go +++ b/internal/aws/ec2/vpc_resource_gen_test.go @@ -27,6 +27,25 @@ func TestAccAWSEC2VPC_basic(t *testing.T) { }) } +func TestAccAWSEC2VPC_metadata(t *testing.T) { + td := acctest.NewTestData(t, "AWS::EC2::VPC", "awscc_ec2_vpc", "test") + + td.ResourceTest(t, []resource.TestStep{ + { + Config: td.MetadataConfig(), + Check: resource.ComposeTestCheckFunc( + td.CheckExistsInAWS(), + ), + }, + { + ResourceName: td.ResourceName, + ImportState: true, + ImportStateVerify: true, + }, + }) +} + + func TestAccAWSEC2VPC_disappears(t *testing.T) { td := acctest.NewTestData(t, "AWS::EC2::VPC", "awscc_ec2_vpc", "test") diff --git a/internal/generic/resource.go b/internal/generic/resource.go index b2245478b9..269864df93 100644 --- a/internal/generic/resource.go +++ b/internal/generic/resource.go @@ -13,6 +13,7 @@ import ( hclog "github.com/hashicorp/go-hclog" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tftypes" "github.com/hashicorp/terraform-plugin-log/tflog" tfcloudcontrol "github.com/hashicorp/terraform-provider-awscc/internal/service/cloudcontrol" @@ -394,11 +395,33 @@ var ( idAttributePath = tftypes.NewAttributePath().WithAttributeName("id") ) +type meta struct { + UserAgents []userAgentProduct `tfsdk:"user_agent"` +} + +type userAgentProduct struct { + ProductName types.String `tfsdk:"product_name"` + ProductVersion types.String `tfsdk:"product_version"` + Comment types.String `tfsdk:"comment"` +} + func (r *resource) Create(ctx context.Context, request tfsdk.CreateResourceRequest, response *tfsdk.CreateResourceResponse) { ctx = r.cfnTypeContext(ctx) traceEntry(ctx, "Resource.Create") + var m []meta + providerMeta := request.ProviderMeta + + diags := providerMeta.Get(ctx, &m) + + // userAgentProducts + + //(*(*r).resourceType).cfTypeName == "AWS::EC2::VPC" + //providerMeta.Raw.value != nil + // newCtx := context.WithValue(ctx, "meta", m) + + // conn := r.provider.CloudControlApiClient(newCtx) conn := r.provider.CloudControlApiClient(ctx) tflog.Debug(ctx, "Request.Plan.Raw", map[string]interface{}{ @@ -480,7 +503,7 @@ func (r *resource) Create(ctx context.Context, request tfsdk.CreateResourceReque } } - diags := r.populateUnknownValues(ctx, id, &response.State) + diags = r.populateUnknownValues(ctx, id, &response.State) if diags.HasError() { response.Diagnostics.Append(diags...) diff --git a/internal/provider/provider.go b/internal/provider/provider.go index de3cfa3bc8..fbe75c032a 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -304,6 +304,9 @@ type providerData struct { UserAgent []userAgentProduct `tfsdk:"user_agent"` terraformVersion string } +type providerMeta struct { + UserAgent []userAgentProduct `tfsdk:"user_agent"` +} type userAgentProduct struct { ProductName types.String `tfsdk:"product_name"` @@ -508,6 +511,8 @@ func (p *AwsCloudControlApiProvider) RoleARN(_ context.Context) string { // newCloudControlClient configures and returns a fully initialized AWS Cloud Control API client with the configured region. func newCloudControlClient(ctx context.Context, pd *providerData) (*cloudcontrol.Client, string, error) { + // m, _ := ctx.Value("meta").(*tfsdk.Config) // _ was ok + config := awsbase.Config{ AccessKey: pd.AccessKey.Value, CallerDocumentationURL: "https://registry.terraform.io/providers/hashicorp/awscc", @@ -527,6 +532,7 @@ func newCloudControlClient(ctx context.Context, pd *providerData) (*cloudcontrol }, } config.UserAgent = userAgentProducts(pd.UserAgent) + // config.UserAgent = userAgentProducts(m.Schema.) if pd.MaxRetries.Null { config.MaxRetries = defaultMaxRetries } else { From 2a01ff755929971e4317199afa55da7dac22ae17 Mon Sep 17 00:00:00 2001 From: drewmullen Date: Thu, 16 Jun 2022 16:35:42 -0400 Subject: [PATCH 04/24] wip --- internal/generic/resource.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/internal/generic/resource.go b/internal/generic/resource.go index 269864df93..a4154dfa9a 100644 --- a/internal/generic/resource.go +++ b/internal/generic/resource.go @@ -413,16 +413,19 @@ func (r *resource) Create(ctx context.Context, request tfsdk.CreateResourceReque var m []meta providerMeta := request.ProviderMeta + // diags := providerMeta.GetAttribute(ctx, idAttributePath, &m) + diags := providerMeta.Get(ctx, &m) + // userAgentProducts //(*(*r).resourceType).cfTypeName == "AWS::EC2::VPC" //providerMeta.Raw.value != nil - // newCtx := context.WithValue(ctx, "meta", m) + newCtx := context.WithValue(ctx, "meta", m) - // conn := r.provider.CloudControlApiClient(newCtx) - conn := r.provider.CloudControlApiClient(ctx) + conn := r.provider.CloudControlApiClient(newCtx) + // conn := r.provider.CloudControlApiClient(ctx) tflog.Debug(ctx, "Request.Plan.Raw", map[string]interface{}{ "value": hclog.Fmt("%v", request.Plan.Raw), From 06c5066c6e350ef6e8f2a6ae35353d9677bf73dd Mon Sep 17 00:00:00 2001 From: drewmullen Date: Fri, 17 Jun 2022 10:42:09 -0400 Subject: [PATCH 05/24] extra providerMeta with ElementAs() --- go.mod | 6 ++++ internal/generic/resource.go | 56 +++++++++++++++++++++++++----------- 2 files changed, 45 insertions(+), 17 deletions(-) diff --git a/go.mod b/go.mod index f658a145f6..7c150349a5 100644 --- a/go.mod +++ b/go.mod @@ -91,3 +91,9 @@ require ( google.golang.org/grpc v1.46.0 // indirect google.golang.org/protobuf v1.28.0 // indirect ) + +// replace github.com/hashicorp/aws-sdk-go-base => ../aws-sdk-go-base + +replace github.com/hashicorp/aws-sdk-go-base => ../aws-sdk-go-base + +// replace github.com/hashicorp/aws-sdk-go-base/v2 => ../aws-sdk-go-base/v2 diff --git a/internal/generic/resource.go b/internal/generic/resource.go index a4154dfa9a..31027920b9 100644 --- a/internal/generic/resource.go +++ b/internal/generic/resource.go @@ -10,6 +10,7 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/cloudcontrol" cctypes "github.com/aws/aws-sdk-go-v2/service/cloudcontrol/types" + awsbase "github.com/hashicorp/aws-sdk-go-base/v2" hclog "github.com/hashicorp/go-hclog" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/tfsdk" @@ -395,36 +396,57 @@ var ( idAttributePath = tftypes.NewAttributePath().WithAttributeName("id") ) -type meta struct { - UserAgents []userAgentProduct `tfsdk:"user_agent"` +type ProviderMetaData struct { + UserAgent types.List `tfsdk:"user_agent"` } -type userAgentProduct struct { - ProductName types.String `tfsdk:"product_name"` - ProductVersion types.String `tfsdk:"product_version"` - Comment types.String `tfsdk:"comment"` -} +// type meta struct { +// UserAgents []userAgentProduct `tfsdk:"user_agent"` +// } + +// type userAgentProduct struct { +// Name types.String `tfsdk:"product_name"` +// Version types.String `tfsdk:"product_version"` +// Comment types.String `tfsdk:"comment"` +// } func (r *resource) Create(ctx context.Context, request tfsdk.CreateResourceRequest, response *tfsdk.CreateResourceResponse) { ctx = r.cfnTypeContext(ctx) traceEntry(ctx, "Resource.Create") - var m []meta - providerMeta := request.ProviderMeta + var providerMetaData ProviderMetaData + var userAgentProducts []awsbase.UserAgentProduct + + response.Diagnostics.Append(request.ProviderMeta.Get(ctx, &providerMetaData)...) + + if response.Diagnostics.HasError() { + return + } + + if !providerMetaData.UserAgent.Null && !providerMetaData.UserAgent.Unknown { + response.Diagnostics.Append(providerMetaData.UserAgent.ElementsAs(ctx, &userAgentProducts, false)...) - // diags := providerMeta.GetAttribute(ctx, idAttributePath, &m) + if response.Diagnostics.HasError() { + return + } + } + + // var m []meta + // providerMeta := request.ProviderMeta - diags := providerMeta.Get(ctx, &m) - + // // diags := providerMeta.GetAttribute(ctx, idAttributePath, &m) - // userAgentProducts + // diags := providerMeta.Get(ctx, &m) - //(*(*r).resourceType).cfTypeName == "AWS::EC2::VPC" - //providerMeta.Raw.value != nil - newCtx := context.WithValue(ctx, "meta", m) + // // userAgentProducts + + // //(*(*r).resourceType).cfTypeName == "AWS::EC2::VPC" + // //providerMeta.Raw.value != nil + newCtx := context.WithValue(ctx, "awsbase.ContextScopedUserAgent", userAgentProducts) conn := r.provider.CloudControlApiClient(newCtx) + // conn := r.provider.CloudControlApiClient(ctx) tflog.Debug(ctx, "Request.Plan.Raw", map[string]interface{}{ @@ -506,7 +528,7 @@ func (r *resource) Create(ctx context.Context, request tfsdk.CreateResourceReque } } - diags = r.populateUnknownValues(ctx, id, &response.State) + diags := r.populateUnknownValues(ctx, id, &response.State) if diags.HasError() { response.Diagnostics.Append(diags...) From 192c90055610a66825abaa6e5b26ffad27ab3201 Mon Sep 17 00:00:00 2001 From: drewmullen Date: Fri, 17 Jun 2022 15:17:34 -0400 Subject: [PATCH 06/24] wip --- go.mod | 5 ++--- internal/acctest/data.go | 16 ---------------- internal/generic/resource.go | 21 --------------------- internal/provider/provider.go | 3 --- 4 files changed, 2 insertions(+), 43 deletions(-) diff --git a/go.mod b/go.mod index 7c150349a5..0f3d579338 100644 --- a/go.mod +++ b/go.mod @@ -92,8 +92,7 @@ require ( google.golang.org/protobuf v1.28.0 // indirect ) -// replace github.com/hashicorp/aws-sdk-go-base => ../aws-sdk-go-base - +// grahams fork +// https://github.com/hashicorp/aws-sdk-go-base/tree/user-agent-from-context replace github.com/hashicorp/aws-sdk-go-base => ../aws-sdk-go-base -// replace github.com/hashicorp/aws-sdk-go-base/v2 => ../aws-sdk-go-base/v2 diff --git a/internal/acctest/data.go b/internal/acctest/data.go index 51a5c9f7bf..28c0d6b468 100644 --- a/internal/acctest/data.go +++ b/internal/acctest/data.go @@ -39,22 +39,6 @@ provider "awscc" { role_arn = %[1]q } } - -terraform { - provider_meta "awscc" { - user_agent = [{ - name = "my-test-module" - version = "0.0.1" - comment = "testing user-agent comment" - }, - { - name = "2nd-user-agent" - version = "0.0.1" - comment = "2nd user agent" - } - ] - } -} `, role) + config } return config diff --git a/internal/generic/resource.go b/internal/generic/resource.go index 31027920b9..ef93d56594 100644 --- a/internal/generic/resource.go +++ b/internal/generic/resource.go @@ -400,16 +400,6 @@ type ProviderMetaData struct { UserAgent types.List `tfsdk:"user_agent"` } -// type meta struct { -// UserAgents []userAgentProduct `tfsdk:"user_agent"` -// } - -// type userAgentProduct struct { -// Name types.String `tfsdk:"product_name"` -// Version types.String `tfsdk:"product_version"` -// Comment types.String `tfsdk:"comment"` -// } - func (r *resource) Create(ctx context.Context, request tfsdk.CreateResourceRequest, response *tfsdk.CreateResourceResponse) { ctx = r.cfnTypeContext(ctx) @@ -432,17 +422,6 @@ func (r *resource) Create(ctx context.Context, request tfsdk.CreateResourceReque } } - // var m []meta - // providerMeta := request.ProviderMeta - - // // diags := providerMeta.GetAttribute(ctx, idAttributePath, &m) - - // diags := providerMeta.Get(ctx, &m) - - // // userAgentProducts - - // //(*(*r).resourceType).cfTypeName == "AWS::EC2::VPC" - // //providerMeta.Raw.value != nil newCtx := context.WithValue(ctx, "awsbase.ContextScopedUserAgent", userAgentProducts) conn := r.provider.CloudControlApiClient(newCtx) diff --git a/internal/provider/provider.go b/internal/provider/provider.go index fbe75c032a..0f9bc684f3 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -511,8 +511,6 @@ func (p *AwsCloudControlApiProvider) RoleARN(_ context.Context) string { // newCloudControlClient configures and returns a fully initialized AWS Cloud Control API client with the configured region. func newCloudControlClient(ctx context.Context, pd *providerData) (*cloudcontrol.Client, string, error) { - // m, _ := ctx.Value("meta").(*tfsdk.Config) // _ was ok - config := awsbase.Config{ AccessKey: pd.AccessKey.Value, CallerDocumentationURL: "https://registry.terraform.io/providers/hashicorp/awscc", @@ -532,7 +530,6 @@ func newCloudControlClient(ctx context.Context, pd *providerData) (*cloudcontrol }, } config.UserAgent = userAgentProducts(pd.UserAgent) - // config.UserAgent = userAgentProducts(m.Schema.) if pd.MaxRetries.Null { config.MaxRetries = defaultMaxRetries } else { From 59857a6e880ea157fc2d16b70d16a65ac9ef2ba4 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Mon, 27 Jun 2022 16:15:02 -0700 Subject: [PATCH 07/24] Updates provider metadata deserialization --- internal/generic/resource.go | 24 ++++---------- internal/provider/provider.go | 25 ++------------- internal/provider/provider_test.go | 46 --------------------------- internal/types/user_agent.go | 26 +++++++++++++++ internal/types/user_agent_test.go | 51 ++++++++++++++++++++++++++++++ 5 files changed, 86 insertions(+), 86 deletions(-) create mode 100644 internal/types/user_agent.go create mode 100644 internal/types/user_agent_test.go diff --git a/internal/generic/resource.go b/internal/generic/resource.go index ef93d56594..f1663bfbd6 100644 --- a/internal/generic/resource.go +++ b/internal/generic/resource.go @@ -10,15 +10,14 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/cloudcontrol" cctypes "github.com/aws/aws-sdk-go-v2/service/cloudcontrol/types" - awsbase "github.com/hashicorp/aws-sdk-go-base/v2" hclog "github.com/hashicorp/go-hclog" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/tfsdk" - "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tftypes" "github.com/hashicorp/terraform-plugin-log/tflog" tfcloudcontrol "github.com/hashicorp/terraform-provider-awscc/internal/service/cloudcontrol" "github.com/hashicorp/terraform-provider-awscc/internal/tfresource" + "github.com/hashicorp/terraform-provider-awscc/internal/types" "github.com/hashicorp/terraform-provider-awscc/internal/validate" "github.com/mattbaird/jsonpatch" ) @@ -396,8 +395,8 @@ var ( idAttributePath = tftypes.NewAttributePath().WithAttributeName("id") ) -type ProviderMetaData struct { - UserAgent types.List `tfsdk:"user_agent"` +type providerMetaData struct { + UserAgent types.UserAgentProducts `tfsdk:"user_agent"` } func (r *resource) Create(ctx context.Context, request tfsdk.CreateResourceRequest, response *tfsdk.CreateResourceResponse) { @@ -405,26 +404,17 @@ func (r *resource) Create(ctx context.Context, request tfsdk.CreateResourceReque traceEntry(ctx, "Resource.Create") - var providerMetaData ProviderMetaData - var userAgentProducts []awsbase.UserAgentProduct + var metadata providerMetaData - response.Diagnostics.Append(request.ProviderMeta.Get(ctx, &providerMetaData)...) + response.Diagnostics.Append(request.ProviderMeta.Get(ctx, &metadata)...) if response.Diagnostics.HasError() { return } - if !providerMetaData.UserAgent.Null && !providerMetaData.UserAgent.Unknown { - response.Diagnostics.Append(providerMetaData.UserAgent.ElementsAs(ctx, &userAgentProducts, false)...) + ctx = context.WithValue(ctx, "awsbase.ContextScopedUserAgent", metadata.UserAgent.UserAgentProducts()) - if response.Diagnostics.HasError() { - return - } - } - - newCtx := context.WithValue(ctx, "awsbase.ContextScopedUserAgent", userAgentProducts) - - conn := r.provider.CloudControlApiClient(newCtx) + conn := r.provider.CloudControlApiClient(ctx) // conn := r.provider.CloudControlApiClient(ctx) diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 0f9bc684f3..a3b081cd80 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -301,18 +301,9 @@ type providerData struct { Token types.String `tfsdk:"token"` AssumeRole *assumeRoleData `tfsdk:"assume_role"` AssumeRoleWithWebIdentity *assumeRoleWithWebIdentityData `tfsdk:"assume_role_with_web_identity"` - UserAgent []userAgentProduct `tfsdk:"user_agent"` + UserAgent cctypes.UserAgentProducts `tfsdk:"user_agent"` terraformVersion string } -type providerMeta struct { - UserAgent []userAgentProduct `tfsdk:"user_agent"` -} - -type userAgentProduct struct { - ProductName types.String `tfsdk:"product_name"` - ProductVersion types.String `tfsdk:"product_version"` - Comment types.String `tfsdk:"comment"` -} type assumeRoleData struct { RoleARN types.String `tfsdk:"role_arn"` @@ -529,7 +520,7 @@ func newCloudControlClient(ctx context.Context, pd *providerData) (*cloudcontrol }, }, } - config.UserAgent = userAgentProducts(pd.UserAgent) + config.UserAgent = pd.UserAgent.UserAgentProducts() if pd.MaxRetries.Null { config.MaxRetries = defaultMaxRetries } else { @@ -603,15 +594,3 @@ func (l awsSdkContextLogger) Logf(classification logging.Classification, format }) } } - -func userAgentProducts(products []userAgentProduct) []awsbase.UserAgentProduct { - results := make([]awsbase.UserAgentProduct, len(products)) - for i, p := range products { - results[i] = awsbase.UserAgentProduct{ - Name: p.ProductName.Value, - Version: p.ProductVersion.Value, - Comment: p.Comment.Value, - } - } - return results -} diff --git a/internal/provider/provider_test.go b/internal/provider/provider_test.go index 914d63c2a8..b0c058c9cc 100644 --- a/internal/provider/provider_test.go +++ b/internal/provider/provider_test.go @@ -2,52 +2,6 @@ package provider import ( "testing" - - "github.com/google/go-cmp/cmp" - awsbase "github.com/hashicorp/aws-sdk-go-base/v2" - "github.com/hashicorp/terraform-plugin-framework/types" ) func TestProvider(t *testing.T) {} - -func TestUserAgentProducts(t *testing.T) { - t.Parallel() - - simpleProduct := awsbase.UserAgentProduct{Name: "simple", Version: "t", Comment: "t"} - simpleAddProduct := userAgentProduct{ProductName: types.String{Value: simpleProduct.Name}, ProductVersion: types.String{Value: simpleProduct.Version}, Comment: types.String{Value: simpleProduct.Comment}} - minimalProduct := awsbase.UserAgentProduct{Name: "minimal"} - minimalAddProduct := userAgentProduct{ProductName: types.String{Value: minimalProduct.Name}} - - testcases := map[string]struct { - addProducts []userAgentProduct - expected []awsbase.UserAgentProduct - }{ - "none_added": { - addProducts: []userAgentProduct{}, - expected: []awsbase.UserAgentProduct{}, - }, - "simple_added": { - addProducts: []userAgentProduct{simpleAddProduct}, - expected: []awsbase.UserAgentProduct{simpleProduct}, - }, - "minimal_added": { - addProducts: []userAgentProduct{minimalAddProduct}, - expected: []awsbase.UserAgentProduct{minimalProduct}, - }, - "both_added": { - addProducts: []userAgentProduct{simpleAddProduct, minimalAddProduct}, - expected: []awsbase.UserAgentProduct{simpleProduct, minimalProduct}, - }, - } - - for name, testcase := range testcases { - name, testcase := name, testcase - - t.Run(name, func(t *testing.T) { - actual := userAgentProducts(testcase.addProducts) - if !cmp.Equal(testcase.expected, actual) { - t.Errorf("expected %q, got %q", testcase.expected, actual) - } - }) - } -} diff --git a/internal/types/user_agent.go b/internal/types/user_agent.go new file mode 100644 index 0000000000..691fdc80d3 --- /dev/null +++ b/internal/types/user_agent.go @@ -0,0 +1,26 @@ +package types + +import ( + awsbase "github.com/hashicorp/aws-sdk-go-base/v2" + tftypes "github.com/hashicorp/terraform-plugin-framework/types" +) + +type UserAgentProducts []userAgentProduct + +type userAgentProduct struct { + ProductName tftypes.String `tfsdk:"product_name"` + ProductVersion tftypes.String `tfsdk:"product_version"` + Comment tftypes.String `tfsdk:"comment"` +} + +func (uap UserAgentProducts) UserAgentProducts() []awsbase.UserAgentProduct { + results := make([]awsbase.UserAgentProduct, len(uap)) + for i, p := range uap { + results[i] = awsbase.UserAgentProduct{ + Name: p.ProductName.Value, + Version: p.ProductVersion.Value, + Comment: p.Comment.Value, + } + } + return results +} diff --git a/internal/types/user_agent_test.go b/internal/types/user_agent_test.go new file mode 100644 index 0000000000..2f1eca0eac --- /dev/null +++ b/internal/types/user_agent_test.go @@ -0,0 +1,51 @@ +package types + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + awsbase "github.com/hashicorp/aws-sdk-go-base/v2" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +func TestUserAgentProducts(t *testing.T) { + t.Parallel() + + simpleProduct := awsbase.UserAgentProduct{Name: "simple", Version: "t", Comment: "t"} + simpleAddProduct := userAgentProduct{ProductName: types.String{Value: simpleProduct.Name}, ProductVersion: types.String{Value: simpleProduct.Version}, Comment: types.String{Value: simpleProduct.Comment}} + minimalProduct := awsbase.UserAgentProduct{Name: "minimal"} + minimalAddProduct := userAgentProduct{ProductName: types.String{Value: minimalProduct.Name}} + + testcases := map[string]struct { + addProducts UserAgentProducts + expected []awsbase.UserAgentProduct + }{ + "none_added": { + addProducts: []userAgentProduct{}, + expected: []awsbase.UserAgentProduct{}, + }, + "simple_added": { + addProducts: []userAgentProduct{simpleAddProduct}, + expected: []awsbase.UserAgentProduct{simpleProduct}, + }, + "minimal_added": { + addProducts: []userAgentProduct{minimalAddProduct}, + expected: []awsbase.UserAgentProduct{minimalProduct}, + }, + "both_added": { + addProducts: []userAgentProduct{simpleAddProduct, minimalAddProduct}, + expected: []awsbase.UserAgentProduct{simpleProduct, minimalProduct}, + }, + } + + for name, testcase := range testcases { + name, testcase := name, testcase + + t.Run(name, func(t *testing.T) { + actual := testcase.addProducts.UserAgentProducts() + if !cmp.Equal(testcase.expected, actual) { + t.Errorf("expected %q, got %q", testcase.expected, actual) + } + }) + } +} From f949e74ec657ecacecd81c32c466c3d7118c422c Mon Sep 17 00:00:00 2001 From: Jared Baker Date: Wed, 24 Sep 2025 10:56:11 -0400 Subject: [PATCH 08/24] chore(deps): remove `replace` directive for `aws-sdk-go-base` The changes from this branch were merged into main in August 2022. https://github.com/hashicorp/aws-sdk-go-base/pull/318 --- .gitignore | 1 - go.mod | 5 ----- 2 files changed, 6 deletions(-) diff --git a/.gitignore b/.gitignore index b41d32e68d..ce241b3690 100644 --- a/.gitignore +++ b/.gitignore @@ -23,7 +23,6 @@ website/node_modules .idea *.iml *.test -*__debug_bin website/vendor diff --git a/go.mod b/go.mod index 107e1c278d..ea1c9d6c7a 100644 --- a/go.mod +++ b/go.mod @@ -118,8 +118,3 @@ require ( google.golang.org/grpc v1.75.1 // indirect google.golang.org/protobuf v1.36.9 // indirect ) - -// grahams fork -// https://github.com/hashicorp/aws-sdk-go-base/tree/user-agent-from-context -replace github.com/hashicorp/aws-sdk-go-base => ../aws-sdk-go-base - From 5dd814b07df933276f67e454f1703ffcd216c865 Mon Sep 17 00:00:00 2001 From: Jared Baker Date: Wed, 24 Sep 2025 16:14:50 -0400 Subject: [PATCH 09/24] internal/types: fix unit tests --- internal/types/user_agent_test.go | 40 ++++++++++++++++++------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/internal/types/user_agent_test.go b/internal/types/user_agent_test.go index 2f1eca0eac..50a8d280b7 100644 --- a/internal/types/user_agent_test.go +++ b/internal/types/user_agent_test.go @@ -12,29 +12,35 @@ func TestUserAgentProducts(t *testing.T) { t.Parallel() simpleProduct := awsbase.UserAgentProduct{Name: "simple", Version: "t", Comment: "t"} - simpleAddProduct := userAgentProduct{ProductName: types.String{Value: simpleProduct.Name}, ProductVersion: types.String{Value: simpleProduct.Version}, Comment: types.String{Value: simpleProduct.Comment}} + simpleAddProduct := userAgentProduct{ + ProductName: types.StringValue(simpleProduct.Name), + ProductVersion: types.StringValue(simpleProduct.Version), + Comment: types.StringValue(simpleProduct.Comment), + } minimalProduct := awsbase.UserAgentProduct{Name: "minimal"} - minimalAddProduct := userAgentProduct{ProductName: types.String{Value: minimalProduct.Name}} + minimalAddProduct := userAgentProduct{ + ProductName: types.StringValue(minimalProduct.Name), + } testcases := map[string]struct { - addProducts UserAgentProducts - expected []awsbase.UserAgentProduct + add UserAgentProducts + expected []awsbase.UserAgentProduct }{ - "none_added": { - addProducts: []userAgentProduct{}, - expected: []awsbase.UserAgentProduct{}, + "none": { + add: []userAgentProduct{}, + expected: []awsbase.UserAgentProduct{}, }, - "simple_added": { - addProducts: []userAgentProduct{simpleAddProduct}, - expected: []awsbase.UserAgentProduct{simpleProduct}, + "simple": { + add: []userAgentProduct{simpleAddProduct}, + expected: []awsbase.UserAgentProduct{simpleProduct}, }, - "minimal_added": { - addProducts: []userAgentProduct{minimalAddProduct}, - expected: []awsbase.UserAgentProduct{minimalProduct}, + "minimal": { + add: []userAgentProduct{minimalAddProduct}, + expected: []awsbase.UserAgentProduct{minimalProduct}, }, - "both_added": { - addProducts: []userAgentProduct{simpleAddProduct, minimalAddProduct}, - expected: []awsbase.UserAgentProduct{simpleProduct, minimalProduct}, + "both": { + add: []userAgentProduct{simpleAddProduct, minimalAddProduct}, + expected: []awsbase.UserAgentProduct{simpleProduct, minimalProduct}, }, } @@ -42,7 +48,7 @@ func TestUserAgentProducts(t *testing.T) { name, testcase := name, testcase t.Run(name, func(t *testing.T) { - actual := testcase.addProducts.UserAgentProducts() + actual := testcase.add.UserAgentProducts() if !cmp.Equal(testcase.expected, actual) { t.Errorf("expected %q, got %q", testcase.expected, actual) } From 4ea6dbbe71e98098d93f2dbc21613ed00ee51a34 Mon Sep 17 00:00:00 2001 From: Jared Baker Date: Wed, 24 Sep 2025 16:43:48 -0400 Subject: [PATCH 10/24] internal/provider: verify provider interfaces are satisfied at comp time --- internal/provider/provider.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 67707594af..883d699363 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -69,6 +69,10 @@ func (p *providerData) RoleARN(_ context.Context) string { return p.roleARN } +var _ provider.Provider = &ccProvider{} +var _ provider.ProviderWithMetaSchema = &ccProvider{} +var _ provider.ProviderWithListResources = &ccProvider{} + type ccProvider struct { providerData *providerData // Used in acceptance tests. } From 04c74f75224fbae71a02e9249a3dd485a38d1596 Mon Sep 17 00:00:00 2001 From: Jared Baker Date: Wed, 24 Sep 2025 16:47:33 -0400 Subject: [PATCH 11/24] internal/generic: prefer `useragent.Context` to set additional user agent products --- internal/generic/resource.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/generic/resource.go b/internal/generic/resource.go index 92c63be219..082abb2791 100644 --- a/internal/generic/resource.go +++ b/internal/generic/resource.go @@ -12,6 +12,7 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/cloudcontrol" cctypes "github.com/aws/aws-sdk-go-v2/service/cloudcontrol/types" + "github.com/hashicorp/aws-sdk-go-base/v2/useragent" hclog "github.com/hashicorp/go-hclog" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" @@ -404,7 +405,7 @@ func (r *genericResource) Create(ctx context.Context, request resource.CreateReq if metadata != nil { var uap inttypes.UserAgentProducts metadata.UserAgent.ElementsAs(ctx, &uap, false) - ctx = context.WithValue(ctx, "awsbase.ContextScopedUserAgent", uap.UserAgentProducts()) + ctx = useragent.Context(ctx, uap.UserAgentProducts()) } conn := r.provider.CloudControlAPIClient(ctx) From 9a4ebd6159a85a083f6768b630f2bd5cb5edda69 Mon Sep 17 00:00:00 2001 From: Jared Baker Date: Wed, 24 Sep 2025 16:48:18 -0400 Subject: [PATCH 12/24] [wip]internal/acctest: tidy EmptyConfigWithProviderMeta --- internal/acctest/data.go | 27 +++++++++-------------- internal/aws/ec2/vpc_resource_gen_test.go | 19 ---------------- internal/aws/ec2/vpc_resource_test.go | 18 +++++++++++++++ 3 files changed, 28 insertions(+), 36 deletions(-) diff --git a/internal/acctest/data.go b/internal/acctest/data.go index 173177b247..ecbb880567 100644 --- a/internal/acctest/data.go +++ b/internal/acctest/data.go @@ -48,33 +48,26 @@ provider "awscc" { return config } -func (td *TestData) MetadataConfig() string { - config := fmt.Sprintf(` -resource %[1]q %[2]q {} -`, td.TerraformResourceType, td.ResourceLabel) - - config = fmt.Sprintf(` -resource awscc_ec2_vpc drew { - cidr_block = "10.0.0.0/16" -} - +// EmptyConfigWithProviderMeta injects a provider_meta block into the base EmptyConfig. +func (td *TestData) EmptyConfigWithProviderMeta() string { + return td.EmptyConfig() + ` terraform { provider_meta "awscc" { - user_agent = [{ + user_agent = [ + { product_name = "my-test-module" product_version = "0.0.1" - comment = "testing user-agent comment" + comment = "testing user-agent comment" }, { - product_name = "2nd-user-agent" - product_version = "0.0.1" - comment = "2nd user agent" + product_name = "other-test-module" + product_version = "0.0.2" + comment = "second user agent" } ] } } -` + config) - return config +` } // DataSourceWithEmptyResourceConfig returns a Terraform configuration for the data source and its respective resource. diff --git a/internal/aws/ec2/vpc_resource_gen_test.go b/internal/aws/ec2/vpc_resource_gen_test.go index 9120e061bd..8dd37d1e8c 100644 --- a/internal/aws/ec2/vpc_resource_gen_test.go +++ b/internal/aws/ec2/vpc_resource_gen_test.go @@ -30,25 +30,6 @@ func TestAccAWSEC2VPC_basic(t *testing.T) { }) } -func TestAccAWSEC2VPC_metadata(t *testing.T) { - td := acctest.NewTestData(t, "AWS::EC2::VPC", "awscc_ec2_vpc", "test") - - td.ResourceTest(t, []resource.TestStep{ - { - Config: td.MetadataConfig(), - Check: resource.ComposeTestCheckFunc( - td.CheckExistsInAWS(), - ), - }, - { - ResourceName: td.ResourceName, - ImportState: true, - ImportStateVerify: true, - }, - }) -} - - func TestAccAWSEC2VPC_disappears(t *testing.T) { td := acctest.NewTestData(t, "AWS::EC2::VPC", "awscc_ec2_vpc", "test") diff --git a/internal/aws/ec2/vpc_resource_test.go b/internal/aws/ec2/vpc_resource_test.go index 15d461a784..9387ec19e0 100644 --- a/internal/aws/ec2/vpc_resource_test.go +++ b/internal/aws/ec2/vpc_resource_test.go @@ -36,6 +36,24 @@ func TestAccAWSEC2VPC_CidrBlock(t *testing.T) { }) } +func TestAccAWSEC2VPC_providerMeta(t *testing.T) { + td := acctest.NewTestData(t, "AWS::EC2::VPC", "awscc_ec2_vpc", "test") + + td.ResourceTest(t, []resource.TestStep{ + { + Config: td.EmptyConfigWithProviderMeta(), + Check: resource.ComposeTestCheckFunc( + td.CheckExistsInAWS(), + ), + }, + { + ResourceName: td.ResourceName, + ImportState: true, + ImportStateVerify: true, + }, + }) +} + func testAccAWSEC2VPCCidrBlockConfig(td *acctest.TestData, rName, cidrBlock string) string { return fmt.Sprintf(` resource %[1]q %[2]q { From 80c1909522deae4dd51246434d290d4e6bbb284b Mon Sep 17 00:00:00 2001 From: Jared Baker Date: Tue, 30 Sep 2025 09:11:37 -0400 Subject: [PATCH 13/24] internal/generic: read provider meta in all CRUD ops --- internal/generic/resource.go | 61 ++++++++++++++++++++++++++---------- 1 file changed, 45 insertions(+), 16 deletions(-) diff --git a/internal/generic/resource.go b/internal/generic/resource.go index 082abb2791..24c65ddc07 100644 --- a/internal/generic/resource.go +++ b/internal/generic/resource.go @@ -393,20 +393,20 @@ func (r *genericResource) Configure(_ context.Context, request resource.Configur } func (r *genericResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { - ctx = r.bootstrapContext(ctx) + ctx = r.bootstrapContext(ctx, bootstrapContextWithProviderMeta(request.ProviderMeta)) traceEntry(ctx, "Resource.Create") - var metadata *providerMetaData - response.Diagnostics.Append(request.ProviderMeta.Get(ctx, &metadata)...) - if response.Diagnostics.HasError() { - return - } - if metadata != nil { - var uap inttypes.UserAgentProducts - metadata.UserAgent.ElementsAs(ctx, &uap, false) - ctx = useragent.Context(ctx, uap.UserAgentProducts()) - } + // var metadata *providerMetaData + // response.Diagnostics.Append(request.ProviderMeta.Get(ctx, &metadata)...) + // if response.Diagnostics.HasError() { + // return + // } + // if metadata != nil { + // var uap inttypes.UserAgentProducts + // metadata.UserAgent.ElementsAs(ctx, &uap, false) + // ctx = useragent.Context(ctx, uap.UserAgentProducts()) + // } conn := r.provider.CloudControlAPIClient(ctx) @@ -507,7 +507,7 @@ func (r *genericResource) Create(ctx context.Context, request resource.CreateReq } func (r *genericResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { - ctx = r.bootstrapContext(ctx) + ctx = r.bootstrapContext(ctx, bootstrapContextWithProviderMeta(request.ProviderMeta)) traceEntry(ctx, "Resource.Read") @@ -591,7 +591,7 @@ func (r *genericResource) Read(ctx context.Context, request resource.ReadRequest } func (r *genericResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) { - ctx = r.bootstrapContext(ctx) + ctx = r.bootstrapContext(ctx, bootstrapContextWithProviderMeta(request.ProviderMeta)) traceEntry(ctx, "Resource.Update") @@ -716,7 +716,7 @@ func (r *genericResource) Update(ctx context.Context, request resource.UpdateReq } func (r *genericResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { - ctx = r.bootstrapContext(ctx) + ctx = r.bootstrapContext(ctx, bootstrapContextWithProviderMeta(request.ProviderMeta)) traceEntry(ctx, "Resource.Delete") @@ -930,14 +930,43 @@ func (r *genericResource) populateUnknownValues(ctx context.Context, id string, return nil } -// bootstrapContext injects the CloudFormation type name into logger contexts. -func (r *genericResource) bootstrapContext(ctx context.Context) context.Context { +// bootstrapContext injects the CloudFormation type name into logger contexts and +// runs any configured functional options +func (r *genericResource) bootstrapContext(ctx context.Context, optFn ...bootrapContextOptionsFunc) context.Context { ctx = tflog.SetField(ctx, LoggingKeyCFNType, r.cfTypeName) ctx = r.provider.RegisterLogger(ctx) + for _, fn := range optFn { + ctx = fn(ctx) + } + return ctx } +type bootrapContextOptionsFunc func(ctx context.Context) context.Context + +// bootstrapContextWithProviderMeta injects details from the provider_meta block into context +func bootstrapContextWithProviderMeta(providerMeta tfsdk.Config) bootrapContextOptionsFunc { + return func(ctx context.Context) context.Context { + var metadata *providerMetaData + var d diag.Diagnostics + + // TODO: return diags + d.Append(providerMeta.Get(ctx, &metadata)...) + if d.HasError() { + panic("todo - implement diag handling") + } + + if metadata != nil { + var uap inttypes.UserAgentProducts + metadata.UserAgent.ElementsAs(ctx, &uap, false) + ctx = useragent.Context(ctx, uap.UserAgentProducts()) + } + + return ctx + } +} + // propertyPathToAttributePath returns the AttributePath for the specified JSON Pointer property path. func (r *genericResource) propertyPathToAttributePath(propertyPath string) (*path.Path, error) { segments := strings.Split(propertyPath, "/") From 0a2b09844dc1ad2f15d2c6864e0fe79604ff53c4 Mon Sep 17 00:00:00 2001 From: Jared Baker Date: Tue, 30 Sep 2025 10:28:18 -0400 Subject: [PATCH 14/24] internal/generic: simplify bootstrap context implementation --- internal/generic/resource.go | 75 +++++++++++++++--------------------- 1 file changed, 31 insertions(+), 44 deletions(-) diff --git a/internal/generic/resource.go b/internal/generic/resource.go index 24c65ddc07..471ed06f38 100644 --- a/internal/generic/resource.go +++ b/internal/generic/resource.go @@ -393,21 +393,13 @@ func (r *genericResource) Configure(_ context.Context, request resource.Configur } func (r *genericResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { - ctx = r.bootstrapContext(ctx, bootstrapContextWithProviderMeta(request.ProviderMeta)) + ctx = r.bootstrapContextWithProviderMeta(ctx, request.ProviderMeta, &response.Diagnostics) + if response.Diagnostics.HasError() { + return + } traceEntry(ctx, "Resource.Create") - // var metadata *providerMetaData - // response.Diagnostics.Append(request.ProviderMeta.Get(ctx, &metadata)...) - // if response.Diagnostics.HasError() { - // return - // } - // if metadata != nil { - // var uap inttypes.UserAgentProducts - // metadata.UserAgent.ElementsAs(ctx, &uap, false) - // ctx = useragent.Context(ctx, uap.UserAgentProducts()) - // } - conn := r.provider.CloudControlAPIClient(ctx) tflog.Debug(ctx, "Request.Plan.Raw", map[string]interface{}{ @@ -507,7 +499,10 @@ func (r *genericResource) Create(ctx context.Context, request resource.CreateReq } func (r *genericResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { - ctx = r.bootstrapContext(ctx, bootstrapContextWithProviderMeta(request.ProviderMeta)) + ctx = r.bootstrapContextWithProviderMeta(ctx, request.ProviderMeta, &response.Diagnostics) + if response.Diagnostics.HasError() { + return + } traceEntry(ctx, "Resource.Read") @@ -591,7 +586,10 @@ func (r *genericResource) Read(ctx context.Context, request resource.ReadRequest } func (r *genericResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) { - ctx = r.bootstrapContext(ctx, bootstrapContextWithProviderMeta(request.ProviderMeta)) + ctx = r.bootstrapContextWithProviderMeta(ctx, request.ProviderMeta, &response.Diagnostics) + if response.Diagnostics.HasError() { + return + } traceEntry(ctx, "Resource.Update") @@ -716,7 +714,10 @@ func (r *genericResource) Update(ctx context.Context, request resource.UpdateReq } func (r *genericResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { - ctx = r.bootstrapContext(ctx, bootstrapContextWithProviderMeta(request.ProviderMeta)) + ctx = r.bootstrapContextWithProviderMeta(ctx, request.ProviderMeta, &response.Diagnostics) + if response.Diagnostics.HasError() { + return + } traceEntry(ctx, "Resource.Delete") @@ -930,41 +931,27 @@ func (r *genericResource) populateUnknownValues(ctx context.Context, id string, return nil } -// bootstrapContext injects the CloudFormation type name into logger contexts and -// runs any configured functional options -func (r *genericResource) bootstrapContext(ctx context.Context, optFn ...bootrapContextOptionsFunc) context.Context { +// bootstrapContext injects the CloudFormation type name into logger contexts +func (r *genericResource) bootstrapContext(ctx context.Context) context.Context { ctx = tflog.SetField(ctx, LoggingKeyCFNType, r.cfTypeName) - ctx = r.provider.RegisterLogger(ctx) - - for _, fn := range optFn { - ctx = fn(ctx) - } - - return ctx + return r.provider.RegisterLogger(ctx) } -type bootrapContextOptionsFunc func(ctx context.Context) context.Context - -// bootstrapContextWithProviderMeta injects details from the provider_meta block into context -func bootstrapContextWithProviderMeta(providerMeta tfsdk.Config) bootrapContextOptionsFunc { - return func(ctx context.Context) context.Context { - var metadata *providerMetaData - var d diag.Diagnostics - - // TODO: return diags - d.Append(providerMeta.Get(ctx, &metadata)...) - if d.HasError() { - panic("todo - implement diag handling") - } +// bootstrapContextWithProviderMeta is an extenion of bootstrapContext which +// also injects details from the provider_meta block into context +func (r *genericResource) bootstrapContextWithProviderMeta(ctx context.Context, providerMeta tfsdk.Config, d *diag.Diagnostics) context.Context { + ctx = r.bootstrapContext(ctx) - if metadata != nil { - var uap inttypes.UserAgentProducts - metadata.UserAgent.ElementsAs(ctx, &uap, false) - ctx = useragent.Context(ctx, uap.UserAgentProducts()) - } + var metadata *providerMetaData + d.Append(providerMeta.Get(ctx, &metadata)...) - return ctx + if metadata != nil { + var uap inttypes.UserAgentProducts + d.Append(metadata.UserAgent.ElementsAs(ctx, &uap, false)...) + ctx = useragent.Context(ctx, uap.UserAgentProducts()) } + + return ctx } // propertyPathToAttributePath returns the AttributePath for the specified JSON Pointer property path. From 0da5397952bc7a75d2ee0aef07d96773939d94d1 Mon Sep 17 00:00:00 2001 From: Jared Baker Date: Tue, 30 Sep 2025 10:28:49 -0400 Subject: [PATCH 15/24] internal/types: align `UserAgentProduct` field names with aws-sdk-go-base --- internal/provider/provider.go | 50 +++++++++++++++---------------- internal/types/user_agent.go | 14 ++++----- internal/types/user_agent_test.go | 10 +++---- 3 files changed, 37 insertions(+), 37 deletions(-) diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 883d699363..681e391e78 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -26,7 +26,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-provider-awscc/internal/flex" "github.com/hashicorp/terraform-provider-awscc/internal/registry" - cctypes "github.com/hashicorp/terraform-provider-awscc/internal/types" + inttypes "github.com/hashicorp/terraform-provider-awscc/internal/types" ) const ( @@ -101,7 +101,7 @@ func (p *ccProvider) Schema(ctx context.Context, request provider.SchemaRequest, "assume_role": schema.SingleNestedAttribute{ Attributes: map[string]schema.Attribute{ "duration": schema.StringAttribute{ - CustomType: cctypes.DurationType, + CustomType: inttypes.DurationType, Description: "The duration, between 15 minutes and 12 hours, of the role session. Valid time units are ns, us (or µs), ms, s, h, or m.", Optional: true, }, @@ -115,12 +115,12 @@ func (p *ccProvider) Schema(ctx context.Context, request provider.SchemaRequest, Optional: true, }, "policy_arns": schema.ListAttribute{ - ElementType: cctypes.ARNType, + ElementType: inttypes.ARNType, Description: "Amazon Resource Names (ARNs) of IAM Policies to use as managed session policies. The effective permissions for the session will be the intersection between these polcy and the role's policies.", Optional: true, }, "role_arn": schema.StringAttribute{ - CustomType: cctypes.ARNType, + CustomType: inttypes.ARNType, Description: "Amazon Resource Name (ARN) of the IAM Role to assume.", Required: true, }, @@ -145,7 +145,7 @@ func (p *ccProvider) Schema(ctx context.Context, request provider.SchemaRequest, "assume_role_with_web_identity": schema.SingleNestedAttribute{ Attributes: map[string]schema.Attribute{ "duration": schema.StringAttribute{ - CustomType: cctypes.DurationType, + CustomType: inttypes.DurationType, Description: "The duration, between 15 minutes and 12 hours, of the role session. Valid time units are ns, us (or µs), ms, s, h, or m.", Optional: true, }, @@ -155,12 +155,12 @@ func (p *ccProvider) Schema(ctx context.Context, request provider.SchemaRequest, Optional: true, }, "policy_arns": schema.ListAttribute{ - ElementType: cctypes.ARNType, + ElementType: inttypes.ARNType, Description: "Amazon Resource Names (ARNs) of IAM Policies to use as managed session policies. The effective permissions for the session will be the intersection between these polcy and the role's policies.", Optional: true, }, "role_arn": schema.StringAttribute{ - CustomType: cctypes.ARNType, + CustomType: inttypes.ARNType, Description: "Amazon Resource Name (ARN) of the IAM Role to assume. Can also be set with the environment variable `AWS_ROLE_ARN`.", Required: true, }, @@ -234,7 +234,7 @@ func (p *ccProvider) Schema(ctx context.Context, request provider.SchemaRequest, Optional: true, }, "role_arn": schema.StringAttribute{ - CustomType: cctypes.ARNType, + CustomType: inttypes.ARNType, Description: "Amazon Resource Name of the AWS CloudFormation service role that is used on your behalf to perform operations.", Optional: true, }, @@ -328,27 +328,27 @@ type configModel struct { NoProxy types.String `tfsdk:"no_proxy"` Profile types.String `tfsdk:"profile"` Region types.String `tfsdk:"region"` - RoleARN cctypes.ARN `tfsdk:"role_arn"` + RoleARN inttypes.ARN `tfsdk:"role_arn"` SecretKey types.String `tfsdk:"secret_key"` SharedConfigFiles types.List `tfsdk:"shared_config_files"` SharedCredentialsFiles types.List `tfsdk:"shared_credentials_files"` SkipMedatadataApiCheck types.Bool `tfsdk:"skip_medatadata_api_check"` SkipMetadataApiCheck types.Bool `tfsdk:"skip_metadata_api_check"` Token types.String `tfsdk:"token"` - UserAgent cctypes.UserAgentProducts `tfsdk:"user_agent"` + UserAgent inttypes.UserAgentProducts `tfsdk:"user_agent"` terraformVersion string } type assumeRoleModel struct { - Duration cctypes.Duration `tfsdk:"duration"` - ExternalID types.String `tfsdk:"external_id"` - Policy jsontypes.Exact `tfsdk:"policy"` - PolicyARNs types.List `tfsdk:"policy_arns"` - RoleARN cctypes.ARN `tfsdk:"role_arn"` - SessionName types.String `tfsdk:"session_name"` - Tags types.Map `tfsdk:"tags"` - TransitiveTagKeys types.Set `tfsdk:"transitive_tag_keys"` + Duration inttypes.Duration `tfsdk:"duration"` + ExternalID types.String `tfsdk:"external_id"` + Policy jsontypes.Exact `tfsdk:"policy"` + PolicyARNs types.List `tfsdk:"policy_arns"` + RoleARN inttypes.ARN `tfsdk:"role_arn"` + SessionName types.String `tfsdk:"session_name"` + Tags types.Map `tfsdk:"tags"` + TransitiveTagKeys types.Set `tfsdk:"transitive_tag_keys"` } func (a assumeRoleModel) Config() awsbase.AssumeRole { @@ -392,13 +392,13 @@ type endpointData struct { } type assumeRoleWithWebIdentityData struct { - Duration cctypes.Duration `tfsdk:"duration"` - Policy jsontypes.Exact `tfsdk:"policy"` - PolicyARNs types.List `tfsdk:"policy_arns"` - RoleARN cctypes.ARN `tfsdk:"role_arn"` - SessionName types.String `tfsdk:"session_name"` - WebIdentityToken types.String `tfsdk:"web_identity_token"` - WebIdentityTokenFile types.String `tfsdk:"web_identity_token_file"` + Duration inttypes.Duration `tfsdk:"duration"` + Policy jsontypes.Exact `tfsdk:"policy"` + PolicyARNs types.List `tfsdk:"policy_arns"` + RoleARN inttypes.ARN `tfsdk:"role_arn"` + SessionName types.String `tfsdk:"session_name"` + WebIdentityToken types.String `tfsdk:"web_identity_token"` + WebIdentityTokenFile types.String `tfsdk:"web_identity_token_file"` } func (a assumeRoleWithWebIdentityData) Config() *awsbase.AssumeRoleWithWebIdentity { diff --git a/internal/types/user_agent.go b/internal/types/user_agent.go index 6f7e8dbacd..d5a76b135b 100644 --- a/internal/types/user_agent.go +++ b/internal/types/user_agent.go @@ -5,20 +5,20 @@ import ( tftypes "github.com/hashicorp/terraform-plugin-framework/types" ) -type UserAgentProducts []userAgentProduct - type userAgentProduct struct { - ProductName tftypes.String `tfsdk:"product_name"` - ProductVersion tftypes.String `tfsdk:"product_version"` - Comment tftypes.String `tfsdk:"comment"` + Name tftypes.String `tfsdk:"name"` + Version tftypes.String `tfsdk:"version"` + Comment tftypes.String `tfsdk:"comment"` } +type UserAgentProducts []userAgentProduct + func (uap UserAgentProducts) UserAgentProducts() []awsbase.UserAgentProduct { results := make([]awsbase.UserAgentProduct, len(uap)) for i, p := range uap { results[i] = awsbase.UserAgentProduct{ - Name: p.ProductName.ValueString(), - Version: p.ProductVersion.ValueString(), + Name: p.Name.ValueString(), + Version: p.Version.ValueString(), Comment: p.Comment.ValueString(), } } diff --git a/internal/types/user_agent_test.go b/internal/types/user_agent_test.go index 50a8d280b7..543b4c3718 100644 --- a/internal/types/user_agent_test.go +++ b/internal/types/user_agent_test.go @@ -11,15 +11,15 @@ import ( func TestUserAgentProducts(t *testing.T) { t.Parallel() - simpleProduct := awsbase.UserAgentProduct{Name: "simple", Version: "t", Comment: "t"} + simpleProduct := awsbase.UserAgentProduct{Name: "simple", Version: "0.0.1", Comment: "test comment"} simpleAddProduct := userAgentProduct{ - ProductName: types.StringValue(simpleProduct.Name), - ProductVersion: types.StringValue(simpleProduct.Version), - Comment: types.StringValue(simpleProduct.Comment), + Name: types.StringValue(simpleProduct.Name), + Version: types.StringValue(simpleProduct.Version), + Comment: types.StringValue(simpleProduct.Comment), } minimalProduct := awsbase.UserAgentProduct{Name: "minimal"} minimalAddProduct := userAgentProduct{ - ProductName: types.StringValue(minimalProduct.Name), + Name: types.StringValue(minimalProduct.Name), } testcases := map[string]struct { From 79fba75584fe61cbe2dbd409821f81e8ea457b1b Mon Sep 17 00:00:00 2001 From: Jared Baker Date: Tue, 30 Sep 2025 10:38:18 -0400 Subject: [PATCH 16/24] chore: fix `misspell` linter finding --- internal/generic/resource.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/generic/resource.go b/internal/generic/resource.go index 471ed06f38..b4a512fd90 100644 --- a/internal/generic/resource.go +++ b/internal/generic/resource.go @@ -937,7 +937,7 @@ func (r *genericResource) bootstrapContext(ctx context.Context) context.Context return r.provider.RegisterLogger(ctx) } -// bootstrapContextWithProviderMeta is an extenion of bootstrapContext which +// bootstrapContextWithProviderMeta is an extension of bootstrapContext which // also injects details from the provider_meta block into context func (r *genericResource) bootstrapContextWithProviderMeta(ctx context.Context, providerMeta tfsdk.Config, d *diag.Diagnostics) context.Context { ctx = r.bootstrapContext(ctx) From ec4280dd176f8953851bd05e8acb5f7213fe6f0b Mon Sep 17 00:00:00 2001 From: Jared Baker Date: Tue, 30 Sep 2025 11:27:22 -0400 Subject: [PATCH 17/24] internal/provider: align `user_agent` field names with aws-sdk-go-base --- internal/provider/provider.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 681e391e78..22c17def00 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -282,7 +282,7 @@ func (p *ccProvider) Schema(ctx context.Context, request provider.SchemaRequest, }, }, }, - Description: "Product details to append to User-Agent string in all AWS API calls.", + Description: "Product details to append to the User-Agent string sent in all AWS API calls.", Optional: true, }, }, @@ -299,17 +299,17 @@ func (p *ccProvider) MetaSchema(ctx context.Context, req provider.MetaSchemaRequ Description: "User-Agent comment. At least one of `comment` or `product_name` must be set.", Optional: true, }, - "product_name": metaschema.StringAttribute{ + "name": metaschema.StringAttribute{ Description: "Product name. At least one of `product_name` or `comment` must be set.", Required: true, }, - "product_version": metaschema.StringAttribute{ + "version": metaschema.StringAttribute{ Description: "Product version. Optional, and should only be set when `product_name` is set.", Optional: true, }, }, }, - Description: "Product details to append to User-Agent string in all AWS API calls.", + Description: "Product details to append to the User-Agent string sent in all AWS API calls.", Optional: true, }, }, From b71ba9cefc7e324a47c78cc09e9020811773018c Mon Sep 17 00:00:00 2001 From: Jared Baker Date: Tue, 30 Sep 2025 12:01:51 -0400 Subject: [PATCH 18/24] chore: make docs --- docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index b023319b93..f4a8fa988a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -251,7 +251,7 @@ credential_process = custom-process --username jdoe - `skip_medatadata_api_check` (Boolean, Deprecated) Skip the AWS Metadata API check. Useful for AWS API implementations that do not have a metadata API endpoint. Setting to `true` prevents Terraform from authenticating via the Metadata API. You may need to use other authentication methods like static credentials, configuration variables, or environment variables. - `skip_metadata_api_check` (Boolean) Skip the AWS Metadata API check. Useful for AWS API implementations that do not have a metadata API endpoint. Setting to `true` prevents Terraform from authenticating via the Metadata API. You may need to use other authentication methods like static credentials, configuration variables, or environment variables. - `token` (String) Session token for validating temporary credentials. Typically provided after successful identity federation or Multi-Factor Authentication (MFA) login. With MFA login, this is the session token provided afterward, not the 6 digit MFA code used to get temporary credentials. It can also be sourced from the `AWS_SESSION_TOKEN` environment variable. -- `user_agent` (Attributes List) Product details to append to User-Agent string in all AWS API calls. (see [below for nested schema](#nestedatt--user_agent)) +- `user_agent` (Attributes List) Product details to append to the User-Agent string sent in all AWS API calls. (see [below for nested schema](#nestedatt--user_agent)) ### Nested Schema for `assume_role` From d0104034169c2ccb41b8d06197bbc52105aa18ff Mon Sep 17 00:00:00 2001 From: Jared Baker Date: Tue, 30 Sep 2025 13:39:04 -0400 Subject: [PATCH 19/24] docs: add `provider_meta` section --- docs/index.md | 31 +++++++++++++++++++++++++++++++ templates/index.md.tmpl | 31 +++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/docs/index.md b/docs/index.md index f4a8fa988a..8c8cae6976 100644 --- a/docs/index.md +++ b/docs/index.md @@ -228,6 +228,37 @@ provider "awscc" { credential_process = custom-process --username jdoe ``` +### Module-scoped User-Agent Information with `provider_meta` + +The AWSCC provider supports sending provider metadata via the [`provider_meta` block](https://developer.hashicorp.com/terraform/internals/provider-meta). +This block allows module authors to provide additional information in the `User-Agent` header, scoped only to resources defined in a given module. + +For example, the following `terraform` block can be used to append additional User-Agent details. + +```terraform +terraform { + required_providers { + awscc = { + source = "hashicorp/awscc" + version = "~> 1.0" + } + } + + provider_meta "awscc" { + user_agent = [ + { + name = "example-demo" + version = "0.0.1" + comment = "a demo module" + }, + ] + } +} +``` + +Note that `provider_meta` is defined within the `terraform` block. +The `provider` block is inherited from the root module. + ## Schema diff --git a/templates/index.md.tmpl b/templates/index.md.tmpl index 91cad03fdd..f3647f31cf 100644 --- a/templates/index.md.tmpl +++ b/templates/index.md.tmpl @@ -228,4 +228,35 @@ provider "awscc" { credential_process = custom-process --username jdoe ``` +### Module-scoped User-Agent Information with `provider_meta` + +The AWSCC provider supports sending provider metadata via the [`provider_meta` block](https://developer.hashicorp.com/terraform/internals/provider-meta). +This block allows module authors to provide additional information in the `User-Agent` header, scoped only to resources defined in a given module. + +For example, the following `terraform` block can be used to append additional User-Agent details. + +```terraform +terraform { + required_providers { + awscc = { + source = "hashicorp/awscc" + version = "~> 1.0" + } + } + + provider_meta "awscc" { + user_agent = [ + { + name = "example-demo" + version = "0.0.1" + comment = "a demo module" + }, + ] + } +} +``` + +Note that `provider_meta` is defined within the `terraform` block. +The `provider` block is inherited from the root module. + {{ .SchemaMarkdown | trimspace }} From 804219a1f0b399a074f242dbe035ce80da74e065 Mon Sep 17 00:00:00 2001 From: Jared Baker Date: Thu, 2 Oct 2025 10:19:29 -0400 Subject: [PATCH 20/24] internal/aws/ec2: first working `_providerMeta` test with VPC resource ```console % make testacc PKG_NAME=internal/aws/ec2 TESTARGS='-run=TestAccAWSEC2VPC_providerMeta' TF_ACC=1 go test ./internal/aws/ec2 -v -count 1 -parallel 20 -run=TestAccAWSEC2VPC_providerMeta -timeout 180m --- PASS: TestAccAWSEC2VPC_providerMeta (36.16s) PASS ok github.com/hashicorp/terraform-provider-awscc/internal/aws/ec2 37.026s ``` --- internal/acctest/data.go | 56 ++++++++++++++++----------- internal/aws/ec2/vpc_resource_test.go | 9 ++++- 2 files changed, 42 insertions(+), 23 deletions(-) diff --git a/internal/acctest/data.go b/internal/acctest/data.go index ecbb880567..eba72cbe8c 100644 --- a/internal/acctest/data.go +++ b/internal/acctest/data.go @@ -6,6 +6,7 @@ package acctest import ( "fmt" "os" + "strings" "testing" fwprovider "github.com/hashicorp/terraform-plugin-framework/provider" @@ -48,28 +49,6 @@ provider "awscc" { return config } -// EmptyConfigWithProviderMeta injects a provider_meta block into the base EmptyConfig. -func (td *TestData) EmptyConfigWithProviderMeta() string { - return td.EmptyConfig() + ` -terraform { - provider_meta "awscc" { - user_agent = [ - { - product_name = "my-test-module" - product_version = "0.0.1" - comment = "testing user-agent comment" - }, - { - product_name = "other-test-module" - product_version = "0.0.2" - comment = "second user agent" - } - ] - } -} -` -} - // DataSourceWithEmptyResourceConfig returns a Terraform configuration for the data source and its respective resource. func (td *TestData) DataSourceWithEmptyResourceConfig() string { return td.EmptyConfig() + fmt.Sprintf(` @@ -124,3 +103,36 @@ func NewTestData(_ *testing.T, cfResourceType, tfResourceType, resourceLabel str return data } + +// WithProviderMeta returns a terraform block with provider_meta configured +func WithProviderMeta() string { + return ` +terraform { + provider_meta "awscc" { + user_agent = [ + { + name = "my-test-module" + version = "0.0.1" + comment = "testing user-agent comment" + }, + { + name = "other-test-module" + version = "0.0.2" + comment = "second user agent" + } + ] + } +} +` +} + +// ConfigCompose can be called to concatenate multiple strings to build test configurations +func ConfigCompose(config ...string) string { + var str strings.Builder + + for _, conf := range config { + str.WriteString(conf) + } + + return str.String() +} diff --git a/internal/aws/ec2/vpc_resource_test.go b/internal/aws/ec2/vpc_resource_test.go index 9387ec19e0..f187bdddd1 100644 --- a/internal/aws/ec2/vpc_resource_test.go +++ b/internal/aws/ec2/vpc_resource_test.go @@ -38,12 +38,19 @@ func TestAccAWSEC2VPC_CidrBlock(t *testing.T) { func TestAccAWSEC2VPC_providerMeta(t *testing.T) { td := acctest.NewTestData(t, "AWS::EC2::VPC", "awscc_ec2_vpc", "test") + resourceName := td.ResourceName + rName := td.RandomName() + cidrBlock := "10.0.0.0/16" td.ResourceTest(t, []resource.TestStep{ { - Config: td.EmptyConfigWithProviderMeta(), + Config: acctest.ConfigCompose( + acctest.WithProviderMeta(), + testAccAWSEC2VPCCidrBlockConfig(&td, rName, cidrBlock), + ), Check: resource.ComposeTestCheckFunc( td.CheckExistsInAWS(), + resource.TestCheckResourceAttr(resourceName, "cidr_block", cidrBlock), ), }, { From 00fb7bd0d0a7ef7ac923278299db7c824addc3a5 Mon Sep 17 00:00:00 2001 From: Jared Baker Date: Thu, 2 Oct 2025 14:34:06 -0400 Subject: [PATCH 21/24] internal/aws/logs: additional `provider_meta` tests Includes one additional test with `provider_meta` configured in the root directory, and another configured within a module. Because terraform-plugin-framework does not currently copy module contents when using the `ConfigDirectory` field for a test case, this test is always skipped. Once support for modules is available, we can enable this test. ```console % make testacc PKG_NAME=internal/aws/logs TESTARGS='-run=TestAccAWSLogsLogGroup_providerMeta' TF_ACC=1 go test ./internal/aws/logs -v -count 1 -parallel 20 -run=TestAccAWSLogsLogGroup_providerMeta -timeout 180m === RUN TestAccAWSLogsLogGroup_providerMetaWithModule log_group_resource_test.go:90: The ConfigDirectory field does not currently support copying modules to the working directory --- SKIP: TestAccAWSLogsLogGroup_providerMetaWithModule (0.00s) --- PASS: TestAccAWSLogsLogGroup_providerMeta (25.31s) PASS ok github.com/hashicorp/terraform-provider-awscc/internal/aws/logs 26.313s ``` --- internal/aws/logs/log_group_resource_test.go | 44 +++++++++++++++++++ .../testdata/LogGroup/providerMeta/main.tf | 23 ++++++++++ .../demo-module/main.tf | 23 ++++++++++ .../LogGroup/providerMetaWithModule/main.tf | 16 +++++++ 4 files changed, 106 insertions(+) create mode 100644 internal/aws/logs/testdata/LogGroup/providerMeta/main.tf create mode 100644 internal/aws/logs/testdata/LogGroup/providerMetaWithModule/demo-module/main.tf create mode 100644 internal/aws/logs/testdata/LogGroup/providerMetaWithModule/main.tf diff --git a/internal/aws/logs/log_group_resource_test.go b/internal/aws/logs/log_group_resource_test.go index a47f4c2c71..7b01301497 100644 --- a/internal/aws/logs/log_group_resource_test.go +++ b/internal/aws/logs/log_group_resource_test.go @@ -7,6 +7,7 @@ import ( "fmt" "testing" + "github.com/hashicorp/terraform-plugin-testing/config" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-provider-awscc/internal/acctest" ) @@ -64,6 +65,49 @@ func TestAccAWSLogsLogGroupWithDataProtectionPolicy_create(t *testing.T) { }) } +func TestAccAWSLogsLogGroup_providerMeta(t *testing.T) { + td := acctest.NewTestData(t, "AWS::Logs::LogGroup", "awscc_logs_log_group", "test") + resourceName := td.ResourceName + rName := td.RandomName() + + td.ResourceTestNoProviderFactories(t, []resource.TestStep{ + { + ProtoV6ProviderFactories: td.ProviderFactories(), + ConfigDirectory: config.StaticDirectory("testdata/LogGroup/providerMeta/"), + ConfigVariables: config.Variables{ + "rName": config.StringVariable(rName), + }, + Check: resource.ComposeTestCheckFunc( + td.CheckExistsInAWS(), + resource.TestCheckResourceAttr(resourceName, "log_group_name", rName), + ), + }, + }) +} + +func TestAccAWSLogsLogGroup_providerMetaWithModule(t *testing.T) { + // https://github.com/hashicorp/terraform-plugin-testing/issues/277 + t.Skip("The ConfigDirectory field does not currently support copying modules to the working directory") + + td := acctest.NewTestData(t, "AWS::Logs::LogGroup", "awscc_logs_log_group", "test") + resourceName := td.ResourceName + rName := td.RandomName() + + td.ResourceTestNoProviderFactories(t, []resource.TestStep{ + { + ProtoV6ProviderFactories: td.ProviderFactories(), + ConfigDirectory: config.StaticDirectory("testdata/LogGroup/providerMetaWithModule/"), + ConfigVariables: config.Variables{ + "rName": config.StringVariable(rName), + }, + Check: resource.ComposeTestCheckFunc( + td.CheckExistsInAWS(), + resource.TestCheckResourceAttr(resourceName, "log_group_name", rName), + ), + }, + }) +} + func testAccAWSLogsLogGroupRetentionConfig(td *acctest.TestData, rName string, retentionInDays int) string { return fmt.Sprintf(` resource %[1]q %[2]q { diff --git a/internal/aws/logs/testdata/LogGroup/providerMeta/main.tf b/internal/aws/logs/testdata/LogGroup/providerMeta/main.tf new file mode 100644 index 0000000000..87cea838ab --- /dev/null +++ b/internal/aws/logs/testdata/LogGroup/providerMeta/main.tf @@ -0,0 +1,23 @@ +terraform { + provider_meta "awscc" { + user_agent = [ + { + name = "example-demo" + version = "0.0.1" + comment = "a demo module" + }, + ] + } +} + +resource "awscc_logs_log_group" "test" { + log_group_name = var.rName + retention_in_days = 7 +} + +variable "rName" { + description = "Name for resource" + type = string + nullable = false +} + diff --git a/internal/aws/logs/testdata/LogGroup/providerMetaWithModule/demo-module/main.tf b/internal/aws/logs/testdata/LogGroup/providerMetaWithModule/demo-module/main.tf new file mode 100644 index 0000000000..eb3fadfd42 --- /dev/null +++ b/internal/aws/logs/testdata/LogGroup/providerMetaWithModule/demo-module/main.tf @@ -0,0 +1,23 @@ +terraform { + # custom provider_meta which should appear appended to user agent + provider_meta "awscc" { + user_agent = [ + { + name = "example-demo" + version = "0.0.1" + comment = "a demo module" + }, + ] + } +} + +resource "awscc_logs_log_group" "test_module" { + log_group_name = var.rName + retention_in_days = 7 +} + +variable "rName" { + description = "Name for resource" + type = string + nullable = false +} diff --git a/internal/aws/logs/testdata/LogGroup/providerMetaWithModule/main.tf b/internal/aws/logs/testdata/LogGroup/providerMetaWithModule/main.tf new file mode 100644 index 0000000000..9f238328f1 --- /dev/null +++ b/internal/aws/logs/testdata/LogGroup/providerMetaWithModule/main.tf @@ -0,0 +1,16 @@ +module "demo-module" { + source = "./demo-module" + rName = var.rName +} + +resource "awscc_logs_log_group" "test" { + log_group_name = var.rName + retention_in_days = 7 +} + +variable "rName" { + description = "Name for resource" + type = string + nullable = false +} + From 23b90f9841361b7d5551190cc590dee5c700e580 Mon Sep 17 00:00:00 2001 From: Jared Baker Date: Fri, 3 Oct 2025 13:21:52 -0400 Subject: [PATCH 22/24] internal/provider: prefer `product_name`, `product_version` for user agent This naming convention aligns the `provider_meta` schema with the arguments nested inside the existing `user_agent` argument in the provider schema. ```console % make testacc PKG_NAME=internal/aws/logs TESTARGS='-run=TestAccAWSLogsLogGroup_providerMeta' TF_ACC=1 go test ./internal/aws/logs -v -count 1 -parallel 20 -run=TestAccAWSLogsLogGroup_providerMeta -timeout 180m === RUN TestAccAWSLogsLogGroup_providerMetaWithModule log_group_resource_test.go:90: The ConfigDirectory field does not currently support copying modules to the working directory --- SKIP: TestAccAWSLogsLogGroup_providerMetaWithModule (0.00s) --- PASS: TestAccAWSLogsLogGroup_providerMeta (27.26s) PASS ok github.com/hashicorp/terraform-provider-awscc/internal/aws/logs 28.270s ``` ```console % make testacc PKG_NAME=internal/aws/ec2 TESTARGS='-run=TestAccAWSEC2VPC_providerMeta' TF_ACC=1 go test ./internal/aws/ec2 -v -count 1 -parallel 20 -run=TestAccAWSEC2VPC_providerMeta -timeout 180m --- PASS: TestAccAWSEC2VPC_providerMeta (38.53s) PASS ok github.com/hashicorp/terraform-provider-awscc/internal/aws/ec2 39.509s ``` --- docs/index.md | 10 +++++----- internal/acctest/data.go | 12 ++++++------ .../logs/testdata/LogGroup/providerMeta/main.tf | 6 +++--- .../providerMetaWithModule/demo-module/main.tf | 6 +++--- internal/provider/provider.go | 14 +++++++------- internal/types/user_agent.go | 10 +++++----- internal/types/user_agent_test.go | 8 ++++---- templates/index.md.tmpl | 6 +++--- 8 files changed, 36 insertions(+), 36 deletions(-) diff --git a/docs/index.md b/docs/index.md index 8c8cae6976..85da14a92b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -247,9 +247,9 @@ terraform { provider_meta "awscc" { user_agent = [ { - name = "example-demo" - version = "0.0.1" - comment = "a demo module" + product_name = "example-demo" + product_version = "0.0.1" + comment = "a demo module" }, ] } @@ -335,9 +335,9 @@ Optional: Required: -- `product_name` (String) Product name. At least one of `product_name` or `comment` must be set. +- `product_name` (String) Product name. Optional: -- `comment` (String) User-Agent comment. At least one of `comment` or `product_name` must be set. +- `comment` (String) Comment describing any additional product details. - `product_version` (String) Product version. Optional, and should only be set when `product_name` is set. diff --git a/internal/acctest/data.go b/internal/acctest/data.go index eba72cbe8c..beb16f0e98 100644 --- a/internal/acctest/data.go +++ b/internal/acctest/data.go @@ -111,14 +111,14 @@ terraform { provider_meta "awscc" { user_agent = [ { - name = "my-test-module" - version = "0.0.1" - comment = "testing user-agent comment" + product_name = "test-module" + product_version = "0.0.1" + comment = "test comment" }, { - name = "other-test-module" - version = "0.0.2" - comment = "second user agent" + product_name = "second-test-module" + product_version = "0.0.2" + comment = "second test comment" } ] } diff --git a/internal/aws/logs/testdata/LogGroup/providerMeta/main.tf b/internal/aws/logs/testdata/LogGroup/providerMeta/main.tf index 87cea838ab..ef728de56a 100644 --- a/internal/aws/logs/testdata/LogGroup/providerMeta/main.tf +++ b/internal/aws/logs/testdata/LogGroup/providerMeta/main.tf @@ -2,9 +2,9 @@ terraform { provider_meta "awscc" { user_agent = [ { - name = "example-demo" - version = "0.0.1" - comment = "a demo module" + product_name = "example-demo" + product_version = "0.0.1" + comment = "a demo module" }, ] } diff --git a/internal/aws/logs/testdata/LogGroup/providerMetaWithModule/demo-module/main.tf b/internal/aws/logs/testdata/LogGroup/providerMetaWithModule/demo-module/main.tf index eb3fadfd42..f16322497e 100644 --- a/internal/aws/logs/testdata/LogGroup/providerMetaWithModule/demo-module/main.tf +++ b/internal/aws/logs/testdata/LogGroup/providerMetaWithModule/demo-module/main.tf @@ -3,9 +3,9 @@ terraform { provider_meta "awscc" { user_agent = [ { - name = "example-demo" - version = "0.0.1" - comment = "a demo module" + product_name = "example-demo" + product_version = "0.0.1" + comment = "a demo module" }, ] } diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 22c17def00..67991ac6ea 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -269,11 +269,11 @@ func (p *ccProvider) Schema(ctx context.Context, request provider.SchemaRequest, NestedObject: schema.NestedAttributeObject{ Attributes: map[string]schema.Attribute{ "comment": schema.StringAttribute{ - Description: "User-Agent comment. At least one of `comment` or `product_name` must be set.", + Description: "Comment describing any additional product details.", Optional: true, }, "product_name": schema.StringAttribute{ - Description: "Product name. At least one of `product_name` or `comment` must be set.", + Description: "Product name.", Required: true, }, "product_version": schema.StringAttribute{ @@ -295,15 +295,15 @@ func (p *ccProvider) MetaSchema(ctx context.Context, req provider.MetaSchemaRequ "user_agent": metaschema.ListNestedAttribute{ NestedObject: metaschema.NestedAttributeObject{ Attributes: map[string]metaschema.Attribute{ - "comment": metaschema.StringAttribute{ - Description: "User-Agent comment. At least one of `comment` or `product_name` must be set.", + "comment": schema.StringAttribute{ + Description: "Comment describing any additional product details.", Optional: true, }, - "name": metaschema.StringAttribute{ - Description: "Product name. At least one of `product_name` or `comment` must be set.", + "product_name": schema.StringAttribute{ + Description: "Product name.", Required: true, }, - "version": metaschema.StringAttribute{ + "product_version": schema.StringAttribute{ Description: "Product version. Optional, and should only be set when `product_name` is set.", Optional: true, }, diff --git a/internal/types/user_agent.go b/internal/types/user_agent.go index d5a76b135b..2070b641c3 100644 --- a/internal/types/user_agent.go +++ b/internal/types/user_agent.go @@ -6,9 +6,9 @@ import ( ) type userAgentProduct struct { - Name tftypes.String `tfsdk:"name"` - Version tftypes.String `tfsdk:"version"` - Comment tftypes.String `tfsdk:"comment"` + Comment tftypes.String `tfsdk:"comment"` + ProductName tftypes.String `tfsdk:"product_name"` + ProductVersion tftypes.String `tfsdk:"product_version"` } type UserAgentProducts []userAgentProduct @@ -17,9 +17,9 @@ func (uap UserAgentProducts) UserAgentProducts() []awsbase.UserAgentProduct { results := make([]awsbase.UserAgentProduct, len(uap)) for i, p := range uap { results[i] = awsbase.UserAgentProduct{ - Name: p.Name.ValueString(), - Version: p.Version.ValueString(), Comment: p.Comment.ValueString(), + Name: p.ProductName.ValueString(), + Version: p.ProductVersion.ValueString(), } } return results diff --git a/internal/types/user_agent_test.go b/internal/types/user_agent_test.go index 543b4c3718..bd8f3dad3d 100644 --- a/internal/types/user_agent_test.go +++ b/internal/types/user_agent_test.go @@ -13,13 +13,13 @@ func TestUserAgentProducts(t *testing.T) { simpleProduct := awsbase.UserAgentProduct{Name: "simple", Version: "0.0.1", Comment: "test comment"} simpleAddProduct := userAgentProduct{ - Name: types.StringValue(simpleProduct.Name), - Version: types.StringValue(simpleProduct.Version), - Comment: types.StringValue(simpleProduct.Comment), + ProductName: types.StringValue(simpleProduct.Name), + ProductVersion: types.StringValue(simpleProduct.Version), + Comment: types.StringValue(simpleProduct.Comment), } minimalProduct := awsbase.UserAgentProduct{Name: "minimal"} minimalAddProduct := userAgentProduct{ - Name: types.StringValue(minimalProduct.Name), + ProductName: types.StringValue(minimalProduct.Name), } testcases := map[string]struct { diff --git a/templates/index.md.tmpl b/templates/index.md.tmpl index f3647f31cf..7e0006c95f 100644 --- a/templates/index.md.tmpl +++ b/templates/index.md.tmpl @@ -247,9 +247,9 @@ terraform { provider_meta "awscc" { user_agent = [ { - name = "example-demo" - version = "0.0.1" - comment = "a demo module" + product_name = "example-demo" + product_version = "0.0.1" + comment = "a demo module" }, ] } From 99e22fb887a0644eda437181eca3676e73cae08c Mon Sep 17 00:00:00 2001 From: Jared Baker Date: Fri, 3 Oct 2025 15:15:58 -0400 Subject: [PATCH 23/24] chore: changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed76afe0c5..78e8e82738 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## 1.59.0 (Unreleased) +FEATURES: + +* provider: The `provider_meta` block is now supported. This enables module authors to include additional product information in the `User-Agent` header sent during all AWS API requests made during Create, Read, Update, and Delete operations. + ## 1.58.0 (October 2, 2025) FEATURES: From 5bc051bee2d7c45499a50eec0f95c4556f3c0660 Mon Sep 17 00:00:00 2001 From: Jared Baker Date: Fri, 10 Oct 2025 11:04:41 -0400 Subject: [PATCH 24/24] chore: fix changelog placement after merge --- CHANGELOG.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d9d947cf1c..61c3a3111d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## 1.60.0 (Unreleased) +FEATURES: + +* provider: The `provider_meta` block is now supported. This enables module authors to include additional product information in the `User-Agent` header sent during all AWS API requests made during Create, Read, Update, and Delete operations. + ## 1.59.0 (October 9, 2025) BUG FIXES: @@ -24,10 +28,6 @@ FEATURES: * **New Resource:** `awscc_observabilityadmin_organization_centralization_rule` * **New Resource:** `awscc_servicecatalog_resource_update_constraint` -FEATURES: - -* provider: The `provider_meta` block is now supported. This enables module authors to include additional product information in the `User-Agent` header sent during all AWS API requests made during Create, Read, Update, and Delete operations. - ## 1.58.0 (October 2, 2025) FEATURES: