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

Add telemetry for SDK usage from DBR #851

Merged
merged 25 commits into from
Mar 15, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
58 changes: 58 additions & 0 deletions client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,64 @@ GET /a
}
}

func TestUserAgentForDBR(t *testing.T) {
goVersion := strings.TrimPrefix(runtime.Version(), "go")
cicdHeader := ""
if useragent.CiCdProvider() != "" {
cicdHeader = fmt.Sprintf(" cicd/%s", useragent.CiCdProvider())
}
t.Setenv("DATABRICKS_RUNTIME_VERSION", "15.1")
expectedUserAgent := `unknown/0.0.0 databricks-sdk-go/` + version.Version + ` go/` + goVersion + ` os/` + runtime.GOOS + ` auth/pat` + cicdHeader + ` runtime/15.1`
shreyas-goenka marked this conversation as resolved.
Show resolved Hide resolved

var userAgent string
c, err := New(&config.Config{
Host: "some",
Token: "token",
ConfigFile: "/dev/null",
HTTPTransport: hc(func(r *http.Request) (*http.Response, error) {
userAgent = r.UserAgent()
return &http.Response{
StatusCode: 200,
Body: io.NopCloser(strings.NewReader(`{}`)),
Request: r,
}, nil
}),
})
require.NoError(t, err)
err = c.Do(context.Background(), "GET", "/a", nil, nil, nil)
assert.Equal(t, userAgent, expectedUserAgent)
require.NoError(t, err)
}

func TestUserAgentForServerless(t *testing.T) {
goVersion := strings.TrimPrefix(runtime.Version(), "go")
cicdHeader := ""
if useragent.CiCdProvider() != "" {
cicdHeader = fmt.Sprintf(" cicd/%s", useragent.CiCdProvider())
}
t.Setenv("DATABRICKS_RUNTIME_VERSION", "client.1")
expectedUserAgent := `unknown/0.0.0 databricks-sdk-go/` + version.Version + ` go/` + goVersion + ` os/` + runtime.GOOS + ` auth/pat` + cicdHeader + ` runtime/client.1`

var userAgent string
c, err := New(&config.Config{
Host: "some",
Token: "token",
ConfigFile: "/dev/null",
HTTPTransport: hc(func(r *http.Request) (*http.Response, error) {
userAgent = r.UserAgent()
return &http.Response{
StatusCode: 200,
Body: io.NopCloser(strings.NewReader(`{}`)),
Request: r,
}, nil
}),
})
require.NoError(t, err)
err = c.Do(context.Background(), "GET", "/a", nil, nil, nil)
assert.Equal(t, userAgent, expectedUserAgent)
require.NoError(t, err)
}
shreyas-goenka marked this conversation as resolved.
Show resolved Hide resolved

