Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
03bf044
build provider_meta feturea into provider
drewmullen Apr 25, 2022
809e567
add provider_meta block for experimental testing
drewmullen May 23, 2022
9c7563f
structs for useragent
drewmullen Jun 3, 2022
2a01ff7
wip
drewmullen Jun 16, 2022
06c5066
extra providerMeta with ElementAs()
drewmullen Jun 17, 2022
192c900
wip
drewmullen Jun 17, 2022
59857a6
Updates provider metadata deserialization
gdavison Jun 27, 2022
434c7df
Merge branch 'main' into providermeta
jar-b Sep 24, 2025
f949e74
chore(deps): remove `replace` directive for `aws-sdk-go-base`
jar-b Sep 24, 2025
5dd814b
internal/types: fix unit tests
jar-b Sep 24, 2025
4ea6dbb
internal/provider: verify provider interfaces are satisfied at comp time
jar-b Sep 24, 2025
04c74f7
internal/generic: prefer `useragent.Context` to set additional user a…
jar-b Sep 24, 2025
9a4ebd6
[wip]internal/acctest: tidy EmptyConfigWithProviderMeta
jar-b Sep 24, 2025
80c1909
internal/generic: read provider meta in all CRUD ops
jar-b Sep 30, 2025
0a2b098
internal/generic: simplify bootstrap context implementation
jar-b Sep 30, 2025
0da5397
internal/types: align `UserAgentProduct` field names with aws-sdk-go-…
jar-b Sep 30, 2025
79fba75
chore: fix `misspell` linter finding
jar-b Sep 30, 2025
ec4280d
internal/provider: align `user_agent` field names with aws-sdk-go-base
jar-b Sep 30, 2025
b71ba9c
chore: make docs
jar-b Sep 30, 2025
d010403
docs: add `provider_meta` section
jar-b Sep 30, 2025
804219a
internal/aws/ec2: first working `_providerMeta` test with VPC resource
jar-b Oct 2, 2025
00fb7bd
internal/aws/logs: additional `provider_meta` tests
jar-b Oct 2, 2025
23b90f9
internal/provider: prefer `product_name`, `product_version` for user …
jar-b Oct 3, 2025
209a49d
Merge branch 'main' into providermeta
jar-b Oct 3, 2025
99e22fb
chore: changelog
jar-b Oct 3, 2025
56d7c69
Merge branch 'main' into providermeta
jar-b Oct 10, 2025
9947538
Merge branch 'main' into providermeta
jar-b Oct 10, 2025
5bc051b
chore: fix changelog placement after merge
jar-b Oct 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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:
Expand Down
37 changes: 34 additions & 3 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
{
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.

<!-- schema generated by tfplugindocs -->
## Schema

Expand All @@ -251,7 +282,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))

<a id="nestedatt--assume_role"></a>
### Nested Schema for `assume_role`
Expand Down Expand Up @@ -304,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.
34 changes: 34 additions & 0 deletions internal/acctest/data.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package acctest
import (
"fmt"
"os"
"strings"
"testing"

fwprovider "github.com/hashicorp/terraform-plugin-framework/provider"
Expand Down Expand Up @@ -102,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 = [
{
product_name = "test-module"
product_version = "0.0.1"
comment = "test comment"
},
{
product_name = "second-test-module"
product_version = "0.0.2"
comment = "second test comment"
}
]
}
}
`
}

// 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()
}
25 changes: 25 additions & 0 deletions internal/aws/ec2/vpc_resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,31 @@ 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: acctest.ConfigCompose(
acctest.WithProviderMeta(),
testAccAWSEC2VPCCidrBlockConfig(&td, rName, cidrBlock),
),
Check: resource.ComposeTestCheckFunc(
td.CheckExistsInAWS(),
resource.TestCheckResourceAttr(resourceName, "cidr_block", cidrBlock),
),
},
{
ResourceName: td.ResourceName,
ImportState: true,
ImportStateVerify: true,
},
})
}

func testAccAWSEC2VPCCidrBlockConfig(td *acctest.TestData, rName, cidrBlock string) string {
return fmt.Sprintf(`
resource %[1]q %[2]q {
Expand Down
44 changes: 44 additions & 0 deletions internal/aws/logs/log_group_resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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 {
Expand Down
23 changes: 23 additions & 0 deletions internal/aws/logs/testdata/LogGroup/providerMeta/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
terraform {
provider_meta "awscc" {
user_agent = [
{
product_name = "example-demo"
product_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
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
terraform {
# custom provider_meta which should appear appended to user agent
provider_meta "awscc" {
user_agent = [
{
product_name = "example-demo"
product_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
}
Original file line number Diff line number Diff line change
@@ -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
}

45 changes: 39 additions & 6 deletions internal/generic/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -26,6 +27,7 @@ import (
"github.com/hashicorp/terraform-provider-awscc/internal/identity"
tfcloudcontrol "github.com/hashicorp/terraform-provider-awscc/internal/service/cloudcontrol"
"github.com/hashicorp/terraform-provider-awscc/internal/tfresource"
inttypes "github.com/hashicorp/terraform-provider-awscc/internal/types"
)

// ResourceOptionsFunc is a type alias for a resource type functional option.
Expand Down Expand Up @@ -368,6 +370,10 @@ var (
idAttributePath = path.Root("id")
)

type providerMetaData struct {
UserAgent types.List `tfsdk:"user_agent"`
}

func (r *genericResource) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) {
response.TypeName = r.tfTypeName

Expand All @@ -387,7 +393,10 @@ 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.bootstrapContextWithProviderMeta(ctx, request.ProviderMeta, &response.Diagnostics)
if response.Diagnostics.HasError() {
return
}

traceEntry(ctx, "Resource.Create")

Expand Down Expand Up @@ -490,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)
ctx = r.bootstrapContextWithProviderMeta(ctx, request.ProviderMeta, &response.Diagnostics)
if response.Diagnostics.HasError() {
return
}

traceEntry(ctx, "Resource.Read")

Expand Down Expand Up @@ -574,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)
ctx = r.bootstrapContextWithProviderMeta(ctx, request.ProviderMeta, &response.Diagnostics)
if response.Diagnostics.HasError() {
return
}

traceEntry(ctx, "Resource.Update")

Expand Down Expand Up @@ -712,7 +727,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)
ctx = r.bootstrapContextWithProviderMeta(ctx, request.ProviderMeta, &response.Diagnostics)
if response.Diagnostics.HasError() {
return
}

traceEntry(ctx, "Resource.Delete")

Expand Down Expand Up @@ -926,10 +944,25 @@ func (r *genericResource) populateUnknownValues(ctx context.Context, id string,
return nil
}

// bootstrapContext injects the CloudFormation type name into logger contexts.
// 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)
return r.provider.RegisterLogger(ctx)
}

// 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)

var metadata *providerMetaData
d.Append(providerMeta.Get(ctx, &metadata)...)

if metadata != nil {
var uap inttypes.UserAgentProducts
d.Append(metadata.UserAgent.ElementsAs(ctx, &uap, false)...)
ctx = useragent.Context(ctx, uap.UserAgentProducts())
}

return ctx
}
Expand Down
Loading