8000 feat: Add support for checking for updates · coder/coder@6beb53d · GitHub
[go: up one dir, main page]

Skip to content

Commit 6beb53d

Browse files
committed
feat: Add support for checking for updates
1 parent 898ba11 commit 6beb53d

File tree

17 files changed

+664
-12
lines changed

17 files changed

+664
-12
lines changed

buildinfo/buildinfo.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@ func VersionsMatch(v1, v2 string) bool {
6868
return semver.MajorMinor(v1) == semver.MajorMinor(v2)
6969
}
7070

71+
// IsDev returns true if this is a development build.
72+
func IsDev() bool {
73+
return strings.HasPrefix(Version(), develPrefix)
74+
}
75+
7176
// ExternalURL returns a URL referencing the current Coder version.
7277
// For production builds, this will link directly to a release.
7378
// For development builds, this will link to a commit.

cli/deployment/config.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/spf13/viper"
1515
"golang.org/x/xerrors"
1616

17+
"github.com/coder/coder/buildinfo"
1718
"github.com/coder/coder/cli/cliui"
1819
"github.com/coder/coder/cli/config"
1920
"github.com/coder/coder/codersdk"
@@ -405,6 +406,12 @@ func newConfig() *codersdk.DeploymentConfig {
405406
Usage: "Enable experimental features. Experimental features are not ready for production.",
406407
Flag: "experimental",
407408
},
409+
UpdateCheck: &codersdk.DeploymentConfigField[bool]{
410+
Name: "Update Check",
411+
Usage: "Periodically check for new releases of Coder and inform the owner.",
412+
Flag: "update-check",
413+
Default: flag.Lookup("test.v") == nil && !buildinfo.IsDev(),
414+
},
408415
}
409416
}
410417

