mirror of
https://github.com/coder/coder.git
synced 2025-07-03 16:13:58 +00:00
This PR is the coder/coder part of [the open_in parameter issue](https://github.com/coder/terraform-provider-coder/issues/297) aiming to add a new optional parameter to choose how to open modules. This PR is heavily linked [to this PR](https://github.com/coder/terraform-provider-coder/pull/321). ℹ️ For now, some integrations tests can not be pushed as it requires a release on the terraform-provider repo.
296 lines
12 KiB
Go
296 lines
12 KiB
Go
package telemetry_test
|
|
|
|
import (
|
|
"database/sql"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"sort"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/google/uuid"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"go.uber.org/goleak"
|
|
|
|
"github.com/coder/coder/v2/buildinfo"
|
|
"github.com/coder/coder/v2/coderd/database"
|
|
"github.com/coder/coder/v2/coderd/database/dbgen"
|
|
"github.com/coder/coder/v2/coderd/database/dbmem"
|
|
"github.com/coder/coder/v2/coderd/database/dbtestutil"
|
|
"github.com/coder/coder/v2/coderd/database/dbtime"
|
|
"github.com/coder/coder/v2/coderd/telemetry"
|
|
"github.com/coder/coder/v2/testutil"
|
|
)
|
|
|
|
func TestMain(m *testing.M) {
|
|
goleak.VerifyTestMain(m)
|
|
}
|
|
|
|
func TestTelemetry(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("Snapshot", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var err error
|
|
|
|
db := dbmem.New()
|
|
|
|
ctx := testutil.Context(t, testutil.WaitMedium)
|
|
_, _ = dbgen.APIKey(t, db, database.APIKey{})
|
|
_ = dbgen.ProvisionerJob(t, db, nil, database.ProvisionerJob{
|
|
Provisioner: database.ProvisionerTypeTerraform,
|
|
StorageMethod: database.ProvisionerStorageMethodFile,
|
|
Type: database.ProvisionerJobTypeTemplateVersionDryRun,
|
|
})
|
|
_ = dbgen.Template(t, db, database.Template{
|
|
Provisioner: database.ProvisionerTypeTerraform,
|
|
})
|
|
sourceExampleID := uuid.NewString()
|
|
_ = dbgen.TemplateVersion(t, db, database.TemplateVersion{
|
|
SourceExampleID: sql.NullString{String: sourceExampleID, Valid: true},
|
|
})
|
|
_ = dbgen.TemplateVersion(t, db, database.TemplateVersion{})
|
|
user := dbgen.User(t, db, database.User{})
|
|
_ = dbgen.Workspace(t, db, database.WorkspaceTable{})
|
|
_ = dbgen.WorkspaceApp(t, db, database.WorkspaceApp{
|
|
SharingLevel: database.AppSharingLevelOwner,
|
|
Health: database.WorkspaceAppHealthDisabled,
|
|
OpenIn: database.WorkspaceAppOpenInSlimWindow,
|
|
})
|
|
group := dbgen.Group(t, db, database.Group{})
|
|
_ = dbgen.GroupMember(t, db, database.GroupMemberTable{UserID: user.ID, GroupID: group.ID})
|
|
wsagent := dbgen.WorkspaceAgent(t, db, database.WorkspaceAgent{})
|
|
// Update the workspace agent to have a valid subsystem.
|
|
err = db.UpdateWorkspaceAgentStartupByID(ctx, database.UpdateWorkspaceAgentStartupByIDParams{
|
|
ID: wsagent.ID,
|
|
Version: wsagent.Version,
|
|
ExpandedDirectory: wsagent.ExpandedDirectory,
|
|
Subsystems: []database.WorkspaceAgentSubsystem{
|
|
database.WorkspaceAgentSubsystemEnvbox,
|
|
database.WorkspaceAgentSubsystemExectrace,
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
_ = dbgen.WorkspaceBuild(t, db, database.WorkspaceBuild{
|
|
Transition: database.WorkspaceTransitionStart,
|
|
Reason: database.BuildReasonAutostart,
|
|
})
|
|
_ = dbgen.WorkspaceResource(t, db, database.WorkspaceResource{
|
|
Transition: database.WorkspaceTransitionStart,
|
|
})
|
|
_ = dbgen.WorkspaceAgentStat(t, db, database.WorkspaceAgentStat{})
|
|
_, err = db.InsertLicense(ctx, database.InsertLicenseParams{
|
|
UploadedAt: dbtime.Now(),
|
|
JWT: "",
|
|
Exp: dbtime.Now().Add(time.Hour),
|
|
UUID: uuid.New(),
|
|
})
|
|
assert.NoError(t, err)
|
|
_, _ = dbgen.WorkspaceProxy(t, db, database.WorkspaceProxy{})
|
|
|
|
_ = dbgen.WorkspaceModule(t, db, database.WorkspaceModule{})
|
|
|
|
_, snapshot := collectSnapshot(t, db, nil)
|
|
require.Len(t, snapshot.ProvisionerJobs, 1)
|
|
require.Len(t, snapshot.Licenses, 1)
|
|
require.Len(t, snapshot.Templates, 1)
|
|
require.Len(t, snapshot.TemplateVersions, 2)
|
|
require.Len(t, snapshot.Users, 1)
|
|
require.Len(t, snapshot.Groups, 2)
|
|
// 1 member in the everyone group + 1 member in the custom group
|
|
require.Len(t, snapshot.GroupMembers, 2)
|
|
require.Len(t, snapshot.Workspaces, 1)
|
|
require.Len(t, snapshot.WorkspaceApps, 1)
|
|
require.Len(t, snapshot.WorkspaceAgents, 1)
|
|
require.Len(t, snapshot.WorkspaceBuilds, 1)
|
|
require.Len(t, snapshot.WorkspaceResources, 1)
|
|
require.Len(t, snapshot.WorkspaceAgentStats, 1)
|
|
require.Len(t, snapshot.WorkspaceProxies, 1)
|
|
require.Len(t, snapshot.WorkspaceModules, 1)
|
|
|
|
wsa := snapshot.WorkspaceAgents[0]
|
|
require.Len(t, wsa.Subsystems, 2)
|
|
require.Equal(t, string(database.WorkspaceAgentSubsystemEnvbox), wsa.Subsystems[0])
|
|
require.Equal(t, string(database.WorkspaceAgentSubsystemExectrace), wsa.Subsystems[1])
|
|
|
|
tvs := snapshot.TemplateVersions
|
|
sort.Slice(tvs, func(i, j int) bool {
|
|
// Sort by SourceExampleID presence (non-nil comes before nil)
|
|
if (tvs[i].SourceExampleID != nil) != (tvs[j].SourceExampleID != nil) {
|
|
return tvs[i].SourceExampleID != nil
|
|
}
|
|
return false
|
|
})
|
|
require.Equal(t, tvs[0].SourceExampleID, &sourceExampleID)
|
|
require.Nil(t, tvs[1].SourceExampleID)
|
|
})
|
|
t.Run("HashedEmail", func(t *testing.T) {
|
|
t.Parallel()
|
|
db := dbmem.New()
|
|
_ = dbgen.User(t, db, database.User{
|
|
Email: "kyle@coder.com",
|
|
})
|
|
_, snapshot := collectSnapshot(t, db, nil)
|
|
require.Len(t, snapshot.Users, 1)
|
|
require.Equal(t, snapshot.Users[0].EmailHashed, "bb44bf07cf9a2db0554bba63a03d822c927deae77df101874496df5a6a3e896d@coder.com")
|
|
})
|
|
t.Run("HashedModule", func(t *testing.T) {
|
|
t.Parallel()
|
|
db, _ := dbtestutil.NewDB(t)
|
|
pj := dbgen.ProvisionerJob(t, db, nil, database.ProvisionerJob{})
|
|
_ = dbgen.WorkspaceModule(t, db, database.WorkspaceModule{
|
|
JobID: pj.ID,
|
|
Source: "registry.coder.com/terraform/aws",
|
|
Version: "1.0.0",
|
|
})
|
|
_ = dbgen.WorkspaceModule(t, db, database.WorkspaceModule{
|
|
JobID: pj.ID,
|
|
Source: "https://internal-url.com/some-module",
|
|
Version: "1.0.0",
|
|
})
|
|
_, snapshot := collectSnapshot(t, db, nil)
|
|
require.Len(t, snapshot.WorkspaceModules, 2)
|
|
modules := snapshot.WorkspaceModules
|
|
sort.Slice(modules, func(i, j int) bool {
|
|
return modules[i].Source < modules[j].Source
|
|
})
|
|
require.Equal(t, modules[0].Source, "ed662ec0396db67e77119f14afcb9253574cc925b04a51d4374bcb1eae299f5d")
|
|
require.Equal(t, modules[0].Version, "92521fc3cbd964bdc9f584a991b89fddaa5754ed1cc96d6d42445338669c1305")
|
|
require.Equal(t, modules[0].SourceType, telemetry.ModuleSourceTypeHTTP)
|
|
require.Equal(t, modules[1].Source, "registry.coder.com/terraform/aws")
|
|
require.Equal(t, modules[1].Version, "1.0.0")
|
|
require.Equal(t, modules[1].SourceType, telemetry.ModuleSourceTypeCoderRegistry)
|
|
})
|
|
t.Run("ModuleSourceType", func(t *testing.T) {
|
|
t.Parallel()
|
|
cases := []struct {
|
|
source string
|
|
want telemetry.ModuleSourceType
|
|
}{
|
|
// Local relative paths
|
|
{source: "./modules/terraform-aws-vpc", want: telemetry.ModuleSourceTypeLocal},
|
|
{source: "../shared/modules/vpc", want: telemetry.ModuleSourceTypeLocal},
|
|
{source: " ./my-module ", want: telemetry.ModuleSourceTypeLocal}, // with whitespace
|
|
|
|
// Local absolute paths
|
|
{source: "/opt/terraform/modules/vpc", want: telemetry.ModuleSourceTypeLocalAbs},
|
|
{source: "/Users/dev/modules/app", want: telemetry.ModuleSourceTypeLocalAbs},
|
|
{source: "/etc/terraform/modules/network", want: telemetry.ModuleSourceTypeLocalAbs},
|
|
|
|
// Public registry
|
|
{source: "hashicorp/consul/aws", want: telemetry.ModuleSourceTypePublicRegistry},
|
|
{source: "registry.terraform.io/hashicorp/aws", want: telemetry.ModuleSourceTypePublicRegistry},
|
|
{source: "terraform-aws-modules/vpc/aws", want: telemetry.ModuleSourceTypePublicRegistry},
|
|
{source: "hashicorp/consul/aws//modules/consul-cluster", want: telemetry.ModuleSourceTypePublicRegistry},
|
|
{source: "hashicorp/co-nsul/aw_s//modules/consul-cluster", want: telemetry.ModuleSourceTypePublicRegistry},
|
|
|
|
// Private registry
|
|
{source: "app.terraform.io/company/vpc/aws", want: telemetry.ModuleSourceTypePrivateRegistry},
|
|
{source: "localterraform.com/org/module", want: telemetry.ModuleSourceTypePrivateRegistry},
|
|
{source: "APP.TERRAFORM.IO/test/module", want: telemetry.ModuleSourceTypePrivateRegistry}, // case insensitive
|
|
|
|
// Coder registry
|
|
{source: "registry.coder.com/terraform/aws", want: telemetry.ModuleSourceTypeCoderRegistry},
|
|
{source: "registry.coder.com/modules/base", want: telemetry.ModuleSourceTypeCoderRegistry},
|
|
{source: "REGISTRY.CODER.COM/test/module", want: telemetry.ModuleSourceTypeCoderRegistry}, // case insensitive
|
|
|
|
// GitHub
|
|
{source: "github.com/hashicorp/terraform-aws-vpc", want: telemetry.ModuleSourceTypeGitHub},
|
|
{source: "git::https://github.com/org/repo.git", want: telemetry.ModuleSourceTypeGitHub},
|
|
{source: "git::https://github.com/org/repo//modules/vpc", want: telemetry.ModuleSourceTypeGitHub},
|
|
|
|
// Bitbucket
|
|
{source: "bitbucket.org/hashicorp/terraform-aws-vpc", want: telemetry.ModuleSourceTypeBitbucket},
|
|
{source: "git::https://bitbucket.org/org/repo.git", want: telemetry.ModuleSourceTypeBitbucket},
|
|
{source: "https://bitbucket.org/org/repo//modules/vpc", want: telemetry.ModuleSourceTypeBitbucket},
|
|
|
|
// Generic Git
|
|
{source: "git::ssh://git.internal.com/repo.git", want: telemetry.ModuleSourceTypeGit},
|
|
{source: "git@gitlab.com:org/repo.git", want: telemetry.ModuleSourceTypeGit},
|
|
{source: "git::https://git.internal.com/repo.git?ref=v1.0.0", want: telemetry.ModuleSourceTypeGit},
|
|
|
|
// Mercurial
|
|
{source: "hg::https://example.com/vpc.hg", want: telemetry.ModuleSourceTypeMercurial},
|
|
{source: "hg::http://example.com/vpc.hg", want: telemetry.ModuleSourceTypeMercurial},
|
|
{source: "hg::ssh://example.com/vpc.hg", want: telemetry.ModuleSourceTypeMercurial},
|
|
|
|
// HTTP
|
|
{source: "https://example.com/vpc-module.zip", want: telemetry.ModuleSourceTypeHTTP},
|
|
{source: "http://example.com/modules/vpc", want: telemetry.ModuleSourceTypeHTTP},
|
|
{source: "https://internal.network/terraform/modules", want: telemetry.ModuleSourceTypeHTTP},
|
|
|
|
// S3
|
|
{source: "s3::https://s3-eu-west-1.amazonaws.com/bucket/vpc", want: telemetry.ModuleSourceTypeS3},
|
|
{source: "s3::https://bucket.s3.amazonaws.com/vpc", want: telemetry.ModuleSourceTypeS3},
|
|
{source: "s3::http://bucket.s3.amazonaws.com/vpc?version=1", want: telemetry.ModuleSourceTypeS3},
|
|
|
|
// GCS
|
|
{source: "gcs::https://www.googleapis.com/storage/v1/bucket/vpc", want: telemetry.ModuleSourceTypeGCS},
|
|
{source: "gcs::https://storage.googleapis.com/bucket/vpc", want: telemetry.ModuleSourceTypeGCS},
|
|
{source: "gcs::https://bucket.storage.googleapis.com/vpc", want: telemetry.ModuleSourceTypeGCS},
|
|
|
|
// Unknown
|
|
{source: "custom://example.com/vpc", want: telemetry.ModuleSourceTypeUnknown},
|
|
{source: "something-random", want: telemetry.ModuleSourceTypeUnknown},
|
|
{source: "", want: telemetry.ModuleSourceTypeUnknown},
|
|
}
|
|
for _, c := range cases {
|
|
require.Equal(t, c.want, telemetry.GetModuleSourceType(c.source))
|
|
}
|
|
})
|
|
}
|
|
|
|
// nolint:paralleltest
|
|
func TestTelemetryInstallSource(t *testing.T) {
|
|
t.Setenv("CODER_TELEMETRY_INSTALL_SOURCE", "aws_marketplace")
|
|
db := dbmem.New()
|
|
deployment, _ := collectSnapshot(t, db, nil)
|
|
require.Equal(t, "aws_marketplace", deployment.InstallSource)
|
|
}
|
|
|
|
func collectSnapshot(t *testing.T, db database.Store, addOptionsFn func(opts telemetry.Options) telemetry.Options) (*telemetry.Deployment, *telemetry.Snapshot) {
|
|
t.Helper()
|
|
deployment := make(chan *telemetry.Deployment, 64)
|
|
snapshot := make(chan *telemetry.Snapshot, 64)
|
|
r := chi.NewRouter()
|
|
r.Post("/deployment", func(w http.ResponseWriter, r *http.Request) {
|
|
require.Equal(t, buildinfo.Version(), r.Header.Get(telemetry.VersionHeader))
|
|
w.WriteHeader(http.StatusAccepted)
|
|
dd := &telemetry.Deployment{}
|
|
err := json.NewDecoder(r.Body).Decode(dd)
|
|
require.NoError(t, err)
|
|
deployment <- dd
|
|
})
|
|
r.Post("/snapshot", func(w http.ResponseWriter, r *http.Request) {
|
|
require.Equal(t, buildinfo.Version(), r.Header.Get(telemetry.VersionHeader))
|
|
w.WriteHeader(http.StatusAccepted)
|
|
ss := &telemetry.Snapshot{}
|
|
err := json.NewDecoder(r.Body).Decode(ss)
|
|
require.NoError(t, err)
|
|
snapshot <- ss
|
|
})
|
|
server := httptest.NewServer(r)
|
|
t.Cleanup(server.Close)
|
|
serverURL, err := url.Parse(server.URL)
|
|
require.NoError(t, err)
|
|
options := telemetry.Options{
|
|
Database: db,
|
|
Logger: testutil.Logger(t),
|
|
URL: serverURL,
|
|
DeploymentID: uuid.NewString(),
|
|
}
|
|
if addOptionsFn != nil {
|
|
options = addOptionsFn(options)
|
|
}
|
|
|
|
reporter, err := telemetry.New(options)
|
|
require.NoError(t, err)
|
|
t.Cleanup(reporter.Close)
|
|
return <-deployment, <-snapshot
|
|
}
|