Files
coder/codersdk/agentsdk/convert.go
Spike Curtis 2aff014e5d feat: add logSender for sending logs on agent v2 API (#12046)
Adds a new subcomponent of the agent for queueing up logs until they can be sent over the Agent API.

Subsequent PR will change the agent to use this instead of the HTTP API for posting logs.

Relates to #10534
2024-02-15 16:57:17 +04:00

314 lines
11 KiB
Go

package agentsdk
import (
"strings"
"time"
"github.com/google/uuid"
"golang.org/x/xerrors"
"google.golang.org/protobuf/types/known/durationpb"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/coder/coder/v2/agent/proto"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/tailnet"
)
func ManifestFromProto(manifest *proto.Manifest) (Manifest, error) {
apps, err := AppsFromProto(manifest.Apps)
if err != nil {
return Manifest{}, xerrors.Errorf("error converting workspace agent apps: %w", err)
}
scripts, err := AgentScriptsFromProto(manifest.Scripts)
if err != nil {
return Manifest{}, xerrors.Errorf("error converting workspace agent scripts: %w", err)
}
agentID, err := uuid.FromBytes(manifest.AgentId)
if err != nil {
return Manifest{}, xerrors.Errorf("error converting workspace agent ID: %w", err)
}
workspaceID, err := uuid.FromBytes(manifest.WorkspaceId)
if err != nil {
return Manifest{}, xerrors.Errorf("error converting workspace ID: %w", err)
}
return Manifest{
AgentID: agentID,
AgentName: manifest.AgentName,
OwnerName: manifest.OwnerUsername,
WorkspaceID: workspaceID,
WorkspaceName: manifest.WorkspaceName,
Apps: apps,
Scripts: scripts,
DERPMap: tailnet.DERPMapFromProto(manifest.DerpMap),
DERPForceWebSockets: manifest.DerpForceWebsockets,
GitAuthConfigs: int(manifest.GitAuthConfigs),
EnvironmentVariables: manifest.EnvironmentVariables,
Directory: manifest.Directory,
VSCodePortProxyURI: manifest.VsCodePortProxyUri,
MOTDFile: manifest.MotdPath,
DisableDirectConnections: manifest.DisableDirectConnections,
Metadata: MetadataDescriptionsFromProto(manifest.Metadata),
}, nil
}
func ProtoFromManifest(manifest Manifest) (*proto.Manifest, error) {
apps, err := ProtoFromApps(manifest.Apps)
if err != nil {
return nil, xerrors.Errorf("convert workspace apps: %w", err)
}
return &proto.Manifest{
AgentId: manifest.AgentID[:],
AgentName: manifest.AgentName,
OwnerUsername: manifest.OwnerName,
WorkspaceId: manifest.WorkspaceID[:],
WorkspaceName: manifest.WorkspaceName,
GitAuthConfigs: uint32(manifest.GitAuthConfigs),
EnvironmentVariables: manifest.EnvironmentVariables,
Directory: manifest.Directory,
VsCodePortProxyUri: manifest.VSCodePortProxyURI,
MotdPath: manifest.MOTDFile,
DisableDirectConnections: manifest.DisableDirectConnections,
DerpForceWebsockets: manifest.DERPForceWebSockets,
DerpMap: tailnet.DERPMapToProto(manifest.DERPMap),
Scripts: ProtoFromScripts(manifest.Scripts),
Apps: apps,
Metadata: ProtoFromMetadataDescriptions(manifest.Metadata),
}, nil
}
func MetadataDescriptionsFromProto(descriptions []*proto.WorkspaceAgentMetadata_Description) []codersdk.WorkspaceAgentMetadataDescription {
ret := make([]codersdk.WorkspaceAgentMetadataDescription, len(descriptions))
for i, description := range descriptions {
ret[i] = MetadataDescriptionFromProto(description)
}
return ret
}
func ProtoFromMetadataDescriptions(descriptions []codersdk.WorkspaceAgentMetadataDescription) []*proto.WorkspaceAgentMetadata_Description {
ret := make([]*proto.WorkspaceAgentMetadata_Description, len(descriptions))
for i, d := range descriptions {
ret[i] = ProtoFromMetadataDescription(d)
}
return ret
}
func MetadataDescriptionFromProto(description *proto.WorkspaceAgentMetadata_Description) codersdk.WorkspaceAgentMetadataDescription {
return codersdk.WorkspaceAgentMetadataDescription{
DisplayName: description.DisplayName,
Key: description.Key,
Script: description.Script,
Interval: int64(description.Interval.AsDuration()),
Timeout: int64(description.Timeout.AsDuration()),
}
}
func ProtoFromMetadataDescription(d codersdk.WorkspaceAgentMetadataDescription) *proto.WorkspaceAgentMetadata_Description {
return &proto.WorkspaceAgentMetadata_Description{
DisplayName: d.DisplayName,
Key: d.Key,
Script: d.Script,
Interval: durationpb.New(time.Duration(d.Interval)),
Timeout: durationpb.New(time.Duration(d.Timeout)),
}
}
func AgentScriptsFromProto(protoScripts []*proto.WorkspaceAgentScript) ([]codersdk.WorkspaceAgentScript, error) {
ret := make([]codersdk.WorkspaceAgentScript, len(protoScripts))
for i, protoScript := range protoScripts {
app, err := AgentScriptFromProto(protoScript)
if err != nil {
return nil, xerrors.Errorf("parse script %v: %w", i, err)
}
ret[i] = app
}
return ret, nil
}
func ProtoFromScripts(scripts []codersdk.WorkspaceAgentScript) []*proto.WorkspaceAgentScript {
ret := make([]*proto.WorkspaceAgentScript, len(scripts))
for i, script := range scripts {
ret[i] = ProtoFromScript(script)
}
return ret
}
func AgentScriptFromProto(protoScript *proto.WorkspaceAgentScript) (codersdk.WorkspaceAgentScript, error) {
id, err := uuid.FromBytes(protoScript.LogSourceId)
if err != nil {
return codersdk.WorkspaceAgentScript{}, xerrors.Errorf("parse id: %w", err)
}
return codersdk.WorkspaceAgentScript{
LogSourceID: id,
LogPath: protoScript.LogPath,
Script: protoScript.Script,
Cron: protoScript.Cron,
RunOnStart: protoScript.RunOnStart,
RunOnStop: protoScript.RunOnStop,
StartBlocksLogin: protoScript.StartBlocksLogin,
Timeout: protoScript.Timeout.AsDuration(),
}, nil
}
func ProtoFromScript(s codersdk.WorkspaceAgentScript) *proto.WorkspaceAgentScript {
return &proto.WorkspaceAgentScript{
LogSourceId: s.LogSourceID[:],
LogPath: s.LogPath,
Script: s.Script,
Cron: s.Cron,
RunOnStart: s.RunOnStart,
RunOnStop: s.RunOnStop,
StartBlocksLogin: s.StartBlocksLogin,
Timeout: durationpb.New(s.Timeout),
}
}
func AppsFromProto(protoApps []*proto.WorkspaceApp) ([]codersdk.WorkspaceApp, error) {
ret := make([]codersdk.WorkspaceApp, len(protoApps))
for i, protoApp := range protoApps {
app, err := AppFromProto(protoApp)
if err != nil {
return nil, xerrors.Errorf("parse app %v (%q): %w", i, protoApp.Slug, err)
}
ret[i] = app
}
return ret, nil
}
func ProtoFromApps(apps []codersdk.WorkspaceApp) ([]*proto.WorkspaceApp, error) {
ret := make([]*proto.WorkspaceApp, len(apps))
var err error
for i, a := range apps {
ret[i], err = ProtoFromApp(a)
if err != nil {
return nil, err
}
}
return ret, nil
}
func AppFromProto(protoApp *proto.WorkspaceApp) (codersdk.WorkspaceApp, error) {
id, err := uuid.FromBytes(protoApp.Id)
if err != nil {
return codersdk.WorkspaceApp{}, xerrors.Errorf("parse id: %w", err)
}
sharingLevel := codersdk.WorkspaceAppSharingLevel(strings.ToLower(protoApp.SharingLevel.String()))
if _, ok := codersdk.MapWorkspaceAppSharingLevels[sharingLevel]; !ok {
return codersdk.WorkspaceApp{}, xerrors.Errorf("unknown app sharing level: %v (%q)", protoApp.SharingLevel, protoApp.SharingLevel.String())
}
health := codersdk.WorkspaceAppHealth(strings.ToLower(protoApp.Health.String()))
if _, ok := codersdk.MapWorkspaceAppHealths[health]; !ok {
return codersdk.WorkspaceApp{}, xerrors.Errorf("unknown app health: %v (%q)", protoApp.Health, protoApp.Health.String())
}
return codersdk.WorkspaceApp{
ID: id,
URL: protoApp.Url,
External: protoApp.External,
Slug: protoApp.Slug,
DisplayName: protoApp.DisplayName,
Command: protoApp.Command,
Icon: protoApp.Icon,
Subdomain: protoApp.Subdomain,
SubdomainName: protoApp.SubdomainName,
SharingLevel: sharingLevel,
Healthcheck: codersdk.Healthcheck{
URL: protoApp.Healthcheck.Url,
Interval: int32(protoApp.Healthcheck.Interval.AsDuration().Seconds()),
Threshold: protoApp.Healthcheck.Threshold,
},
Health: health,
}, nil
}
func ProtoFromApp(a codersdk.WorkspaceApp) (*proto.WorkspaceApp, error) {
sharingLevel, ok := proto.WorkspaceApp_SharingLevel_value[strings.ToUpper(string(a.SharingLevel))]
if !ok {
return nil, xerrors.Errorf("unknown sharing level %s", a.SharingLevel)
}
health, ok := proto.WorkspaceApp_Health_value[strings.ToUpper(string(a.Health))]
if !ok {
return nil, xerrors.Errorf("unknown health %s", a.Health)
}
return &proto.WorkspaceApp{
Id: a.ID[:],
Url: a.URL,
External: a.External,
Slug: a.Slug,
DisplayName: a.DisplayName,
Command: a.Command,
Icon: a.Icon,
Subdomain: a.Subdomain,
SubdomainName: a.SubdomainName,
SharingLevel: proto.WorkspaceApp_SharingLevel(sharingLevel),
Healthcheck: &proto.WorkspaceApp_Healthcheck{
Url: a.Healthcheck.URL,
Interval: durationpb.New(time.Duration(a.Healthcheck.Interval) * time.Second),
Threshold: a.Healthcheck.Threshold,
},
Health: proto.WorkspaceApp_Health(health),
}, nil
}
func ServiceBannerFromProto(sbp *proto.ServiceBanner) codersdk.ServiceBannerConfig {
return codersdk.ServiceBannerConfig{
Enabled: sbp.GetEnabled(),
Message: sbp.GetMessage(),
BackgroundColor: sbp.GetBackgroundColor(),
}
}
func ProtoFromServiceBanner(sb codersdk.ServiceBannerConfig) *proto.ServiceBanner {
return &proto.ServiceBanner{
Enabled: sb.Enabled,
Message: sb.Message,
BackgroundColor: sb.BackgroundColor,
}
}
func ProtoFromSubsystems(ss []codersdk.AgentSubsystem) ([]proto.Startup_Subsystem, error) {
ret := make([]proto.Startup_Subsystem, len(ss))
for i, s := range ss {
pi, ok := proto.Startup_Subsystem_value[strings.ToUpper(string(s))]
if !ok {
return nil, xerrors.Errorf("unknown subsystem: %s", s)
}
ret[i] = proto.Startup_Subsystem(pi)
}
return ret, nil
}
func ProtoFromAppHealthsRequest(req PostAppHealthsRequest) (*proto.BatchUpdateAppHealthRequest, error) {
pReq := &proto.BatchUpdateAppHealthRequest{}
for id, h := range req.Healths {
hp, ok := proto.AppHealth_value[strings.ToUpper(string(h))]
if !ok {
return nil, xerrors.Errorf("unknown app health: %s", h)
}
// Copy the ID, otherwise all updates will have the same ID (the last
// one in the list).
var idCopy uuid.UUID
copy(idCopy[:], id[:])
pReq.Updates = append(pReq.Updates, &proto.BatchUpdateAppHealthRequest_HealthUpdate{
Id: idCopy[:],
Health: proto.AppHealth(hp),
})
}
return pReq, nil
}
func ProtoFromLog(log Log) (*proto.Log, error) {
lvl, ok := proto.Log_Level_value[strings.ToUpper(string(log.Level))]
if !ok {
return nil, xerrors.Errorf("unknown log level: %s", log.Level)
}
return &proto.Log{
CreatedAt: timestamppb.New(log.CreatedAt),
Output: log.Output,
Level: proto.Log_Level(lvl),
}, nil
}