cli/deployment/config_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ func TestConfig(t *testing.T) {
3838
"CODER_TELEMETRY": "false",
3939
"CODER_TELEMETRY_TRACE": "false",
4040
"CODER_WILDCARD_ACCESS_URL": "something-wildcard.com",
41+
"CODER_UPDATE_CHECK": "false",
4142
},
4243
Valid: func(config *codersdk.DeploymentConfig) {
4344
require.Equal(t, config.Address.Value, "0.0.0.0:8443")
@@ -53,6 +54,7 @@ func TestConfig(t *testing.T) {
5354
require.Equal(t, config.Telemetry.Enable.Value, false)
5455
require.Equal(t, config.Telemetry.Trace.Value, false)
5556
require.Equal(t, config.WildcardAccessURL.Value, "something-wildcard.com")
57+
require.Equal(t, config.UpdateCheck.Value, false)
5658
},
5759
}, {
5860
Name: "DERP",

cli/server.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ import (
6363
"github.com/coder/coder/coderd/prometheusmetrics"
6464
"github.com/coder/coder/coderd/telemetry"
6565
"github.com/coder/coder/coderd/tracing"
66+
"github.com/coder/coder/coderd/updatecheck"
6667
"github.com/coder/coder/codersdk"
6768
"github.com/coder/coder/cryptorand"
6869
"github.com/coder/coder/provisioner/echo"
@@ -370,6 +371,25 @@ func Server(vip *viper.Viper, newAPI func(context.Context, *coderd.Options) (*co
370371
options.TLSCertificates = tlsConfig.Certificates
371372
}
372373

374+
if cfg.UpdateCheck.Value {
375+
options.UpdateCheckOptions = &updatecheck.Options{
376+
// Avoid spamming GitHub API checking for updates.
377+
Interval: 24 * time.Hour,
378+
// Inform server admins of new versions.
379+
Notify: func(r updatecheck.Result) {
380+
if semver.Compare(r.Version, buildinfo.Version()) > 0 {
381+
options.Logger.Info(
382+
context.Background(),
383+
"new version of coder available",
384+
slog.F("new_version", r.Version),
385+
slog.F("url", r.URL),
386+
slog.F("upgrade_instructions", "https://coder.com/docs/coder-oss/latest/admin/upgrade"),
387+
)
388+
}
389+
},
390+
}
391+
}
392+
373393
if cfg.OAuth2.Github.ClientSecret.Value != "" {
374394
options.GithubOAuth2Config, err = configureGithubOAuth2(accessURLParsed,
375395
cfg.OAuth2.Github.ClientID.Value,

coderd/coderd.go

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import (
4747
"github.com/coder/coder/coderd/rbac"
4848
"github.com/coder/coder/coderd/telemetry"
4949
"github.com/coder/coder/coderd/tracing"
50+
"github.com/coder/coder/coderd/updatecheck"
5051
"github.com/coder/coder/coderd/wsconncache"
5152
"github.com/coder/coder/codersdk"
5253
"github.com/coder/coder/provisionerd/proto"
@@ -105,6 +106,7 @@ type Options struct {
105106
AgentStatsRefreshInterval time.Duration
106107
Experimental bool
107108
DeploymentConfig *codersdk.DeploymentConfig
109+
UpdateCheckOptions *updatecheck.Options // Set non-nil to enable update checking.
108110
}
109111

110112
// New constructs a Coder API handler.
@@ -123,20 +125,14 @@ func New(options *Options) *API {
123125
options.AgentInactiveDisconnectTimeout = options.AgentConnectionUpdateFrequency * 2
124126
}
125127
if options.AgentStatsRefreshInterval == 0 {
126-
options.AgentStatsRefreshInterval = 10 * time.Minute
128+
options.AgentStatsRefreshInterval = 5 * time.Minute
127129
}
128130
if options.MetricsCacheRefreshInterval == 0 {
129131
options.MetricsCacheRefreshInterval = time.Hour
130132
}
131133
if options.APIRateLimit == 0 {
132134
options.APIRateLimit = 512
133135
}
134-
if options.AgentStatsRefreshInterval == 0 {
135-
options.AgentStatsRefreshInterval = 5 * time.Minute
136-
}
137-
if options.MetricsCacheRefreshInterval == 0 {
138-
options.MetricsCacheRefreshInterval = time.Hour
139-
}
140136
if options.Authorizer == nil {
141137
options.Authorizer = rbac.NewAuthorizer()
142138
}
@@ -181,6 +177,13 @@ func New(options *Options) *API {
181177
metricsCache: metricsCache,
182178
Auditor: atomic.Pointer[audit.Auditor]{},
183179
}
180+
if options.UpdateCheckOptions != nil {
181+
api.updateChecker = updatecheck.New(
182+
options.Database,
183+
options.Logger.Named("update_checker"),
184+
*options.UpdateCheckOptions,
185+
)
186+
}
184187
api.Auditor.Store(&options.Auditor)
185188
api.workspaceAgentCache = wsconncache.New(api.dialWorkspaceAgentTailnet, 0)
186189
api.TailnetCoordinator.Store(&options.TailnetCoordinator)
@@ -308,6 +311,9 @@ func New(options *Options) *API {
308311
})
309312
})
310313
})
314+
r.Route("/updatecheck", func(r chi.Router) {
315+
r.Get("/", api.updateCheck)
316+
})
311317
r.Route("/config", func(r chi.Router) {
312318
r.Use(apiKeyMiddleware)
313319
r.Get("/deployment", api.deploymentConfig)
@@ -598,13 +604,14 @@ type API struct {
598604
// RootHandler serves "/"
599605
RootHandler chi.Router
600606

601-
metricsCache *metricscache.Cache
602-
siteHandler http.Handler
607+
siteHandler http.Handler
603608

604609
WebsocketWaitMutex sync.Mutex
605610
WebsocketWaitGroup sync.WaitGroup
606611

612+
metricsCache *metricscache.Cache
607613
workspaceAgentCache *wsconncache.Cache
614+
updateChecker *updatecheck.Checker
608615
}
609616

610617
// Close waits for all WebSocket connections to drain before returning.
@@ -614,6 +621,9 @@ func (api *API) Close() error {
614621
api.WebsocketWaitMutex.Unlock()
615622

616623
api.metricsCache.Close()
624+
if api.updateChecker != nil {
625+
api.updateChecker.Close()
626+
}
617627
coordinator := api.TailnetCoordinator.Load()
618628
if coordinator != nil {
619629
_ = (*coordinator).Close()

coderd/coderdtest/coderdtest.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ import (
6464
"github.com/coder/coder/coderd/httpmw"
6565
"github.com/coder/coder/coderd/rbac"
6666
"github.com/coder/coder/coderd/telemetry"
67+
"github.com/coder/coder/coderd/updatecheck"
6768
"github.com/coder/coder/coderd/util/ptr"
6869
"github.com/coder/coder/codersdk"
6970
"github.com/coder/coder/cryptorand"
@@ -102,6 +103,9 @@ type Options struct {
102103
AgentStatsRefreshInterval time.Duration
103104
DeploymentConfig *codersdk.DeploymentConfig
104105

106+
// Set update check options to enable update check.
107+
UpdateCheckOptions *updatecheck.Options
108+
105109
// Overriding the database is heavily discouraged.
106110
// It should only be used in cases where multiple Coder
107111
// test instances are running against the same database.
@@ -283,6 +287,7 @@ func NewOptions(t *testing.T, options *Options) (func(http.Handler), context.Can
283287
MetricsCacheRefreshInterval: options.MetricsCacheRefreshInterval,
284288
AgentStatsRefreshInterval: options.AgentStatsRefreshInterval,
285289
DeploymentConfig: options.DeploymentConfig,
290+
UpdateCheckOptions: options.UpdateCheckOptions,
286291
}
287292
}
288293

coderd/database/databasefake/databasefake.go

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,9 +112,10 @@ type data struct {
112112
workspaceResources []database.WorkspaceResource
113113
workspaces []database.Workspace
114114

115-
deploymentID string
116-
derpMeshKey string
117-
lastLicenseID int32
115+
deploymentID string
116+
derpMeshKey string
117+
lastUpdateCheck []byte
118+
lastLicenseID int32
118119
}
119120

120121
func (*fakeQuerier) Ping(_ context.Context) (time.Duration, error) {
@@ -3234,6 +3235,24 @@ func (q *fakeQuerier) GetDERPMeshKey(_ context.Context) (string, error) {
32343235
return q.derpMeshKey, nil
32353236
}
32363237

3238+
func (q *fakeQuerier) InsertOrUpdateLastUpdateCheck(_ context.Context, data string) error {
3239+
q.mutex.RLock()
3240+
defer q.mutex.RUnlock()
3241+
3242+
q.lastUpdateCheck = []byte(data)
3243+
return nil
3244+
}
3245+
3246+
func (q *fakeQuerier) GetLastUpdateCheck(_ context.Context) (string, error) {
3247+
q.mutex.RLock()
3248+
defer q.mutex.RUnlock()
3249+
3250+
if q.lastUpdateCheck == nil {
3251+
return "", sql.ErrNoRows
3252+
}
3253+
return string(q.lastUpdateCheck), nil
3254+
}
3255+
32373256
func (q *fakeQuerier) InsertLicense(
32383257
_ context.Context, arg database.InsertLicenseParams,
32393258
) (database.License, error) {

coderd/database/querier.go

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries.sql.go

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries/siteconfig.sql

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,10 @@ INSERT INTO site_configs (key, value) VALUES ('derp_mesh_key', $1);
99

1010
-- name: GetDERPMeshKey :one
1111
SELECT value FROM site_configs WHERE key = 'derp_mesh_key';
12+
13+
-- name: InsertOrUpdateLastUpdateCheck :exec
14+
INSERT INTO site_configs (key, value) VALUES ('last_update_check', $1)
15+
ON CONFLICT (key) DO UPDATE SET value = $1 WHERE site_configs.key = 'last_update_check';
16+
17+
-- name: GetLastUpdateCheck :one
18+
SELECT value FROM site_configs WHERE key = 'last_update_check';

0 commit comments

Comments
 (0)
0