Ctrl K
ComunidadGuías…

codersdk/agentsdk/convert.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
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"
)

// ManifestFromProto converts the proto manifest to the SDK Manifest.
// Secrets are intentionally NOT included on the returned Manifest:
// keeping them off of the SDK type makes it impossible for any code
// path that only holds a *Manifest to leak secret values via
// logging, JSON encoding, fmt verbs, or debug endpoints.
func ManifestFromProto(manifest *proto.Manifest) (Manifest, error) {
	parentID := uuid.Nil
	if pid := manifest.GetParentId(); pid != nil {
		var err error
		parentID, err = uuid.FromBytes(pid)
		if err != nil {
			return Manifest{}, xerrors.Errorf("error converting workspace agent parent ID: %w", err)
		}
	}
	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)
	}
	workspaceBuildID, err := uuid.FromBytes(manifest.WorkspaceBuildId)
	if err != nil {
		return Manifest{}, xerrors.Errorf("error converting workspace build ID: %w", err)
	}
	templateID, err := uuid.FromBytes(manifest.TemplateId)
	if err != nil {
		return Manifest{}, xerrors.Errorf("error converting template ID: %w", err)
	}
	devcontainers, err := DevcontainersFromProto(manifest.Devcontainers)
	if err != nil {
		return Manifest{}, xerrors.Errorf("error converting workspace agent devcontainers: %w", err)
	}
	return Manifest{
		ParentID:                 parentID,
		AgentID:                  agentID,
		AgentName:                manifest.AgentName,
		OwnerName:                manifest.OwnerUsername,
		WorkspaceID:              workspaceID,
		WorkspaceName:            manifest.WorkspaceName,
		WorkspaceBuildID:         workspaceBuildID,
		TemplateID:               templateID,
		TemplateName:             manifest.TemplateName,
		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),
		Devcontainers:            devcontainers,
	}, nil
}

// ProtoFromManifest converts the SDK Manifest to the proto manifest.
// It does not populate the proto's Secrets field because the SDK
// Manifest intentionally does not carry secrets (see ManifestFromProto).
func ProtoFromManifest(manifest Manifest) (*proto.Manifest, error) {
	apps, err := ProtoFromApps(manifest.Apps)
	if err != nil {
		return nil, xerrors.Errorf("convert workspace apps: %w", err)
	}
	scripts, err := ProtoFromScripts(manifest.Scripts)
	if err != nil {
		return nil, xerrors.Errorf("convert workspace agent scripts: %w", err)
	}
	return &proto.Manifest{
		ParentId:         manifest.ParentID[:],
		AgentId:          manifest.AgentID[:],
		AgentName:        manifest.AgentName,
		OwnerUsername:    manifest.OwnerName,
		WorkspaceId:      manifest.WorkspaceID[:],
		WorkspaceName:    manifest.WorkspaceName,
		WorkspaceBuildId: manifest.WorkspaceBuildID[:],
		TemplateId:       manifest.TemplateID[:],
		TemplateName:     manifest.TemplateName,
		// #nosec G115 - Safe conversion for GitAuthConfigs which is expected to be small and positive
		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:                  scripts,
		Apps:                     apps,
		Metadata:                 ProtoFromMetadataDescriptions(manifest.Metadata),
		Devcontainers:            ProtoFromDevcontainers(manifest.Devcontainers),
	}, 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 ProtoFromMetadataResult(r codersdk.WorkspaceAgentMetadataResult) *proto.WorkspaceAgentMetadata_Result {
	return &proto.WorkspaceAgentMetadata_Result{
		CollectedAt: timestamppb.New(r.CollectedAt),
		Age:         r.Age,
		Value:       r.Value,
		Error:       r.Error,
	}
}

func MetadataResultFromProto(r *proto.WorkspaceAgentMetadata_Result) codersdk.WorkspaceAgentMetadataResult {
	return codersdk.WorkspaceAgentMetadataResult{
		CollectedAt: r.GetCollectedAt().AsTime(),
		Age:         r.GetAge(),
		Value:       r.GetValue(),
		Error:       r.GetError(),
	}
}

func MetadataFromProto(m *proto.Metadata) Metadata {
	return Metadata{
		Key:                          m.GetKey(),
		WorkspaceAgentMetadataResult: MetadataResultFromProto(m.GetResult()),
	}
}

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, error) {
	ret := make([]*proto.WorkspaceAgentScript, len(scripts))
	for i, script := range scripts {
		converted, err := ProtoFromScript(script)
		if err != nil {
			return nil, xerrors.Errorf("script %d: %w", i, err)
		}
		ret[i] = converted
	}
	return ret, nil
}

