Skip to content

Commit 51a99c4

Browse files
authored
Merge branch 'main' into mafredri/fix-authorize-nil-error
2 parents 5fd6b2f + 9a0a6b7 commit 51a99c4

39 files changed

+1109
-45
lines changed

.github/workflows/coder.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -428,7 +428,7 @@ jobs:
428428
fetch-depth: 0
429429

430430
- name: Authenticate to Google Cloud
431-
uses: google-github-actions/auth@v0
431+
uses: google-github-actions/auth@v1
432432
with:
433433
workload_identity_provider: projects/573722524737/locations/global/workloadIdentityPools/github/providers/github
434434
service_account: coder-ci@coder-dogfood.iam.gserviceaccount.com

.github/workflows/dependabot.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@ jobs:
99
permissions:
1010
pull-requests: write
1111
steps:
12-
- uses: hmarr/auto-approve-action@v2
12+
- uses: hmarr/auto-approve-action@v3
1313
if: github.actor == 'dependabot[bot]'

.github/workflows/dogfood.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
steps:
1818
- name: Get branch name
1919
id: branch-name
20-
uses: tj-actions/branch-names@v6.2
20+
uses: tj-actions/branch-names@v6.3
2121

2222
- name: "Branch name to Docker tag name"
2323
id: docker-tag-name

.github/workflows/release.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ jobs:
170170
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
171171

172172
- name: Authenticate to Google Cloud
173-
uses: google-github-actions/auth@v0
173+
uses: google-github-actions/auth@v1
174174
with:
175175
workload_identity_provider: ${{ secrets.GCP_WORKLOAD_ID_PROVIDER }}
176176
service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }}

.github/workflows/stale.yaml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@ jobs:
1717
with:
1818
stale-issue-label: 'stale'
1919
stale-pr-label: 'stale'
20-
exempt-issue-labels: 'never stale'
21-
exempt-pr-labels: 'never stale'
2220
# Pull Requests become stale more quickly due to merge conflicts.
2321
# Also, we promote minimizing WIP.
2422
days-before-pr-stale: 7

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. 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+
if cfg.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+
if semver.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
if cfg.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
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)
@@ -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+
if api.updateChecker != nil {
617+
api.updateChecker.Close()
618+
}
609619
coordinator := api.TailnetCoordinator.Load()
610620
if coordinator != 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-
deploymentID string
123-
derpMeshKey string
124-
lastLicenseID int32
122+
deploymentID string
123+
derpMeshKey string
124+
lastUpdateCheck []byte
125+
lastLicenseID int32
125126
}
126127

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

3276+
func (q *fakeQuerier) InsertOrUpdateLastUpdateCheck(_ context.Context, data string) error {
3277+
q.mutex.RLock()
3278+
defer q.mutex.RUnlock()
3279+
3280+
q.lastUpdateCheck = []byte(data)
3281+
return nil
3282+
}
3283+
3284+
func (q *fakeQuerier) GetLastUpdateCheck(_ context.Context) (string, error) {
3285+
q.mutex.RLock()
3286+
defer q.mutex.RUnlock()
3287+
3288+
if q.lastUpdateCheck == nil {
3289+
return "", sql.ErrNoRows
3290+
}
3291+
return string(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 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)