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

V2: Updates APN user-agent configuration #82

Merged
merged 2 commits into from
Sep 27, 2021
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 2 additions & 2 deletions aws_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,11 +129,11 @@ func commonLoadOptions(c *Config) []func(*config.LoadOptions) error {
}

apiOptions := make([]func(*middleware.Stack) error, 0)
if len(c.UserAgentProducts) > 0 {
if c.APNInfo != nil {
apiOptions = append(apiOptions, func(stack *middleware.Stack) error {
// Because the default User-Agent middleware prepends itself to the contents of the User-Agent header,
// we have to run after it and also prepend our custom User-Agent
return stack.Build.Add(customUserAgentMiddleware(c), middleware.After)
return stack.Build.Add(apnUserAgentMiddleware(*c.APNInfo), middleware.After)
})
}
if v := os.Getenv(constants.AppendUserAgentEnvVar); v != "" {
Expand Down
49 changes: 27 additions & 22 deletions aws_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -997,43 +997,48 @@ func TestUserAgentProducts(t *testing.T) {
AccessKey: servicemocks.MockStaticAccessKey,
Region: "us-east-1",
SecretKey: servicemocks.MockStaticSecretKey,
UserAgentProducts: []*UserAgentProduct{
{
Name: "first",
Version: "1.0",
},
{
Name: "second",
Version: "1.2.3",
Extra: []string{"+https://www.example.com/"},
APNInfo: &APNInfo{
PartnerName: "partner",
Products: []APNProduct{
{
Name: "first",
Version: "1.2.3",
},
{
Name: "second",
Version: "1.0.2",
Comment: "a comment",
},
},
},
},
Description: "customized User-Agent Products",
ExpectedUserAgent: "first/1.0 second/1.2.3 (+https://www.example.com/) " + awsSdkGoUserAgent(),
Description: "APN User-Agent Products",
ExpectedUserAgent: "APN/1.0 partner/1.0 first/1.2.3 second/1.0.2 (a comment) " + awsSdkGoUserAgent(),
},
{
Config: &Config{
AccessKey: servicemocks.MockStaticAccessKey,
Region: "us-east-1",
SecretKey: servicemocks.MockStaticSecretKey,
UserAgentProducts: []*UserAgentProduct{
{
Name: "first",
Version: "1.0",
},
{
Name: "second",
Version: "1.2.3",
Extra: []string{"+https://www.example.com/"},
APNInfo: &APNInfo{
PartnerName: "partner",
Products: []APNProduct{
{
Name: "first",
Version: "1.2.3",
},
{
Name: "second",
Version: "1.0.2",
},
},
},
},
Description: "customized User-Agent Products and TF_APPEND_USER_AGENT",
Description: "APN User-Agent Products and TF_APPEND_USER_AGENT",
EnvironmentVariables: map[string]string{
constants.AppendUserAgentEnvVar: "Last",
},
ExpectedUserAgent: "first/1.0 second/1.2.3 (+https://www.example.com/) " + awsSdkGoUserAgent() + " Last",
ExpectedUserAgent: "APN/1.0 partner/1.0 first/1.2.3 second/1.0.2 " + awsSdkGoUserAgent() + " Last",
},
}

Expand Down
19 changes: 12 additions & 7 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package awsbase

type Config struct {
AccessKey string
APNInfo *APNInfo
AssumeRole *AssumeRole
CallerDocumentationURL string
CallerName string
Expand All @@ -18,7 +19,17 @@ type Config struct {
SkipMetadataApiCheck bool
StsEndpoint string
Token string
UserAgentProducts []*UserAgentProduct
}

type APNInfo struct {
PartnerName string
Products []APNProduct
}

type APNProduct struct {
Name string
Version string
Comment string
}

type AssumeRole struct {
Expand All @@ -31,9 +42,3 @@ type AssumeRole struct {
Tags map[string]string
TransitiveTagKeys []string
}

type UserAgentProduct struct {
Extra []string
Name string
Version string
}
51 changes: 29 additions & 22 deletions user_agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,41 +3,48 @@ package awsbase
import (
"context"
"fmt"
"strings"

"github.com/aws/smithy-go/middleware"
smithyhttp "github.com/aws/smithy-go/transport/http"
)

func customUserAgentMiddleware(c *Config) middleware.BuildMiddleware {
return middleware.BuildMiddlewareFunc("CustomUserAgent",
// Builds the user-agent string for APN
func (apn APNInfo) BuildUserAgentString() string {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm guessing no publicly available documentation for this format?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's under NDA

builder := smithyhttp.NewUserAgentBuilder()
builder.AddKeyValue("APN", "1.0")
builder.AddKeyValue(apn.PartnerName, "1.0")
for _, p := range apn.Products {
p.buildUserAgentPart(builder)
}
return builder.Build()
}

func (p APNProduct) buildUserAgentPart(b *smithyhttp.UserAgentBuilder) {
if p.Name != "" {
if p.Version != "" {
b.AddKeyValue(p.Name, p.Version)
} else {
b.AddKey(p.Name)
}
}
if p.Comment != "" {
b.AddKey("(" + p.Comment + ")")
}
}

func apnUserAgentMiddleware(apn APNInfo) middleware.BuildMiddleware {
return middleware.BuildMiddlewareFunc("APNUserAgent",
func(ctx context.Context, in middleware.BuildInput, next middleware.BuildHandler) (middleware.BuildOutput, middleware.Metadata, error) {
request, ok := in.Request.(*smithyhttp.Request)
if !ok {
return middleware.BuildOutput{}, middleware.Metadata{}, fmt.Errorf("unknown request type %T", in.Request)
}

builder := smithyhttp.NewUserAgentBuilder()
for _, v := range c.UserAgentProducts {
builder.AddKey(userAgentProduct(v))
}
prependUserAgentHeader(request, builder.Build())
prependUserAgentHeader(request, apn.BuildUserAgentString())

return next.HandleBuild(ctx, in)
})
}

func userAgentProduct(product *UserAgentProduct) string {
var b strings.Builder
fmt.Fprint(&b, product.Name)
if product.Version != "" {
fmt.Fprintf(&b, "/%s", product.Version)
}
if len(product.Extra) > 0 {
fmt.Fprintf(&b, " (%s)", strings.Join(product.Extra, "; "))
}

return b.String()
},
)
}

// Because the default User-Agent middleware prepends itself to the contents of the User-Agent header,
Expand Down
11 changes: 5 additions & 6 deletions v2/awsv1shim/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,11 @@ func GetSession(awsC *awsv2.Config, c *awsbase.Config) (*session.Session, error)
// The configuration of additional User-Agent header products should take
// precedence over that product. Since the AWS SDK Go request package
// functions only append, we must PushFront on the build handlers instead
// of PushBack. To properly keep the order given by the configuration, we
// must reverse iterate through the products so the last item is PushFront
// first through the first item being PushFront last.
for i := len(c.UserAgentProducts) - 1; i >= 0; i-- {
product := c.UserAgentProducts[i]
sess.Handlers.Build.PushFront(request.MakeAddToUserAgentHandler(product.Name, product.Version, product.Extra...))
// of PushBack.
if c.APNInfo != nil {
sess.Handlers.Build.PushFront(
request.MakeAddToUserAgentFreeFormHandler(c.APNInfo.BuildUserAgentString()),
)
}

// Add custom input from ENV to the User-Agent request header
Expand Down
49 changes: 27 additions & 22 deletions v2/awsv1shim/session_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1045,20 +1045,23 @@ func TestUserAgentProducts(t *testing.T) {
AccessKey: servicemocks.MockStaticAccessKey,
Region: "us-east-1",
SecretKey: servicemocks.MockStaticSecretKey,
UserAgentProducts: []*awsbase.UserAgentProduct{
{
Name: "first",
Version: "1.0",
},
{
Name: "second",
Version: "1.2.3",
Extra: []string{"+https://www.example.com/"},
APNInfo: &awsbase.APNInfo{
PartnerName: "partner",
Products: []awsbase.APNProduct{
{
Name: "first",
Version: "1.2.3",
},
{
Name: "second",
Version: "1.0.2",
Comment: "a comment",
},
},
},
},
Description: "customized User-Agent",
ExpectedUserAgent: "first/1.0 second/1.2.3 (+https://www.example.com/) " + awsSdkGoUserAgent(),
Description: "APN User-Agent Products",
ExpectedUserAgent: "APN/1.0 partner/1.0 first/1.2.3 second/1.0.2 (a comment) " + awsSdkGoUserAgent(),
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
Expand All @@ -1068,23 +1071,25 @@ func TestUserAgentProducts(t *testing.T) {
AccessKey: servicemocks.MockStaticAccessKey,
Region: "us-east-1",
SecretKey: servicemocks.MockStaticSecretKey,
UserAgentProducts: []*awsbase.UserAgentProduct{
{
Name: "first",
Version: "1.0",
},
{
Name: "second",
Version: "1.2.3",
Extra: []string{"+https://www.example.com/"},
APNInfo: &awsbase.APNInfo{
PartnerName: "partner",
Products: []awsbase.APNProduct{
{
Name: "first",
Version: "1.2.3",
},
{
Name: "second",
Version: "1.0.2",
},
},
},
},
Description: "customized User-Agent Products and TF_APPEND_USER_AGENT",
Description: "APN User-Agent Products and TF_APPEND_USER_AGENT",
EnvironmentVariables: map[string]string{
constants.AppendUserAgentEnvVar: "Last",
},
ExpectedUserAgent: "first/1.0 second/1.2.3 (+https://www.example.com/) " + awsSdkGoUserAgent() + " Last",
ExpectedUserAgent: "APN/1.0 partner/1.0 first/1.2.3 second/1.0.2 " + awsSdkGoUserAgent() + " Last",
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
Expand Down