diff --git a/internal/conns/config.go b/internal/conns/config.go index 6c47241c1150..76a2c24b7454 100644 --- a/internal/conns/config.go +++ b/internal/conns/config.go @@ -61,6 +61,7 @@ type Config struct { TokenBucketRateLimiterCapacity int UseDualStackEndpoint bool UseFIPSEndpoint bool + UserAgent awsbase.UserAgentProducts } // ConfigureProvider configures the provided provider Meta (instance data). @@ -112,6 +113,7 @@ func (c *Config) ConfigureProvider(ctx context.Context, client *AWSClient) (*AWS TokenBucketRateLimiterCapacity: c.TokenBucketRateLimiterCapacity, UseDualStackEndpoint: c.UseDualStackEndpoint, UseFIPSEndpoint: c.UseFIPSEndpoint, + UserAgent: c.UserAgent, } if c.CustomCABundle != "" { diff --git a/internal/provider/framework/provider.go b/internal/provider/framework/provider.go index 36df45dc55f8..f6ae5cf3c0c4 100644 --- a/internal/provider/framework/provider.go +++ b/internal/provider/framework/provider.go @@ -24,6 +24,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/function" "github.com/hashicorp/terraform-plugin-framework/list" "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/resource" resourceschema "github.com/hashicorp/terraform-plugin-framework/resource/schema" @@ -49,6 +50,7 @@ var ( _ provider.ProviderWithFunctions = &frameworkProvider{} _ provider.ProviderWithEphemeralResources = &frameworkProvider{} _ provider.ProviderWithListResources = &frameworkProvider{} + _ provider.ProviderWithMetaSchema = &frameworkProvider{} ) type frameworkProvider struct { @@ -339,7 +341,78 @@ func (*frameworkProvider) Schema(ctx context.Context, request provider.SchemaReq }, }, }, + "user_agent": schema.ListNestedBlock{ + Description: "Product details to append to the User-Agent string sent in all AWS API calls.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "comment": schema.StringAttribute{ + Optional: true, + Description: "Comment describing any additional product details.", + }, + "product_name": schema.StringAttribute{ + Required: true, + Description: "Product name.", + }, + "product_version": schema.StringAttribute{ + Optional: true, + Description: "Product version. Optional, and should only be set when `product_name` is set.", + }, + }, + }, + }, + }, + } +} + +func (p *frameworkProvider) MetaSchema(ctx context.Context, req provider.MetaSchemaRequest, resp *provider.MetaSchemaResponse) { + resp.Schema = metaschema.Schema{ + Attributes: map[string]metaschema.Attribute{ + "user_agent": metaschema.ListNestedAttribute{ + Description: "Product details to append to the User-Agent string sent in all AWS API calls.", + Optional: true, + NestedObject: metaschema.NestedAttributeObject{ + Attributes: map[string]metaschema.Attribute{ + "comment": schema.StringAttribute{ + Description: "Comment describing any additional product details.", + Optional: true, + }, + "product_name": schema.StringAttribute{ + Description: "Product name.", + Required: true, + }, + "product_version": schema.StringAttribute{ + Description: "Product version. Optional, and should only be set when `product_name` is set.", + Optional: true, + }, + }, + }, + }, }, + + // TODO + // metaschema.Schema does not support a Blocks field. Can we mux this if we're on protocol V5? + // + // Blocks: map[string]schema.Block{ + // "user_agent": schema.ListNestedBlock{ + // Description: "Product details to append to the User-Agent string sent in all AWS API calls.", + // NestedObject: schema.NestedBlockObject{ + // Attributes: map[string]schema.Attribute{ + // "comment": schema.StringAttribute{ + // Optional: true, + // Description: "Comment describing any additional product details.", + // }, + // "product_name": schema.StringAttribute{ + // Required: true, + // Description: "Product name.", + // }, + // "product_version": schema.StringAttribute{ + // Optional: true, + // Description: "Product version. Optional, and should only be set when `product_name` is set.", + // }, + // }, + // }, + // }, + // }, } } diff --git a/internal/provider/sdkv2/provider.go b/internal/provider/sdkv2/provider.go index a81e267369e0..b4c1b60ff9ec 100644 --- a/internal/provider/sdkv2/provider.go +++ b/internal/provider/sdkv2/provider.go @@ -270,6 +270,58 @@ func NewProvider(ctx context.Context) (*schema.Provider, error) { Optional: true, Description: "Resolve an endpoint with FIPS capability", }, + "user_agent": { + Type: schema.TypeList, + Optional: true, + Description: "Product details to append to the User-Agent string sent in all AWS API calls.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "comment": { + Type: schema.TypeString, + Optional: true, + Description: "Comment describing any additional product details.", + }, + "product_name": { + Type: schema.TypeString, + Required: true, + Description: "Product name.", + }, + "product_version": { + Type: schema.TypeString, + Optional: true, + Description: "Product version. Optional, and should only be set when `product_name` is set.", + }, + }, + }, + }, + }, + + // ProviderMetaSchema enables module-scoped User-Agent modifications + ProviderMetaSchema: map[string]*schema.Schema{ + "user_agent": { + Type: schema.TypeList, + Optional: true, + Description: "Product details to append to the User-Agent string sent in all AWS API calls.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "comment": { + Type: schema.TypeString, + Optional: true, + Description: "Comment describing any additional product details.", + }, + "product_name": { + Type: schema.TypeString, + Required: true, + Description: "Product name.", + }, + "product_version": { + Type: schema.TypeString, + Optional: true, + Description: "Product version. Optional, and should only be set when `product_name` is set.", + }, + }, + }, + }, }, // Data sources and resources implemented using Terraform Plugin SDK diff --git a/website/docs/index.html.markdown b/website/docs/index.html.markdown index 997870617ba8..f5f0036a777f 100644 --- a/website/docs/index.html.markdown +++ b/website/docs/index.html.markdown @@ -232,8 +232,40 @@ provider "aws" { ```ini [profile customprofile] credential_process = custom-process --username jdoe + ``` +### Module-scoped User-Agent Information with `provider_meta` + +The AWS 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/aws" + version = "~> 6.0" + } + } + + provider_meta "aws" { + user_agent = [ + { + product_name = "example-demo" + product_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. + ## AWS Configuration Reference |Setting|Provider|[Environment Variable][envvars]|[Shared Config][config]| @@ -301,12 +333,27 @@ See the assume role documentation [section on web identities](https://docs.aws.a ## Custom User-Agent Information -By default, the underlying AWS client used by the Terraform AWS Provider creates requests with User-Agent headers including information about Terraform and AWS SDK for Go versions. To provide additional information in the User-Agent headers, the `TF_APPEND_USER_AGENT` environment variable can be set and its value will be directly added to HTTP requests. E.g., +By default, the underlying AWS client used by the Terraform AWS Provider creates requests with User-Agent headers including information about Terraform and AWS SDK for Go versions. +To provide additional information in the User-Agent headers, set the `TF_APPEND_USER_AGENT` environment variable, or use the `user_agent` block. + +When using the environment variable, the value will be directly appended to the User-Agent header. +For example, ```console % export TF_APPEND_USER_AGENT="JenkinsAgent/i-12345678 BuildID/1234 (Optional Extra Information)" ``` +When using the `user_agent` block, the components will be parsed and appended to the User-Agent in the form `{product_name}/{product_version} ({comment})`. +For example, the configuration below would append `example-demo/0.0.1 (a demo module)`. + +```terraform +user_agent { + product_name = "example-demo" + product_version = "0.0.1" + comment = "a demo module" +} +``` + ## Argument Reference In addition to [generic `provider` arguments](https://www.terraform.io/docs/configuration/providers.html)