Skip to content

Commit 8dddc9c

Browse files
authored
add SDK-specific feature tracking (#2682)
1 parent 54f11c0 commit 8dddc9c

File tree

16,248 files changed

+106015
-31332
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

16,248 files changed

+106015
-31332
lines changed

.changelog/85f058b6d0bb40eea8548e539473d404.json

+401
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"id": "a048e0bc-5088-4e76-a29c-9524e4ee34eb",
3+
"type": "feature",
4+
"collapse": true,
5+
"description": "Add framework for tracking specific features in user-agent string.",
6+
"modules": [
7+
"."
8+
]
9+
}

aws/middleware/user_agent.go

+44
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"os"
77
"runtime"
8+
"sort"
89
"strings"
910

1011
"github.com/aws/aws-sdk-go-v2/aws"
@@ -30,6 +31,7 @@ const (
3031
FrameworkMetadata
3132
AdditionalMetadata
3233
ApplicationIdentifier
34+
FeatureMetadata2
3335
)
3436

3537
func (k SDKAgentKeyType) string() string {
@@ -50,6 +52,8 @@ func (k SDKAgentKeyType) string() string {
5052
return "lib"
5153
case ApplicationIdentifier:
5254
return "app"
55+
case FeatureMetadata2:
56+
return "m"
5357
case AdditionalMetadata:
5458
fallthrough
5559
default:
@@ -64,9 +68,29 @@ var validChars = map[rune]bool{
6468
'-': true, '.': true, '^': true, '_': true, '`': true, '|': true, '~': true,
6569
}
6670

71+
// UserAgentFeature enumerates tracked SDK features.
72+
type UserAgentFeature string
73+
74+
// Enumerates UserAgentFeature.
75+
const (
76+
UserAgentFeatureResourceModel UserAgentFeature = "A" // n/a (we don't generate separate resource types)
77+
UserAgentFeatureWaiter = "B"
78+
UserAgentFeaturePaginator = "C"
79+
UserAgentFeatureRetryModeLegacy = "D" // n/a (equivalent to standard)
80+
UserAgentFeatureRetryModeStandard = "E"
81+
UserAgentFeatureRetryModeAdaptive = "F"
82+
UserAgentFeatureS3Transfer = "G"
83+
UserAgentFeatureS3CryptoV1N = "H" // n/a (crypto client is external)
84+
UserAgentFeatureS3CryptoV2 = "I" // n/a
85+
UserAgentFeatureS3ExpressBucket = "J"
86+
UserAgentFeatureS3AccessGrants = "K" // not yet implemented
87+
UserAgentFeatureGZIPRequestCompression = "L"
88+
)
89+
6790
// RequestUserAgent is a build middleware that set the User-Agent for the request.
6891
type RequestUserAgent struct {
6992
sdkAgent, userAgent *smithyhttp.UserAgentBuilder
93+
features map[UserAgentFeature]struct{}
7094
}
7195

7296
// NewRequestUserAgent returns a new requestUserAgent which will set the User-Agent and X-Amz-User-Agent for the
@@ -87,6 +111,7 @@ func NewRequestUserAgent() *RequestUserAgent {
87111
r := &RequestUserAgent{
88112
sdkAgent: sdkAgent,
89113
userAgent: userAgent,
114+
features: map[UserAgentFeature]struct{}{},
90115
}
91116

92117
addSDKMetadata(r)
@@ -191,6 +216,12 @@ func (u *RequestUserAgent) AddUserAgentKeyValue(key, value string) {
191216
u.userAgent.AddKeyValue(strings.Map(rules, key), strings.Map(rules, value))
192217
}
193218

219+
// AddUserAgentFeature adds the feature ID to the tracking list to be emitted
220+
// in the final User-Agent string.
221+
func (u *RequestUserAgent) AddUserAgentFeature(feature UserAgentFeature) {
222+
u.features[feature] = struct{}{}
223+
}
224+
194225
// AddSDKAgentKey adds the component identified by name to the User-Agent string.
195226
func (u *RequestUserAgent) AddSDKAgentKey(keyType SDKAgentKeyType, key string) {
196227
// TODO: should target sdkAgent
@@ -227,6 +258,9 @@ func (u *RequestUserAgent) HandleBuild(ctx context.Context, in middleware.BuildI
227258
func (u *RequestUserAgent) addHTTPUserAgent(request *smithyhttp.Request) {
228259
const userAgent = "User-Agent"
229260
updateHTTPHeader(request, userAgent, u.userAgent.Build())
261+
if len(u.features) > 0 {
262+
updateHTTPHeader(request, userAgent, buildFeatureMetrics(u.features))
263+
}
230264
}
231265

232266
func (u *RequestUserAgent) addHTTPSDKAgent(request *smithyhttp.Request) {
@@ -259,3 +293,13 @@ func rules(r rune) rune {
259293
return '-'
260294
}
261295
}
296+
297+
func buildFeatureMetrics(features map[UserAgentFeature]struct{}) string {
298+
fs := make([]string, 0, len(features))
299+
for f := range features {
300+
fs = append(fs, string(f))
301+
}
302+
303+
sort.Strings(fs)
304+
return fmt.Sprintf("%s/%s", FeatureMetadata2.string(), strings.Join(fs, ","))
305+
}

aws/middleware/user_agent_test.go

+65
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,71 @@ func TestAddUserAgentKeyValue(t *testing.T) {
231231
}
232232
}
233233

234+
func TestAddUserAgentFeature(t *testing.T) {
235+
restoreEnv := clearEnv()
236+
defer restoreEnv()
237+
238+
cases := map[string]struct {
239+
Features []UserAgentFeature
240+
Expect string
241+
}{
242+
"none": {
243+
Features: []UserAgentFeature{},
244+
Expect: expectedAgent,
245+
},
246+
"one": {
247+
Features: []UserAgentFeature{
248+
UserAgentFeatureWaiter,
249+
},
250+
Expect: "m/B " + expectedAgent,
251+
},
252+
"two": {
253+
Features: []UserAgentFeature{
254+
UserAgentFeatureRetryModeAdaptive, // ensure stable order, and idempotent
255+
UserAgentFeatureRetryModeAdaptive,
256+
UserAgentFeatureWaiter,
257+
},
258+
Expect: "m/B,F " + expectedAgent,
259+
},
260+
}
261+
262+
for name, c := range cases {
263+
t.Run(name, func(t *testing.T) {
264+
b := NewRequestUserAgent()
265+
stack := middleware.NewStack("testStack", smithyhttp.NewStackRequest)
266+
err := stack.Build.Add(b, middleware.After)
267+
if err != nil {
268+
t.Fatalf("expect no error, got %v", err)
269+
}
270+
271+
for _, f := range c.Features {
272+
b.AddUserAgentFeature(f)
273+
}
274+
275+
in := middleware.BuildInput{
276+
Request: &smithyhttp.Request{
277+
Request: &http.Request{
278+
Header: map[string][]string{},
279+
},
280+
},
281+
}
282+
_, _, err = b.HandleBuild(context.Background(), in, middleware.BuildHandlerFunc(func(ctx context.Context, input middleware.BuildInput) (o middleware.BuildOutput, m middleware.Metadata, err error) {
283+
return o, m, err
284+
}))
285+
if err != nil {
286+
t.Fatalf("expect no error, got %v", err)
287+
}
288+
ua, ok := in.Request.(*smithyhttp.Request).Header["User-Agent"]
289+
if !ok {
290+
t.Fatalf("expect User-Agent to be present")
291+
}
292+
if ua[0] != c.Expect {
293+
t.Errorf("User-Agent did not match expected, %v != %v", c.Expect, ua[0])
294+
}
295+
})
296+
}
297+
}
298+
234299
func TestAddSDKAgentKey(t *testing.T) {
235300
restoreEnv := clearEnv()
236301
defer restoreEnv()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.smithy.aws.go.codegen.customization;
17+
18+
import java.util.Map;
19+
import java.util.Set;
20+
21+
import software.amazon.smithy.aws.go.codegen.AwsGoDependency;
22+
import software.amazon.smithy.codegen.core.Symbol;
23+
import software.amazon.smithy.codegen.core.SymbolProvider;
24+
import software.amazon.smithy.go.codegen.GoDelegator;
25+
import software.amazon.smithy.go.codegen.GoSettings;
26+
import software.amazon.smithy.go.codegen.SmithyGoDependency;
27+
import software.amazon.smithy.go.codegen.integration.Paginators;
28+
import software.amazon.smithy.model.Model;
29+
30+
import static software.amazon.smithy.go.codegen.GoWriter.goTemplate;
31+
import static software.amazon.smithy.go.codegen.SymbolUtils.buildPackageSymbol;
32+
33+
/**
34+
* Extends the base smithy Paginators integration to track in the User-Agent string.
35+
*/
36+
public class AwsPaginators extends Paginators {
37+
@Override
38+
public Set<Symbol> getAdditionalClientOptions() {
39+
return Set.of(buildPackageSymbol("addIsPaginatorUserAgent"));
40+
}
41+
42+
@Override
43+
public void writeAdditionalFiles(GoSettings settings, Model model, SymbolProvider symbolProvider, GoDelegator goDelegator) {
44+
super.writeAdditionalFiles(settings, model, symbolProvider, goDelegator);
45+
46+
goDelegator.useFileWriter("api_client.go", settings.getModuleName(), goTemplate("""
47+
func addIsPaginatorUserAgent(o *Options) {
48+
o.APIOptions = append(o.APIOptions, func(stack $stack:P) error {
49+
ua, err := getOrAddRequestUserAgent(stack)
50+
if err != nil {
51+
return err
52+
}
53+
54+
ua.AddUserAgentFeature($featurePaginator:T)
55+
return nil
56+
})
57+
}""",
58+
Map.of(
59+
"stack", SmithyGoDependency.SMITHY_MIDDLEWARE.struct("Stack"),
60+
"featurePaginator", AwsGoDependency.AWS_MIDDLEWARE.valueSymbol("UserAgentFeaturePaginator")
61+
)));
62+
}
63+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.smithy.aws.go.codegen.customization;
17+
18+
import java.util.Map;
19+
import java.util.Set;
20+
21+
import software.amazon.smithy.aws.go.codegen.AwsGoDependency;
22+
import software.amazon.smithy.codegen.core.Symbol;
23+
import software.amazon.smithy.codegen.core.SymbolProvider;
24+
import software.amazon.smithy.go.codegen.GoDelegator;
25+
import software.amazon.smithy.go.codegen.GoSettings;
26+
import software.amazon.smithy.go.codegen.SmithyGoDependency;
27+
import software.amazon.smithy.go.codegen.integration.Waiters;
28+
import software.amazon.smithy.model.Model;
29+
30+
import static software.amazon.smithy.go.codegen.GoWriter.goTemplate;
31+
import static software.amazon.smithy.go.codegen.SymbolUtils.buildPackageSymbol;
32+
33+
/**
34+
* Extends the base smithy Waiters integration to track in the User-Agent string.
35+
*/
36+
public class AwsWaiters extends Waiters {
37+
@Override
38+
public Set<Symbol> getAdditionalClientOptions() {
39+
return Set.of(buildPackageSymbol("addIsWaiterUserAgent"));
40+
}
41+
42+
@Override
43+
public void writeAdditionalFiles(GoSettings settings, Model model, SymbolProvider symbolProvider, GoDelegator goDelegator) {
44+
super.writeAdditionalFiles(settings, model, symbolProvider, goDelegator);
45+
46+
goDelegator.useFileWriter("api_client.go", settings.getModuleName(), goTemplate("""
47+
func addIsWaiterUserAgent(o *Options) {
48+
o.APIOptions = append(o.APIOptions, func(stack $stack:P) error {
49+
ua, err := getOrAddRequestUserAgent(stack)
50+
if err != nil {
51+
return err
52+
}
53+
54+
ua.AddUserAgentFeature($featureWaiter:T)
55+
return nil
56+
})
57+
}""",
58+
Map.of(
59+
"stack", SmithyGoDependency.SMITHY_MIDDLEWARE.struct("Stack"),
60+
"featureWaiter", AwsGoDependency.AWS_MIDDLEWARE.valueSymbol("UserAgentFeatureWaiter")
61+
)));
62+
}
63+
}

0 commit comments

Comments
 (0)