mirror of
https://github.com/coder/coder.git
synced 2025-07-03 16:13:58 +00:00
feat: use appearance.Fetcher in agentapi (#11770)
This PR updates the Agent API to use the appearance.Fetcher, which is set by entitlement code in Enterprise coderd. This brings the agentapi into compliance with the Enterprise feature.
This commit is contained in:
@ -17,6 +17,7 @@ import (
|
||||
|
||||
"cdr.dev/slog"
|
||||
agentproto "github.com/coder/coder/v2/agent/proto"
|
||||
"github.com/coder/coder/v2/coderd/appearance"
|
||||
"github.com/coder/coder/v2/coderd/database"
|
||||
"github.com/coder/coder/v2/coderd/database/pubsub"
|
||||
"github.com/coder/coder/v2/coderd/externalauth"
|
||||
@ -61,6 +62,7 @@ type Options struct {
|
||||
TailnetCoordinator *atomic.Pointer[tailnet.Coordinator]
|
||||
TemplateScheduleStore *atomic.Pointer[schedule.TemplateScheduleStore]
|
||||
StatsBatcher StatsBatcher
|
||||
AppearanceFetcher *atomic.Pointer[appearance.Fetcher]
|
||||
PublishWorkspaceUpdateFn func(ctx context.Context, workspaceID uuid.UUID)
|
||||
PublishWorkspaceAgentLogsUpdateFn func(ctx context.Context, workspaceAgentID uuid.UUID, msg agentsdk.LogsNotifyMessage)
|
||||
|
||||
@ -98,7 +100,7 @@ func New(opts Options) *API {
|
||||
}
|
||||
|
||||
api.ServiceBannerAPI = &ServiceBannerAPI{
|
||||
Database: opts.Database,
|
||||
appearanceFetcher: opts.AppearanceFetcher,
|
||||
}
|
||||
|
||||
api.StatsAPI = &StatsAPI{
|
||||
|
@ -2,37 +2,22 @@ package agentapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"sync/atomic"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
agentproto "github.com/coder/coder/v2/agent/proto"
|
||||
"github.com/coder/coder/v2/coderd/database"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
"github.com/coder/coder/v2/agent/proto"
|
||||
"github.com/coder/coder/v2/coderd/appearance"
|
||||
)
|
||||
|
||||
type ServiceBannerAPI struct {
|
||||
Database database.Store
|
||||
appearanceFetcher *atomic.Pointer[appearance.Fetcher]
|
||||
}
|
||||
|
||||
func (a *ServiceBannerAPI) GetServiceBanner(ctx context.Context, _ *agentproto.GetServiceBannerRequest) (*agentproto.ServiceBanner, error) {
|
||||
serviceBannerJSON, err := a.Database.GetServiceBanner(ctx)
|
||||
if err != nil && !xerrors.Is(err, sql.ErrNoRows) {
|
||||
return nil, xerrors.Errorf("get service banner: %w", err)
|
||||
func (a *ServiceBannerAPI) GetServiceBanner(ctx context.Context, _ *proto.GetServiceBannerRequest) (*proto.ServiceBanner, error) {
|
||||
cfg, err := (*a.appearanceFetcher.Load()).Fetch(ctx)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("fetch appearance: %w", err)
|
||||
}
|
||||
|
||||
var cfg codersdk.ServiceBannerConfig
|
||||
if serviceBannerJSON != "" {
|
||||
err = json.Unmarshal([]byte(serviceBannerJSON), &cfg)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("unmarshal json: %w, raw: %s", err, serviceBannerJSON)
|
||||
}
|
||||
}
|
||||
|
||||
return &agentproto.ServiceBanner{
|
||||
Enabled: cfg.Enabled,
|
||||
Message: cfg.Message,
|
||||
BackgroundColor: cfg.BackgroundColor,
|
||||
}, nil
|
||||
return proto.ServiceBannerFromSDK(cfg.ServiceBanner), nil
|
||||
}
|
||||
|
72
coderd/agentapi/servicebanner_internal_test.go
Normal file
72
coderd/agentapi/servicebanner_internal_test.go
Normal file
@ -0,0 +1,72 @@
|
||||
package agentapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
agentproto "github.com/coder/coder/v2/agent/proto"
|
||||
"github.com/coder/coder/v2/coderd/appearance"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGetServiceBanner(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cfg := codersdk.ServiceBannerConfig{
|
||||
Enabled: true,
|
||||
Message: "hello world",
|
||||
BackgroundColor: "#000000",
|
||||
}
|
||||
|
||||
var ff appearance.Fetcher = fakeFetcher{cfg: codersdk.AppearanceConfig{ServiceBanner: cfg}}
|
||||
ptr := atomic.Pointer[appearance.Fetcher]{}
|
||||
ptr.Store(&ff)
|
||||
|
||||
api := &ServiceBannerAPI{
|
||||
appearanceFetcher: &ptr,
|
||||
}
|
||||
|
||||
resp, err := api.GetServiceBanner(context.Background(), &agentproto.GetServiceBannerRequest{})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, &agentproto.ServiceBanner{
|
||||
Enabled: cfg.Enabled,
|
||||
Message: cfg.Message,
|
||||
BackgroundColor: cfg.BackgroundColor,
|
||||
}, resp)
|
||||
})
|
||||
|
||||
t.Run("FetchError", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
expectedErr := xerrors.New("badness")
|
||||
var ff appearance.Fetcher = fakeFetcher{err: expectedErr}
|
||||
ptr := atomic.Pointer[appearance.Fetcher]{}
|
||||
ptr.Store(&ff)
|
||||
|
||||
api := &ServiceBannerAPI{
|
||||
appearanceFetcher: &ptr,
|
||||
}
|
||||
|
||||
resp, err := api.GetServiceBanner(context.Background(), &agentproto.GetServiceBannerRequest{})
|
||||
require.Error(t, err)
|
||||
require.ErrorIs(t, err, expectedErr)
|
||||
require.Nil(t, resp)
|
||||
})
|
||||
}
|
||||
|
||||
type fakeFetcher struct {
|
||||
cfg codersdk.AppearanceConfig
|
||||
err error
|
||||
}
|
||||
|
||||
func (f fakeFetcher) Fetch(context.Context) (codersdk.AppearanceConfig, error) {
|
||||
return f.cfg, f.err
|
||||
}
|
@ -1,84 +0,0 @@
|
||||
package agentapi_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
|
||||
agentproto "github.com/coder/coder/v2/agent/proto"
|
||||
"github.com/coder/coder/v2/coderd/agentapi"
|
||||
"github.com/coder/coder/v2/coderd/database/dbmock"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
)
|
||||
|
||||
func TestGetServiceBanner(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cfg := codersdk.ServiceBannerConfig{
|
||||
Enabled: true,
|
||||
Message: "hello world",
|
||||
BackgroundColor: "#000000",
|
||||
}
|
||||
cfgJSON, err := json.Marshal(cfg)
|
||||
require.NoError(t, err)
|
||||
|
||||
dbM := dbmock.NewMockStore(gomock.NewController(t))
|
||||
dbM.EXPECT().GetServiceBanner(gomock.Any()).Return(string(cfgJSON), nil)
|
||||
|
||||
api := &agentapi.ServiceBannerAPI{
|
||||
Database: dbM,
|
||||
}
|
||||
|
||||
resp, err := api.GetServiceBanner(context.Background(), &agentproto.GetServiceBannerRequest{})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, &agentproto.ServiceBanner{
|
||||
Enabled: cfg.Enabled,
|
||||
Message: cfg.Message,
|
||||
BackgroundColor: cfg.BackgroundColor,
|
||||
}, resp)
|
||||
})
|
||||
|
||||
t.Run("None", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dbM := dbmock.NewMockStore(gomock.NewController(t))
|
||||
dbM.EXPECT().GetServiceBanner(gomock.Any()).Return("", sql.ErrNoRows)
|
||||
|
||||
api := &agentapi.ServiceBannerAPI{
|
||||
Database: dbM,
|
||||
}
|
||||
|
||||
resp, err := api.GetServiceBanner(context.Background(), &agentproto.GetServiceBannerRequest{})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, &agentproto.ServiceBanner{
|
||||
Enabled: false,
|
||||
Message: "",
|
||||
BackgroundColor: "",
|
||||
}, resp)
|
||||
})
|
||||
|
||||
t.Run("BadJSON", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dbM := dbmock.NewMockStore(gomock.NewController(t))
|
||||
dbM.EXPECT().GetServiceBanner(gomock.Any()).Return("hi", nil)
|
||||
|
||||
api := &agentapi.ServiceBannerAPI{
|
||||
Database: dbM,
|
||||
}
|
||||
|
||||
resp, err := api.GetServiceBanner(context.Background(), &agentproto.GetServiceBannerRequest{})
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "unmarshal json")
|
||||
require.Nil(t, resp)
|
||||
})
|
||||
}
|
Reference in New Issue
Block a user