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 CI/CD platform to useragent #665

Merged
merged 12 commits into from
Oct 31, 2023
14 changes: 14 additions & 0 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,19 @@ func (c *DatabricksClient) addAuthHeaderToUserAgent(r *http.Request) error {
return nil
}

func (c *DatabricksClient) addCiCdProviderToUserAgent(r *http.Request) error {
shreyas-goenka marked this conversation as resolved.
Show resolved Hide resolved
// Detect if we are running in a CI/CD environment
provider := useragent.CiCdProvider()
if provider == "" {
return nil
}

// Add the detected CI/CD provider to the user agent
ctx := useragent.InContext(r.Context(), "cicd", provider)
*r = *r.WithContext(ctx) // replace request
return nil
}

func (c *DatabricksClient) perform(
ctx context.Context,
method,
Expand All @@ -407,6 +420,7 @@ func (c *DatabricksClient) perform(
c.Config.Authenticate,
c.addHostToRequestUrl,
c.addAuthHeaderToUserAgent,
c.addCiCdProviderToUserAgent,
}, visitors...)
resp, err := retries.Poll(ctx, c.retryTimeout,
c.attempt(ctx, method, requestURL, headers, requestBody, visitors...))
Expand Down
77 changes: 77 additions & 0 deletions useragent/cicd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package useragent

import (
"os"
"sync"
)

type envVar struct {
// Name of the environment variable.
name string

// Expected value of the environment variable. If empty, only the presence
// of the environment variable is checked. If non-empty, the value must
// match exactly.
expectedValue string
shreyas-goenka marked this conversation as resolved.
Show resolved Hide resolved
}

type cicdProvider struct {
// The name of the CI/CD provider. This is the name included in the user
// agent string.
name string

// The env vars that are expected to be set in the CI/CD provider's runner.
envVars []envVar
}

// List of CI/CD providers and their env vars we can rely on to detect them.
func listCiCdProviders() []cicdProvider {
return []cicdProvider{
{"github", []envVar{{"GITHUB_ACTIONS", "true"}}},
{"gitlab", []envVar{{"GITLAB_CI", "true"}}},
{"jenkins", []envVar{{"JENKINS_URL", ""}}},
{"azure-devops", []envVar{{"TF_BUILD", "True"}}},
{"circle", []envVar{{"CIRCLECI", "true"}}},
{"travis", []envVar{{"TRAVIS", "true"}}},
{"bitbucket", []envVar{{"BITBUCKET_BUILD_NUMBER", ""}}},
{"google-cloud-build", []envVar{{"PROJECT_ID", ""}, {"BUILD_ID", ""}, {"PROJECT_NUMBER", ""}, {"LOCATION", ""}}},
{"aws-code-build", []envVar{{"CODEBUILD_BUILD_ARN", ""}}},
{"tf-cloud", []envVar{{"TFC_RUN_ID", ""}}},
}
}

// detect returns true if all env vars are set and have expected values.
func (p cicdProvider) detect() bool {
for _, envVar := range p.envVars {
v, ok := os.LookupEnv(envVar.name)
shreyas-goenka marked this conversation as resolved.
Show resolved Hide resolved
if !ok {
return false
}
if envVar.expectedValue != "" && v != envVar.expectedValue {
return false
}
}
return true
}

// lookupCiCdProvider returns the name of the CI/CD provider if detected. Returns the
// first one, if multiple are detected.
func lookupCiCdProvider() string {
for _, p := range listCiCdProviders() {
if p.detect() {
return p.name
}
}
return ""
}

var provider string

var providerOnce sync.Once

func CiCdProvider() string {
providerOnce.Do(func() {
provider = lookupCiCdProvider()
})
return provider
}
123 changes: 123 additions & 0 deletions useragent/cicd_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package useragent

import (
"testing"

"github.com/databricks/databricks-sdk-go/internal/env"
"github.com/stretchr/testify/assert"
)

