Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 61ff880

Browse files
committed
deferred initialization of OrganizationSyncEnabled
1 parent 24b29e2 commit 61ff880

File tree

3 files changed

+97
-55
lines changed

3 files changed

+97
-55
lines changed

cli/server.go

Lines changed: 55 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -781,6 +781,47 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
781781
// This should be output before the logs start streaming.
782782
cliui.Infof(inv.Stdout, "\n==> Logs will stream in below (press ctrl+c to gracefully exit):")
783783

784+
// We set this to a valid function pointer later
785+
orgSyncEnabledFn := new(func() bool)
786+
if vals.Telemetry.Enable {
787+
vals, err := vals.WithoutSecrets()
788+
if err != nil {
789+
return xerrors.Errorf("remove secrets from deployment values: %w", err)
790+
}
791+
options.Telemetry, err = telemetry.New(telemetry.Options{
792+
BuiltinPostgres: builtinPostgres,
793+
DeploymentID: deploymentID,
794+
Database: options.Database,
795+
Logger: logger.Named("telemetry"),
796+
URL: vals.Telemetry.URL.Value(),
797+
Tunnel: tunnel != nil,
798+
DeploymentConfig: vals,
799+
ParseLicenseJWT: func(lic *telemetry.License) error {
800+
// This will be nil when running in AGPL-only mode.
801+
if options.ParseLicenseClaims == nil {
802+
return nil
803+
}
804+
805+
email, trial, err := options.ParseLicenseClaims(lic.JWT)
806+
if err != nil {
807+
return err
808+
}
809+
if email != "" {
810+
lic.Email = &email
811+
}
812+
lic.Trial = &trial
813+
return nil
814+
},
815+
OrganizationSyncEnabled: orgSyncEnabledFn,
816+
})
817+
if err != nil {
818+
return xerrors.Errorf("create telemetry reporter: %w", err)
819+
}
820+
defer options.Telemetry.Close()
821+
} else {
822+
logger.Warn(ctx, fmt.Sprintf(`telemetry disabled, unable to notify of security issues. Read more: %s/admin/setup/telemetry`, vals.DocsURL.String()))
823+
}
824+
784825
// This prevents the pprof import from being accidentally deleted.
785826
_ = pprof.Handler
786827
if vals.Pprof.Enable {
@@ -822,50 +863,22 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
822863
return xerrors.Errorf("create coder API: %w", err)
823864
}
824865

