coder/codersdk/agentsdk/convert.go

301 lines
10 KiB
Go

package agentsdk
import (
"strings"
"time"
"github.com/google/uuid"
"golang.org/x/xerrors"
"google.golang.org/protobuf/types/known/durationpb"
"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
}