func TestCiCdProviderDetect(t *testing.T) {
shreyas-goenka marked this conversation as resolved.
Show resolved Hide resolved
cicdProvider := cicdProvider{
name: "foo",
envVars: []envVar{
{"APPLE", "123"},
{"BANANA", "456"},
{"CHERRY", ""},
},
}

// Set some of the env vars.
t.Setenv("APPLE", "123")
t.Setenv("CHERRY", "000")
assert.False(t, cicdProvider.detect(), "should not detect when not all env vars are set")

// Set the rest of the env vars.
t.Setenv("BANANA", "456")
assert.True(t, cicdProvider.detect(), "should detect when all env vars are set")
}

func TestCiCdProviderGithubActions(t *testing.T) {
env.CleanupEnvironment(t)

// No provider detected.
assert.Equal(t, "", lookupCiCdProvider())

// Github Actions detected.
t.Setenv("GITHUB_ACTIONS", "true")
assert.Equal(t, "github", lookupCiCdProvider())
}

func TestCiCdProviderGitlab(t *testing.T) {
env.CleanupEnvironment(t)

// Gitlab detected.
t.Setenv("GITLAB_CI", "true")
assert.Equal(t, "gitlab", lookupCiCdProvider())
}

func TestCiCdProviderJenkins(t *testing.T) {
env.CleanupEnvironment(t)

// Jenkins detected.
t.Setenv("JENKINS_URL", "https://jenkins.example.com")
assert.Equal(t, "jenkins", lookupCiCdProvider())
}

func TestCiCdProviderAzureDevops(t *testing.T) {
env.CleanupEnvironment(t)

// Azure Devops detected.
t.Setenv("TF_BUILD", "True")
assert.Equal(t, "azure-devops", lookupCiCdProvider())
}

func TestCiCdProviderCircle(t *testing.T) {
env.CleanupEnvironment(t)

// Circle detected.
t.Setenv("CIRCLECI", "true")
assert.Equal(t, "circle", lookupCiCdProvider())
}

func TestCiCdProviderTravis(t *testing.T) {
env.CleanupEnvironment(t)

// Travis detected.
t.Setenv("TRAVIS", "true")
assert.Equal(t, "travis", lookupCiCdProvider())
}

func TestCiCdProviderBitbucket(t *testing.T) {
env.CleanupEnvironment(t)

// Bitbucket detected.
t.Setenv("BITBUCKET_BUILD_NUMBER", "123")
assert.Equal(t, "bitbucket", lookupCiCdProvider())
}

func TestCiCdProviderGoogleCloudBuild(t *testing.T) {
env.CleanupEnvironment(t)

// Google Cloud Build detected.
t.Setenv("PROJECT_ID", "foo")
t.Setenv("BUILD_ID", "bar")
t.Setenv("PROJECT_NUMBER", "baz")
t.Setenv("LOCATION", "")
assert.Equal(t, "google-cloud-build", lookupCiCdProvider())
}

func TestCiCdProviderAwsCodeBuild(t *testing.T) {
env.CleanupEnvironment(t)

// AWS Code Build detected.
t.Setenv("CODEBUILD_BUILD_ARN", "arn:aws:codebuild:us-east-1:123456789012:build/my-demo-project:b1e6deae-e4f2-4151-be79-3cc4e82a0bf0")
assert.Equal(t, "aws-code-build", lookupCiCdProvider())
}

func TestCiCdProviderTfCloud(t *testing.T) {
env.CleanupEnvironment(t)

// Terraform Cloud detected.
t.Setenv("TFC_RUN_ID", "run-123")
assert.Equal(t, "tf-cloud", lookupCiCdProvider())
}

func TestCiCdProviderMultiple(t *testing.T) {
env.CleanupEnvironment(t)

// Multiple providers detected. The first one detected is set.
t.Setenv("GITHUB_ACTIONS", "true")
t.Setenv("GITLAB_CI", "true")
assert.Equal(t, "github", lookupCiCdProvider())
}
Loading