825-
if vals.Telemetry.Enable {
826-
vals, err := vals.WithoutSecrets()
827-
if err != nil {
828-
return xerrors.Errorf("remove secrets from deployment values: %w", err)
866+
if vals.Telemetry.Enable && options.Telemetry != nil {
867+
// We can initialize this pointer only now because the function
868+
// depends on the coderAPI. Read the docstring on
869+
// Options.OrganizationSyncEnabled in coderd/telemetry/telemetry.go
870+
// for more context.
871+
*orgSyncEnabledFn = func() bool {
872+
// Sanity check just in case.
873+
if coderAPI == nil || coderAPI.IDPSync == nil {
874+
return false
875+
}
876+
// nolint:gocritic // AsSystemRestricted is fine here because it's a read-only operation
877+
// used for telemetry reporting.
878+
return coderAPI.IDPSync.OrganizationSyncEnabled(dbauthz.AsSystemRestricted(ctx), options.Database)
829879
}
830-
options.Telemetry, err = telemetry.New(telemetry.Options{
831-
BuiltinPostgres: builtinPostgres,
832-
DeploymentID: deploymentID,
833-
Database: options.Database,
834-
Logger: logger.Named("telemetry"),
835-
URL: vals.Telemetry.URL.Value(),
836-
Tunnel: tunnel != nil,
837-
DeploymentConfig: vals,
838-
ParseLicenseJWT: func(lic *telemetry.License) error {
839-
// This will be nil when running in AGPL-only mode.
840-
if options.ParseLicenseClaims == nil {
841-
return nil
842-
}
843880

844-
email, trial, err := options.ParseLicenseClaims(lic.JWT)
845-
if err != nil {
846-
return err
847-
}
848-
if email != "" {
849-
lic.Email = &email
850-
}
851-
lic.Trial = &trial
852-
return nil
853-
},
854-
OrganizationSyncEnabled: func() bool {
855-
if coderAPI == nil || coderAPI.IDPSync == nil {
856-
return false
857-
}
858-
// nolint:gocritic // AsSystemRestricted is fine here because it's a read-only operation
859-
// used for telemetry reporting.
860-
return coderAPI.IDPSync.OrganizationSyncEnabled(dbauthz.AsSystemRestricted(ctx), options.Database)
861-
},
862-
})
863-
if err != nil {
864-
return xerrors.Errorf("create telemetry reporter: %w", err)
865-
}
866-
defer options.Telemetry.Close()
867-
} else {
868-
logger.Warn(ctx, fmt.Sprintf(`telemetry disabled, unable to notify of security issues. Read more: %s/admin/setup/telemetry`, vals.DocsURL.String()))
881+
options.Telemetry.Start()
869882
}
870883

871884
if vals.Prometheus.Enable {

coderd/telemetry/telemetry.go

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -54,15 +54,33 @@ type Options struct {
5454

5555
SnapshotFrequency time.Duration
5656
ParseLicenseJWT func(lic *License) error
57-
// We pass this function instead of the IDPSync interface because it creates
58-
// a circular import. We don't need all the other methods, and this approach
59-
// is simpler than refactoring the package structure.
60-
OrganizationSyncEnabled func() bool
57+
58+
// OrganizationSyncEnabled is stored as a function pointer for two reasons:
59+
//
60+
// 1. It avoids a circular import with the IDPSync interface. We only need the
61+
// OrganizationSyncEnabled() method, so passing it as a function pointer is
62+
// simpler than including the entire interface, which would require restructuring
63+
// packages.
64+
//
65+
// 2. It works with Coder's initialization order:
66+
// - Telemetry is created first (so this pointer starts as nil).
67+
// - Next, the IDPSync interface is created by the Coder API, which depends
68+
// on the telemetry reporter being created first.
69+
// - Finally, this function pointer is set to a closure that calls
70+
// IDPSync’s OrganizationSyncEnabled method.
71+
//
72+
// This is extremely janky, but we'd need to refactor the entire initialization
73+
// process to avoid it.
74+
OrganizationSyncEnabled *func() bool
6175
}
6276

6377
// New constructs a reporter for telemetry data.
6478
// Duplicate data will be sent, it's on the server-side to index by UUID.
6579
// Data is anonymized prior to being sent!
80+
//
81+
// The reporter has to be started with Start() before it will begin sending data.
82+
// This allows for the deferred initialization of the OrganizationSyncEnabled
83+
// function on the Options struct.
6684
func New(options Options) (Reporter, error) {
6785
if options.SnapshotFrequency == 0 {
6886
// Report once every 30mins by default!
@@ -87,7 +105,6 @@ func New(options Options) (Reporter, error) {
87105
snapshotURL: snapshotURL,
88106
startedAt: dbtime.Now(),
89107
}
90-
go reporter.runSnapshotter()
91108
return reporter, nil
92109
}
93110

@@ -105,13 +122,16 @@ type Reporter interface {
105122
Report(snapshot *Snapshot)
106123
Enabled() bool
107124
Close()
125+
Start()
108126
}
109127

110128
type remoteReporter struct {
111-
ctx context.Context
112-
closed chan struct{}
113-
closeMutex sync.Mutex
114-
closeFunc context.CancelFunc
129+
ctx context.Context
130+
closed chan struct{}
131+
closeMutex sync.Mutex
132+
closeFunc context.CancelFunc
133+
startedOnce sync.Once
134+
started bool
115135

116136
options Options
117137
deploymentURL,
@@ -120,8 +140,15 @@ type remoteReporter struct {
120140
shutdownAt *time.Time
121141
}
122142

123-
func (*remoteReporter) Enabled() bool {
124-
return true
143+
func (r *remoteReporter) Start() {
144+
r.startedOnce.Do(func() {
145+
r.started = true
146+
go r.runSnapshotter()
147+
})
148+
}
149+
150+
func (r *remoteReporter) Enabled() bool {
151+
return r.started
125152
}
126153

127154
func (r *remoteReporter) Report(snapshot *Snapshot) {
@@ -250,8 +277,8 @@ func (r *remoteReporter) deployment() error {
250277
}
251278

252279
idpOrgSync := false
253-
if r.options.OrganizationSyncEnabled != nil {
254-
idpOrgSync = r.options.OrganizationSyncEnabled()
280+
if r.options.OrganizationSyncEnabled != nil && *r.options.OrganizationSyncEnabled != nil {
281+
idpOrgSync = (*r.options.OrganizationSyncEnabled)()
255282
} else {
256283
r.options.Logger.Debug(r.ctx, "organization sync enabled function is nil, skipping IDP org sync check")
257284
}
@@ -1509,3 +1536,4 @@ type noopReporter struct{}
15091536
func (*noopReporter) Report(_ *Snapshot) {}
15101537
func (*noopReporter) Enabled() bool { return false }
15111538
func (*noopReporter) Close() {}
1539+
func (*noopReporter) Start() {}

coderd/telemetry/telemetry_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,7 @@ func collectSnapshot(t *testing.T, db database.Store, addOptionsFn func(opts tel
315315

316316
reporter, err := telemetry.New(options)
317317
require.NoError(t, err)
318+
reporter.Start()
318319
t.Cleanup(reporter.Close)
319320
return <-deployment, <-snapshot
320321
}

0 commit comments

Comments
 (0)