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/go.mod b/go.mod index f658a145f6..0f3d579338 100644 --- a/go.mod +++ b/go.mod @@ -91,3 +91,8 @@ require ( google.golang.org/grpc v1.46.0 // indirect google.golang.org/protobuf v1.28.0 // 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 + diff --git a/internal/acctest/data.go b/internal/acctest/data.go index b7f0e5a453..28c0d6b468 100644 --- a/internal/acctest/data.go +++ b/internal/acctest/data.go @@ -44,6 +44,35 @@ 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" +} + +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..f1663bfbd6 100644 --- a/internal/generic/resource.go +++ b/internal/generic/resource.go @@ -17,6 +17,7 @@ import ( "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" ) @@ -394,13 +395,29 @@ var ( idAttributePath = tftypes.NewAttributePath().WithAttributeName("id") ) +type providerMetaData struct { + UserAgent types.UserAgentProducts `tfsdk:"user_agent"` +} + func (r *resource) Create(ctx context.Context, request tfsdk.CreateResourceRequest, response *tfsdk.CreateResourceResponse) { ctx = r.cfnTypeContext(ctx) traceEntry(ctx, "Resource.Create") + var metadata providerMetaData + + response.Diagnostics.Append(request.ProviderMeta.Get(ctx, &metadata)...) + + if response.Diagnostics.HasError() { + return + } + + ctx = context.WithValue(ctx, "awsbase.ContextScopedUserAgent", metadata.UserAgent.UserAgentProducts()) + conn := r.provider.CloudControlApiClient(ctx) + // conn := r.provider.CloudControlApiClient(ctx) + tflog.Debug(ctx, "Request.Plan.Raw", map[string]interface{}{ "value": hclog.Fmt("%v", request.Plan.Raw), }) diff --git a/internal/provider/provider.go b/internal/provider/provider.go index a7d89a452b..a3b081cd80 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -301,16 +301,10 @@ 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 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"` Duration cctypes.Duration `tfsdk:"duration"` @@ -462,6 +456,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 } @@ -494,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 { @@ -568,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) + } + }) + } +}