func AgentScriptFromProto(protoScript *proto.WorkspaceAgentScript) (codersdk.WorkspaceAgentScript, error) {
	id, err := uuid.FromBytes(protoScript.Id)
	if err != nil {
		return codersdk.WorkspaceAgentScript{}, xerrors.Errorf("parse id: %w", err)
	}

	logSourceID, err := uuid.FromBytes(protoScript.LogSourceId)
	if err != nil {
		return codersdk.WorkspaceAgentScript{}, xerrors.Errorf("parse log source id: %w", err)
	}

	dependencies, err := agentScriptDependenciesFromProto(protoScript.Dependencies)
	if err != nil {
		return codersdk.WorkspaceAgentScript{}, xerrors.Errorf("parse dependencies: %w", err)
	}

	return codersdk.WorkspaceAgentScript{
		ID:               id,
		LogSourceID:      logSourceID,
		LogPath:          protoScript.LogPath,
		Script:           protoScript.Script,
		Cron:             protoScript.Cron,
		RunOnStart:       protoScript.RunOnStart,
		RunOnStop:        protoScript.RunOnStop,
		StartBlocksLogin: protoScript.StartBlocksLogin,
		Timeout:          protoScript.Timeout.AsDuration(),
		DisplayName:      protoScript.DisplayName,
		ResourceAddress:  protoScript.ResourceAddress,
		Dependencies:     dependencies,
	}, nil
}

func ProtoFromScript(s codersdk.WorkspaceAgentScript) (*proto.WorkspaceAgentScript, error) {
	dependencies, err := protoFromAgentScriptDependencies(s.Dependencies)
	if err != nil {
		return nil, xerrors.Errorf("convert dependencies: %w", err)
	}
	return &proto.WorkspaceAgentScript{
		Id:               s.ID[:],
		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),
		DisplayName:      s.DisplayName,
		ResourceAddress:  s.ResourceAddress,
		Dependencies:     dependencies,
	}, nil
}

// agentScriptDependenciesFromProto rejects an empty address or an unknown
// enum value, including UNSPECIFIED. The error fails the whole manifest,
// so the agent stays offline rather than start scripts from a list it only
// half understands. Coderd avoids this by sending a new Requirement value
// only to agents on the API version that added it; see the enum in
// agent.proto.
func agentScriptDependenciesFromProto(dependencies []*proto.WorkspaceAgentScriptDependency) ([]codersdk.WorkspaceAgentScriptDependency, error) {
	if len(dependencies) == 0 {
		return nil, nil
	}
	converted := make([]codersdk.WorkspaceAgentScriptDependency, len(dependencies))
	for i, dependency := range dependencies {
		if dependency.GetPrerequisiteResourceAddress() == "" {
			return nil, xerrors.Errorf("dependency %d has an empty prerequisite resource address", i)
		}
		requirement, err := agentScriptDependencyRequirementFromProto(dependency.GetRequirement())
		if err != nil {
			return nil, xerrors.Errorf("dependency %d: %w", i, err)
		}
		converted[i] = codersdk.WorkspaceAgentScriptDependency{
			PrerequisiteResourceAddress: dependency.GetPrerequisiteResourceAddress(),
			Requirement:                 requirement,
		}
	}
	return converted, nil
}

