From 3dfba1ab65308d1fd7fb45d832f75ff0b81a934b Mon Sep 17 00:00:00 2001 From: Wei Lim Date: Thu, 7 Jul 2022 17:19:02 -0700 Subject: [PATCH 1/6] Add context to user-agent when running in GitHub ACtions. This change adds a product identifier `GhActions` when the CLI is running on GitHub Actions. --- cli/azd/internal/useragent.go | 21 +++++++++++---------- cli/azd/internal/useragent_test.go | 20 ++++++++++---------- cli/azd/pkg/commands/util.go | 17 +++++++++++++++-- cli/azd/pkg/tools/azcli.go | 8 ++++---- cli/azd/pkg/tools/azcli_test.go | 6 ++---- 5 files changed, 42 insertions(+), 30 deletions(-) diff --git a/cli/azd/internal/useragent.go b/cli/azd/internal/useragent.go index 4fbd1d7c768..e27700607ea 100644 --- a/cli/azd/internal/useragent.go +++ b/cli/azd/internal/useragent.go @@ -6,10 +6,11 @@ import ( "strings" ) -// FormatUserAgent formats the user agent with its base information (azd/), information -// that's included from the environment (`AZURE_DEV_USER_AGENT`) and any extra text passed -// via 'extra'. -func FormatUserAgent(extras []string) string { +// MakeUserAgent creates a user agent string that contains all necessary product identifiers, in increasing order: +// - The Azure Developer CLI version, formatted as `azdev/` +// - The user agent set by the user, from `AZURE_DEV_USER_AGENT` environment variable +// - Any additional product identifiers specified in `additionalProductIdentifiers`. +func MakeUserAgent(additionalProductIdentifiers []string) string { // like the Azure CLI (via it's `AZURE_HTTP_USER_AGENT` env variable) we allow for a user to append // information to the UserAgent by setting an environment variable. devUserAgent := os.Getenv("AZURE_DEV_USER_AGENT") @@ -18,19 +19,19 @@ func FormatUserAgent(extras []string) string { devUserAgent = " " + devUserAgent } - var appendUA string + var remainingProductIdentifiers string - if len(extras) > 0 { - appendUA = " " + strings.Join(extras, " ") + if len(additionalProductIdentifiers) > 0 { + remainingProductIdentifiers = " " + strings.Join(additionalProductIdentifiers, " ") } // and by default we always include azdev and our version number. // Ex: AZURECLI/2.34.1 (DEB) azdev/0.0.0-alpha.0 azdtempl/functestapp@v1 - return fmt.Sprintf("azdev/%s%s%s", GetVersionNumber(), devUserAgent, appendUA) + return fmt.Sprintf("azdev/%s%s%s", GetVersionNumber(), devUserAgent, remainingProductIdentifiers) } -// FormatTemplateForUserAgent formats the template into a UserAgent value. -func FormatTemplateForUserAgent(template string) string { +// FormatTemplateAsProductIdentifier formats the template as a user-agent product identifier. +func FormatTemplateAsProductIdentifier(template string) string { if template == "" { template = "[none]" } diff --git a/cli/azd/internal/useragent_test.go b/cli/azd/internal/useragent_test.go index adee6654b91..e5df8e5de4e 100644 --- a/cli/azd/internal/useragent_test.go +++ b/cli/azd/internal/useragent_test.go @@ -17,19 +17,19 @@ func TestGetUserAgent(t *testing.T) { os.Setenv("AZURE_DEV_USER_AGENT", "") - require.Equal(t, fmt.Sprintf("azdev/%s", version), FormatUserAgent([]string{})) - require.Equal(t, fmt.Sprintf("azdev/%s", version), FormatUserAgent(nil)) - require.Equal(t, fmt.Sprintf("azdev/%s extra values", version), FormatUserAgent([]string{"extra", "values"})) + require.Equal(t, fmt.Sprintf("azdev/%s", version), MakeUserAgent([]string{})) + require.Equal(t, fmt.Sprintf("azdev/%s", version), MakeUserAgent(nil)) + require.Equal(t, fmt.Sprintf("azdev/%s extra values", version), MakeUserAgent([]string{"extra", "values"})) os.Setenv("AZURE_DEV_USER_AGENT", "dev_user_agent") - require.Equal(t, fmt.Sprintf("azdev/%s dev_user_agent", version), FormatUserAgent([]string{})) - require.Equal(t, fmt.Sprintf("azdev/%s dev_user_agent", version), FormatUserAgent(nil)) - require.Equal(t, fmt.Sprintf("azdev/%s dev_user_agent extra values", version), FormatUserAgent([]string{"extra", "values"})) + require.Equal(t, fmt.Sprintf("azdev/%s dev_user_agent", version), MakeUserAgent([]string{})) + require.Equal(t, fmt.Sprintf("azdev/%s dev_user_agent", version), MakeUserAgent(nil)) + require.Equal(t, fmt.Sprintf("azdev/%s dev_user_agent extra values", version), MakeUserAgent([]string{"extra", "values"})) } -func TestFormatTemplateForUserAgent(t *testing.T) { - require.Equal(t, "azdtempl/[none]", FormatTemplateForUserAgent("")) - require.Equal(t, "azdtempl/todo-python-mongo", FormatTemplateForUserAgent("todo-python-mongo")) - require.Equal(t, "azdtempl/todo-csharp-sql@0.0.1-beta", FormatTemplateForUserAgent("todo-csharp-sql@0.0.1-beta")) +func TestFormatTemplateAsProductIdentifier(t *testing.T) { + require.Equal(t, "azdtempl/[none]", FormatTemplateAsProductIdentifier("")) + require.Equal(t, "azdtempl/todo-python-mongo", FormatTemplateAsProductIdentifier("todo-python-mongo")) + require.Equal(t, "azdtempl/todo-csharp-sql@0.0.1-beta", FormatTemplateAsProductIdentifier("todo-csharp-sql@0.0.1-beta")) } diff --git a/cli/azd/pkg/commands/util.go b/cli/azd/pkg/commands/util.go index a0abd8b77f2..4ed23b1bf7d 100644 --- a/cli/azd/pkg/commands/util.go +++ b/cli/azd/pkg/commands/util.go @@ -2,6 +2,7 @@ package commands import ( "context" + "os" "strings" "github.com/azure/azure-dev/cli/azd/internal" @@ -31,12 +32,24 @@ func GetAzCliFromContext(ctx context.Context) tools.AzCli { azCli = tools.NewAzCli(azCliArgs) } + // List of additional product identifiers that is appended to the User-Agent header + productIdentifiers := make([]string, 0) + // Set the user agent if a template has been selected template, ok := ctx.Value(environment.TemplateContextKey).(string) if ok && strings.TrimSpace(template) != "" { - userAgent := internal.FormatTemplateForUserAgent(template) - azCli.SetUserAgent([]string{userAgent}) + productIdentifiers = append(productIdentifiers, internal.FormatTemplateAsProductIdentifier(template)) } + isRunningInGithubActions := os.Getenv("GITHUB_ACTIONS") + + // Must be set to 'true' if running in GitHub Actions, + // see https://docs.github.com/en/actions/learn-github-actions/environment-variables#default-environment-variables + if isRunningInGithubActions == "true" { + productIdentifiers = append(productIdentifiers, "GhActions") + } + + azCli.SetUserAgent(internal.MakeUserAgent(productIdentifiers)) + return azCli } diff --git a/cli/azd/pkg/tools/azcli.go b/cli/azd/pkg/tools/azcli.go index c7e8c7ef6b1..14263ff24b3 100644 --- a/cli/azd/pkg/tools/azcli.go +++ b/cli/azd/pkg/tools/azcli.go @@ -39,7 +39,7 @@ type AzCli interface { // SetUserAgent sets the user agent that's sent with each call to the Azure // CLI via the `AZURE_HTTP_USER_AGENT` environment variable. - SetUserAgent(extraUserAgentData []string) + SetUserAgent(userAgent string) // UserAgent gets the currently configured user agent UserAgent() string @@ -297,7 +297,7 @@ func NewAzCli(args NewAzCliArgs) AzCli { } return &azCli{ - userAgent: azdinternal.FormatUserAgent(nil), + userAgent: azdinternal.MakeUserAgent(nil), enableDebug: args.EnableDebug, enableTelemetry: args.EnableTelemetry, runWithResultFn: args.RunWithResultFn, @@ -327,8 +327,8 @@ func (cli *azCli) CheckInstalled(_ context.Context) (bool, error) { // SetUserAgent sets the user agent that's sent with each call to the Azure // CLI via the `AZURE_HTTP_USER_AGENT` environment variable. -func (cli *azCli) SetUserAgent(userAgentData []string) { - cli.userAgent = azdinternal.FormatUserAgent(userAgentData) +func (cli *azCli) SetUserAgent(userAgent string) { + cli.userAgent = userAgent } func (cli *azCli) UserAgent() string { diff --git a/cli/azd/pkg/tools/azcli_test.go b/cli/azd/pkg/tools/azcli_test.go index 35b87e94999..27dddf42a4d 100644 --- a/cli/azd/pkg/tools/azcli_test.go +++ b/cli/azd/pkg/tools/azcli_test.go @@ -42,7 +42,7 @@ func TestAzCli(t *testing.T) { require.NoError(t, err) require.Equal(t, []string{ - fmt.Sprintf("AZURE_HTTP_USER_AGENT=%s", azdinternal.FormatUserAgent(nil)), + fmt.Sprintf("AZURE_HTTP_USER_AGENT=%s", azdinternal.MakeUserAgent(nil)), }, env) require.Equal(t, []string{"hello", "--debug"}, commandArgs) @@ -85,9 +85,7 @@ func TestAZCLIWithUserAgent(t *testing.T) { EnableDebug: true, }) - tempAZCLI.SetUserAgent([]string{ - "AZTesting=yes", - }) + tempAZCLI.SetUserAgent("AZTesting=yes") azcli := tempAZCLI.(*azCli) From 2149f1eb0d4022637e3ef17e24da323d9447ba7b Mon Sep 17 00:00:00 2001 From: Wei Lim Date: Fri, 8 Jul 2022 12:45:34 -0700 Subject: [PATCH 2/6] Centralize user agent logic. --- cli/azd/internal/useragent.go | 95 ++++++++++++++++---- cli/azd/internal/useragent_test.go | 138 +++++++++++++++++++++++++---- cli/azd/main.go | 2 +- cli/azd/pkg/commands/util.go | 19 +--- cli/azd/pkg/tools/azcli.go | 2 +- cli/azd/pkg/tools/azcli_test.go | 5 +- 6 files changed, 211 insertions(+), 50 deletions(-) diff --git a/cli/azd/internal/useragent.go b/cli/azd/internal/useragent.go index e27700607ea..8d3d771ab3e 100644 --- a/cli/azd/internal/useragent.go +++ b/cli/azd/internal/useragent.go @@ -6,34 +6,95 @@ import ( "strings" ) -// MakeUserAgent creates a user agent string that contains all necessary product identifiers, in increasing order: +const userSpecifiedAgentEnvironmentVariableName = "AZURE_DEV_USER_AGENT" +const githubActionsEnvironmentVariableName = "GITHUB_ACTIONS" + +const azDevProductIdentifierKey = "azdev" +const templateProductIdentifierKey = "azdtempl" +const githubActionsProductIdentifierKey = "GhActions" + +type UserAgent struct { + // Azure Developer CLI product identifier. Formatted as `azdev/` + azDevCliIdentifier string + + // (Optional) User specified identifier, set from `AZURE_DEV_USER_AGENT` environment variable + userSpecifiedIdentifier string + + // (Optional) Identifier for the template used, if applicable. Formatted as `azdevtempl/` + templateIdentifier string + + // (Optional) Identifier for GitHub Actions, if applicable + githubActionsIdentifier string +} + +// String creates a user agent string that contains all necessary product identifiers, in increasing order: // - The Azure Developer CLI version, formatted as `azdev/` -// - The user agent set by the user, from `AZURE_DEV_USER_AGENT` environment variable -// - Any additional product identifiers specified in `additionalProductIdentifiers`. -func MakeUserAgent(additionalProductIdentifiers []string) string { +// - The user specified identifier, set from `AZURE_DEV_USER_AGENT` environment variable +// - The identifier for the template used, if applicable +// - The identifier for GitHub Actions, if applicable +// Examples: +// - `azdev/1.0.0 (Windows NT)` +// - `azdev/1.0.0 (Windows NT) Custom-foo/1.0.0 azdtempl/my-template@1.0.0 GhActions` +func (userAgent *UserAgent) String() string { + var sb strings.Builder + sb.WriteString(userAgent.azDevCliIdentifier) + appendIdentifier(&sb, userAgent.userSpecifiedIdentifier) + appendIdentifier(&sb, userAgent.templateIdentifier) + appendIdentifier(&sb, userAgent.githubActionsIdentifier) + + return sb.String() +} + +func appendIdentifier(sb *strings.Builder, identifier string) { + if identifier != "" { + sb.WriteString(" " + identifier) + } +} + +func MakeUserAgent(template string) UserAgent { + userAgent := UserAgent{} + userAgent.azDevCliIdentifier = getAzDevCliIdentifier() + userAgent.userSpecifiedIdentifier = getUserSpecifiedIdentifier() + userAgent.githubActionsIdentifier = getGithubActionsIdentifier() + userAgent.templateIdentifier = formatTemplateIdentifier(template) + + return userAgent +} + +func MakeUserAgentString(template string) string { + userAgent := MakeUserAgent(template) + + return userAgent.String() +} + +func getAzDevCliIdentifier() string { + return fmt.Sprintf("%s/%s", azDevProductIdentifierKey, GetVersionNumber()) +} + +func getUserSpecifiedIdentifier() string { // like the Azure CLI (via it's `AZURE_HTTP_USER_AGENT` env variable) we allow for a user to append // information to the UserAgent by setting an environment variable. - devUserAgent := os.Getenv("AZURE_DEV_USER_AGENT") - - if devUserAgent != "" { - devUserAgent = " " + devUserAgent + if devUserAgent := os.Getenv(userSpecifiedAgentEnvironmentVariableName); devUserAgent != "" { + return devUserAgent } - var remainingProductIdentifiers string + return "" +} - if len(additionalProductIdentifiers) > 0 { - remainingProductIdentifiers = " " + strings.Join(additionalProductIdentifiers, " ") +func getGithubActionsIdentifier() string { + // `GITHUB_ACTIONS` must be set to 'true' if running in GitHub Actions, + // see https://docs.github.com/en/actions/learn-github-actions/environment-variables#default-environment-variables + if isRunningInGithubActions := os.Getenv(githubActionsEnvironmentVariableName); isRunningInGithubActions == "true" { + return githubActionsProductIdentifierKey } - // and by default we always include azdev and our version number. - // Ex: AZURECLI/2.34.1 (DEB) azdev/0.0.0-alpha.0 azdtempl/functestapp@v1 - return fmt.Sprintf("azdev/%s%s%s", GetVersionNumber(), devUserAgent, remainingProductIdentifiers) + return "" } -// FormatTemplateAsProductIdentifier formats the template as a user-agent product identifier. -func FormatTemplateAsProductIdentifier(template string) string { +func formatTemplateIdentifier(template string) string { if template == "" { template = "[none]" } - return fmt.Sprintf("azdtempl/%s", template) + + return fmt.Sprintf("%s/%s", templateProductIdentifierKey, template) } diff --git a/cli/azd/internal/useragent_test.go b/cli/azd/internal/useragent_test.go index e5df8e5de4e..be0f1c226a3 100644 --- a/cli/azd/internal/useragent_test.go +++ b/cli/azd/internal/useragent_test.go @@ -8,28 +8,136 @@ import ( "github.com/stretchr/testify/require" ) -func TestGetUserAgent(t *testing.T) { - orig := os.Getenv("AZURE_DEV_USER_AGENT") - defer func() { os.Setenv("AZURE_DEV_USER_AGENT", orig) }() +func TestGetAzDevCliIdentifier(t *testing.T) { + version := GetVersionNumber() + require.NotEmpty(t, version) + + require.Equal(t, fmt.Sprintf("%s/%s", azDevProductIdentifierKey, version), getAzDevCliIdentifier()) +} + +func TestUserSpecifiedAgentIdentifier(t *testing.T) { + devUserAgent := "MyAgent/1.0.0" + restorer := EnvironmentVariablesSetter(map[string]string{ + userSpecifiedAgentEnvironmentVariableName: devUserAgent, + }) + + require.Equal(t, devUserAgent, getUserSpecifiedIdentifier()) + + // Empty case + os.Setenv(userSpecifiedAgentEnvironmentVariableName, "") + require.Equal(t, "", getUserSpecifiedIdentifier()) + + t.Cleanup(restorer) +} + +func TestGithubActionIdentifier(t *testing.T) { + restorer := EnvironmentVariablesSetter(map[string]string{ + githubActionsEnvironmentVariableName: "", + }) + + require.Equal(t, "", getGithubActionsIdentifier()) + + // Empty case + os.Setenv(githubActionsEnvironmentVariableName, "true") + require.Equal(t, "GhActions", getGithubActionsIdentifier()) + + t.Cleanup(restorer) +} + +func TestFormatTemplate(t *testing.T) { + require.Equal(t, fmt.Sprintf("%s/[none]", templateProductIdentifierKey), formatTemplateIdentifier("")) + require.Equal(t, fmt.Sprintf("%s/todo-python-mongo", templateProductIdentifierKey), formatTemplateIdentifier("todo-python-mongo")) + require.Equal(t, fmt.Sprintf("%s/todo-csharp-sql@0.0.1-beta", templateProductIdentifierKey), formatTemplateIdentifier("todo-csharp-sql@0.0.1-beta")) +} + +// Scenario tests +func TestUserAgentStringScenarios(t *testing.T) { + restorer := EnvironmentVariablesSetter(map[string]string{ + userSpecifiedAgentEnvironmentVariableName: "", + githubActionsEnvironmentVariableName: "", + }) version := GetVersionNumber() require.NotEmpty(t, version) - os.Setenv("AZURE_DEV_USER_AGENT", "") + // Scenario: default agent + userAgent := MakeUserAgent("") + require.Equal(t, fmt.Sprintf("azdev/%s azdtempl/[none]", version), userAgent.String()) - require.Equal(t, fmt.Sprintf("azdev/%s", version), MakeUserAgent([]string{})) - require.Equal(t, fmt.Sprintf("azdev/%s", version), MakeUserAgent(nil)) - require.Equal(t, fmt.Sprintf("azdev/%s extra values", version), MakeUserAgent([]string{"extra", "values"})) + // Scenario: user specifies agent variable + os.Setenv(userSpecifiedAgentEnvironmentVariableName, "dev_user_agent") + userAgent = MakeUserAgent("") + require.Equal(t, fmt.Sprintf("azdev/%s dev_user_agent azdtempl/[none]", version), userAgent.String()) + os.Setenv(userSpecifiedAgentEnvironmentVariableName, "") - os.Setenv("AZURE_DEV_USER_AGENT", "dev_user_agent") + // Scenario: running on github actions + os.Setenv(githubActionsEnvironmentVariableName, "true") + userAgent = MakeUserAgent("") + require.Equal(t, fmt.Sprintf("azdev/%s azdtempl/[none] GhActions", version), userAgent.String()) + os.Setenv(githubActionsEnvironmentVariableName, "") - require.Equal(t, fmt.Sprintf("azdev/%s dev_user_agent", version), MakeUserAgent([]string{})) - require.Equal(t, fmt.Sprintf("azdev/%s dev_user_agent", version), MakeUserAgent(nil)) - require.Equal(t, fmt.Sprintf("azdev/%s dev_user_agent extra values", version), MakeUserAgent([]string{"extra", "values"})) + // Scenario: template present + userAgent = MakeUserAgent("template@0.0.1") + require.Equal(t, fmt.Sprintf("azdev/%s azdtempl/template@0.0.1", version), userAgent.String()) + + // Scenario: full combination + os.Setenv(userSpecifiedAgentEnvironmentVariableName, "dev_user_agent") + os.Setenv(githubActionsEnvironmentVariableName, "true") + userAgent = MakeUserAgent("template@0.0.1") + require.Equal(t, fmt.Sprintf("azdev/%s dev_user_agent azdtempl/template@0.0.1 GhActions", version), userAgent.String()) + + t.Cleanup(restorer) } -func TestFormatTemplateAsProductIdentifier(t *testing.T) { - require.Equal(t, "azdtempl/[none]", FormatTemplateAsProductIdentifier("")) - require.Equal(t, "azdtempl/todo-python-mongo", FormatTemplateAsProductIdentifier("todo-python-mongo")) - require.Equal(t, "azdtempl/todo-csharp-sql@0.0.1-beta", FormatTemplateAsProductIdentifier("todo-csharp-sql@0.0.1-beta")) +func TestUserAgentString(t *testing.T) { + userAgent := UserAgent{ + azDevCliIdentifier: "cli/1.0.0", + } + + require.Equal(t, userAgent.String(), userAgent.azDevCliIdentifier) + + // Verify complete formatting + userAgent = UserAgent{ + azDevCliIdentifier: "cli/1.0.0", + userSpecifiedIdentifier: "dev-user-agent/0.0.0-beta", + templateIdentifier: "template/mytemplate@2.0.0", + githubActionsIdentifier: "gh/3.0.2", + } + + require.Equal( + t, + userAgent.String(), + fmt.Sprintf("%s %s %s %s", userAgent.azDevCliIdentifier, userAgent.userSpecifiedIdentifier, userAgent.templateIdentifier, userAgent.githubActionsIdentifier)) +} + +// EnvironmentVariablesSetter sets the provided environment variables, +// returning a function that restores the environment variables to their original values. +// Example usage: +// fn test(t *testing.T) { +// closer := helpers.EnvironmentVariablesSetter(map[string]string { "FOO_ENV": "Bar", "OTHER_FOO_ENV": "Bar2"}) +// require.Equal(t, os.GetEnv("FOO_ENV"), "Bar") +// require.Equal(t, os.GetEnv("OTHER_FOO_ENV"), "Bar2") +// t.Cleanup(closer) +// } +// +func EnvironmentVariablesSetter(envContext map[string]string) func() { + restoreContext := map[string]string{} + for key, value := range envContext { + orig, present := os.LookupEnv(key) + if present { + restoreContext[key] = orig + } + + os.Setenv(key, value) + } + + return func() { + for key := range envContext { + if restoreValue, present := restoreContext[key]; present { + os.Setenv(key, restoreValue) + } else { + os.Unsetenv(key) + } + } + } } diff --git a/cli/azd/main.go b/cli/azd/main.go index 44e8f7c30c3..702bf115cff 100644 --- a/cli/azd/main.go +++ b/cli/azd/main.go @@ -151,7 +151,7 @@ func fetchLatestVersion(version chan<- semver.Version) { log.Printf("failed to create request object: %v, skipping update check", err) } - req.Header.Set("User-Agent", internal.FormatUserAgent(nil)) + req.Header.Set("User-Agent", internal.MakeUserAgentString("")) res, err := http.DefaultClient.Do(req) if err != nil { diff --git a/cli/azd/pkg/commands/util.go b/cli/azd/pkg/commands/util.go index 4ed23b1bf7d..0f8b039d807 100644 --- a/cli/azd/pkg/commands/util.go +++ b/cli/azd/pkg/commands/util.go @@ -2,7 +2,6 @@ package commands import ( "context" - "os" "strings" "github.com/azure/azure-dev/cli/azd/internal" @@ -32,24 +31,14 @@ func GetAzCliFromContext(ctx context.Context) tools.AzCli { azCli = tools.NewAzCli(azCliArgs) } - // List of additional product identifiers that is appended to the User-Agent header - productIdentifiers := make([]string, 0) + selectedTemplate := "" // Set the user agent if a template has been selected - template, ok := ctx.Value(environment.TemplateContextKey).(string) - if ok && strings.TrimSpace(template) != "" { - productIdentifiers = append(productIdentifiers, internal.FormatTemplateAsProductIdentifier(template)) + if template, ok := ctx.Value(environment.TemplateContextKey).(string); ok && strings.TrimSpace(template) != "" { + selectedTemplate = template } - isRunningInGithubActions := os.Getenv("GITHUB_ACTIONS") - - // Must be set to 'true' if running in GitHub Actions, - // see https://docs.github.com/en/actions/learn-github-actions/environment-variables#default-environment-variables - if isRunningInGithubActions == "true" { - productIdentifiers = append(productIdentifiers, "GhActions") - } - - azCli.SetUserAgent(internal.MakeUserAgent(productIdentifiers)) + azCli.SetUserAgent(internal.MakeUserAgentString(selectedTemplate)) return azCli } diff --git a/cli/azd/pkg/tools/azcli.go b/cli/azd/pkg/tools/azcli.go index 14263ff24b3..ea3a0778f89 100644 --- a/cli/azd/pkg/tools/azcli.go +++ b/cli/azd/pkg/tools/azcli.go @@ -297,7 +297,7 @@ func NewAzCli(args NewAzCliArgs) AzCli { } return &azCli{ - userAgent: azdinternal.MakeUserAgent(nil), + userAgent: azdinternal.MakeUserAgentString(""), enableDebug: args.EnableDebug, enableTelemetry: args.EnableTelemetry, runWithResultFn: args.RunWithResultFn, diff --git a/cli/azd/pkg/tools/azcli_test.go b/cli/azd/pkg/tools/azcli_test.go index 34b747efa92..b6e1790a41f 100644 --- a/cli/azd/pkg/tools/azcli_test.go +++ b/cli/azd/pkg/tools/azcli_test.go @@ -11,6 +11,7 @@ import ( "regexp" "testing" + "github.com/azure/azure-dev/cli/azd/internal" azdinternal "github.com/azure/azure-dev/cli/azd/internal" "github.com/azure/azure-dev/cli/azd/pkg/executil" "github.com/stretchr/testify/assert" @@ -85,7 +86,7 @@ func TestAZCLIWithUserAgent(t *testing.T) { EnableDebug: true, }) - tempAZCLI.SetUserAgent("AZTesting=yes") + tempAZCLI.SetUserAgent(internal.MakeUserAgentString("AZTesting=yes")) azcli := tempAZCLI.(*azCli) @@ -93,12 +94,14 @@ func TestAZCLIWithUserAgent(t *testing.T) { userAgent := runAndCaptureUserAgent(t, azcli, account.Id) require.Contains(t, userAgent, "AZTesting=yes") + require.Contains(t, userAgent, "azdev") // now disable telemetry, which doesn't appear to affect our user agent azcli.enableTelemetry = false userAgentWithTelemetryDisabled := runAndCaptureUserAgent(t, azcli, account.Id) require.Contains(t, userAgentWithTelemetryDisabled, "AZTesting=yes") + require.Contains(t, userAgent, "azdev") } func mustGetDefaultAccount(t *testing.T, azcli AzCli) AzCliSubscriptionInfo { From a71c0392e259eab750ce488a3325f0731750e3ff Mon Sep 17 00:00:00 2001 From: Wei Lim Date: Fri, 8 Jul 2022 13:22:54 -0700 Subject: [PATCH 3/6] Add platform info --- cli/azd/internal/useragent.go | 7 ++++++- cli/azd/internal/useragent_test.go | 14 ++++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/cli/azd/internal/useragent.go b/cli/azd/internal/useragent.go index 8d3d771ab3e..557cbaf7306 100644 --- a/cli/azd/internal/useragent.go +++ b/cli/azd/internal/useragent.go @@ -3,6 +3,7 @@ package internal import ( "fmt" "os" + "runtime" "strings" ) @@ -68,7 +69,11 @@ func MakeUserAgentString(template string) string { } func getAzDevCliIdentifier() string { - return fmt.Sprintf("%s/%s", azDevProductIdentifierKey, GetVersionNumber()) + return fmt.Sprintf("%s/%s %s", azDevProductIdentifierKey, GetVersionNumber(), getPlatformInfo()) +} + +func getPlatformInfo() string { + return fmt.Sprintf("(Go %s; %s)", runtime.Version(), runtime.GOOS) } func getUserSpecifiedIdentifier() string { diff --git a/cli/azd/internal/useragent_test.go b/cli/azd/internal/useragent_test.go index be0f1c226a3..e0ac8b6840a 100644 --- a/cli/azd/internal/useragent_test.go +++ b/cli/azd/internal/useragent_test.go @@ -12,7 +12,7 @@ func TestGetAzDevCliIdentifier(t *testing.T) { version := GetVersionNumber() require.NotEmpty(t, version) - require.Equal(t, fmt.Sprintf("%s/%s", azDevProductIdentifierKey, version), getAzDevCliIdentifier()) + require.Equal(t, fmt.Sprintf("%s/%s %s", azDevProductIdentifierKey, version, getPlatformInfo()), getAzDevCliIdentifier()) } func TestUserSpecifiedAgentIdentifier(t *testing.T) { @@ -60,31 +60,33 @@ func TestUserAgentStringScenarios(t *testing.T) { version := GetVersionNumber() require.NotEmpty(t, version) + azDevIdentifier := fmt.Sprintf("azdev/%s %s", version, getPlatformInfo()) + // Scenario: default agent userAgent := MakeUserAgent("") - require.Equal(t, fmt.Sprintf("azdev/%s azdtempl/[none]", version), userAgent.String()) + require.Equal(t, fmt.Sprintf("%s azdtempl/[none]", azDevIdentifier), userAgent.String()) // Scenario: user specifies agent variable os.Setenv(userSpecifiedAgentEnvironmentVariableName, "dev_user_agent") userAgent = MakeUserAgent("") - require.Equal(t, fmt.Sprintf("azdev/%s dev_user_agent azdtempl/[none]", version), userAgent.String()) + require.Equal(t, fmt.Sprintf("%s dev_user_agent azdtempl/[none]", azDevIdentifier), userAgent.String()) os.Setenv(userSpecifiedAgentEnvironmentVariableName, "") // Scenario: running on github actions os.Setenv(githubActionsEnvironmentVariableName, "true") userAgent = MakeUserAgent("") - require.Equal(t, fmt.Sprintf("azdev/%s azdtempl/[none] GhActions", version), userAgent.String()) + require.Equal(t, fmt.Sprintf("%s azdtempl/[none] GhActions", azDevIdentifier), userAgent.String()) os.Setenv(githubActionsEnvironmentVariableName, "") // Scenario: template present userAgent = MakeUserAgent("template@0.0.1") - require.Equal(t, fmt.Sprintf("azdev/%s azdtempl/template@0.0.1", version), userAgent.String()) + require.Equal(t, fmt.Sprintf("%s azdtempl/template@0.0.1", azDevIdentifier), userAgent.String()) // Scenario: full combination os.Setenv(userSpecifiedAgentEnvironmentVariableName, "dev_user_agent") os.Setenv(githubActionsEnvironmentVariableName, "true") userAgent = MakeUserAgent("template@0.0.1") - require.Equal(t, fmt.Sprintf("azdev/%s dev_user_agent azdtempl/template@0.0.1 GhActions", version), userAgent.String()) + require.Equal(t, fmt.Sprintf("%s dev_user_agent azdtempl/template@0.0.1 GhActions", azDevIdentifier), userAgent.String()) t.Cleanup(restorer) } From 5c9360fdd4ee1e83914429b6df9d7c753c7b6c0a Mon Sep 17 00:00:00 2001 From: Wei Lim Date: Fri, 8 Jul 2022 13:35:26 -0700 Subject: [PATCH 4/6] Hide MakeUserAgent --- cli/azd/internal/useragent.go | 22 +++++++++++----------- cli/azd/internal/useragent_test.go | 15 +++++---------- cli/azd/pkg/tools/azcli_test.go | 2 +- 3 files changed, 17 insertions(+), 22 deletions(-) diff --git a/cli/azd/internal/useragent.go b/cli/azd/internal/useragent.go index 557cbaf7306..d4c7d568e59 100644 --- a/cli/azd/internal/useragent.go +++ b/cli/azd/internal/useragent.go @@ -28,14 +28,6 @@ type UserAgent struct { githubActionsIdentifier string } -// String creates a user agent string that contains all necessary product identifiers, in increasing order: -// - The Azure Developer CLI version, formatted as `azdev/` -// - The user specified identifier, set from `AZURE_DEV_USER_AGENT` environment variable -// - The identifier for the template used, if applicable -// - The identifier for GitHub Actions, if applicable -// Examples: -// - `azdev/1.0.0 (Windows NT)` -// - `azdev/1.0.0 (Windows NT) Custom-foo/1.0.0 azdtempl/my-template@1.0.0 GhActions` func (userAgent *UserAgent) String() string { var sb strings.Builder sb.WriteString(userAgent.azDevCliIdentifier) @@ -52,7 +44,7 @@ func appendIdentifier(sb *strings.Builder, identifier string) { } } -func MakeUserAgent(template string) UserAgent { +func makeUserAgent(template string) UserAgent { userAgent := UserAgent{} userAgent.azDevCliIdentifier = getAzDevCliIdentifier() userAgent.userSpecifiedIdentifier = getUserSpecifiedIdentifier() @@ -62,8 +54,16 @@ func MakeUserAgent(template string) UserAgent { return userAgent } +// MakeUserAgentString creates a user agent string that contains all necessary product identifiers, in increasing order: +// - The Azure Developer CLI version, formatted as `azdev/` +// - The user specified identifier, set from `AZURE_DEV_USER_AGENT` environment variable +// - The identifier for the template used, if applicable +// - The identifier for GitHub Actions, if applicable +// Examples (see test `TestUserAgentStringScenarios` for all scenarios ): +// - `azdev/1.0.0 (Go 1.18; windows/amd64) azdtempl/[none]` +// - `azdev/1.0.0 (Go 1.18; windows/amd64) Custom-foo/1.0.0 azdtempl/my-template@1.0.0 GhActions` func MakeUserAgentString(template string) string { - userAgent := MakeUserAgent(template) + userAgent := makeUserAgent(template) return userAgent.String() } @@ -73,7 +73,7 @@ func getAzDevCliIdentifier() string { } func getPlatformInfo() string { - return fmt.Sprintf("(Go %s; %s)", runtime.Version(), runtime.GOOS) + return fmt.Sprintf("(Go %s; %s/%s)", runtime.Version(), runtime.GOOS, runtime.GOARCH) } func getUserSpecifiedIdentifier() string { diff --git a/cli/azd/internal/useragent_test.go b/cli/azd/internal/useragent_test.go index e0ac8b6840a..ed6321b566d 100644 --- a/cli/azd/internal/useragent_test.go +++ b/cli/azd/internal/useragent_test.go @@ -63,30 +63,25 @@ func TestUserAgentStringScenarios(t *testing.T) { azDevIdentifier := fmt.Sprintf("azdev/%s %s", version, getPlatformInfo()) // Scenario: default agent - userAgent := MakeUserAgent("") - require.Equal(t, fmt.Sprintf("%s azdtempl/[none]", azDevIdentifier), userAgent.String()) + require.Equal(t, fmt.Sprintf("%s azdtempl/[none]", azDevIdentifier), MakeUserAgentString("")) // Scenario: user specifies agent variable os.Setenv(userSpecifiedAgentEnvironmentVariableName, "dev_user_agent") - userAgent = MakeUserAgent("") - require.Equal(t, fmt.Sprintf("%s dev_user_agent azdtempl/[none]", azDevIdentifier), userAgent.String()) + require.Equal(t, fmt.Sprintf("%s dev_user_agent azdtempl/[none]", azDevIdentifier), MakeUserAgentString("")) os.Setenv(userSpecifiedAgentEnvironmentVariableName, "") // Scenario: running on github actions os.Setenv(githubActionsEnvironmentVariableName, "true") - userAgent = MakeUserAgent("") - require.Equal(t, fmt.Sprintf("%s azdtempl/[none] GhActions", azDevIdentifier), userAgent.String()) + require.Equal(t, fmt.Sprintf("%s azdtempl/[none] GhActions", azDevIdentifier), MakeUserAgentString("")) os.Setenv(githubActionsEnvironmentVariableName, "") // Scenario: template present - userAgent = MakeUserAgent("template@0.0.1") - require.Equal(t, fmt.Sprintf("%s azdtempl/template@0.0.1", azDevIdentifier), userAgent.String()) + require.Equal(t, fmt.Sprintf("%s azdtempl/template@0.0.1", azDevIdentifier), MakeUserAgentString("template@0.0.1")) // Scenario: full combination os.Setenv(userSpecifiedAgentEnvironmentVariableName, "dev_user_agent") os.Setenv(githubActionsEnvironmentVariableName, "true") - userAgent = MakeUserAgent("template@0.0.1") - require.Equal(t, fmt.Sprintf("%s dev_user_agent azdtempl/template@0.0.1 GhActions", azDevIdentifier), userAgent.String()) + require.Equal(t, fmt.Sprintf("%s dev_user_agent azdtempl/template@0.0.1 GhActions", azDevIdentifier), MakeUserAgentString("template@0.0.1")) t.Cleanup(restorer) } diff --git a/cli/azd/pkg/tools/azcli_test.go b/cli/azd/pkg/tools/azcli_test.go index b6e1790a41f..b565e7ed8e3 100644 --- a/cli/azd/pkg/tools/azcli_test.go +++ b/cli/azd/pkg/tools/azcli_test.go @@ -43,7 +43,7 @@ func TestAzCli(t *testing.T) { require.NoError(t, err) require.Equal(t, []string{ - fmt.Sprintf("AZURE_HTTP_USER_AGENT=%s", azdinternal.MakeUserAgent(nil)), + fmt.Sprintf("AZURE_HTTP_USER_AGENT=%s", azdinternal.MakeUserAgentString("")), }, env) require.Equal(t, []string{"hello", "--debug"}, commandArgs) From 2cc370c7048257b1db5e1fddb63aaac3770a0257 Mon Sep 17 00:00:00 2001 From: Wei Lim Date: Fri, 8 Jul 2022 14:31:34 -0700 Subject: [PATCH 5/6] Fix test assertion --- cli/azd/internal/useragent.go | 4 ++-- cli/azd/internal/useragent_test.go | 8 ++++---- cli/azd/pkg/tools/azcli_test.go | 9 +-------- 3 files changed, 7 insertions(+), 14 deletions(-) diff --git a/cli/azd/internal/useragent.go b/cli/azd/internal/useragent.go index d4c7d568e59..897905e4011 100644 --- a/cli/azd/internal/useragent.go +++ b/cli/azd/internal/useragent.go @@ -60,7 +60,7 @@ func makeUserAgent(template string) UserAgent { // - The identifier for the template used, if applicable // - The identifier for GitHub Actions, if applicable // Examples (see test `TestUserAgentStringScenarios` for all scenarios ): -// - `azdev/1.0.0 (Go 1.18; windows/amd64) azdtempl/[none]` +// - `azdev/1.0.0 (Go 1.18; windows/amd64)` // - `azdev/1.0.0 (Go 1.18; windows/amd64) Custom-foo/1.0.0 azdtempl/my-template@1.0.0 GhActions` func MakeUserAgentString(template string) string { userAgent := makeUserAgent(template) @@ -98,7 +98,7 @@ func getGithubActionsIdentifier() string { func formatTemplateIdentifier(template string) string { if template == "" { - template = "[none]" + return "" } return fmt.Sprintf("%s/%s", templateProductIdentifierKey, template) diff --git a/cli/azd/internal/useragent_test.go b/cli/azd/internal/useragent_test.go index ed6321b566d..de070899fde 100644 --- a/cli/azd/internal/useragent_test.go +++ b/cli/azd/internal/useragent_test.go @@ -45,7 +45,7 @@ func TestGithubActionIdentifier(t *testing.T) { } func TestFormatTemplate(t *testing.T) { - require.Equal(t, fmt.Sprintf("%s/[none]", templateProductIdentifierKey), formatTemplateIdentifier("")) + require.Equal(t, "", formatTemplateIdentifier("")) require.Equal(t, fmt.Sprintf("%s/todo-python-mongo", templateProductIdentifierKey), formatTemplateIdentifier("todo-python-mongo")) require.Equal(t, fmt.Sprintf("%s/todo-csharp-sql@0.0.1-beta", templateProductIdentifierKey), formatTemplateIdentifier("todo-csharp-sql@0.0.1-beta")) } @@ -63,16 +63,16 @@ func TestUserAgentStringScenarios(t *testing.T) { azDevIdentifier := fmt.Sprintf("azdev/%s %s", version, getPlatformInfo()) // Scenario: default agent - require.Equal(t, fmt.Sprintf("%s azdtempl/[none]", azDevIdentifier), MakeUserAgentString("")) + require.Equal(t, fmt.Sprintf("%s", azDevIdentifier), MakeUserAgentString("")) // Scenario: user specifies agent variable os.Setenv(userSpecifiedAgentEnvironmentVariableName, "dev_user_agent") - require.Equal(t, fmt.Sprintf("%s dev_user_agent azdtempl/[none]", azDevIdentifier), MakeUserAgentString("")) + require.Equal(t, fmt.Sprintf("%s dev_user_agent", azDevIdentifier), MakeUserAgentString("")) os.Setenv(userSpecifiedAgentEnvironmentVariableName, "") // Scenario: running on github actions os.Setenv(githubActionsEnvironmentVariableName, "true") - require.Equal(t, fmt.Sprintf("%s azdtempl/[none] GhActions", azDevIdentifier), MakeUserAgentString("")) + require.Equal(t, fmt.Sprintf("%s GhActions", azDevIdentifier), MakeUserAgentString("")) os.Setenv(githubActionsEnvironmentVariableName, "") // Scenario: template present diff --git a/cli/azd/pkg/tools/azcli_test.go b/cli/azd/pkg/tools/azcli_test.go index b565e7ed8e3..08f1d381095 100644 --- a/cli/azd/pkg/tools/azcli_test.go +++ b/cli/azd/pkg/tools/azcli_test.go @@ -72,7 +72,7 @@ func TestAzCli(t *testing.T) { require.NoError(t, err) require.Equal(t, []string{ - "AZURE_HTTP_USER_AGENT=azdev/0.0.0-dev.0", + fmt.Sprintf("AZURE_HTTP_USER_AGENT=%s", azdinternal.MakeUserAgentString("")), "AZURE_CORE_COLLECT_TELEMETRY=no", }, env) @@ -95,13 +95,6 @@ func TestAZCLIWithUserAgent(t *testing.T) { userAgent := runAndCaptureUserAgent(t, azcli, account.Id) require.Contains(t, userAgent, "AZTesting=yes") require.Contains(t, userAgent, "azdev") - - // now disable telemetry, which doesn't appear to affect our user agent - azcli.enableTelemetry = false - - userAgentWithTelemetryDisabled := runAndCaptureUserAgent(t, azcli, account.Id) - require.Contains(t, userAgentWithTelemetryDisabled, "AZTesting=yes") - require.Contains(t, userAgent, "azdev") } func mustGetDefaultAccount(t *testing.T, azcli AzCli) AzCliSubscriptionInfo { From 3884b9fd0824a293ed55b6d6a47f7db4a5075171 Mon Sep 17 00:00:00 2001 From: Wei Lim Date: Fri, 8 Jul 2022 14:49:50 -0700 Subject: [PATCH 6/6] Fix lint issues --- cli/azd/internal/useragent.go | 2 +- cli/azd/internal/useragent_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/azd/internal/useragent.go b/cli/azd/internal/useragent.go index 897905e4011..e359e3b712f 100644 --- a/cli/azd/internal/useragent.go +++ b/cli/azd/internal/useragent.go @@ -21,7 +21,7 @@ type UserAgent struct { // (Optional) User specified identifier, set from `AZURE_DEV_USER_AGENT` environment variable userSpecifiedIdentifier string - // (Optional) Identifier for the template used, if applicable. Formatted as `azdevtempl/` + // (Optional) Identifier for the template used, if applicable. Formatted as `azdtempl/` templateIdentifier string // (Optional) Identifier for GitHub Actions, if applicable diff --git a/cli/azd/internal/useragent_test.go b/cli/azd/internal/useragent_test.go index de070899fde..bd1bef5a467 100644 --- a/cli/azd/internal/useragent_test.go +++ b/cli/azd/internal/useragent_test.go @@ -63,7 +63,7 @@ func TestUserAgentStringScenarios(t *testing.T) { azDevIdentifier := fmt.Sprintf("azdev/%s %s", version, getPlatformInfo()) // Scenario: default agent - require.Equal(t, fmt.Sprintf("%s", azDevIdentifier), MakeUserAgentString("")) + require.Equal(t, azDevIdentifier, MakeUserAgentString("")) // Scenario: user specifies agent variable os.Setenv(userSpecifiedAgentEnvironmentVariableName, "dev_user_agent")