func testNonJSONResponseIncludedInError(t *testing.T, statusCode int, status, errorMessage string) {
c, err := New(&config.Config{
Host: "some",
Expand Down
12 changes: 12 additions & 0 deletions config/api_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"net/http"
"net/url"
"os"
"time"

"github.com/databricks/databricks-sdk-go/apierr"
Expand Down Expand Up @@ -64,6 +65,17 @@ func (c *Config) NewApiClient() (*httpclient.ApiClient, error) {
*r = *r.WithContext(ctx) // replace request
return nil
},
func(r *http.Request) error {
// Detect if the SDK is being run in a Databricks Runtime.
v, ok := os.LookupEnv("DATABRICKS_RUNTIME_VERSION")
if !ok {
return nil
}
// Add the detected Databricks Runtime version to the user agent
ctx := useragent.InContext(r.Context(), "runtime", v)
shreyas-goenka marked this conversation as resolved.
Show resolved Hide resolved
*r = *r.WithContext(ctx) // replace request
return nil
},
},
TransientErrors: []string{
"REQUEST_LIMIT_EXCEEDED", // This is temporary workaround for SCIM API returning 500. Remove when it's fixed
Expand Down
8 changes: 8 additions & 0 deletions useragent/patterns.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,11 @@ func matchAlphanumOrSemVer(s string) error {
}
return fmt.Errorf("invalid alphanumeric or semver string: %s", s)
}

func isAlphanum(s string) bool {
return regexpAlphanum.MatchString(s)
shreyas-goenka marked this conversation as resolved.
Show resolved Hide resolved
}

func isSemVer(s string) bool {
return regexpSemVer.MatchString(s)
}
29 changes: 29 additions & 0 deletions useragent/patterns_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,54 @@ import (

func TestMatchSemVer(t *testing.T) {
assert.NoError(t, matchSemVer("1.2.3"))
assert.True(t, isSemVer("1.2.3"))

assert.NoError(t, matchSemVer("0.0.0-dev+2e014739024a"))
assert.True(t, isSemVer("0.0.0-dev+2e014739024a"))

assert.Error(t, matchSemVer("1.2.3.4"))
assert.False(t, isSemVer("1.2.3.4"))

assert.Error(t, matchSemVer("1.2"))
assert.False(t, isSemVer("1.2"))
}

func TestMatchAlphanum(t *testing.T) {
assert.NoError(t, matchAlphanum("foo"))
assert.True(t, isAlphanum("foo"))

assert.NoError(t, matchAlphanum("FOO"))
assert.True(t, isAlphanum("FOO"))

assert.NoError(t, matchAlphanum("FOO123"))
assert.True(t, isAlphanum("FOO123"))

assert.NoError(t, matchAlphanum("foo_bar"))
assert.True(t, isAlphanum("foo_bar"))

assert.NoError(t, matchAlphanum("foo-bar"))
assert.True(t, isAlphanum("foo-bar"))

assert.Error(t, matchAlphanum("foo bar"))
assert.False(t, isAlphanum("foo bar"))

assert.Error(t, matchAlphanum("foo/bar"))
assert.False(t, isAlphanum("foo/bar"))
}

func TestMatchAlphanumOrSemVer(t *testing.T) {
shreyas-goenka marked this conversation as resolved.
Show resolved Hide resolved
assert.NoError(t, matchAlphanumOrSemVer("foo"))
assert.True(t, isAlphanum("foo") || isSemVer("foo"))

assert.NoError(t, matchAlphanumOrSemVer("1.2.3"))
assert.True(t, isAlphanum("1.2.3") || isSemVer("1.2.3"))

assert.NoError(t, matchAlphanumOrSemVer("0.0.0-dev+2e014739024a"))
assert.True(t, isAlphanum("0.0.0-dev+2e014739024a") || isSemVer("0.0.0-dev+2e014739024a"))

assert.Error(t, matchAlphanumOrSemVer("foo/bar"))
assert.False(t, isAlphanum("foo/bar") || isSemVer("foo/bar"))

assert.Error(t, matchAlphanumOrSemVer("1/2/3"))
assert.False(t, isAlphanum("1/2/3") || isSemVer("1/2/3"))
}
22 changes: 18 additions & 4 deletions useragent/user_agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,27 @@ func (u info) String() string {

type data []info

// Validate the key value pair being set in the user agent. Error if invalid.
func validate(key, value string) error {
// DBR versions as set in the `DATABRICKS_RUNTIME_VERSION` environment variable
// are not valid semver strings. Eg: 15.1 or client.0. Thus we allow arbitrary
// values for the runtime key.
shreyas-goenka marked this conversation as resolved.
Show resolved Hide resolved
if key == "runtime" {
return nil
}
if !isAlphanum(key) {
return fmt.Errorf("expected user agent key to be alphanumeric: %s", key)
shreyas-goenka marked this conversation as resolved.
Show resolved Hide resolved
}
if !isAlphanum(value) && !isSemVer(value) {
shreyas-goenka marked this conversation as resolved.
Show resolved Hide resolved
return fmt.Errorf("expected user agent value for key %q to be alphanumeric or semver: %s", key, value)
}
return nil
}

shreyas-goenka marked this conversation as resolved.
Show resolved Hide resolved
// With always uses the latest value for a given alphanumeric key.
// Panics if key or value don't satisfy alphanumeric or semver format.
func (d data) With(key, value string) data {
shreyas-goenka marked this conversation as resolved.
Show resolved Hide resolved
if err := matchAlphanum(key); err != nil {
panic(err)
}
if err := matchAlphanumOrSemVer(value); err != nil {
if err := validate(key, value); err != nil {
panic(err)
}
var c data
Expand Down
12 changes: 12 additions & 0 deletions useragent/user_agent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,15 @@ func TestFromContext_Custom(t *testing.T) {
func TestDefaultsAreValid(t *testing.T) {
WithProduct(productName, productVersion)
}

func TestUserAgentValidate(t *testing.T) {
assert.EqualError(t, validate("non-alphanumic!", "abc"), "expected user agent key to be alphanumeric: non-alphanumic!")
assert.EqualError(t, validate("abc", "non-alphanumeric!"), "expected user agent value for key \"abc\" to be alphanumeric or semver: non-alphanumeric!")
assert.EqualError(t, validate("abc", "1.1.invalid"), "expected user agent value for key \"abc\" to be alphanumeric or semver: 1.1.invalid")

assert.NoError(t, validate("runtime", "7.3"))
assert.NoError(t, validate("runtime", "client.7"))
assert.NoError(t, validate("runtime", "whatever#!@"))
assert.NoError(t, validate("abc", "123"))
assert.NoError(t, validate("abc", "1.1.1"))
}