func protoFromAgentScriptDependencies(dependencies []codersdk.WorkspaceAgentScriptDependency) ([]*proto.WorkspaceAgentScriptDependency, error) {
	if len(dependencies) == 0 {
		return nil, nil
	}
	converted := make([]*proto.WorkspaceAgentScriptDependency, len(dependencies))
	for i, dependency := range dependencies {
		if dependency.PrerequisiteResourceAddress == "" {
			return nil, xerrors.Errorf("dependency %d has an empty prerequisite resource address", i)
		}
		requirement, err := agentScriptDependencyRequirementToProto(dependency.Requirement)
		if err != nil {
			return nil, xerrors.Errorf("dependency %d: %w", i, err)
		}
		converted[i] = &proto.WorkspaceAgentScriptDependency{
			PrerequisiteResourceAddress: dependency.PrerequisiteResourceAddress,
			Requirement:                 requirement,
		}
	}
	return converted, nil
}

func agentScriptDependencyRequirementFromProto(requirement proto.WorkspaceAgentScriptDependency_Requirement) (codersdk.WorkspaceAgentScriptDependencyRequirement, error) {
	switch requirement {
	case proto.WorkspaceAgentScriptDependency_REQUIREMENT_SUCCESS:
		return codersdk.WorkspaceAgentScriptDependencyRequirementSuccess, nil
	case proto.WorkspaceAgentScriptDependency_REQUIREMENT_COMPLETION:
		return codersdk.WorkspaceAgentScriptDependencyRequirementCompletion, nil
	default:
		return "", xerrors.Errorf("unsupported requirement %q", requirement)
	}
}

func agentScriptDependencyRequirementToProto(requirement codersdk.WorkspaceAgentScriptDependencyRequirement) (proto.WorkspaceAgentScriptDependency_Requirement, error) {
	switch requirement {
	case codersdk.WorkspaceAgentScriptDependencyRequirementSuccess:
		return proto.WorkspaceAgentScriptDependency_REQUIREMENT_SUCCESS, nil
	case codersdk.WorkspaceAgentScriptDependencyRequirementCompletion:
		return proto.WorkspaceAgentScriptDependency_REQUIREMENT_COMPLETION, nil
	default:
		return proto.WorkspaceAgentScriptDependency_REQUIREMENT_UNSPECIFIED, xerrors.Errorf("unsupported requirement %q", requirement)
	}
}

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,
		Hidden: protoApp.Hidden,
	}, 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),
		Hidden: a.Hidden,
	}, nil
}

func ServiceBannerFromProto(sbp *proto.ServiceBanner) codersdk.BannerConfig {
	return codersdk.BannerConfig{
		Enabled:         sbp.GetEnabled(),
		Message:         sbp.GetMessage(),
		BackgroundColor: sbp.GetBackgroundColor(),
	}
}

func ProtoFromServiceBanner(sb codersdk.BannerConfig) *proto.ServiceBanner {
	return &proto.ServiceBanner{
		Enabled:         sb.Enabled,
		Message:         sb.Message,
		BackgroundColor: sb.BackgroundColor,
	}
}

func BannerConfigFromProto(sbp *proto.BannerConfig) codersdk.BannerConfig {
	return codersdk.BannerConfig{
		Enabled:         sbp.GetEnabled(),
		Message:         sbp.GetMessage(),
		BackgroundColor: sbp.GetBackgroundColor(),
	}
}

func ProtoFromBannerConfig(sb codersdk.BannerConfig) *proto.BannerConfig {
	return &proto.BannerConfig{
		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:    SanitizeLogOutput(log.Output),
		Level:     proto.Log_Level(lvl),
	}, nil
}

func ProtoFromLifecycle(req PostLifecycleRequest) (*proto.Lifecycle, error) {
	s, ok := proto.Lifecycle_State_value[strings.ToUpper(string(req.State))]
	if !ok {
		return nil, xerrors.Errorf("unknown lifecycle state: %s", req.State)
	}
	return &proto.Lifecycle{
		State:     proto.Lifecycle_State(s),
		ChangedAt: timestamppb.New(req.ChangedAt),
	}, nil
}

func LifecycleStateFromProto(s proto.Lifecycle_State) (codersdk.WorkspaceAgentLifecycle, error) {
	caps, ok := proto.Lifecycle_State_name[int32(s)]
	if !ok {
		return "", xerrors.Errorf("unknown lifecycle state: %d", s)
	}
	return codersdk.WorkspaceAgentLifecycle(strings.ToLower(caps)), nil
}

