mirror of
https://github.com/coder/coder.git
synced 2025-03-16 23:40:29 +00:00
feat: use Agent v2 API for Service Banner (#11806)
Agent uses the v2 API for the service banner, rather than the v1 HTTP API. One of several for #10534
This commit is contained in:
@ -41,6 +41,7 @@ import (
|
||||
"github.com/coder/coder/v2/agent/agentproc"
|
||||
"github.com/coder/coder/v2/agent/agentscripts"
|
||||
"github.com/coder/coder/v2/agent/agentssh"
|
||||
"github.com/coder/coder/v2/agent/proto"
|
||||
"github.com/coder/coder/v2/agent/reconnectingpty"
|
||||
"github.com/coder/coder/v2/buildinfo"
|
||||
"github.com/coder/coder/v2/cli/gitauth"
|
||||
@ -95,7 +96,6 @@ type Client interface {
|
||||
PostStartup(ctx context.Context, req agentsdk.PostStartupRequest) error
|
||||
PostMetadata(ctx context.Context, req agentsdk.PostMetadataRequest) error
|
||||
PatchLogs(ctx context.Context, req agentsdk.PatchLogs) error
|
||||
GetServiceBanner(ctx context.Context) (codersdk.ServiceBannerConfig, error)
|
||||
}
|
||||
|
||||
type Agent interface {
|
||||
@ -269,7 +269,6 @@ func (a *agent) init(ctx context.Context) {
|
||||
func (a *agent) runLoop(ctx context.Context) {
|
||||
go a.reportLifecycleLoop(ctx)
|
||||
go a.reportMetadataLoop(ctx)
|
||||
go a.fetchServiceBannerLoop(ctx)
|
||||
go a.manageProcessPriorityLoop(ctx)
|
||||
|
||||
for retrier := retry.New(100*time.Millisecond, 10*time.Second); retrier.Wait(ctx); {
|
||||
@ -662,22 +661,23 @@ func (a *agent) setLifecycle(ctx context.Context, state codersdk.WorkspaceAgentL
|
||||
// fetchServiceBannerLoop fetches the service banner on an interval. It will
|
||||
// not be fetched immediately; the expectation is that it is primed elsewhere
|
||||
// (and must be done before the session actually starts).
|
||||
func (a *agent) fetchServiceBannerLoop(ctx context.Context) {
|
||||
func (a *agent) fetchServiceBannerLoop(ctx context.Context, aAPI proto.DRPCAgentClient) error {
|
||||
ticker := time.NewTicker(a.serviceBannerRefreshInterval)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
return ctx.Err()
|
||||
case <-ticker.C:
|
||||
serviceBanner, err := a.client.GetServiceBanner(ctx)
|
||||
sbp, err := aAPI.GetServiceBanner(ctx, &proto.GetServiceBannerRequest{})
|
||||
if err != nil {
|
||||
if ctx.Err() != nil {
|
||||
return
|
||||
return ctx.Err()
|
||||
}
|
||||
a.logger.Error(ctx, "failed to update service banner", slog.Error(err))
|
||||
continue
|
||||
return err
|
||||
}
|
||||
serviceBanner := proto.SDKServiceBannerFromProto(sbp)
|
||||
a.serviceBanner.Store(&serviceBanner)
|
||||
}
|
||||
}
|
||||
@ -693,10 +693,24 @@ func (a *agent) run(ctx context.Context) error {
|
||||
}
|
||||
a.sessionToken.Store(&sessionToken)
|
||||
|
||||
serviceBanner, err := a.client.GetServiceBanner(ctx)
|
||||
// Listen returns the dRPC connection we use for the Agent v2+ API
|
||||
conn, err := a.client.Listen(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
cErr := conn.Close()
|
||||
if cErr != nil {
|
||||
a.logger.Debug(ctx, "error closing drpc connection", slog.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
aAPI := proto.NewDRPCAgentClient(conn)
|
||||
sbp, err := aAPI.GetServiceBanner(ctx, &proto.GetServiceBannerRequest{})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("fetch service banner: %w", err)
|
||||
}
|
||||
serviceBanner := proto.SDKServiceBannerFromProto(sbp)
|
||||
a.serviceBanner.Store(&serviceBanner)
|
||||
|
||||
manifest, err := a.client.Manifest(ctx)
|
||||
@ -821,18 +835,6 @@ func (a *agent) run(ctx context.Context) error {
|
||||
network.SetBlockEndpoints(manifest.DisableDirectConnections)
|
||||
}
|
||||
|
||||
// Listen returns the dRPC connection we use for both Coordinator and DERPMap updates
|
||||
conn, err := a.client.Listen(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
cErr := conn.Close()
|
||||
if cErr != nil {
|
||||
a.logger.Debug(ctx, "error closing drpc connection", slog.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
eg, egCtx := errgroup.WithContext(ctx)
|
||||
eg.Go(func() error {
|
||||
a.logger.Debug(egCtx, "running tailnet connection coordinator")
|
||||
@ -852,6 +854,15 @@ func (a *agent) run(ctx context.Context) error {
|
||||
return nil
|
||||
})
|
||||
|
||||
eg.Go(func() error {
|
||||
a.logger.Debug(egCtx, "running fetch server banner loop")
|
||||
err := a.fetchServiceBannerLoop(egCtx, aAPI)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("fetch server banner loop: %w", err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return eg.Wait()
|
||||
}
|
||||
|
||||
|
@ -35,6 +35,7 @@ import (
|
||||
"github.com/spf13/afero"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/valyala/fasthttp/fasthttputil"
|
||||
"go.uber.org/goleak"
|
||||
"go.uber.org/mock/gomock"
|
||||
"golang.org/x/crypto/ssh"
|
||||
@ -2026,7 +2027,10 @@ func setupAgent(t *testing.T, metadata agentsdk.Manifest, ptyTimeout time.Durati
|
||||
afero.Fs,
|
||||
agent.Agent,
|
||||
) {
|
||||
logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug)
|
||||
logger := slogtest.Make(t, &slogtest.Options{
|
||||
// we get this error when closing the Agent API
|
||||
IgnoredErrorIs: append(slogtest.DefaultIgnoredErrorIs, fasthttputil.ErrInmemoryListenerClosed),
|
||||
}).Leveled(slog.LevelDebug)
|
||||
if metadata.DERPMap == nil {
|
||||
metadata.DERPMap, _ = tailnettest.RunDERPAndSTUN(t)
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
"tailscale.com/tailcfg"
|
||||
|
||||
"cdr.dev/slog"
|
||||
agentproto "github.com/coder/coder/v2/agent/proto"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
"github.com/coder/coder/v2/codersdk/agentsdk"
|
||||
drpcsdk "github.com/coder/coder/v2/codersdk/drpc"
|
||||
@ -48,6 +49,9 @@ func NewClient(t testing.TB,
|
||||
}
|
||||
err := proto.DRPCRegisterTailnet(mux, drpcService)
|
||||
require.NoError(t, err)
|
||||
fakeAAPI := NewFakeAgentAPI(t, logger)
|
||||
err = agentproto.DRPCRegisterAgent(mux, fakeAAPI)
|
||||
require.NoError(t, err)
|
||||
server := drpcserver.NewWithOptions(mux, drpcserver.Options{
|
||||
Log: func(err error) {
|
||||
if xerrors.Is(err, io.EOF) {
|
||||
@ -64,22 +68,23 @@ func NewClient(t testing.TB,
|
||||
statsChan: statsChan,
|
||||
coordinator: coordinator,
|
||||
server: server,
|
||||
fakeAgentAPI: fakeAAPI,
|
||||
derpMapUpdates: derpMapUpdates,
|
||||
}
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
t testing.TB
|
||||
logger slog.Logger
|
||||
agentID uuid.UUID
|
||||
manifest agentsdk.Manifest
|
||||
metadata map[string]agentsdk.Metadata
|
||||
statsChan chan *agentsdk.Stats
|
||||
coordinator tailnet.Coordinator
|
||||
server *drpcserver.Server
|
||||
LastWorkspaceAgent func()
|
||||
PatchWorkspaceLogs func() error
|
||||
GetServiceBannerFunc func() (codersdk.ServiceBannerConfig, error)
|
||||
t testing.TB
|
||||
logger slog.Logger
|
||||
agentID uuid.UUID
|
||||
manifest agentsdk.Manifest
|
||||
metadata map[string]agentsdk.Metadata
|
||||
statsChan chan *agentsdk.Stats
|
||||
coordinator tailnet.Coordinator
|
||||
server *drpcserver.Server
|
||||
fakeAgentAPI *FakeAgentAPI
|
||||
LastWorkspaceAgent func()
|
||||
PatchWorkspaceLogs func() error
|
||||
|
||||
mu sync.Mutex // Protects following.
|
||||
lifecycleStates []codersdk.WorkspaceAgentLifecycle
|
||||
@ -221,20 +226,7 @@ func (c *Client) PatchLogs(ctx context.Context, logs agentsdk.PatchLogs) error {
|
||||
}
|
||||
|
||||
func (c *Client) SetServiceBannerFunc(f func() (codersdk.ServiceBannerConfig, error)) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
c.GetServiceBannerFunc = f
|
||||
}
|
||||
|
||||
func (c *Client) GetServiceBanner(ctx context.Context) (codersdk.ServiceBannerConfig, error) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.logger.Debug(ctx, "get service banner")
|
||||
if c.GetServiceBannerFunc != nil {
|
||||
return c.GetServiceBannerFunc()
|
||||
}
|
||||
return codersdk.ServiceBannerConfig{}, nil
|
||||
c.fakeAgentAPI.SetServiceBannerFunc(f)
|
||||
}
|
||||
|
||||
func (c *Client) PushDERPMapUpdate(update *tailcfg.DERPMap) error {
|
||||
@ -254,3 +246,73 @@ type closeFunc func() error
|
||||
func (c closeFunc) Close() error {
|
||||
return c()
|
||||
}
|
||||
|
||||
type FakeAgentAPI struct {
|
||||
sync.Mutex
|
||||
t testing.TB
|
||||
logger slog.Logger
|
||||
|
||||
getServiceBannerFunc func() (codersdk.ServiceBannerConfig, error)
|
||||
}
|
||||
|
||||
func (*FakeAgentAPI) GetManifest(context.Context, *agentproto.GetManifestRequest) (*agentproto.Manifest, error) {
|
||||
// TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (f *FakeAgentAPI) SetServiceBannerFunc(fn func() (codersdk.ServiceBannerConfig, error)) {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
f.getServiceBannerFunc = fn
|
||||
f.logger.Info(context.Background(), "updated ServiceBannerFunc")
|
||||
}
|
||||
|
||||
func (f *FakeAgentAPI) GetServiceBanner(context.Context, *agentproto.GetServiceBannerRequest) (*agentproto.ServiceBanner, error) {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
if f.getServiceBannerFunc == nil {
|
||||
return &agentproto.ServiceBanner{}, nil
|
||||
}
|
||||
sb, err := f.getServiceBannerFunc()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return agentproto.ServiceBannerFromSDK(sb), nil
|
||||
}
|
||||
|
||||
func (*FakeAgentAPI) UpdateStats(context.Context, *agentproto.UpdateStatsRequest) (*agentproto.UpdateStatsResponse, error) {
|
||||
// TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (*FakeAgentAPI) UpdateLifecycle(context.Context, *agentproto.UpdateLifecycleRequest) (*agentproto.Lifecycle, error) {
|
||||
// TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (*FakeAgentAPI) BatchUpdateAppHealths(context.Context, *agentproto.BatchUpdateAppHealthRequest) (*agentproto.BatchUpdateAppHealthResponse, error) {
|
||||
// TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (*FakeAgentAPI) UpdateStartup(context.Context, *agentproto.UpdateStartupRequest) (*agentproto.Startup, error) {
|
||||
// TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (*FakeAgentAPI) BatchUpdateMetadata(context.Context, *agentproto.BatchUpdateMetadataRequest) (*agentproto.BatchUpdateMetadataResponse, error) {
|
||||
// TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (*FakeAgentAPI) BatchCreateLogs(context.Context, *agentproto.BatchCreateLogsRequest) (*agentproto.BatchCreateLogsResponse, error) {
|
||||
// TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func NewFakeAgentAPI(t testing.TB, logger slog.Logger) *FakeAgentAPI {
|
||||
return &FakeAgentAPI{
|
||||
t: t,
|
||||
logger: logger.Named("FakeAgentAPI"),
|
||||
}
|
||||
}
|
||||
|
@ -83,7 +83,8 @@ func TestWorkspaceAgent(t *testing.T) {
|
||||
|
||||
ctx := inv.Context()
|
||||
clitest.Start(t, inv)
|
||||
coderdtest.AwaitWorkspaceAgents(t, client, r.Workspace.ID)
|
||||
coderdtest.NewWorkspaceAgentWaiter(t, client, r.Workspace.ID).
|
||||
MatchResources(matchAgentWithVersion).Wait()
|
||||
workspace, err := client.Workspace(ctx, r.Workspace.ID)
|
||||
require.NoError(t, err)
|
||||
resources := workspace.LatestBuild.Resources
|
||||
@ -120,7 +121,9 @@ func TestWorkspaceAgent(t *testing.T) {
|
||||
|
||||
clitest.Start(t, inv)
|
||||
ctx := inv.Context()
|
||||
coderdtest.AwaitWorkspaceAgents(t, client, r.Workspace.ID)
|
||||
coderdtest.NewWorkspaceAgentWaiter(t, client, r.Workspace.ID).
|
||||
MatchResources(matchAgentWithVersion).
|
||||
Wait()
|
||||
workspace, err := client.Workspace(ctx, r.Workspace.ID)
|
||||
require.NoError(t, err)
|
||||
resources := workspace.LatestBuild.Resources
|
||||
@ -161,7 +164,9 @@ func TestWorkspaceAgent(t *testing.T) {
|
||||
)
|
||||
|
||||
ctx := inv.Context()
|
||||
coderdtest.AwaitWorkspaceAgents(t, client, r.Workspace.ID)
|
||||
coderdtest.NewWorkspaceAgentWaiter(t, client, r.Workspace.ID).
|
||||
MatchResources(matchAgentWithVersion).
|
||||
Wait()
|
||||
workspace, err := client.Workspace(ctx, r.Workspace.ID)
|
||||
require.NoError(t, err)
|
||||
resources := workspace.LatestBuild.Resources
|
||||
@ -212,7 +217,8 @@ func TestWorkspaceAgent(t *testing.T) {
|
||||
|
||||
clitest.Start(t, inv)
|
||||
|
||||
resources := coderdtest.AwaitWorkspaceAgents(t, client, r.Workspace.ID)
|
||||
resources := coderdtest.NewWorkspaceAgentWaiter(t, client, r.Workspace.ID).
|
||||
MatchResources(matchAgentWithSubsystems).Wait()
|
||||
require.Len(t, resources, 1)
|
||||
require.Len(t, resources[0].Agents, 1)
|
||||
require.Len(t, resources[0].Agents[0].Subsystems, 2)
|
||||
@ -221,3 +227,29 @@ func TestWorkspaceAgent(t *testing.T) {
|
||||
require.Equal(t, codersdk.AgentSubsystemExectrace, resources[0].Agents[0].Subsystems[1])
|
||||
})
|
||||
}
|
||||
|
||||
func matchAgentWithVersion(rs []codersdk.WorkspaceResource) bool {
|
||||
if len(rs) < 1 {
|
||||
return false
|
||||
}
|
||||
if len(rs[0].Agents) < 1 {
|
||||
return false
|
||||
}
|
||||
if rs[0].Agents[0].Version == "" {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func matchAgentWithSubsystems(rs []codersdk.WorkspaceResource) bool {
|
||||
if len(rs) < 1 {
|
||||
return false
|
||||
}
|
||||
if len(rs[0].Agents) < 1 {
|
||||
return false
|
||||
}
|
||||
if len(rs[0].Agents[0].Subsystems) < 1 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
@ -5,12 +5,12 @@ import (
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"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) {
|
||||
|
@ -915,23 +915,67 @@ func AwaitWorkspaceBuildJobCompleted(t testing.TB, client *codersdk.Client, buil
|
||||
// AwaitWorkspaceAgents waits for all resources with agents to be connected. If
|
||||
// specific agents are provided, it will wait for those agents to be connected
|
||||
// but will not fail if other agents are not connected.
|
||||
//
|
||||
// Deprecated: Use NewWorkspaceAgentWaiter
|
||||
func AwaitWorkspaceAgents(t testing.TB, client *codersdk.Client, workspaceID uuid.UUID, agentNames ...string) []codersdk.WorkspaceResource {
|
||||
t.Helper()
|
||||
return NewWorkspaceAgentWaiter(t, client, workspaceID).AgentNames(agentNames).Wait()
|
||||
}
|
||||
|
||||
agentNamesMap := make(map[string]struct{}, len(agentNames))
|
||||
for _, name := range agentNames {
|
||||
// WorkspaceAgentWaiter waits for all resources with agents to be connected. If
|
||||
// specific agents are provided using AgentNames(), it will wait for those agents
|
||||
// to be connected but will not fail if other agents are not connected.
|
||||
type WorkspaceAgentWaiter struct {
|
||||
t testing.TB
|
||||
client *codersdk.Client
|
||||
workspaceID uuid.UUID
|
||||
agentNames []string
|
||||
resourcesMatcher func([]codersdk.WorkspaceResource) bool
|
||||
}
|
||||
|
||||
// NewWorkspaceAgentWaiter returns an object that waits for agents to connect when
|
||||
// you call Wait() on it.
|
||||
func NewWorkspaceAgentWaiter(t testing.TB, client *codersdk.Client, workspaceID uuid.UUID) WorkspaceAgentWaiter {
|
||||
return WorkspaceAgentWaiter{
|
||||
t: t,
|
||||
client: client,
|
||||
workspaceID: workspaceID,
|
||||
}
|
||||
}
|
||||
|
||||
// AgentNames instructs the waiter to wait for the given, named agents to be connected and will
|
||||
// return even if other agents are not connected.
|
||||
func (w WorkspaceAgentWaiter) AgentNames(names []string) WorkspaceAgentWaiter {
|
||||
//nolint: revive // returns modified struct
|
||||
w.agentNames = names
|
||||
return w
|
||||
}
|
||||
|
||||
// MatchResources instructs the waiter to wait until the workspace has resources that cause the
|
||||
// provided matcher function to return true.
|
||||
func (w WorkspaceAgentWaiter) MatchResources(m func([]codersdk.WorkspaceResource) bool) WorkspaceAgentWaiter {
|
||||
//nolint: revive // returns modified struct
|
||||
w.resourcesMatcher = m
|
||||
return w
|
||||
}
|
||||
|
||||
// Wait waits for the agent(s) to connect and fails the test if they do not within testutil.WaitLong
|
||||
func (w WorkspaceAgentWaiter) Wait() []codersdk.WorkspaceResource {
|
||||
w.t.Helper()
|
||||
|
||||
agentNamesMap := make(map[string]struct{}, len(w.agentNames))
|
||||
for _, name := range w.agentNames {
|
||||
agentNamesMap[name] = struct{}{}
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
t.Logf("waiting for workspace agents (workspace %s)", workspaceID)
|
||||
w.t.Logf("waiting for workspace agents (workspace %s)", w.workspaceID)
|
||||
var resources []codersdk.WorkspaceResource
|
||||
require.Eventually(t, func() bool {
|
||||
require.Eventually(w.t, func() bool {
|
||||
var err error
|
||||
workspace, err := client.Workspace(ctx, workspaceID)
|
||||
if !assert.NoError(t, err) {
|
||||
workspace, err := w.client.Workspace(ctx, w.workspaceID)
|
||||
if !assert.NoError(w.t, err) {
|
||||
return false
|
||||
}
|
||||
if workspace.LatestBuild.Job.CompletedAt == nil {
|
||||
@ -943,23 +987,25 @@ func AwaitWorkspaceAgents(t testing.TB, client *codersdk.Client, workspaceID uui
|
||||
|
||||
for _, resource := range workspace.LatestBuild.Resources {
|
||||
for _, agent := range resource.Agents {
|
||||
if len(agentNames) > 0 {
|
||||
if len(w.agentNames) > 0 {
|
||||
if _, ok := agentNamesMap[agent.Name]; !ok {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if agent.Status != codersdk.WorkspaceAgentConnected {
|
||||
t.Logf("agent %s not connected yet", agent.Name)
|
||||
w.t.Logf("agent %s not connected yet", agent.Name)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
resources = workspace.LatestBuild.Resources
|
||||
|
||||
return true
|
||||
if w.resourcesMatcher == nil {
|
||||
return true
|
||||
}
|
||||
return w.resourcesMatcher(resources)
|
||||
}, testutil.WaitLong, testutil.IntervalMedium)
|
||||
t.Logf("got workspace agents (workspace %s)", workspaceID)
|
||||
w.t.Logf("got workspace agents (workspace %s)", w.workspaceID)
|
||||
return resources
|
||||
}
|
||||
|
||||
|
@ -29,6 +29,8 @@ import (
|
||||
"cdr.dev/slog"
|
||||
"cdr.dev/slog/sloggers/slogtest"
|
||||
"github.com/coder/coder/v2/agent"
|
||||
"github.com/coder/coder/v2/agent/agenttest"
|
||||
agentproto "github.com/coder/coder/v2/agent/proto"
|
||||
"github.com/coder/coder/v2/coderd/wsconncache"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
"github.com/coder/coder/v2/codersdk/agentsdk"
|
||||
@ -171,13 +173,12 @@ func setupAgent(t *testing.T, manifest agentsdk.Manifest, ptyTimeout time.Durati
|
||||
_ = coordinator.Close()
|
||||
})
|
||||
manifest.AgentID = uuid.New()
|
||||
aC := &client{
|
||||
t: t,
|
||||
agentID: manifest.AgentID,
|
||||
manifest: manifest,
|
||||
coordinator: coordinator,
|
||||
derpMapUpdates: make(chan *tailcfg.DERPMap),
|
||||
}
|
||||
aC := newClient(
|
||||
t,
|
||||
slogtest.Make(t, nil).Leveled(slog.LevelDebug),
|
||||
manifest,
|
||||
coordinator,
|
||||
)
|
||||
t.Cleanup(aC.close)
|
||||
closer := agent.New(agent.Options{
|
||||
Client: aC,
|
||||
@ -239,6 +240,45 @@ type client struct {
|
||||
coordinator tailnet.Coordinator
|
||||
closeOnce sync.Once
|
||||
derpMapUpdates chan *tailcfg.DERPMap
|
||||
server *drpcserver.Server
|
||||
fakeAgentAPI *agenttest.FakeAgentAPI
|
||||
}
|
||||
|
||||
func newClient(t *testing.T, logger slog.Logger, manifest agentsdk.Manifest, coordinator tailnet.Coordinator) *client {
|
||||
logger = logger.Named("drpc")
|
||||
coordPtr := atomic.Pointer[tailnet.Coordinator]{}
|
||||
coordPtr.Store(&coordinator)
|
||||
mux := drpcmux.New()
|
||||
derpMapUpdates := make(chan *tailcfg.DERPMap)
|
||||
drpcService := &tailnet.DRPCService{
|
||||
CoordPtr: &coordPtr,
|
||||
Logger: logger,
|
||||
DerpMapUpdateFrequency: time.Microsecond,
|
||||
DerpMapFn: func() *tailcfg.DERPMap { return <-derpMapUpdates },
|
||||
}
|
||||
err := proto.DRPCRegisterTailnet(mux, drpcService)
|
||||
require.NoError(t, err)
|
||||
fakeAAPI := agenttest.NewFakeAgentAPI(t, logger)
|
||||
err = agentproto.DRPCRegisterAgent(mux, fakeAAPI)
|
||||
require.NoError(t, err)
|
||||
server := drpcserver.NewWithOptions(mux, drpcserver.Options{
|
||||
Log: func(err error) {
|
||||
if xerrors.Is(err, io.EOF) {
|
||||
return
|
||||
}
|
||||
logger.Debug(context.Background(), "drpc server error", slog.Error(err))
|
||||
},
|
||||
})
|
||||
|
||||
return &client{
|
||||
t: t,
|
||||
agentID: manifest.AgentID,
|
||||
manifest: manifest,
|
||||
coordinator: coordinator,
|
||||
derpMapUpdates: derpMapUpdates,
|
||||
server: server,
|
||||
fakeAgentAPI: fakeAAPI,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *client) close() {
|
||||
@ -250,35 +290,12 @@ func (c *client) Manifest(_ context.Context) (agentsdk.Manifest, error) {
|
||||
}
|
||||
|
||||
func (c *client) Listen(_ context.Context) (drpc.Conn, error) {
|
||||
logger := slogtest.Make(c.t, nil).Leveled(slog.LevelDebug).Named("drpc")
|
||||
conn, lis := drpcsdk.MemTransportPipe()
|
||||
c.t.Cleanup(func() {
|
||||
_ = conn.Close()
|
||||
_ = lis.Close()
|
||||
})
|
||||
coordPtr := atomic.Pointer[tailnet.Coordinator]{}
|
||||
coordPtr.Store(&c.coordinator)
|
||||
mux := drpcmux.New()
|
||||
drpcService := &tailnet.DRPCService{
|
||||
CoordPtr: &coordPtr,
|
||||
Logger: logger,
|
||||
DerpMapUpdateFrequency: time.Microsecond,
|
||||
DerpMapFn: func() *tailcfg.DERPMap { return <-c.derpMapUpdates },
|
||||
}
|
||||
err := proto.DRPCRegisterTailnet(mux, drpcService)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("register DRPC service: %w", err)
|
||||
}
|
||||
server := drpcserver.NewWithOptions(mux, drpcserver.Options{
|
||||
Log: func(err error) {
|
||||
if xerrors.Is(err, io.EOF) ||
|
||||
xerrors.Is(err, context.Canceled) ||
|
||||
xerrors.Is(err, context.DeadlineExceeded) {
|
||||
return
|
||||
}
|
||||
logger.Debug(context.Background(), "drpc server error", slog.Error(err))
|
||||
},
|
||||
})
|
||||
|
||||
serveCtx, cancel := context.WithCancel(context.Background())
|
||||
c.t.Cleanup(cancel)
|
||||
auth := tailnet.AgentTunnelAuth{}
|
||||
@ -289,7 +306,7 @@ func (c *client) Listen(_ context.Context) (drpc.Conn, error) {
|
||||
}
|
||||
serveCtx = tailnet.WithStreamID(serveCtx, streamID)
|
||||
go func() {
|
||||
server.Serve(serveCtx, lis)
|
||||
c.server.Serve(serveCtx, lis)
|
||||
}()
|
||||
return conn, nil
|
||||
}
|
||||
@ -317,7 +334,3 @@ func (*client) PostStartup(_ context.Context, _ agentsdk.PostStartupRequest) err
|
||||
func (*client) PatchLogs(_ context.Context, _ agentsdk.PatchLogs) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*client) GetServiceBanner(_ context.Context) (codersdk.ServiceBannerConfig, error) {
|
||||
return codersdk.ServiceBannerConfig{}, nil
|
||||
}
|
||||
|
@ -637,24 +637,6 @@ func (c *Client) PostLogSource(ctx context.Context, req PostLogSource) (codersdk
|
||||
return logSource, json.NewDecoder(res.Body).Decode(&logSource)
|
||||
}
|
||||
|
||||
// GetServiceBanner relays the service banner config.
|
||||
func (c *Client) GetServiceBanner(ctx context.Context) (codersdk.ServiceBannerConfig, error) {
|
||||
res, err := c.SDK.Request(ctx, http.MethodGet, "/api/v2/appearance", nil)
|
||||
if err != nil {
|
||||
return codersdk.ServiceBannerConfig{}, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
// If the route does not exist then Enterprise code is not enabled.
|
||||
if res.StatusCode == http.StatusNotFound {
|
||||
return codersdk.ServiceBannerConfig{}, nil
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return codersdk.ServiceBannerConfig{}, codersdk.ReadBodyAsError(res)
|
||||
}
|
||||
var cfg codersdk.AppearanceConfig
|
||||
return cfg.ServiceBanner, json.NewDecoder(res.Body).Decode(&cfg)
|
||||
}
|
||||
|
||||
type ExternalAuthResponse struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
TokenExtra map[string]interface{} `json:"token_extra"`
|
||||
|
@ -157,34 +157,25 @@ func TestServiceBanners(t *testing.T) {
|
||||
|
||||
agentClient := agentsdk.New(client.URL)
|
||||
agentClient.SetSessionToken(r.AgentToken)
|
||||
banner, err := agentClient.GetServiceBanner(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, cfg.ServiceBanner, banner)
|
||||
banner = requireGetServiceBannerV2(ctx, t, agentClient)
|
||||
banner := requireGetServiceBanner(ctx, t, agentClient)
|
||||
require.Equal(t, cfg.ServiceBanner, banner)
|
||||
|
||||
// Create an AGPL Coderd against the same database
|
||||
agplClient := coderdtest.New(t, &coderdtest.Options{Database: store, Pubsub: ps})
|
||||
agplAgentClient := agentsdk.New(agplClient.URL)
|
||||
agplAgentClient.SetSessionToken(r.AgentToken)
|
||||
banner, err = agplAgentClient.GetServiceBanner(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, codersdk.ServiceBannerConfig{}, banner)
|
||||
banner = requireGetServiceBannerV2(ctx, t, agplAgentClient)
|
||||
banner = requireGetServiceBanner(ctx, t, agplAgentClient)
|
||||
require.Equal(t, codersdk.ServiceBannerConfig{}, banner)
|
||||
|
||||
// No license means no banner.
|
||||
err = client.DeleteLicense(ctx, lic.ID)
|
||||
require.NoError(t, err)
|
||||
banner, err = agentClient.GetServiceBanner(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, codersdk.ServiceBannerConfig{}, banner)
|
||||
banner = requireGetServiceBannerV2(ctx, t, agentClient)
|
||||
banner = requireGetServiceBanner(ctx, t, agentClient)
|
||||
require.Equal(t, codersdk.ServiceBannerConfig{}, banner)
|
||||
})
|
||||
}
|
||||
|
||||
func requireGetServiceBannerV2(ctx context.Context, t *testing.T, client *agentsdk.Client) codersdk.ServiceBannerConfig {
|
||||
func requireGetServiceBanner(ctx context.Context, t *testing.T, client *agentsdk.Client) codersdk.ServiceBannerConfig {
|
||||
cc, err := client.Listen(ctx)
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
|
2
go.mod
2
go.mod
@ -73,7 +73,7 @@ replace github.com/imulab/go-scim/pkg/v2 => github.com/coder/go-scim/pkg/v2 v2.0
|
||||
replace github.com/pkg/sftp => github.com/mafredri/sftp v1.13.6-0.20231212144145-8218e927edb0
|
||||
|
||||
require (
|
||||
cdr.dev/slog v1.6.2-0.20230929193652-f0c466fabe10
|
||||
cdr.dev/slog v1.6.2-0.20240126064726-20367d4aede6
|
||||
cloud.google.com/go/compute/metadata v0.2.3
|
||||
github.com/AlecAivazis/survey/v2 v2.3.5
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
|
||||
|
4
go.sum
4
go.sum
@ -1,5 +1,5 @@
|
||||
cdr.dev/slog v1.6.2-0.20230929193652-f0c466fabe10 h1:gnB1By6Hzs2PVQXyi/cvo6L3kHPb8utLuzycWHfCztQ=
|
||||
cdr.dev/slog v1.6.2-0.20230929193652-f0c466fabe10/go.mod h1:NaoTA7KwopCrnaSb0JXTC0PTp/O/Y83Lndnq0OEV3ZQ=
|
||||
cdr.dev/slog v1.6.2-0.20240126064726-20367d4aede6 h1:KHblWIE/KHOwQ6lEbMZt6YpcGve2FEZ1sDtrW1Am5UI=
|
||||
cdr.dev/slog v1.6.2-0.20240126064726-20367d4aede6/go.mod h1:NaoTA7KwopCrnaSb0JXTC0PTp/O/Y83Lndnq0OEV3ZQ=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk=
|
||||
cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI=
|
||||
|
Reference in New Issue
Block a user