Skip to content
This repository has been archived by the owner on Feb 16, 2023. It is now read-only.

Commit

Permalink
Merge pull request #184 from secrethub/feature/more-extensible-app-info
Browse files Browse the repository at this point in the history
Make setting app info more extensible
  • Loading branch information
florisvdg authored Apr 30, 2020
2 parents 9ba6b3c + a13fa07 commit f2c311f
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 8 deletions.
39 changes: 35 additions & 4 deletions pkg/secrethub/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package secrethub

import (
"os"
"regexp"
"runtime"
"strings"

Expand Down Expand Up @@ -52,6 +53,13 @@ type ClientInterface interface {

var (
errClient = errio.Namespace("client")

whitelistAppInfoName = regexp.MustCompile("^[a-zA-Z0-9_-]{2,50}$")
)

// Errors
var (
ErrInvalidAppInfoName = errClient.Code("invalid_app_info_name").Error("name must be 2-50 characters long, only alphanumeric, underscore (_), and dash (-)")
)

// Client is a client for the SecretHub HTTP API.
Expand All @@ -72,7 +80,7 @@ type Client struct {
// These are cached
repoIndexKeys map[api.RepoPath]*crypto.SymmetricKey

appInfo *AppInfo
appInfo []*AppInfo
ConfigDir *configdir.Dir
}

Expand All @@ -83,14 +91,23 @@ type AppInfo struct {
Version string
}

func (i AppInfo) userAgentSuffix() string {
func (i AppInfo) userAgentComponent() string {
res := i.Name
if i.Version != "" {
res += "/" + strings.TrimPrefix(i.Version, "v")
}
return res
}

// ValidateName returns an error if the provided app name is not set or doesn't match alphanumeric, underscore (_), and dash (-) characters, or length of 2-50 characters.
func (i AppInfo) ValidateName() error {
if i.Name == "" || !whitelistAppInfoName.MatchString(i.Name) {
return ErrInvalidAppInfoName
}

return nil
}

// NewClient creates a new SecretHub client. Provided options are applied to the client.
//
// If no WithCredentials() option is provided, the client tries to find a key credential at the following locations (in order):
Expand All @@ -102,6 +119,7 @@ func NewClient(with ...ClientOption) (*Client, error) {
client := &Client{
httpClient: http.NewClient(),
repoIndexKeys: make(map[api.RepoPath]*crypto.SymmetricKey),
appInfo: []*AppInfo{},
}
err := client.with(with...)
if err != nil {
Expand Down Expand Up @@ -139,6 +157,19 @@ func NewClient(with ...ClientOption) (*Client, error) {
}
}

appName := os.Getenv("SECRETHUB_APP_INFO_NAME")
if appName != "" {
appVersion := os.Getenv("SECRETHUB_APP_INFO_VERSION")
topLevelAppInfo := &AppInfo{
Name: appName,
Version: appVersion,
}
// Ignore app info from environment variable if name is invalid
if err = topLevelAppInfo.ValidateName(); err == nil {
client.appInfo = append(client.appInfo, topLevelAppInfo)
}
}

userAgent := client.userAgent()

client.httpClient.Options(http.WithUserAgent(userAgent))
Expand Down Expand Up @@ -235,8 +266,8 @@ func (c *Client) DefaultCredential() credentials.Reader {

func (c *Client) userAgent() string {
userAgent := userAgentPrefix
if c.appInfo != nil {
userAgent += " " + c.appInfo.userAgentSuffix()
for _, info := range c.appInfo {
userAgent += " " + info.userAgentComponent()
}
osName, err := operatingsystem.GetOperatingSystem()
if err != nil {
Expand Down
7 changes: 3 additions & 4 deletions pkg/secrethub/client_options.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package secrethub

import (
"errors"
"net/http"
"net/url"
"time"
Expand Down Expand Up @@ -51,10 +50,10 @@ func WithTransport(transport http.RoundTripper) ClientOption {
// WithAppInfo sets the AppInfo to be used for identifying the application that is using the SecretHub Client.
func WithAppInfo(appInfo *AppInfo) ClientOption {
return func(c *Client) error {
if appInfo.Name == "" {
return errors.New("name must be set for AppInfo")
if err := appInfo.ValidateName(); err != nil {
return err
}
c.appInfo = appInfo
c.appInfo = append(c.appInfo, appInfo)
return nil
}
}
Expand Down
80 changes: 80 additions & 0 deletions pkg/secrethub/client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package secrethub

import (
"os"
"regexp"
"testing"

"github.com/secrethub/secrethub-go/internals/assert"
)

func TestClient_userAgent(t *testing.T) {
cases := map[string]struct {
appInfo []*AppInfo
envAppName string
envAppVersion string
expected string
err error
}{
"default": {},
"multiple app info layers": {
appInfo: []*AppInfo{
{Name: "secrethub-xgo", Version: "0.1.0"},
{Name: "secrethub-java", Version: "0.2.0"},
},
expected: "secrethub-xgo/0.1.0 secrethub-java/0.2.0",
},
"no version number": {
appInfo: []*AppInfo{
{Name: "terraform-provider-secrethub"},
},
expected: "terraform-provider-secrethub",
},
"top level app info from environment": {
appInfo: []*AppInfo{
{Name: "secrethub-cli", Version: "0.37.0"},
},
envAppName: "secrethub-circleci-orb",
envAppVersion: "1.0.0",
expected: "secrethub-cli/0.37.0 secrethub-circleci-orb/1.0.0",
},
"invalid app name": {
appInfo: []*AppInfo{
{Name: "illegal-name*%!@", Version: "0.1.0"},
},
err: ErrInvalidAppInfoName,
},
"ignore faulty environment variable": {
appInfo: []*AppInfo{
{Name: "secrethub-cli", Version: "0.37.0"},
},
envAppName: "illegal-name*%!@",
expected: "secrethub-cli/0.37.0",
},
}

for name, tc := range cases {
t.Run(name, func(t *testing.T) {
os.Setenv("SECRETHUB_APP_INFO_NAME", tc.envAppName)
os.Setenv("SECRETHUB_APP_INFO_VERSION", tc.envAppVersion)

var opts []ClientOption
for _, info := range tc.appInfo {
opts = append(opts, WithAppInfo(info))
}
client, err := NewClient(opts...)
assert.Equal(t, err, tc.err)
if err != nil {
return
}

userAgent := client.userAgent()
pattern := tc.expected + " \\(.*\\)"
matched, err := regexp.MatchString(pattern, userAgent)
assert.OK(t, err)
if !matched {
t.Errorf("user agent '%s' doesn't match pattern '%s'", userAgent, pattern)
}
})
}
}

0 comments on commit f2c311f

Please sign in to comment.