2024-01-08 09:29:04 +00:00
package healthcheck
import (
"context"
2024-01-08 13:55:00 +00:00
"sort"
2024-01-08 09:29:04 +00:00
"time"
"golang.org/x/mod/semver"
2024-02-16 18:43:07 +00:00
"github.com/coder/coder/v2/apiversion"
2024-01-08 09:29:04 +00:00
"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"
2024-03-26 17:44:31 +00:00
"github.com/coder/coder/v2/codersdk/healthsdk"
2024-02-20 12:44:19 +00:00
"github.com/coder/coder/v2/provisionerd/proto"
2024-01-08 09:29:04 +00:00
)
2024-03-26 17:44:31 +00:00
type ProvisionerDaemonsReport healthsdk . ProvisionerDaemonsReport
2024-01-08 09:29:04 +00:00
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 ) {
2024-03-26 17:44:31 +00:00
r . Items = make ( [ ] healthsdk . ProvisionerDaemonsReportItem , 0 )
2024-01-08 09:29:04 +00:00
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 . DefaultHeartbeatInterval * 3
}
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 // need an actor to fetch provisioner daemons
daemons , err := opts . Store . GetProvisionerDaemons ( dbauthz . AsSystemRestricted ( ctx ) )
if err != nil {
r . Severity = health . SeverityError
r . Error = ptr . Ref ( "error fetching provisioner daemons: " + err . Error ( ) )
return
}
2024-01-08 13:55:00 +00:00
// Ensure stable order for display and for tests
sort . Slice ( daemons , func ( i , j int ) bool {
return daemons [ i ] . Name < daemons [ j ] . Name
} )
2024-01-08 09:29:04 +00:00
for _ , daemon := range daemons {
// Daemon never connected, skip.
if ! daemon . LastSeenAt . Valid {
continue
}
// Daemon has gone away, skip.
if now . Sub ( daemon . LastSeenAt . Time ) > ( opts . StaleInterval ) {
continue
}
2024-03-26 17:44:31 +00:00
it := healthsdk . ProvisionerDaemonsReportItem {
2024-01-08 13:55:00 +00:00
ProvisionerDaemon : db2sdk . ProvisionerDaemon ( daemon ) ,
Warnings : make ( [ ] health . Message , 0 ) ,
}
2024-01-08 09:29:04 +00:00
// 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
}
2024-01-08 13:55:00 +00:00
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 ) )
2024-01-08 09:29:04 +00:00
} else if ! buildinfo . VersionsMatch ( opts . CurrentVersion , daemon . Version ) {
if r . Severity . Value ( ) < health . SeverityWarning . Value ( ) {
r . Severity = health . SeverityWarning
}
2024-01-08 13:55:00 +00:00
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 ) )
2024-01-08 09:29:04 +00:00
}
// 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
}
2024-01-08 13:55:00 +00:00
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
2024-01-08 09:29:04 +00:00
} else if maj != opts . CurrentAPIMajorVersion {
if r . Severity . Value ( ) < health . SeverityWarning . Value ( ) {
r . Severity = health . SeverityWarning
}
2024-01-08 13:55:00 +00:00
r . Warnings = append ( r . Warnings , health . Messagef ( health . CodeProvisionerDaemonAPIMajorVersionDeprecated , "Some provisioner daemons report deprecated major API versions. Consider upgrading!" ) )
2024-02-20 12:44:19 +00:00
it . Warnings = append ( it . Warnings , health . Messagef ( health . CodeProvisionerDaemonAPIMajorVersionDeprecated , "Deprecated major API version %d." , proto . CurrentMajor ) )
2024-01-08 09:29:04 +00:00
}
2024-01-08 13:55:00 +00:00
r . Items = append ( r . Items , it )
2024-01-08 09:29:04 +00:00
}
2024-01-08 13:55:00 +00:00
if len ( r . Items ) == 0 {
2024-01-08 09:29:04 +00:00
r . Severity = health . SeverityError
2024-01-12 11:22:59 +00:00
r . Warnings = append ( r . Warnings , health . Messagef ( health . CodeProvisionerDaemonsNoProvisionerDaemons , "No active provisioner daemons found!" ) )
2024-01-08 09:29:04 +00:00
return
}
}