From 7d8358135df028920b53c38e48bacf5db25114f8 Mon Sep 17 00:00:00 2001 From: Silvestre Zabala Date: Mon, 4 May 2026 14:50:42 +0200 Subject: [PATCH] feat: set User-Agent HTTP header on all outgoing requests # Issue It is currently not possible to distinguish requests coming from this CLI plugin from other clients. # Fix Add a `util/useragent` package that builds a User-Agent string from version, VCS URL, and platform info. Thread it explicitly through the command layer into both `APIHelper` (sets the header on every request) and `NewCFAPIClient` (passes it via the go-cfclient config option). # AI This PR was created using Claude Code. --- api/apihelper.go | 17 +++++++------ api/apihelper_test.go | 16 +++++++++++++ api/cf_api_client.go | 3 ++- api/cfclient.go | 6 +++-- api/endpoint.go | 12 +++++----- api/endpoint_test.go | 29 +++++++++++----------- commands/attach_policy.go | 10 ++++---- commands/autoscaler.go | 1 + commands/detach_policy.go | 10 ++++---- commands/endpoint.go | 16 ++++++------- commands/retrieve_history.go | 10 ++++---- commands/retrieve_metrics.go | 10 ++++---- commands/retrieve_policy.go | 10 ++++---- main.go | 5 ++++ util/useragent/useragent.go | 13 ++++++++++ util/useragent/useragent_test.go | 41 ++++++++++++++++++++++++++++++++ 16 files changed, 146 insertions(+), 63 deletions(-) create mode 100644 util/useragent/useragent.go create mode 100644 util/useragent/useragent_test.go diff --git a/api/apihelper.go b/api/apihelper.go index 1a6395f..c3e1c4e 100644 --- a/api/apihelper.go +++ b/api/apihelper.go @@ -34,17 +34,19 @@ const ( ) type APIHelper struct { - Endpoint *APIEndpoint - Client *CFClient - Logger trace.Printer + Endpoint *APIEndpoint + Client *CFClient + Logger trace.Printer + UserAgent string } -func NewAPIHelper(endpoint *APIEndpoint, cfclient *CFClient, traceEnabled string) *APIHelper { +func NewAPIHelper(endpoint *APIEndpoint, cfclient *CFClient, traceEnabled string, userAgent string) *APIHelper { return &APIHelper{ - Endpoint: endpoint, - Client: cfclient, - Logger: trace.NewLogger(os.Stdout, false, traceEnabled, ""), + Endpoint: endpoint, + Client: cfclient, + Logger: trace.NewLogger(os.Stdout, false, traceEnabled, ""), + UserAgent: userAgent, } } @@ -71,6 +73,7 @@ func makeTransport(skipSSLValidation bool, logger trace.Printer) http.RoundTripp } func (helper *APIHelper) DoRequest(req *http.Request) (*http.Response, error) { + req.Header.Set("User-Agent", helper.UserAgent) client := newHTTPClient(helper.Endpoint.SkipSSLValidation || helper.Client.IsSSLDisabled, helper.Logger) resp, err := client.Do(req) diff --git a/api/apihelper_test.go b/api/apihelper_test.go index 3fc6119..c89dbf5 100644 --- a/api/apihelper_test.go +++ b/api/apihelper_test.go @@ -23,6 +23,7 @@ var _ = Describe("API Helper Test", func() { const ( fakeAppId string = "fakeAppId" fakeAccessToken string = "fakeAccessToken" + fakeUserAgent string = "app-autoscaler-cli-plugin/1.2.3 (https://github.com/test) Go/test" ) var ( @@ -112,6 +113,7 @@ var _ = Describe("API Helper Test", func() { AppName: "fakeAppName", }, "false", + fakeUserAgent, ) }) @@ -120,6 +122,19 @@ var _ = Describe("API Helper Test", func() { apiServer.Close() }) + Context("User-Agent header", func() { + It("is set on requests", func() { + apiServer.AppendHandlers( + ghttp.CombineHandlers( + ghttp.RespondWith(http.StatusOK, ""), + ghttp.VerifyHeaderKV("User-Agent", fakeUserAgent), + ), + ) + err = apihelper.CheckHealth() + Expect(err).NotTo(HaveOccurred()) + }) + }) + Context("Common invalid access errors", func() { Context("Server not started", func() { @@ -184,6 +199,7 @@ var _ = Describe("API Helper Test", func() { AppName: "fakeAppName", }, "false", + fakeUserAgent, ) }) diff --git a/api/cf_api_client.go b/api/cf_api_client.go index 02d3d6a..52029e3 100644 --- a/api/cf_api_client.go +++ b/api/cf_api_client.go @@ -15,7 +15,7 @@ type CFAPIClient struct { client *cf_client.Client } -func NewCFAPIClient(ccAPIEndpoint *url.URL, authToken string, skipTLSValidation bool) (*CFAPIClient, error) { +func NewCFAPIClient(ccAPIEndpoint *url.URL, authToken string, skipTLSValidation bool, userAgent string) (*CFAPIClient, error) { // A refresh token is not provided by the CF CLI Plugin API and is not required as // "AccessToken() now provides a refreshed o-auth token.", // see https://github.com/cloudfoundry/cli/blob/main/plugin/plugin_examples/CHANGELOG.md#changes-in-v614 @@ -23,6 +23,7 @@ func NewCFAPIClient(ccAPIEndpoint *url.URL, authToken string, skipTLSValidation cfClientConfigOptions := []cf_client_config.Option{ cf_client_config.Token(authToken, refreshToken), + cf_client_config.UserAgent(userAgent), } if skipTLSValidation { diff --git a/api/cfclient.go b/api/cfclient.go index 3ad2312..b99f152 100644 --- a/api/cfclient.go +++ b/api/cfclient.go @@ -16,6 +16,7 @@ type CFClient struct { AppId string AppName string IsSSLDisabled bool + UserAgent string } type Connection interface { @@ -27,7 +28,7 @@ type Connection interface { IsSSLDisabled() (bool, error) } -func NewCFClient(connection Connection) (*CFClient, error) { +func NewCFClient(connection Connection, userAgent string) (*CFClient, error) { ccAPIEndpoint, err := connection.ApiEndpoint() if err != nil { @@ -46,6 +47,7 @@ func NewCFClient(connection Connection) (*CFClient, error) { connection: connection, CCAPIEndpoint: ccAPIEndpoint, IsSSLDisabled: isSSLDisabled, + UserAgent: userAgent, } return client, nil @@ -83,7 +85,7 @@ func (client *CFClient) Configure(appName string) error { return err } - cfAPIClient, err := NewCFAPIClient(ccAPIURL, authToken, client.IsSSLDisabled) + cfAPIClient, err := NewCFAPIClient(ccAPIURL, authToken, client.IsSSLDisabled, client.UserAgent) if err != nil { return err } diff --git a/api/endpoint.go b/api/endpoint.go index fe12682..f09a61e 100644 --- a/api/endpoint.go +++ b/api/endpoint.go @@ -41,7 +41,7 @@ func UnsetEndpoint() error { return nil } -func SetEndpoint(cfclient *CFClient, url string, skipSSLValidation bool) error { +func SetEndpoint(cfclient *CFClient, url string, skipSSLValidation bool, userAgent string) error { cfDomain := getDomain(cfclient.CCAPIEndpoint) autoscalerDomain := getDomain(url) @@ -55,7 +55,7 @@ func SetEndpoint(cfclient *CFClient, url string, skipSSLValidation bool) error { SkipSSLValidation: skipSSLValidation, } - apihelper := NewAPIHelper(endpoint, cfclient, os.Getenv("CF_TRACE")) + apihelper := NewAPIHelper(endpoint, cfclient, os.Getenv("CF_TRACE"), userAgent) err := apihelper.CheckHealth() if err != nil { return err @@ -72,7 +72,7 @@ func SetEndpoint(cfclient *CFClient, url string, skipSSLValidation bool) error { return nil } -func GetEndpoint(cfclient *CFClient) (*APIEndpoint, error) { +func GetEndpoint(cfclient *CFClient, userAgent string) (*APIEndpoint, error) { endpoint, err := getEndpointFromConfig() if err != nil { @@ -89,7 +89,7 @@ func GetEndpoint(cfclient *CFClient) (*APIEndpoint, error) { } if endpoint.URL == "" { - endpoint, err = getDefaultEndpoint(cfclient) + endpoint, err = getDefaultEndpoint(cfclient, userAgent) if err != nil { return nil, err } @@ -98,13 +98,13 @@ func GetEndpoint(cfclient *CFClient) (*APIEndpoint, error) { } -func getDefaultEndpoint(cfclient *CFClient) (*APIEndpoint, error) { +func getDefaultEndpoint(cfclient *CFClient, userAgent string) (*APIEndpoint, error) { ccAPIURL := cfclient.CCAPIEndpoint asAPIURL := strings.Replace(ccAPIURL, "api.", "autoscaler.", 1) //ignore all erros here if the default value won't work - SetEndpoint(cfclient, asAPIURL, cfclient.IsSSLDisabled) + SetEndpoint(cfclient, asAPIURL, cfclient.IsSSLDisabled, userAgent) return getEndpointFromConfig() } diff --git a/api/endpoint_test.go b/api/endpoint_test.go index 0bdd8a8..9543bc3 100644 --- a/api/endpoint_test.go +++ b/api/endpoint_test.go @@ -18,6 +18,7 @@ var _ = Describe("Endpoint Helper Test", func() { const ( fakeApiEndpoint = "autoscaler.boshlite.com" + fakeUserAgent = "test-agent" ) var ( @@ -50,18 +51,18 @@ var _ = Describe("Endpoint Helper Test", func() { cliConnection.ApiEndpointReturns(apiServer.URL(), nil) cliConnection.IsSSLDisabledReturns(false, nil) - cfclient, err = NewCFClient(cliConnection) + cfclient, err = NewCFClient(cliConnection, fakeUserAgent) Expect(err).NotTo(HaveOccurred()) }) Context("When endpoint is valid", func() { BeforeEach(func() { - err = SetEndpoint(cfclient, apiServer.URL()+"/", false) + err = SetEndpoint(cfclient, apiServer.URL()+"/", false, fakeUserAgent) Expect(err).NotTo(HaveOccurred()) }) It("Set a valid json to config file", func() { - err = SetEndpoint(cfclient, apiServer.URL(), false) + err = SetEndpoint(cfclient, apiServer.URL(), false, fakeUserAgent) Expect(err).NotTo(HaveOccurred()) content, err = ioutil.ReadFile(configFilePath) @@ -80,11 +81,11 @@ var _ = Describe("Endpoint Helper Test", func() { BeforeEach(func() { cliConnection.ApiEndpointReturns("api.bosh-lite.com", nil) cliConnection.IsSSLDisabledReturns(false, nil) - cfclient, err = NewCFClient(cliConnection) + cfclient, err = NewCFClient(cliConnection, fakeUserAgent) Expect(err).NotTo(HaveOccurred()) }) It("it fails", func() { - err = SetEndpoint(cfclient, apiServer.URL(), false) + err = SetEndpoint(cfclient, apiServer.URL(), false, fakeUserAgent) Expect(err).To(HaveOccurred()) }) }) @@ -97,7 +98,7 @@ var _ = Describe("Endpoint Helper Test", func() { }) It("it fails", func() { - err = SetEndpoint(cfclient, apiServer.URL(), false) + err = SetEndpoint(cfclient, apiServer.URL(), false, fakeUserAgent) Expect(err).To(HaveOccurred()) }) }) @@ -131,7 +132,7 @@ var _ = Describe("Endpoint Helper Test", func() { cliConnection.ApiEndpointReturns(apiServer.URL(), nil) cliConnection.IsSSLDisabledReturns(false, nil) - cfclient, err = NewCFClient(cliConnection) + cfclient, err = NewCFClient(cliConnection, fakeUserAgent) Expect(err).NotTo(HaveOccurred()) }) @@ -144,7 +145,7 @@ var _ = Describe("Endpoint Helper Test", func() { }) It("Return the existing URL when it's domain still consistent with the current cf domain", func() { - endpoint, err = GetEndpoint(cfclient) + endpoint, err = GetEndpoint(cfclient, fakeUserAgent) Expect(err).NotTo(HaveOccurred()) Expect(endpoint.URL).Should(Equal(apiServer.URL())) }) @@ -158,7 +159,7 @@ var _ = Describe("Endpoint Helper Test", func() { }) It("Clear staled setting and return the default autoscaler endpoint if it does work ", func() { - endpoint, err = GetEndpoint(cfclient) + endpoint, err = GetEndpoint(cfclient, fakeUserAgent) Expect(err).NotTo(HaveOccurred()) Expect(endpoint.URL).Should(Equal(apiServer.URL())) }) @@ -171,7 +172,7 @@ var _ = Describe("Endpoint Helper Test", func() { }) It("Clear staled setting and set the endpoint to empty", func() { - endpoint, err = GetEndpoint(cfclient) + endpoint, err = GetEndpoint(cfclient, fakeUserAgent) Expect(err).NotTo(HaveOccurred()) Expect(endpoint.URL).Should(Equal("")) }) @@ -187,7 +188,7 @@ var _ = Describe("Endpoint Helper Test", func() { }) It("Return a default URL when it is an valid autoscaler api server", func() { - endpoint, err = GetEndpoint(cfclient) + endpoint, err = GetEndpoint(cfclient, fakeUserAgent) Expect(err).NotTo(HaveOccurred()) Expect(endpoint.URL).Should(Equal(apiServer.URL())) }) @@ -201,7 +202,7 @@ var _ = Describe("Endpoint Helper Test", func() { }) It("Return empty string ", func() { - endpoint, err = GetEndpoint(cfclient) + endpoint, err = GetEndpoint(cfclient, fakeUserAgent) Expect(err).NotTo(HaveOccurred()) Expect(endpoint.URL).Should(Equal("")) }) @@ -219,7 +220,7 @@ var _ = Describe("Endpoint Helper Test", func() { }) It("Clear the wrong setting and return the default autoscaler endpoint if it works", func() { - endpoint, err = GetEndpoint(cfclient) + endpoint, err = GetEndpoint(cfclient, fakeUserAgent) Expect(err).NotTo(HaveOccurred()) Expect(endpoint.URL).Should(Equal(apiServer.URL())) }) @@ -235,7 +236,7 @@ var _ = Describe("Endpoint Helper Test", func() { }) It("Clear the wrong setting and return the default autoscaler endpoint if it works", func() { - endpoint, err = GetEndpoint(cfclient) + endpoint, err = GetEndpoint(cfclient, fakeUserAgent) Expect(err).NotTo(HaveOccurred()) Expect(endpoint.URL).Should(Equal(apiServer.URL())) }) diff --git a/commands/attach_policy.go b/commands/attach_policy.go index bb31551..10bfc41 100644 --- a/commands/attach_policy.go +++ b/commands/attach_policy.go @@ -21,16 +21,16 @@ type AttachPolicyPositionalArgs struct { } func (command AttachPolicyCommand) Execute([]string) error { - return CreatePolicy(AutoScaler.CLIConnection, command.RequiredlArgs.AppName, command.RequiredlArgs.PolicyFile) + return CreatePolicy(AutoScaler.CLIConnection, command.RequiredlArgs.AppName, command.RequiredlArgs.PolicyFile, AutoScaler.UserAgent) } -func CreatePolicy(cliConnection api.Connection, appName string, policyFile string) error { +func CreatePolicy(cliConnection api.Connection, appName string, policyFile string, userAgent string) error { - cfclient, err := api.NewCFClient(cliConnection) + cfclient, err := api.NewCFClient(cliConnection, userAgent) if err != nil { return err } - endpoint, err := api.GetEndpoint(cfclient) + endpoint, err := api.GetEndpoint(cfclient, userAgent) if err != nil { return err } @@ -43,7 +43,7 @@ func CreatePolicy(cliConnection api.Connection, appName string, policyFile strin return err } - apihelper := api.NewAPIHelper(endpoint, cfclient, os.Getenv("CF_TRACE")) + apihelper := api.NewAPIHelper(endpoint, cfclient, os.Getenv("CF_TRACE"), userAgent) ui.SayMessage(ui.AttachPolicyHint, appName) contents, err := ioutil.ReadFile(policyFile) diff --git a/commands/autoscaler.go b/commands/autoscaler.go index 126e140..832ee19 100644 --- a/commands/autoscaler.go +++ b/commands/autoscaler.go @@ -6,6 +6,7 @@ import ( type AutoScalerCmds struct { CLIConnection api.Connection + UserAgent string API ApiCommand `command:"autoscaling-api" description:"Set or view AutoScaler service API endpoint"` Policy PolicyCommand `command:"autoscaling-policy" description:"Retrieve the scaling policy of an application"` diff --git a/commands/detach_policy.go b/commands/detach_policy.go index 69b62e1..e4f9ea7 100644 --- a/commands/detach_policy.go +++ b/commands/detach_policy.go @@ -17,17 +17,17 @@ type DetachPolicyPositionalArgs struct { } func (command DetachPolicyCommand) Execute([]string) error { - return DetachPolicy(AutoScaler.CLIConnection, command.RequiredlArgs.AppName) + return DetachPolicy(AutoScaler.CLIConnection, command.RequiredlArgs.AppName, AutoScaler.UserAgent) } -func DetachPolicy(cliConnection api.Connection, appName string) error { +func DetachPolicy(cliConnection api.Connection, appName string, userAgent string) error { - cfclient, err := api.NewCFClient(cliConnection) + cfclient, err := api.NewCFClient(cliConnection, userAgent) if err != nil { return err } - endpoint, err := api.GetEndpoint(cfclient) + endpoint, err := api.GetEndpoint(cfclient, userAgent) if err != nil { return err } @@ -40,7 +40,7 @@ func DetachPolicy(cliConnection api.Connection, appName string) error { return err } - apihelper := api.NewAPIHelper(endpoint, cfclient, os.Getenv("CF_TRACE")) + apihelper := api.NewAPIHelper(endpoint, cfclient, os.Getenv("CF_TRACE"), userAgent) ui.SayMessage(ui.DetachPolicyHint, appName) err = apihelper.DeletePolicy() diff --git a/commands/endpoint.go b/commands/endpoint.go index 58cc80e..77cead2 100644 --- a/commands/endpoint.go +++ b/commands/endpoint.go @@ -23,19 +23,19 @@ func (cmd ApiCommand) Execute([]string) error { return cmd.UnsetEndpoint() } if cmd.OptionalArgs.URL == "" { - return cmd.GetEndpoint(AutoScaler.CLIConnection) + return cmd.GetEndpoint(AutoScaler.CLIConnection, AutoScaler.UserAgent) } else { - return cmd.SetEndpoint(AutoScaler.CLIConnection, cmd.OptionalArgs.URL, cmd.SkipSSLValidation) + return cmd.SetEndpoint(AutoScaler.CLIConnection, cmd.OptionalArgs.URL, cmd.SkipSSLValidation, AutoScaler.UserAgent) } } -func (cmd *ApiCommand) GetEndpoint(cliConnection api.Connection) error { +func (cmd *ApiCommand) GetEndpoint(cliConnection api.Connection, userAgent string) error { - cfclient, err := api.NewCFClient(cliConnection) + cfclient, err := api.NewCFClient(cliConnection, userAgent) if err != nil { return err } - endpoint, err := api.GetEndpoint(cfclient) + endpoint, err := api.GetEndpoint(cfclient, userAgent) if err != nil { return err } @@ -61,9 +61,9 @@ func (cmd *ApiCommand) UnsetEndpoint() error { } -func (cmd *ApiCommand) SetEndpoint(cliConnection api.Connection, url string, skipSSLValidation bool) error { +func (cmd *ApiCommand) SetEndpoint(cliConnection api.Connection, url string, skipSSLValidation bool, userAgent string) error { - cfclient, err := api.NewCFClient(cliConnection) + cfclient, err := api.NewCFClient(cliConnection, userAgent) if err != nil { return err } @@ -76,7 +76,7 @@ func (cmd *ApiCommand) SetEndpoint(cliConnection api.Connection, url string, ski } ui.SayMessage(ui.SetAPIEndpoint, url) - err = api.SetEndpoint(cfclient, url, skipSSLValidation) + err = api.SetEndpoint(cfclient, url, skipSSLValidation, userAgent) if err != nil { return err } diff --git a/commands/retrieve_history.go b/commands/retrieve_history.go index 81137b2..b329e1d 100644 --- a/commands/retrieve_history.go +++ b/commands/retrieve_history.go @@ -66,17 +66,17 @@ func (command HistoryCommand) Execute([]string) error { return RetrieveHistory(AutoScaler.CLIConnection, command.RequiredlArgs.AppName, - st, et, fpo, command.Desc, command.Asc, writer, command.Output) + st, et, fpo, command.Desc, command.Asc, writer, command.Output, AutoScaler.UserAgent) } -func RetrieveHistory(cliConnection api.Connection, appName string, startTime, endTime int64, firstPageOnly bool, desc bool, asc bool, writer io.Writer, outputfile string) error { +func RetrieveHistory(cliConnection api.Connection, appName string, startTime, endTime int64, firstPageOnly bool, desc bool, asc bool, writer io.Writer, outputfile string, userAgent string) error { - cfclient, err := api.NewCFClient(cliConnection) + cfclient, err := api.NewCFClient(cliConnection, userAgent) if err != nil { return err } - endpoint, err := api.GetEndpoint(cfclient) + endpoint, err := api.GetEndpoint(cfclient, userAgent) if err != nil { return err } @@ -88,7 +88,7 @@ func RetrieveHistory(cliConnection api.Connection, appName string, startTime, en return err } - apihelper := api.NewAPIHelper(endpoint, cfclient, os.Getenv("CF_TRACE")) + apihelper := api.NewAPIHelper(endpoint, cfclient, os.Getenv("CF_TRACE"), userAgent) if outputfile != "" { ui.SayMessage(ui.SaveHistoryHint, appName, outputfile) diff --git a/commands/retrieve_metrics.go b/commands/retrieve_metrics.go index f0fd38d..2b9aa5d 100644 --- a/commands/retrieve_metrics.go +++ b/commands/retrieve_metrics.go @@ -70,17 +70,17 @@ func (command MetricsCommand) Execute([]string) error { } return RetrieveAggregatedMetrics(AutoScaler.CLIConnection, command.RequiredlArgs.AppName, command.RequiredlArgs.MetricName, - st, et, fpo, command.Desc, command.Asc, writer, command.Output) + st, et, fpo, command.Desc, command.Asc, writer, command.Output, AutoScaler.UserAgent) } -func RetrieveAggregatedMetrics(cliConnection api.Connection, appName, metricName string, startTime, endTime int64, firstPageOnly bool, desc bool, asc bool, writer io.Writer, outputfile string) error { +func RetrieveAggregatedMetrics(cliConnection api.Connection, appName, metricName string, startTime, endTime int64, firstPageOnly bool, desc bool, asc bool, writer io.Writer, outputfile string, userAgent string) error { - cfclient, err := api.NewCFClient(cliConnection) + cfclient, err := api.NewCFClient(cliConnection, userAgent) if err != nil { return err } - endpoint, err := api.GetEndpoint(cfclient) + endpoint, err := api.GetEndpoint(cfclient, userAgent) if err != nil { return err } @@ -93,7 +93,7 @@ func RetrieveAggregatedMetrics(cliConnection api.Connection, appName, metricName return err } - apihelper := api.NewAPIHelper(endpoint, cfclient, os.Getenv("CF_TRACE")) + apihelper := api.NewAPIHelper(endpoint, cfclient, os.Getenv("CF_TRACE"), userAgent) if outputfile != "" { ui.SayMessage(ui.SaveAggregatedMetricHint, appName, outputfile) diff --git a/commands/retrieve_policy.go b/commands/retrieve_policy.go index f79edb3..5ad1851 100644 --- a/commands/retrieve_policy.go +++ b/commands/retrieve_policy.go @@ -36,17 +36,17 @@ func (command PolicyCommand) Execute([]string) error { writer = os.Stdout } - return RetrievePolicy(AutoScaler.CLIConnection, command.RequiredlArgs.AppName, writer, command.Output) + return RetrievePolicy(AutoScaler.CLIConnection, command.RequiredlArgs.AppName, writer, command.Output, AutoScaler.UserAgent) } -func RetrievePolicy(cliConnection api.Connection, appName string, writer io.Writer, outputfile string) error { +func RetrievePolicy(cliConnection api.Connection, appName string, writer io.Writer, outputfile string, userAgent string) error { - cfclient, err := api.NewCFClient(cliConnection) + cfclient, err := api.NewCFClient(cliConnection, userAgent) if err != nil { return err } - endpoint, err := api.GetEndpoint(cfclient) + endpoint, err := api.GetEndpoint(cfclient, userAgent) if err != nil { return err } @@ -59,7 +59,7 @@ func RetrievePolicy(cliConnection api.Connection, appName string, writer io.Writ return err } - apihelper := api.NewAPIHelper(endpoint, cfclient, os.Getenv("CF_TRACE")) + apihelper := api.NewAPIHelper(endpoint, cfclient, os.Getenv("CF_TRACE"), userAgent) if outputfile != "" { ui.SayMessage(ui.SavePolicyHint, appName, outputfile) diff --git a/main.go b/main.go index d3133f0..7d28612 100644 --- a/main.go +++ b/main.go @@ -10,6 +10,7 @@ import ( "code.cloudfoundry.org/app-autoscaler-cli-plugin/commands" "code.cloudfoundry.org/app-autoscaler-cli-plugin/ui" + "code.cloudfoundry.org/app-autoscaler-cli-plugin/util/useragent" ) type AutoScaler struct{} @@ -126,6 +127,10 @@ func getVersion() plugin.VersionType { } func main() { + commands.AutoScaler.UserAgent = useragent.UserAgent( + fmt.Sprintf("%s.%s.%s", BuildMajorVersion, BuildMinorVersion, BuildPatchVersion), + BuildVcsUrl, + ) args := os.Args[1:] if len(args) == 0 { diff --git a/util/useragent/useragent.go b/util/useragent/useragent.go new file mode 100644 index 0000000..19d97de --- /dev/null +++ b/util/useragent/useragent.go @@ -0,0 +1,13 @@ +package useragent + +import ( + "fmt" + "runtime" +) + +const ProductName = "app-autoscaler-cli-plugin" + +func UserAgent(version, vcsURL string) string { + platformInfo := fmt.Sprintf("Go/%s %s/%s", runtime.Version(), runtime.GOOS, runtime.GOARCH) + return fmt.Sprintf("%s/%s (%s) %s", ProductName, version, vcsURL, platformInfo) +} diff --git a/util/useragent/useragent_test.go b/util/useragent/useragent_test.go new file mode 100644 index 0000000..fc982f1 --- /dev/null +++ b/util/useragent/useragent_test.go @@ -0,0 +1,41 @@ +package useragent_test + +import ( + "fmt" + "runtime" + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "code.cloudfoundry.org/app-autoscaler-cli-plugin/util/useragent" +) + +func TestUseragent(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Useragent Suite") +} + +var _ = Describe("UserAgent", func() { + It("returns the formatted user agent string", func() { + ua := useragent.UserAgent("1.2.3", "test-url") + + expected := fmt.Sprintf( + "app-autoscaler-cli-plugin/1.2.3 (test-url) Go/%s %s/%s", + runtime.Version(), runtime.GOOS, runtime.GOARCH, + ) + + Expect(ua).To(Equal(expected)) + }) + + It("handles empty values", func() { + ua := useragent.UserAgent("", "") + + expected := fmt.Sprintf( + "app-autoscaler-cli-plugin/ () Go/%s %s/%s", + runtime.Version(), runtime.GOOS, runtime.GOARCH, + ) + + Expect(ua).To(Equal(expected)) + }) +})