func ProtoFromLifecycleState(s codersdk.WorkspaceAgentLifecycle) (proto.Lifecycle_State, error) {
	caps, ok := proto.Lifecycle_State_value[strings.ToUpper(string(s))]
	if !ok {
		return 0, xerrors.Errorf("unknown lifecycle state: %s", s)
	}
	return proto.Lifecycle_State(caps), nil
}

func DevcontainersFromProto(pdcs []*proto.WorkspaceAgentDevcontainer) ([]codersdk.WorkspaceAgentDevcontainer, error) {
	ret := make([]codersdk.WorkspaceAgentDevcontainer, len(pdcs))
	for i, pdc := range pdcs {
		dc, err := DevcontainerFromProto(pdc)
		if err != nil {
			return nil, xerrors.Errorf("parse devcontainer %v: %w", i, err)
		}
		ret[i] = dc
	}
	return ret, nil
}

func DevcontainerFromProto(pdc *proto.WorkspaceAgentDevcontainer) (codersdk.WorkspaceAgentDevcontainer, error) {
	id, err := uuid.FromBytes(pdc.Id)
	if err != nil {
		return codersdk.WorkspaceAgentDevcontainer{}, xerrors.Errorf("parse id: %w", err)
	}
	var subagentID uuid.NullUUID
	if pdc.SubagentId != nil {
		subagentID.Valid = true
		subagentID.UUID, err = uuid.FromBytes(pdc.SubagentId)
		if err != nil {
			return codersdk.WorkspaceAgentDevcontainer{}, xerrors.Errorf("parse subagent id: %w", err)
		}
	}
	return codersdk.WorkspaceAgentDevcontainer{
		ID:              id,
		Name:            pdc.Name,
		WorkspaceFolder: pdc.WorkspaceFolder,
		ConfigPath:      pdc.ConfigPath,
		SubagentID:      subagentID,
	}, nil
}

func ProtoFromDevcontainers(dcs []codersdk.WorkspaceAgentDevcontainer) []*proto.WorkspaceAgentDevcontainer {
	ret := make([]*proto.WorkspaceAgentDevcontainer, len(dcs))
	for i, dc := range dcs {
		ret[i] = ProtoFromDevcontainer(dc)
	}
	return ret
}

func ProtoFromDevcontainer(dc codersdk.WorkspaceAgentDevcontainer) *proto.WorkspaceAgentDevcontainer {
	var subagentID []byte
	if dc.SubagentID.Valid {
		subagentID = dc.SubagentID.UUID[:]
	}

	return &proto.WorkspaceAgentDevcontainer{
		Id:              dc.ID[:],
		Name:            dc.Name,
		WorkspaceFolder: dc.WorkspaceFolder,
		ConfigPath:      dc.ConfigPath,
		SubagentId:      subagentID,
	}
}

func ProtoFromPatchAppStatus(pas PatchAppStatus) (*proto.UpdateAppStatusRequest, error) {
	state, ok := proto.UpdateAppStatusRequest_AppStatusState_value[strings.ToUpper(string(pas.State))]
	if !ok {
		return nil, xerrors.Errorf("Invalid state: %s", pas.State)
	}
	return &proto.UpdateAppStatusRequest{
		Slug:    pas.AppSlug,
		State:   proto.UpdateAppStatusRequest_AppStatusState(state),
		Message: pas.Message,
		Uri:     pas.URI,
	}, nil
}

func SecretsFromProto(protoSecrets []*proto.WorkspaceSecret) []WorkspaceSecret {
	ret := make([]WorkspaceSecret, len(protoSecrets))
	for i, s := range protoSecrets {
		ret[i] = WorkspaceSecret{
			EnvName:  s.EnvName,
			FilePath: s.FilePath,
			Value:    s.Value,
		}
	}
	return ret
}

func ProtoFromSecrets(secrets []WorkspaceSecret) []*proto.WorkspaceSecret {
	ret := make([]*proto.WorkspaceSecret, len(secrets))
	for i, s := range secrets {
		ret[i] = &proto.WorkspaceSecret{
			EnvName:  s.EnvName,
			FilePath: s.FilePath,
			Value:    s.Value,
		}
	}
	return ret
}