mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
3f55b35f68
Replace overly-broad `AsSystemRestricted` with purpose-built actors:
- **OAuth2 provider paths** → `AsSystemOAuth2` (13 call sites across
`tokens.go`, `registration.go`, `apikey.go`)
- **Provisioner daemon health read** → `AsSystemReadProvisionerDaemons`
(1 site in `healthcheck/provisioner.go`)
- **Provisionerd file cache paths** → `AsProvisionerd` (2 sites in
`provisionerdserver.go`, matching existing usage nearby)
<details>
<summary>Implementation notes</summary>
Each replacement actor is a strict subset of `AsSystemRestricted`. Every
DB method
at each call site is already covered by the narrower actor's
permissions:
- `subjectSystemOAuth2`: OAuth2App/Secret/CodeToken (all), ApiKey (Read,
Delete), User (Read), Organization (Read)
- `subjectSystemReadProvisionerDaemons`: ProvisionerDaemon (Read)
- `subjectProvisionerd`: File (Create, Read) plus provisionerd-scoped
resources
No new permissions added. `nolint:gocritic` comments updated to reflect
the new actors.
</details>
> 🤖 Created by a Coder Agent, reviewed by me.
131 lines
4.9 KiB
Go
131 lines
4.9 KiB
Go
package healthcheck
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"golang.org/x/mod/semver"
|
|
|
|
"github.com/coder/coder/v2/apiversion"
|
|
"github.com/coder/coder/v2/buildinfo"
|
|
"github.com/coder/coder/v2/coderd/database"
|
|
"github.com/coder/coder/v2/coderd/database/db2sdk"
|
|
"github.com/coder/coder/v2/coderd/database/dbauthz"
|
|
"github.com/coder/coder/v2/coderd/database/dbtime"
|
|
"github.com/coder/coder/v2/coderd/healthcheck/health"
|
|
"github.com/coder/coder/v2/coderd/provisionerdserver"
|
|
"github.com/coder/coder/v2/coderd/util/ptr"
|
|
"github.com/coder/coder/v2/codersdk/healthsdk"
|
|
"github.com/coder/coder/v2/provisionerd/proto"
|
|
)
|
|
|
|
type ProvisionerDaemonsReport healthsdk.ProvisionerDaemonsReport
|
|
|
|
type ProvisionerDaemonsReportDeps struct {
|
|
// Required
|
|
CurrentVersion string
|
|
CurrentAPIMajorVersion int
|
|
Store ProvisionerDaemonsStore
|
|
|
|
// Optional
|
|
TimeNow func() time.Time // Defaults to dbtime.Now
|
|
StaleInterval time.Duration // Defaults to 3 heartbeats
|
|
|
|
Dismissed bool
|
|
}
|
|
|
|
type ProvisionerDaemonsStore interface {
|
|
GetProvisionerDaemons(ctx context.Context) ([]database.ProvisionerDaemon, error)
|
|
}
|
|
|
|
func (r *ProvisionerDaemonsReport) Run(ctx context.Context, opts *ProvisionerDaemonsReportDeps) {
|
|
r.Items = make([]healthsdk.ProvisionerDaemonsReportItem, 0)
|
|
r.Severity = health.SeverityOK
|
|
r.Warnings = make([]health.Message, 0)
|
|
r.Dismissed = opts.Dismissed
|
|
|
|
if opts.TimeNow == nil {
|
|
opts.TimeNow = dbtime.Now
|
|
}
|
|
now := opts.TimeNow()
|
|
|
|
if opts.StaleInterval == 0 {
|
|
opts.StaleInterval = provisionerdserver.StaleInterval
|
|
}
|
|
|
|
if opts.CurrentVersion == "" {
|
|
r.Severity = health.SeverityError
|
|
r.Error = ptr.Ref("Developer error: CurrentVersion is empty!")
|
|
return
|
|
}
|
|
|
|
if opts.CurrentAPIMajorVersion == 0 {
|
|
r.Severity = health.SeverityError
|
|
r.Error = ptr.Ref("Developer error: CurrentAPIMajorVersion must be non-zero!")
|
|
return
|
|
}
|
|
|
|
if opts.Store == nil {
|
|
r.Severity = health.SeverityError
|
|
r.Error = ptr.Ref("Developer error: Store is nil!")
|
|
return
|
|
}
|
|
|
|
// nolint: gocritic // Read-only access to provisioner daemons for health check
|
|
daemons, err := opts.Store.GetProvisionerDaemons(dbauthz.AsSystemReadProvisionerDaemons(ctx))
|
|
if err != nil {
|
|
r.Severity = health.SeverityError
|
|
r.Error = ptr.Ref("error fetching provisioner daemons: " + err.Error())
|
|
return
|
|
}
|
|
|
|
recentDaemons := db2sdk.RecentProvisionerDaemons(now, opts.StaleInterval, daemons)
|
|
for _, daemon := range recentDaemons {
|
|
it := healthsdk.ProvisionerDaemonsReportItem{
|
|
ProvisionerDaemon: daemon,
|
|
Warnings: make([]health.Message, 0),
|
|
}
|
|
|
|
// For release versions, just check MAJOR.MINOR and ignore patch.
|
|
if !semver.IsValid(daemon.Version) {
|
|
if r.Severity.Value() < health.SeverityError.Value() {
|
|
r.Severity = health.SeverityError
|
|
}
|
|
r.Warnings = append(r.Warnings, health.Messagef(health.CodeUnknown, "Some provisioner daemons report invalid version information."))
|
|
it.Warnings = append(it.Warnings, health.Messagef(health.CodeUnknown, "Invalid version %q", daemon.Version))
|
|
} else if !buildinfo.VersionsMatch(opts.CurrentVersion, daemon.Version) {
|
|
if r.Severity.Value() < health.SeverityWarning.Value() {
|
|
r.Severity = health.SeverityWarning
|
|
}
|
|
r.Warnings = append(r.Warnings, health.Messagef(health.CodeProvisionerDaemonVersionMismatch, "Some provisioner daemons report mismatched versions."))
|
|
it.Warnings = append(it.Warnings, health.Messagef(health.CodeProvisionerDaemonVersionMismatch, "Mismatched version %q", daemon.Version))
|
|
}
|
|
|
|
// Provisioner daemon API version follows different rules; we just want to check the major API version and
|
|
// warn about potential later deprecations.
|
|
// When we check API versions of connecting provisioner daemons, all active provisioner daemons
|
|
// will, by necessity, have a compatible API version.
|
|
if maj, _, err := apiversion.Parse(daemon.APIVersion); err != nil {
|
|
if r.Severity.Value() < health.SeverityError.Value() {
|
|
r.Severity = health.SeverityError
|
|
}
|
|
r.Warnings = append(r.Warnings, health.Messagef(health.CodeUnknown, "Some provisioner daemons report invalid API version information."))
|
|
it.Warnings = append(it.Warnings, health.Messagef(health.CodeUnknown, "Invalid API version: %s", err.Error())) // contains version string
|
|
} else if maj != opts.CurrentAPIMajorVersion {
|
|
if r.Severity.Value() < health.SeverityWarning.Value() {
|
|
r.Severity = health.SeverityWarning
|
|
}
|
|
r.Warnings = append(r.Warnings, health.Messagef(health.CodeProvisionerDaemonAPIMajorVersionDeprecated, "Some provisioner daemons report deprecated major API versions. Consider upgrading!"))
|
|
it.Warnings = append(it.Warnings, health.Messagef(health.CodeProvisionerDaemonAPIMajorVersionDeprecated, "Deprecated major API version %d.", proto.CurrentMajor))
|
|
}
|
|
|
|
r.Items = append(r.Items, it)
|
|
}
|
|
|
|
if len(r.Items) == 0 {
|
|
r.Severity = health.SeverityError
|
|
r.Warnings = append(r.Warnings, health.Messagef(health.CodeProvisionerDaemonsNoProvisionerDaemons, "No active provisioner daemons found!"))
|
|
return
|
|
}
|
|
}
|