Files
portainer/api/motd/service.go
T
2026-03-26 15:01:48 +02:00

101 lines
2.3 KiB
Go

package motd
import (
"context"
"strings"
"sync/atomic"
"time"
"github.com/portainer/portainer/api/http/client"
"github.com/portainer/portainer/pkg/libcrypto"
libclient "github.com/portainer/portainer/pkg/libhttp/client"
"github.com/portainer/portainer/pkg/schedule"
"github.com/rs/zerolog/log"
"github.com/segmentio/encoding/json"
)
const refreshInterval = 6 * time.Hour
// Motd holds the processed message of the day data.
type Motd struct {
Title string `json:"Title"`
Message string `json:"Message"`
ContentLayout map[string]string `json:"ContentLayout"`
Style string `json:"Style"`
Hash []byte `json:"Hash"`
}
// Service fetches and caches the MOTD from an external URL.
type Service struct {
cached atomic.Pointer[Motd]
motdURL string
}
// NewService creates a new MOTD service that fetches from motdURL.
func NewService(motdURL string) *Service {
return &Service{
motdURL: motdURL,
}
}
// Start warms the cache immediately and refreshes it every refreshInterval.
func (s *Service) Start(ctx context.Context) {
if err := libclient.ExternalRequestDisabled(s.motdURL); err != nil {
return
}
go s.refresh()
go schedule.RunOnInterval(ctx, refreshInterval, s.refresh, nil)
}
// GetCached returns the cached MOTD
func (s *Service) GetCached() Motd {
motd := s.cached.Load()
if motd == nil {
return Motd{}
}
return *motd
}
type motdData struct {
Title string `json:"title"`
Message []string `json:"message"`
ContentLayout map[string]string `json:"contentLayout"`
Style string `json:"style"`
}
func (s *Service) refresh() {
if err := libclient.ExternalRequestDisabled(s.motdURL); err != nil {
log.Debug().Err(err).Msg("External request disabled: MOTD")
s.cached.Store(nil)
return
}
raw, err := client.Get(s.motdURL, 0)
if err != nil {
log.Debug().Err(err).Msg("Failed to fetch MOTD")
return
}
var data motdData
if err := json.Unmarshal(raw, &data); err != nil {
log.Debug().Err(err).Msg("Failed to parse MOTD")
return
}
message := strings.Join(data.Message, "\n")
hash := libcrypto.InsecureHashFromBytes([]byte(message))
s.cached.Store(&Motd{
Title: data.Title,
Message: message,
Hash: hash,
ContentLayout: data.ContentLayout,
Style: data.Style,
})
}