**Related issue:** Resolves #47090 Fleet crashes into `CrashLoopBackOff` on startup when deployed on Kubernetes with `readOnlyRootFilesystem: true` and **no** S3 software installers bucket configured: ``` Failed to start: initializing filesystem org logo store: mkdir /tmp/org-logos: read-only file system ``` I realised I was calling `initFatal` when failing to create a directory on the filesystem which doesn't match the pattern of `logging` + `creating a "failing" store` (one that is initialized but fails all operations) as we do for e.g. software title icons (see NewFailingSoftwareTitleIconStore). Per this slack conversation: https://fleetdm.slack.com/archives/C084F4MKYSJ/p1780931127976389, we decided to fall back to a database-backed storage: <img width="737" height="114" alt="Screenshot 2026-06-08 at 3 16 28 PM" src="https://github.com/user-attachments/assets/2a6ff75f-b382-40ba-81d9-3be3cfbd648a" /> # Checklist for submitter - [x] Changes file added for user-visible changes in `changes/`. ## Testing - [x] Added/updated automated tests - [x] QA'd all new/changed functionality manually Commented out this line to force filesystem usage: <img width="615" height="71" alt="Screenshot 2026-06-08 at 1 18 53 PM" src="https://github.com/user-attachments/assets/85043c88-5c8c-48a0-8145-098fba9513bd" /> #### Before Server crashes <img width="1278" height="124" alt="Screenshot 2026-06-08 at 1 18 17 PM" src="https://github.com/user-attachments/assets/7b788a24-131a-47a3-8580-fcd9fda8b449" /> #### After Server starts and logo upload works - Without --dev_license https://github.com/user-attachments/assets/58c5ebf9-cf52-4ba0-ac98-9675e7eef92c - With --dev_license https://github.com/user-attachments/assets/117bb812-31bd-4849-927c-93cafd1a71d7 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Release Notes * **New Features** - Organization logos now support database storage as the fallback option when S3 software installers bucket is not configured, replacing local filesystem storage for improved reliability. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
67 lines
2.0 KiB
Go
67 lines
2.0 KiB
Go
package mysql
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"database/sql"
|
|
"errors"
|
|
"io"
|
|
|
|
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
|
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
|
"github.com/jmoiron/sqlx"
|
|
)
|
|
|
|
type orgLogoStore struct {
|
|
ds *Datastore
|
|
}
|
|
|
|
var _ fleet.OrgLogoStore = (*orgLogoStore)(nil)
|
|
|
|
func (ds *Datastore) NewOrgLogoStore() fleet.OrgLogoStore {
|
|
return &orgLogoStore{ds: ds}
|
|
}
|
|
|
|
func (s *orgLogoStore) Put(ctx context.Context, mode fleet.OrgLogoMode, content io.ReadSeeker) error {
|
|
data, err := io.ReadAll(content)
|
|
if err != nil {
|
|
return ctxerr.Wrap(ctx, err, "reading org logo content")
|
|
}
|
|
const stmt = `
|
|
INSERT INTO org_logo (mode, data, uploaded_at)
|
|
VALUES (?, ?, NOW(6))
|
|
ON DUPLICATE KEY UPDATE data = ?, uploaded_at = NOW(6)`
|
|
if _, err := s.ds.writer(ctx).ExecContext(ctx, stmt, string(mode), data, data); err != nil {
|
|
return ctxerr.Wrap(ctx, err, "storing org logo")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *orgLogoStore) Get(ctx context.Context, mode fleet.OrgLogoMode) (io.ReadCloser, int64, error) {
|
|
var data []byte
|
|
err := sqlx.GetContext(ctx, s.ds.reader(ctx), &data, `SELECT data FROM org_logo WHERE mode = ?`, string(mode))
|
|
if err != nil {
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return nil, 0, ctxerr.Wrap(ctx, notFound("OrgLogo").WithName(string(mode)), "get org logo")
|
|
}
|
|
return nil, 0, ctxerr.Wrap(ctx, err, "get org logo")
|
|
}
|
|
return io.NopCloser(bytes.NewReader(data)), int64(len(data)), nil
|
|
}
|
|
|
|
func (s *orgLogoStore) Delete(ctx context.Context, mode fleet.OrgLogoMode) error {
|
|
if _, err := s.ds.writer(ctx).ExecContext(ctx, `DELETE FROM org_logo WHERE mode = ?`, string(mode)); err != nil {
|
|
return ctxerr.Wrap(ctx, err, "deleting org logo")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *orgLogoStore) Exists(ctx context.Context, mode fleet.OrgLogoMode) (bool, error) {
|
|
var exists bool
|
|
err := sqlx.GetContext(ctx, s.ds.reader(ctx), &exists, `SELECT EXISTS(SELECT 1 FROM org_logo WHERE mode = ?)`, string(mode))
|
|
if err != nil {
|
|
return false, ctxerr.Wrap(ctx, err, "checking org logo existence")
|
|
}
|
|
return exists, nil
|
|
}
|