Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow setting the User Agent Operator suffix #2831

Merged
merged 8 commits into from
Oct 13, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changelog/2831.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
```release-note:internal
provider: updated user agent string to now be `terraform-provider-cloudflare/<version> <plugin> <operator suffix>`
```

```release-note:enhancement
provider: allow defining a user agent operator suffix through the schema field (`user_agent_operator_suffix`) and via the environment variable (`CLOUDFLARE_USER_AGENT_OPERATOR_SUFFIX`)
```
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,4 @@ resource "cloudflare_page_rule" "www" {
- `min_backoff` (Number) Minimum backoff period in seconds after failed API calls. Alternatively, can be configured using the `CLOUDFLARE_MIN_BACKOFF` environment variable.
- `retries` (Number) Maximum number of retries to perform when an API request fails. Alternatively, can be configured using the `CLOUDFLARE_RETRIES` environment variable.
- `rps` (Number) RPS limit to apply when making calls to the API. Alternatively, can be configured using the `CLOUDFLARE_RPS` environment variable.
- `user_agent_operator_suffix` (String) A value to append to the HTTP User Agent for all API calls. This value is not something most users need to modify however, if you are using a non-standard provider or operator configuration, this is recommended to assist in uniquely identifying your traffic. **Setting this value will remove the Terraform version from the HTTP User Agent string and may have unintended consequences**. Alternatively, can be configured using the `CLOUDFLARE_USER_AGENT_OPERATOR_SUFFIX` environment variable.
8 changes: 7 additions & 1 deletion internal/consts/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ const (
// Default value for the API base path.
APIBasePathDefault = "/client/v4"

// Schema key for the User Agent operator suffix.
UserAgentOperatorSuffixSchemaKey = "user_agent_operator_suffix"

// Environment variable key for the User Agent operator suffix.
UserAgentOperatorSuffixEnvVarKey = "CLOUDFLARE_USER_AGENT_OPERATOR_SUFFIX"

// Schema key for the requests per second configuration.
RPSSchemaKey = "rps"

Expand Down Expand Up @@ -87,7 +93,7 @@ const (
// Deprecated: Use resource specific account ID values instead.
AccountIDEnvVarKey = "CLOUDFLARE_ACCOUNT_ID"

UserAgentDefault = "terraform/%s terraform-plugin-sdk/%s terraform-provider-cloudflare/%s"
UserAgentDefault = "terraform-provider-cloudflare/%s terraform-plugin-%s/%s"

jacobbednarz marked this conversation as resolved.
Show resolved Hide resolved
// Schema key for the account ID configuration.
AccountIDSchemaKey = "account_id"
Expand Down
41 changes: 27 additions & 14 deletions internal/framework/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ import (
"github.com/hashicorp/terraform-plugin-mux/tf5to6server"
"github.com/hashicorp/terraform-plugin-mux/tf6muxserver"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/logging"
"github.com/hashicorp/terraform-plugin-sdk/v2/meta"
)

// Ensure CloudflareProvider satisfies various provider interfaces.
Expand All @@ -47,17 +46,18 @@ type CloudflareProvider struct {

// CloudflareProviderModel describes the provider data model.
type CloudflareProviderModel struct {
APIKey types.String `tfsdk:"api_key"`
APIUserServiceKey types.String `tfsdk:"api_user_service_key"`
Email types.String `tfsdk:"email"`
MinBackOff types.Int64 `tfsdk:"min_backoff"`
RPS types.Int64 `tfsdk:"rps"`
APIBasePath types.String `tfsdk:"api_base_path"`
APIToken types.String `tfsdk:"api_token"`
Retries types.Int64 `tfsdk:"retries"`
MaxBackoff types.Int64 `tfsdk:"max_backoff"`
APIClientLogging types.Bool `tfsdk:"api_client_logging"`
APIHostname types.String `tfsdk:"api_hostname"`
APIKey types.String `tfsdk:"api_key"`
APIUserServiceKey types.String `tfsdk:"api_user_service_key"`
Email types.String `tfsdk:"email"`
MinBackOff types.Int64 `tfsdk:"min_backoff"`
RPS types.Int64 `tfsdk:"rps"`
APIBasePath types.String `tfsdk:"api_base_path"`
APIToken types.String `tfsdk:"api_token"`
Retries types.Int64 `tfsdk:"retries"`
MaxBackoff types.Int64 `tfsdk:"max_backoff"`
APIClientLogging types.Bool `tfsdk:"api_client_logging"`
APIHostname types.String `tfsdk:"api_hostname"`
UserAgentOperatorSuffix types.String `tfsdk:"user_agent_operator_suffix"`
}

func (p *CloudflareProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) {
Expand Down Expand Up @@ -138,6 +138,11 @@ func (p *CloudflareProvider) Schema(ctx context.Context, req provider.SchemaRequ
Optional: true,
MarkdownDescription: fmt.Sprintf("Configure the base path used by the API client. Alternatively, can be configured using the `%s` environment variable.", consts.APIBasePathEnvVarKey),
},

consts.UserAgentOperatorSuffixSchemaKey: schema.StringAttribute{
Optional: true,
MarkdownDescription: fmt.Sprintf("A value to append to the HTTP User Agent for all API calls. This value is not something most users need to modify however, if you are using a non-standard provider or operator configuration, this is recommended to assist in uniquely identifying your traffic. **Setting this value will remove the Terraform version from the HTTP User Agent string and may have unintended consequences**. Alternatively, can be configured using the `%s` environment variable.", consts.UserAgentOperatorSuffixEnvVarKey),
},
},
}
}
Expand Down Expand Up @@ -234,8 +239,16 @@ func (p *CloudflareProvider) Configure(ctx context.Context, req provider.Configu

options = append(options, cloudflare.Debug(logging.IsDebugOrHigher()))

ua := fmt.Sprintf(consts.UserAgentDefault, req.TerraformVersion, meta.SDKVersionString(), p.version)
options = append(options, cloudflare.UserAgent(ua))
userAgentParams := utils.UserAgentBuilderParams{
ProviderVersion: &p.version,
PluginType: cloudflare.StringPtr("terraform-plugin-framework"),
}
if !data.UserAgentOperatorSuffix.IsNull() {
userAgentParams.OperatorSuffix = cloudflare.StringPtr(data.UserAgentOperatorSuffix.String())
} else {
userAgentParams.TerraformVersion = cloudflare.StringPtr(req.TerraformVersion)
}
options = append(options, cloudflare.UserAgent(userAgentParams.String()))

config := Config{Options: options}

Expand Down
19 changes: 17 additions & 2 deletions internal/sdkv2provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,12 @@ func New(version string) func() *schema.Provider {
Optional: true,
Description: fmt.Sprintf("Configure the base path used by the API client. Alternatively, can be configured using the `%s` environment variable.", consts.APIBasePathEnvVarKey),
},

consts.UserAgentOperatorSuffixSchemaKey: {
Type: schema.TypeString,
Optional: true,
Description: fmt.Sprintf("A value to append to the HTTP User Agent for all API calls. This value is not something most users need to modify however, if you are using a non-standard provider or operator configuration, this is recommended to assist in uniquely identifying your traffic. **Setting this value will remove the Terraform version from the HTTP User Agent string and may have unintended consequences**. Alternatively, can be configured using the `%s` environment variable.", consts.UserAgentOperatorSuffixEnvVarKey),
},
},

DataSourcesMap: map[string]*schema.Resource{
Expand Down Expand Up @@ -377,8 +383,17 @@ func configure(version string, p *schema.Provider) func(context.Context, *schema

options = append(options, cloudflare.Debug(logging.IsDebugOrHigher()))

ua := fmt.Sprintf(consts.UserAgentDefault, p.TerraformVersion, meta.SDKVersionString(), version)
options = append(options, cloudflare.UserAgent(ua))
userAgentParams := utils.UserAgentBuilderParams{
ProviderVersion: cloudflare.StringPtr(version),
PluginType: cloudflare.StringPtr("terraform-plugin-sdk"),
PluginVersion: cloudflare.StringPtr(meta.SDKVersionString()),
}
if v, ok := d.GetOk(consts.UserAgentOperatorSuffixSchemaKey); ok {
userAgentParams.OperatorSuffix = cloudflare.StringPtr(v.(string))
} else {
userAgentParams.TerraformVersion = cloudflare.StringPtr(p.TerraformVersion)
}
options = append(options, cloudflare.UserAgent(userAgentParams.String()))

config := Config{Options: options}

Expand Down
42 changes: 42 additions & 0 deletions internal/utils/user_agent_builder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package utils

import (
"fmt"
)

type UserAgentBuilderParams struct {
ProviderVersion *string
PluginVersion *string
PluginType *string
TerraformVersion *string
OperatorSuffix *string
}

func (p *UserAgentBuilderParams) String() string {
var ua string
if p.ProviderVersion != nil {
ua += fmt.Sprintf("terraform-provider-cloudflare/%s", *p.ProviderVersion)
}

if p.PluginType != nil {
ua += fmt.Sprintf(" %s", *p.PluginType)
}

if p.PluginVersion != nil {
ua += fmt.Sprintf("/%s", *p.PluginVersion)
}

// Operator suffix and Terraform version are mutually exclusive and we should
// only ever see one of them.
if p.OperatorSuffix != nil {
ua += fmt.Sprintf(" %s", *p.OperatorSuffix)
} else if p.TerraformVersion != nil {
ua += fmt.Sprintf(" terraform/%s", *p.TerraformVersion)
}

return ua
}

func BuildUserAgent(params UserAgentBuilderParams) string {
return params.String()
}
30 changes: 30 additions & 0 deletions internal/utils/user_agent_builder_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package utils

import (
"reflect"
"testing"

"github.com/cloudflare/cloudflare-go"
)

func TestUserAgentBuilding(t *testing.T) {
tests := []struct {
input UserAgentBuilderParams
expect string
}{
{input: UserAgentBuilderParams{ProviderVersion: cloudflare.StringPtr("1.0")}, expect: "terraform-provider-cloudflare/1.0"},
{input: UserAgentBuilderParams{ProviderVersion: cloudflare.StringPtr("1.0"), PluginType: cloudflare.StringPtr("terraform-plugin-foo")}, expect: "terraform-provider-cloudflare/1.0 terraform-plugin-foo"},
{input: UserAgentBuilderParams{ProviderVersion: cloudflare.StringPtr("1.0"), PluginType: cloudflare.StringPtr("terraform-plugin-foo"), PluginVersion: cloudflare.StringPtr("1.2.3")}, expect: "terraform-provider-cloudflare/1.0 terraform-plugin-foo/1.2.3"},
{input: UserAgentBuilderParams{ProviderVersion: cloudflare.StringPtr("1.0"), PluginType: cloudflare.StringPtr("terraform-plugin-foo"), PluginVersion: cloudflare.StringPtr("1.2.3"), TerraformVersion: cloudflare.StringPtr("9.9.9")}, expect: "terraform-provider-cloudflare/1.0 terraform-plugin-foo/1.2.3 terraform/9.9.9"},
{input: UserAgentBuilderParams{ProviderVersion: cloudflare.StringPtr("1.0"), OperatorSuffix: cloudflare.StringPtr("example/v88")}, expect: "terraform-provider-cloudflare/1.0 example/v88"},
{input: UserAgentBuilderParams{ProviderVersion: cloudflare.StringPtr("1.0"), OperatorSuffix: cloudflare.StringPtr("example/v88"), TerraformVersion: cloudflare.StringPtr("1.2.3")}, expect: "terraform-provider-cloudflare/1.0 example/v88"},
{input: UserAgentBuilderParams{ProviderVersion: cloudflare.StringPtr("1.0"), TerraformVersion: cloudflare.StringPtr("1.2.3")}, expect: "terraform-provider-cloudflare/1.0 terraform/1.2.3"},
}

for _, tc := range tests {
got := BuildUserAgent(tc.input)
if !reflect.DeepEqual(tc.expect, got) {
t.Fatalf("expected: %v, got: %v", tc.expect, got)
}
}
}
Loading