Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

chore: track usage of organizations in telemetry#16323

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
hugodutka merged 8 commits intomainfromhugodutka/org-telemetry-2
Jan 29, 2025
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletionscoderd/idpsync/organization.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -45,6 +45,8 @@ func (s AGPLIDPSync) UpdateOrganizationSettings(ctx context.Context, db database
}

func (s AGPLIDPSync) OrganizationSyncSettings(ctx context.Context, db database.Store) (*OrganizationSyncSettings, error) {
// If this logic is ever updated, make sure to update the corresponding
// checkIDPOrgSync in coderd/telemetry/telemetry.go.
rlv := s.Manager.Resolver(db)
orgSettings, err := s.SyncSettings.Organization.Resolve(ctx, rlv)
if err != nil {
Expand Down
79 changes: 79 additions & 0 deletionscoderd/telemetry/telemetry.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,6 +4,7 @@ import (
"bytes"
"context"
"crypto/sha256"
"database/sql"
"encoding/json"
"errors"
"fmt"
Expand DownExpand Up@@ -244,6 +245,11 @@ func (r *remoteReporter) deployment() error {
return xerrors.Errorf("install source must be <=64 chars: %s", installSource)
}

idpOrgSync, err := checkIDPOrgSync(r.ctx, r.options.Database, r.options.DeploymentConfig)
if err != nil {
r.options.Logger.Debug(r.ctx, "check IDP org sync", slog.Error(err))
}

data, err := json.Marshal(&Deployment{
ID: r.options.DeploymentID,
Architecture: sysInfo.Architecture,
Expand All@@ -263,6 +269,7 @@ func (r *remoteReporter) deployment() error {
MachineID: sysInfo.UniqueID,
StartedAt: r.startedAt,
ShutdownAt: r.shutdownAt,
IDPOrgSync: &idpOrgSync,
})
if err != nil {
return xerrors.Errorf("marshal deployment: %w", err)
Expand All@@ -284,6 +291,45 @@ func (r *remoteReporter) deployment() error {
return nil
}

// idpOrgSyncConfig is a subset of
// https://github.com/coder/coder/blob/5c6578d84e2940b9cfd04798c45e7c8042c3fe0e/coderd/idpsync/organization.go#L148
type idpOrgSyncConfig struct {
Field string `json:"field"`
}

// checkIDPOrgSync inspects the server flags and the runtime config. It's based on
// the OrganizationSyncEnabled function from enterprise/coderd/enidpsync/organizations.go.
// It has one distinct difference: it doesn't check if the license entitles to the
// feature, it only checks if the feature is configured.
//
// The above function is not used because it's very hard to make it available in
// the telemetry package due to coder/coder package structure and initialization
// order of the coder server.
//
// We don't check license entitlements because it's also hard to do from the
// telemetry package, and the config check should be sufficient for telemetry purposes.
//
// While this approach duplicates code, it's simpler than the alternative.
//
// See https://github.com/coder/coder/pull/16323 for more details.
func checkIDPOrgSync(ctx context.Context, db database.Store, values *codersdk.DeploymentValues) (bool, error) {
// key based on https://github.com/coder/coder/blob/5c6578d84e2940b9cfd04798c45e7c8042c3fe0e/coderd/idpsync/idpsync.go#L168
syncConfigRaw, err := db.GetRuntimeConfig(ctx, "organization-sync-settings")
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
// If the runtime config is not set, we check if the deployment config
// has the organization field set.
return values != nil && values.OIDC.OrganizationField != "", nil
}
return false, xerrors.Errorf("get runtime config: %w", err)
}
syncConfig := idpOrgSyncConfig{}
if err := json.Unmarshal([]byte(syncConfigRaw), &syncConfig); err != nil {
return false, xerrors.Errorf("unmarshal runtime config: %w", err)
}
return syncConfig.Field != "", nil
}

// createSnapshot collects a full snapshot from the database.
func (r *remoteReporter) createSnapshot() (*Snapshot, error) {
var (
Expand DownExpand Up@@ -518,6 +564,21 @@ func (r *remoteReporter) createSnapshot() (*Snapshot, error) {
}
return nil
})
eg.Go(func() error {
// Warning: When an organization is deleted, it's completely removed from
// the database. It will no longer be reported, and there will be no other
// indicator that it was deleted. This requires special handling when
// interpreting the telemetry data later.
Comment on lines +568 to +571
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

We do intend to fix that with soft deletes.

#16166

orgs, err := r.options.Database.GetOrganizations(r.ctx, database.GetOrganizationsParams{})
if err != nil {
return xerrors.Errorf("get organizations: %w", err)
}
snapshot.Organizations = make([]Organization, 0, len(orgs))
for _, org := range orgs {
snapshot.Organizations = append(snapshot.Organizations, ConvertOrganization(org))
}
return nil
})

err := eg.Wait()
if err != nil {
Expand DownExpand Up@@ -916,6 +977,14 @@ func ConvertExternalProvisioner(id uuid.UUID, tags map[string]string, provisione
}
}

func ConvertOrganization(org database.Organization) Organization {
return Organization{
ID: org.ID,
CreatedAt: org.CreatedAt,
IsDefault: org.IsDefault,
}
}

// Snapshot represents a point-in-time anonymized database dump.
// Data is aggregated by latest on the server-side, so partial data
// can be sent without issue.
Expand All@@ -942,6 +1011,7 @@ type Snapshot struct {
WorkspaceModules []WorkspaceModule `json:"workspace_modules"`
Workspaces []Workspace `json:"workspaces"`
NetworkEvents []NetworkEvent `json:"network_events"`
Organizations []Organization `json:"organizations"`
}

// Deployment contains information about the host running Coder.
Expand All@@ -964,6 +1034,9 @@ type Deployment struct {
MachineID string `json:"machine_id"`
StartedAt time.Time `json:"started_at"`
ShutdownAt *time.Time `json:"shutdown_at"`
// While IDPOrgSync will always be set, it's nullable to make
// the struct backwards compatible with older coder versions.
IDPOrgSync *bool `json:"idp_org_sync"`
}

type APIKey struct {
Expand DownExpand Up@@ -1457,6 +1530,12 @@ func NetworkEventFromProto(proto *tailnetproto.TelemetryEvent) (NetworkEvent, er
}, nil
}

type Organization struct {
ID uuid.UUID `json:"id"`
IsDefault bool `json:"is_default"`
CreatedAt time.Time `json:"created_at"`
}

type noopReporter struct{}

func (*noopReporter) Report(_ *Snapshot) {}
Expand Down
75 changes: 69 additions & 6 deletionscoderd/telemetry/telemetry_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,7 +22,10 @@ import (
"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/idpsync"
"github.com/coder/coder/v2/coderd/runtimeconfig"
"github.com/coder/coder/v2/coderd/telemetry"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/testutil"
)

Expand All@@ -40,22 +43,33 @@ func TestTelemetry(t *testing.T) {
db := dbmem.New()

ctx := testutil.Context(t, testutil.WaitMedium)

org, err := db.GetDefaultOrganization(ctx)
require.NoError(t, err)

_, _ = dbgen.APIKey(t, db, database.APIKey{})
_ = dbgen.ProvisionerJob(t, db, nil, database.ProvisionerJob{
Provisioner: database.ProvisionerTypeTerraform,
StorageMethod: database.ProvisionerStorageMethodFile,
Type: database.ProvisionerJobTypeTemplateVersionDryRun,
Provisioner: database.ProvisionerTypeTerraform,
StorageMethod: database.ProvisionerStorageMethodFile,
Type: database.ProvisionerJobTypeTemplateVersionDryRun,
OrganizationID: org.ID,
})
_ = dbgen.Template(t, db, database.Template{
Provisioner: database.ProvisionerTypeTerraform,
Provisioner: database.ProvisionerTypeTerraform,
OrganizationID: org.ID,
})
sourceExampleID := uuid.NewString()
_ = dbgen.TemplateVersion(t, db, database.TemplateVersion{
SourceExampleID: sql.NullString{String: sourceExampleID, Valid: true},
OrganizationID: org.ID,
})
_ = dbgen.TemplateVersion(t, db, database.TemplateVersion{
OrganizationID: org.ID,
})
_ = dbgen.TemplateVersion(t, db, database.TemplateVersion{})
user := dbgen.User(t, db, database.User{})
_ = dbgen.Workspace(t, db, database.WorkspaceTable{})
_ = dbgen.Workspace(t, db, database.WorkspaceTable{
OrganizationID: org.ID,
})
_ = dbgen.WorkspaceApp(t, db, database.WorkspaceApp{
SharingLevel: database.AppSharingLevelOwner,
Health: database.WorkspaceAppHealthDisabled,
Expand DownExpand Up@@ -112,6 +126,7 @@ func TestTelemetry(t *testing.T) {
require.Len(t, snapshot.WorkspaceAgentStats, 1)
require.Len(t, snapshot.WorkspaceProxies, 1)
require.Len(t, snapshot.WorkspaceModules, 1)
require.Len(t, snapshot.Organizations, 1)

wsa := snapshot.WorkspaceAgents[0]
require.Len(t, wsa.Subsystems, 2)
Expand All@@ -128,6 +143,19 @@ func TestTelemetry(t *testing.T) {
})
require.Equal(t, tvs[0].SourceExampleID, &sourceExampleID)
require.Nil(t, tvs[1].SourceExampleID)

for _, entity := range snapshot.Workspaces {
require.Equal(t, entity.OrganizationID, org.ID)
}
for _, entity := range snapshot.ProvisionerJobs {
require.Equal(t, entity.OrganizationID, org.ID)
}
for _, entity := range snapshot.TemplateVersions {
require.Equal(t, entity.OrganizationID, org.ID)
}
for _, entity := range snapshot.Templates {
require.Equal(t, entity.OrganizationID, org.ID)
}
})
t.Run("HashedEmail", func(t *testing.T) {
t.Parallel()
Expand DownExpand Up@@ -243,6 +271,41 @@ func TestTelemetry(t *testing.T) {
require.Equal(t, c.want, telemetry.GetModuleSourceType(c.source))
}
})
t.Run("IDPOrgSync", func(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitMedium)
db, _ := dbtestutil.NewDB(t)

// 1. No org sync settings
deployment, _ := collectSnapshot(t, db, nil)
require.False(t, *deployment.IDPOrgSync)

// 2. Org sync settings set in server flags
deployment, _ = collectSnapshot(t, db, func(opts telemetry.Options) telemetry.Options {
opts.DeploymentConfig = &codersdk.DeploymentValues{
OIDC: codersdk.OIDCConfig{
OrganizationField: "organizations",
},
}
return opts
})
require.True(t, *deployment.IDPOrgSync)

// 3. Org sync settings set in runtime config
org, err := db.GetDefaultOrganization(ctx)
require.NoError(t, err)
sync := idpsync.NewAGPLSync(testutil.Logger(t), runtimeconfig.NewManager(), idpsync.DeploymentSyncSettings{})
err = sync.UpdateOrganizationSettings(ctx, db, idpsync.OrganizationSyncSettings{
Field: "organizations",
Mapping: map[string][]uuid.UUID{
"first": {org.ID},
},
AssignDefault: true,
})
require.NoError(t, err)
deployment, _ = collectSnapshot(t, db, nil)
require.True(t, *deployment.IDPOrgSync)
})
}

// nolint:paralleltest
Expand Down
2 changes: 2 additions & 0 deletionsenterprise/coderd/enidpsync/organizations.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,8 @@ func (e EnterpriseIDPSync) OrganizationSyncEnabled(ctx context.Context, db datab
return false
}

// If this logic is ever updated, make sure to update the corresponding
// checkIDPOrgSync in coderd/telemetry/telemetry.go.
settings, err := e.OrganizationSyncSettings(ctx, db)
if err == nil && settings.Field != "" {
return true
Expand Down
Loading

[8]ページ先頭

©2009-2025 Movatter.jp