mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
bddb808b25
Fixes all our Go file imports to match the preferred spec that we've _mostly_ been using. For example: ``` import ( "context" "time" "github.com/prometheus/client_golang/prometheus" "golang.org/x/xerrors" "gopkg.in/natefinch/lumberjack.v2" "cdr.dev/slog/v3" "github.com/coder/coder/v2/codersdk/agentsdk" "github.com/coder/serpent" ) ``` 3 groups: standard library, 3rd partly libs, Coder libs. This PR makes the change across the codebase. The PR in the stack above modifies our formatting to maintain this state of affairs, and is a separate PR so it's possible to review that one in detail.
294 lines
8.8 KiB
Go
294 lines
8.8 KiB
Go
package agentcontainers
|
|
|
|
import (
|
|
"context"
|
|
"slices"
|
|
|
|
"github.com/google/uuid"
|
|
"golang.org/x/xerrors"
|
|
|
|
"cdr.dev/slog/v3"
|
|
agentproto "github.com/coder/coder/v2/agent/proto"
|
|
"github.com/coder/coder/v2/codersdk"
|
|
)
|
|
|
|
// SubAgent represents an agent running in a dev container.
|
|
type SubAgent struct {
|
|
ID uuid.UUID
|
|
Name string
|
|
AuthToken uuid.UUID
|
|
Directory string
|
|
Architecture string
|
|
OperatingSystem string
|
|
Apps []SubAgentApp
|
|
DisplayApps []codersdk.DisplayApp
|
|
}
|
|
|
|
// CloneConfig makes a copy of SubAgent without ID and AuthToken. The
|
|
// name is inherited from the devcontainer.
|
|
func (s SubAgent) CloneConfig(dc codersdk.WorkspaceAgentDevcontainer) SubAgent {
|
|
return SubAgent{
|
|
Name: dc.Name,
|
|
Directory: s.Directory,
|
|
Architecture: s.Architecture,
|
|
OperatingSystem: s.OperatingSystem,
|
|
DisplayApps: slices.Clone(s.DisplayApps),
|
|
Apps: slices.Clone(s.Apps),
|
|
}
|
|
}
|
|
|
|
func (s SubAgent) EqualConfig(other SubAgent) bool {
|
|
return s.Name == other.Name &&
|
|
s.Directory == other.Directory &&
|
|
s.Architecture == other.Architecture &&
|
|
s.OperatingSystem == other.OperatingSystem &&
|
|
slices.Equal(s.DisplayApps, other.DisplayApps) &&
|
|
slices.Equal(s.Apps, other.Apps)
|
|
}
|
|
|
|
type SubAgentApp struct {
|
|
Slug string `json:"slug"`
|
|
Command string `json:"command"`
|
|
DisplayName string `json:"displayName"`
|
|
External bool `json:"external"`
|
|
Group string `json:"group"`
|
|
HealthCheck SubAgentHealthCheck `json:"healthCheck"`
|
|
Hidden bool `json:"hidden"`
|
|
Icon string `json:"icon"`
|
|
OpenIn codersdk.WorkspaceAppOpenIn `json:"openIn"`
|
|
Order int32 `json:"order"`
|
|
Share codersdk.WorkspaceAppSharingLevel `json:"share"`
|
|
Subdomain bool `json:"subdomain"`
|
|
URL string `json:"url"`
|
|
}
|
|
|
|
func (app SubAgentApp) ToProtoApp() (*agentproto.CreateSubAgentRequest_App, error) {
|
|
proto := agentproto.CreateSubAgentRequest_App{
|
|
Slug: app.Slug,
|
|
External: &app.External,
|
|
Hidden: &app.Hidden,
|
|
Order: &app.Order,
|
|
Subdomain: &app.Subdomain,
|
|
}
|
|
|
|
if app.Command != "" {
|
|
proto.Command = &app.Command
|
|
}
|
|
if app.DisplayName != "" {
|
|
proto.DisplayName = &app.DisplayName
|
|
}
|
|
if app.Group != "" {
|
|
proto.Group = &app.Group
|
|
}
|
|
if app.Icon != "" {
|
|
proto.Icon = &app.Icon
|
|
}
|
|
if app.URL != "" {
|
|
proto.Url = &app.URL
|
|
}
|
|
|
|
if app.HealthCheck.URL != "" {
|
|
proto.Healthcheck = &agentproto.CreateSubAgentRequest_App_Healthcheck{
|
|
Interval: app.HealthCheck.Interval,
|
|
Threshold: app.HealthCheck.Threshold,
|
|
Url: app.HealthCheck.URL,
|
|
}
|
|
}
|
|
|
|
if app.OpenIn != "" {
|
|
switch app.OpenIn {
|
|
case codersdk.WorkspaceAppOpenInSlimWindow:
|
|
proto.OpenIn = agentproto.CreateSubAgentRequest_App_SLIM_WINDOW.Enum()
|
|
case codersdk.WorkspaceAppOpenInTab:
|
|
proto.OpenIn = agentproto.CreateSubAgentRequest_App_TAB.Enum()
|
|
default:
|
|
return nil, xerrors.Errorf("unexpected codersdk.WorkspaceAppOpenIn: %#v", app.OpenIn)
|
|
}
|
|
}
|
|
|
|
if app.Share != "" {
|
|
switch app.Share {
|
|
case codersdk.WorkspaceAppSharingLevelAuthenticated:
|
|
proto.Share = agentproto.CreateSubAgentRequest_App_AUTHENTICATED.Enum()
|
|
case codersdk.WorkspaceAppSharingLevelOwner:
|
|
proto.Share = agentproto.CreateSubAgentRequest_App_OWNER.Enum()
|
|
case codersdk.WorkspaceAppSharingLevelPublic:
|
|
proto.Share = agentproto.CreateSubAgentRequest_App_PUBLIC.Enum()
|
|
case codersdk.WorkspaceAppSharingLevelOrganization:
|
|
proto.Share = agentproto.CreateSubAgentRequest_App_ORGANIZATION.Enum()
|
|
default:
|
|
return nil, xerrors.Errorf("unexpected codersdk.WorkspaceAppSharingLevel: %#v", app.Share)
|
|
}
|
|
}
|
|
|
|
return &proto, nil
|
|
}
|
|
|
|
type SubAgentHealthCheck struct {
|
|
Interval int32 `json:"interval"`
|
|
Threshold int32 `json:"threshold"`
|
|
URL string `json:"url"`
|
|
}
|
|
|
|
// SubAgentClient is an interface for managing sub agents and allows
|
|
// changing the implementation without having to deal with the
|
|
// agentproto package directly.
|
|
type SubAgentClient interface {
|
|
// List returns a list of all agents.
|
|
List(ctx context.Context) ([]SubAgent, error)
|
|
// Create adds a new agent.
|
|
Create(ctx context.Context, agent SubAgent) (SubAgent, error)
|
|
// Delete removes an agent by its ID.
|
|
Delete(ctx context.Context, id uuid.UUID) error
|
|
}
|
|
|
|
// NewSubAgentClient returns a SubAgentClient that uses the provided
|
|
// agent API client.
|
|
type subAgentAPIClient struct {
|
|
logger slog.Logger
|
|
api agentproto.DRPCAgentClient27
|
|
}
|
|
|
|
var _ SubAgentClient = (*subAgentAPIClient)(nil)
|
|
|
|
func NewSubAgentClientFromAPI(logger slog.Logger, agentAPI agentproto.DRPCAgentClient27) SubAgentClient {
|
|
if agentAPI == nil {
|
|
panic("developer error: agentAPI cannot be nil")
|
|
}
|
|
return &subAgentAPIClient{
|
|
logger: logger.Named("subagentclient"),
|
|
api: agentAPI,
|
|
}
|
|
}
|
|
|
|
func (a *subAgentAPIClient) List(ctx context.Context) ([]SubAgent, error) {
|
|
a.logger.Debug(ctx, "listing sub agents")
|
|
resp, err := a.api.ListSubAgents(ctx, &agentproto.ListSubAgentsRequest{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
agents := make([]SubAgent, len(resp.Agents))
|
|
for i, agent := range resp.Agents {
|
|
id, err := uuid.FromBytes(agent.GetId())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
authToken, err := uuid.FromBytes(agent.GetAuthToken())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
agents[i] = SubAgent{
|
|
ID: id,
|
|
Name: agent.GetName(),
|
|
AuthToken: authToken,
|
|
}
|
|
}
|
|
return agents, nil
|
|
}
|
|
|
|
func (a *subAgentAPIClient) Create(ctx context.Context, agent SubAgent) (_ SubAgent, err error) {
|
|
a.logger.Debug(ctx, "creating sub agent", slog.F("name", agent.Name), slog.F("directory", agent.Directory))
|
|
|
|
displayApps := make([]agentproto.CreateSubAgentRequest_DisplayApp, 0, len(agent.DisplayApps))
|
|
for _, displayApp := range agent.DisplayApps {
|
|
var app agentproto.CreateSubAgentRequest_DisplayApp
|
|
switch displayApp {
|
|
case codersdk.DisplayAppPortForward:
|
|
app = agentproto.CreateSubAgentRequest_PORT_FORWARDING_HELPER
|
|
case codersdk.DisplayAppSSH:
|
|
app = agentproto.CreateSubAgentRequest_SSH_HELPER
|
|
case codersdk.DisplayAppVSCodeDesktop:
|
|
app = agentproto.CreateSubAgentRequest_VSCODE
|
|
case codersdk.DisplayAppVSCodeInsiders:
|
|
app = agentproto.CreateSubAgentRequest_VSCODE_INSIDERS
|
|
case codersdk.DisplayAppWebTerminal:
|
|
app = agentproto.CreateSubAgentRequest_WEB_TERMINAL
|
|
default:
|
|
return SubAgent{}, xerrors.Errorf("unexpected codersdk.DisplayApp: %#v", displayApp)
|
|
}
|
|
|
|
displayApps = append(displayApps, app)
|
|
}
|
|
|
|
apps := make([]*agentproto.CreateSubAgentRequest_App, 0, len(agent.Apps))
|
|
for _, app := range agent.Apps {
|
|
protoApp, err := app.ToProtoApp()
|
|
if err != nil {
|
|
return SubAgent{}, xerrors.Errorf("convert app: %w", err)
|
|
}
|
|
|
|
apps = append(apps, protoApp)
|
|
}
|
|
|
|
resp, err := a.api.CreateSubAgent(ctx, &agentproto.CreateSubAgentRequest{
|
|
Name: agent.Name,
|
|
Directory: agent.Directory,
|
|
Architecture: agent.Architecture,
|
|
OperatingSystem: agent.OperatingSystem,
|
|
DisplayApps: displayApps,
|
|
Apps: apps,
|
|
})
|
|
if err != nil {
|
|
return SubAgent{}, err
|
|
}
|
|
defer func() {
|
|
if err != nil {
|
|
// Best effort.
|
|
_, _ = a.api.DeleteSubAgent(ctx, &agentproto.DeleteSubAgentRequest{
|
|
Id: resp.GetAgent().GetId(),
|
|
})
|
|
}
|
|
}()
|
|
|
|
agent.Name = resp.GetAgent().GetName()
|
|
agent.ID, err = uuid.FromBytes(resp.GetAgent().GetId())
|
|
if err != nil {
|
|
return SubAgent{}, err
|
|
}
|
|
agent.AuthToken, err = uuid.FromBytes(resp.GetAgent().GetAuthToken())
|
|
if err != nil {
|
|
return SubAgent{}, err
|
|
}
|
|
|
|
for _, appError := range resp.GetAppCreationErrors() {
|
|
app := apps[appError.GetIndex()]
|
|
|
|
a.logger.Warn(ctx, "unable to create app",
|
|
slog.F("agent_name", agent.Name),
|
|
slog.F("agent_id", agent.ID),
|
|
slog.F("directory", agent.Directory),
|
|
slog.F("app_slug", app.Slug),
|
|
slog.F("field", appError.GetField()),
|
|
slog.F("error", appError.GetError()),
|
|
)
|
|
}
|
|
|
|
return agent, nil
|
|
}
|
|
|
|
func (a *subAgentAPIClient) Delete(ctx context.Context, id uuid.UUID) error {
|
|
a.logger.Debug(ctx, "deleting sub agent", slog.F("id", id.String()))
|
|
_, err := a.api.DeleteSubAgent(ctx, &agentproto.DeleteSubAgentRequest{
|
|
Id: id[:],
|
|
})
|
|
return err
|
|
}
|
|
|
|
// noopSubAgentClient is a SubAgentClient that does nothing.
|
|
type noopSubAgentClient struct{}
|
|
|
|
var _ SubAgentClient = noopSubAgentClient{}
|
|
|
|
func (noopSubAgentClient) List(_ context.Context) ([]SubAgent, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (noopSubAgentClient) Create(_ context.Context, _ SubAgent) (SubAgent, error) {
|
|
return SubAgent{}, xerrors.New("noopSubAgentClient does not support creating sub agents")
|
|
}
|
|
|
|
func (noopSubAgentClient) Delete(_ context.Context, _ uuid.UUID) error {
|
|
return xerrors.New("noopSubAgentClient does not support deleting sub agents")
|
|
}
|