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

Commitd9f2aaf

Browse files
feat: Add support for update checks and notifications (#4810)
Co-authored-by: Kira Pilot <kira@coder.com>
1 parent4f1cf6c commitd9f2aaf

32 files changed

+1088
-22
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
returnsemver.MajorMinor(v1)==semver.MajorMinor(v2)
6969
}
7070

71+
// IsDev returns true if this is a development build.
72+
funcIsDev()bool {
73+
returnstrings.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. The check is performed once per day.",
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"
@@ -373,6 +374,25 @@ func Server(vip *viper.Viper, newAPI func(context.Context, *coderd.Options) (*co
373374
options.TLSCertificates=tlsConfig.Certificates
374375
}
375376

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

‎cli/testdata/coder_server_--help.golden

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,10 @@ Flags:
219219
verbose flag was supplied, debug-level
220220
logs will be included.
221221
Consumes $CODER_TRACE_CAPTURE_LOGS
222+
--update-check Periodically check for new releases of
223+
Coder and inform the owner. The check is
224+
performed once per day.
225+
Consumes $CODER_UPDATE_CHECK
222226
--wildcard-access-url string Specifies the wildcard hostname to use
223227
for workspace applications in the form
224228
"*.example.com".

‎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
Experimentalbool
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
ifoptions.AgentStatsRefreshInterval==0 {
126-
options.AgentStatsRefreshInterval=10*time.Minute
128+
options.AgentStatsRefreshInterval=5*time.Minute
127129
}
128130
ifoptions.MetricsCacheRefreshInterval==0 {
129131
options.MetricsCacheRefreshInterval=time.Hour
130132
}
131133
ifoptions.APIRateLimit==0 {
132134
options.APIRateLimit=512
133135
}
134-
ifoptions.AgentStatsRefreshInterval==0 {
135-
options.AgentStatsRefreshInterval=5*time.Minute
136-
}
137-
ifoptions.MetricsCacheRefreshInterval==0 {
138-
options.MetricsCacheRefreshInterval=time.Hour
139-
}
140136
ifoptions.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+
ifoptions.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)
@@ -590,13 +596,14 @@ type API struct {
590596
// RootHandler serves "/"
591597
RootHandler chi.Router
592598

593-
metricsCache*metricscache.Cache
594-
siteHandler http.Handler
599+
siteHandler http.Handler
595600

596601
WebsocketWaitMutex sync.Mutex
597602
WebsocketWaitGroup sync.WaitGroup
598603

604+
metricsCache*metricscache.Cache
599605
workspaceAgentCache*wsconncache.Cache
606+
updateChecker*updatecheck.Checker
600607
}
601608

602609
// Close waits for all WebSocket connections to drain before returning.
@@ -606,6 +613,9 @@ func (api *API) Close() error {
606613
api.WebsocketWaitMutex.Unlock()
607614

608615
api.metricsCache.Close()
616+
ifapi.updateChecker!=nil {
617+
api.updateChecker.Close()
618+
}
609619
coordinator:=api.TailnetCoordinator.Load()
610620
ifcoordinator!=nil {
611621
_= (*coordinator).Close()

‎coderd/coderdtest/authorize.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ func AGPLRoutes(a *AuthTester) (map[string]string, map[string]RouteCheck) {
4848
"GET:/healthz": {NoAuthorize:true},
4949
"GET:/api/v2": {NoAuthorize:true},
5050
"GET:/api/v2/buildinfo": {NoAuthorize:true},
51+
"GET:/api/v2/updatecheck": {NoAuthorize:true},
5152
"GET:/api/v2/users/first": {NoAuthorize:true},
5253
"POST:/api/v2/users/first": {NoAuthorize:true},
5354
"POST:/api/v2/users/login": {NoAuthorize:true},

‎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
@@ -119,9 +119,10 @@ type data struct {
119119
workspaceResources []database.WorkspaceResource
120120
workspaces []database.Workspace
121121

122-
deploymentIDstring
123-
derpMeshKeystring
124-
lastLicenseIDint32
122+
deploymentIDstring
123+
derpMeshKeystring
124+
lastUpdateCheck []byte
125+
lastLicenseIDint32
125126
}
126127

127128
func (fakeQuerier)IsFakeDB() {}
@@ -3272,6 +3273,24 @@ func (q *fakeQuerier) GetDERPMeshKey(_ context.Context) (string, error) {
32723273
returnq.derpMeshKey,nil
32733274
}
32743275

3276+
func (q*fakeQuerier)InsertOrUpdateLastUpdateCheck(_ context.Context,datastring)error {
3277+
q.mutex.RLock()
3278+
deferq.mutex.RUnlock()
3279+
3280+
q.lastUpdateCheck= []byte(data)
3281+
returnnil
3282+
}
3283+
3284+
func (q*fakeQuerier)GetLastUpdateCheck(_ context.Context) (string,error) {
3285+
q.mutex.RLock()
3286+
deferq.mutex.RUnlock()
3287+
3288+
ifq.lastUpdateCheck==nil {
3289+
return"",sql.ErrNoRows
3290+
}
3291+
returnstring(q.lastUpdateCheck),nil
3292+
}
3293+
32753294
func (q*fakeQuerier)InsertLicense(
32763295
_ context.Context,arg database.InsertLicenseParams,
32773296
) (database.License,error) {

‎coderd/database/querier.go

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing 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 aboutcustomizing 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 valueFROM site_configsWHERE 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) DOUPDATESET value= $1WHEREsite_configs.key='last_update_check';
16+
17+
-- name: GetLastUpdateCheck :one
18+
SELECT valueFROM site_configsWHERE key='last_update_check';

‎coderd/updatecheck.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package coderd
2+
3+
import (
4+
"database/sql"
5+
"net/http"
6+
"strings"
7+
8+
"golang.org/x/mod/semver"
9+
"golang.org/x/xerrors"
10+
11+
"github.com/coder/coder/buildinfo"
12+
"github.com/coder/coder/coderd/httpapi"
13+
"github.com/coder/coder/codersdk"
14+
)
15+
16+
func (api*API)updateCheck(rw http.ResponseWriter,r*http.Request) {
17+
ctx:=r.Context()
18+
19+
currentVersion:= codersdk.UpdateCheckResponse{
20+
Current:true,
21+
Version:buildinfo.Version(),
22+
URL:buildinfo.ExternalURL(),
23+
}
24+
25+
ifapi.updateChecker==nil {
26+
// If update checking is disabled, echo the current
27+
// version.
28+
httpapi.Write(ctx,rw,http.StatusOK,currentVersion)
29+
return
30+
}
31+
32+
uc,err:=api.updateChecker.Latest(ctx)
33+
iferr!=nil {
34+
ifxerrors.Is(err,sql.ErrNoRows) {
35+
// Update checking is enabled, but has never
36+
// succeeded, reproduce behavior as if disabled.
37+
httpapi.Write(ctx,rw,http.StatusOK,currentVersion)
38+
return
39+
}
40+
41+
httpapi.InternalServerError(rw,err)
42+
return
43+
}
44+
45+
// Since our dev version (v0.12.9-devel+f7246386) is not semver compatible,
46+
// ignore everything after "-"."
47+
versionWithoutDevel:=strings.SplitN(buildinfo.Version(),"-",2)[0]
48+
49+
httpapi.Write(ctx,rw,http.StatusOK, codersdk.UpdateCheckResponse{
50+
Current:semver.Compare(versionWithoutDevel,uc.Version)>=0,
51+
Version:uc.Version,
52+
URL:uc.URL,
53+
})
54+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp