codersdk/agentsdk/convert_test.go
Rama main · réplica de lectura del repositorio original.
package agentsdk_test
import (
"testing"
"time"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/types/known/timestamppb"
"tailscale.com/tailcfg"
"github.com/coder/coder/v2/agent/proto"
"github.com/coder/coder/v2/coderd/database/dbtime"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/codersdk/agentsdk"
"github.com/coder/coder/v2/tailnet"
)
func TestManifest(t *testing.T) {
t.Parallel()
manifest := agentsdk.Manifest{
ParentID: uuid.New(),
AgentID: uuid.New(),
AgentName: "test-agent",
OwnerName: "test-owner",
WorkspaceID: uuid.New(),
WorkspaceName: "test-workspace",
TemplateID: uuid.New(),
TemplateName: "test-template",
GitAuthConfigs: 3,
VSCodePortProxyURI: "http://proxy.example.com/stuff",
Apps: []codersdk.WorkspaceApp{
{
ID: uuid.New(),
URL: "http://app1.example.com",
External: true,
Slug: "app1",
DisplayName: "App 1",
Command: "app1 -d",
Icon: "app1.png",
Subdomain: true,
SubdomainName: "app1.example.com",
SharingLevel: codersdk.WorkspaceAppSharingLevelAuthenticated,
Healthcheck: codersdk.Healthcheck{
URL: "http://localhost:3030/healthz",
Interval: 55555666,
Threshold: 55555666,
},
Health: codersdk.WorkspaceAppHealthHealthy,
Hidden: false,
},
{
ID: uuid.New(),
URL: "http://app2.example.com",
External: false,
Slug: "app2",
DisplayName: "App 2",
Command: "app2 -d",
Icon: "app2.png",
Subdomain: false,
SubdomainName: "app2.example.com",
SharingLevel: codersdk.WorkspaceAppSharingLevelPublic,
Healthcheck: codersdk.Healthcheck{
URL: "http://localhost:3032/healthz",
Interval: 22555666,
Threshold: 22555666,
},
Health: codersdk.WorkspaceAppHealthInitializing,
Hidden: true,
},
},
DERPMap: &tailcfg.DERPMap{
HomeParams: &tailcfg.DERPHomeParams{RegionScore: map[int]float64{999: 0.025}},
Regions: map[int]*tailcfg.DERPRegion{
999: {
EmbeddedRelay: true,
RegionID: 999,
RegionCode: "default",
RegionName: "HOME",
Avoid: false,
Nodes: []*tailcfg.DERPNode{
{
Name: "Home1",
},
},
},
},
},
DERPForceWebSockets: true,
EnvironmentVariables: map[string]string{"FOO": "bar"},
Directory: "/home/coder",
MOTDFile: "/etc/motd",
DisableDirectConnections: true,
Metadata: []codersdk.WorkspaceAgentMetadataDescription{
{
DisplayName: "CPU",
Key: "cpu",
Script: "getcpu",
Interval: 44444422,
Timeout: 44444411,
},
{
DisplayName: "MEM",
Key: "mem",
Script: "getmem",
Interval: 54444422,
Timeout: 54444411,
},
},
Scripts: []codersdk.WorkspaceAgentScript{
{
ID: uuid.New(),
LogSourceID: uuid.New(),
LogPath: "/var/log/script.log",
Script: "script",
Cron: "somecron",
RunOnStart: true,
RunOnStop: true,
StartBlocksLogin: true,
Timeout: time.Second,
DisplayName: "foo",
ResourceAddress: "coder_script.foo",
Dependencies: []codersdk.WorkspaceAgentScriptDependency{
{
PrerequisiteResourceAddress: "coder_script.bar",
Requirement: codersdk.WorkspaceAgentScriptDependencyRequirementSuccess,
},
{
PrerequisiteResourceAddress: "module.cache.coder_script.warm",
Requirement: codersdk.WorkspaceAgentScriptDependencyRequirementCompletion,
},
},
},
{
ID: uuid.New(),
LogSourceID: uuid.New(),
LogPath: "/var/log/script2.log",
Script: "script2",
Cron: "somecron2",
RunOnStart: false,
RunOnStop: true,
StartBlocksLogin: true,
Timeout: time.Second * 4,
DisplayName: "bar",
ResourceAddress: "coder_script.bar",
},
},
Devcontainers: []codersdk.WorkspaceAgentDevcontainer{
{
ID: uuid.New(),
WorkspaceFolder: "/home/coder/coder",
ConfigPath: "/home/coder/coder/.devcontainer/devcontainer.json",
SubagentID: uuid.NullUUID{Valid: true, UUID: uuid.New()},
},
},
}
p, err := agentsdk.ProtoFromManifest(manifest)
require.NoError(t, err)
back, err := agentsdk.ManifestFromProto(p)
require.NoError(t, err)
require.Equal(t, manifest.ParentID, back.ParentID)
require.Equal(t, manifest.AgentID, back.AgentID)
require.Equal(t, manifest.AgentName, back.AgentName)
require.Equal(t, manifest.OwnerName, back.OwnerName)
require.Equal(t, manifest.WorkspaceID, back.WorkspaceID)
require.Equal(t, manifest.WorkspaceName, back.WorkspaceName)
require.Equal(t, manifest.TemplateID, back.TemplateID)
require.Equal(t, manifest.TemplateName, back.TemplateName)
require.Equal(t, manifest.GitAuthConfigs, back.GitAuthConfigs)
require.Equal(t, manifest.VSCodePortProxyURI, back.VSCodePortProxyURI)
require.Equal(t, manifest.Apps, back.Apps)
require.NotNil(t, back.DERPMap)
require.True(t, tailnet.CompareDERPMaps(manifest.DERPMap, back.DERPMap))
require.Equal(t, manifest.DERPForceWebSockets, back.DERPForceWebSockets)
require.Equal(t, manifest.EnvironmentVariables, back.EnvironmentVariables)
require.Equal(t, manifest.Directory, back.Directory)
require.Equal(t, manifest.MOTDFile, back.MOTDFile)
require.Equal(t, manifest.DisableDirectConnections, back.DisableDirectConnections)
require.Equal(t, manifest.Metadata, back.Metadata)
require.Equal(t, manifest.Scripts, back.Scripts)
require.Equal(t, manifest.Devcontainers, back.Devcontainers)
}
func TestScriptDependencyConversionRejectsInvalidRequirements(t *testing.T) {
t.Parallel()
id, logSourceID := uuid.New(), uuid.New()
t.Run("FromProtoUnspecified", func(t *testing.T) {
t.Parallel()
_, err := agentsdk.AgentScriptFromProto(&proto.WorkspaceAgentScript{
Id: id[:],
LogSourceId: logSourceID[:],
ResourceAddress: "coder_script.install_tools",
Dependencies: []*proto.WorkspaceAgentScriptDependency{{
PrerequisiteResourceAddress: "coder_script.clone_repo",
Requirement: proto.WorkspaceAgentScriptDependency_REQUIREMENT_UNSPECIFIED,
}},
})
require.ErrorContains(t, err, "dependency 0: unsupported requirement")
})
t.Run("FromProtoEmptyAddress", func(t *testing.T) {
t.Parallel()
_, err := agentsdk.AgentScriptFromProto(&proto.WorkspaceAgentScript{
Id: id[:],
LogSourceId: logSourceID[:],
ResourceAddress: "coder_script.install_tools",
Dependencies: []*proto.WorkspaceAgentScriptDependency{{
Requirement: proto.WorkspaceAgentScriptDependency_REQUIREMENT_SUCCESS,
}},
})
require.ErrorContains(t, err, "dependency 0 has an empty prerequisite resource address")
})
t.Run("ToProtoUnknownString", func(t *testing.T) {
t.Parallel()
_, err := agentsdk.ProtoFromScript(codersdk.WorkspaceAgentScript{
ID: id,
LogSourceID: logSourceID,
ResourceAddress: "coder_script.install_tools",
Dependencies: []codersdk.WorkspaceAgentScriptDependency{{
PrerequisiteResourceAddress: "coder_script.clone_repo",
Requirement: codersdk.WorkspaceAgentScriptDependencyRequirement("always"),
}},
})
require.ErrorContains(t, err, `dependency 0: unsupported requirement "always"`)
})
t.Run("ToProtoEmptyAddress", func(t *testing.T) {
t.Parallel()
_, err := agentsdk.ProtoFromScript(codersdk.WorkspaceAgentScript{
ID: id,
LogSourceID: logSourceID,
Dependencies: []codersdk.WorkspaceAgentScriptDependency{{
Requirement: codersdk.WorkspaceAgentScriptDependencyRequirementSuccess,
}},
})
require.ErrorContains(t, err, "dependency 0 has an empty prerequisite resource address")
})
}
func TestSubsystems(t *testing.T) {
t.Parallel()
ss := []codersdk.AgentSubsystem{
codersdk.AgentSubsystemEnvbox,
codersdk.AgentSubsystemEnvbuilder,
codersdk.AgentSubsystemExectrace,
}
ps, err := agentsdk.ProtoFromSubsystems(ss)
require.NoError(t, err)
require.Equal(t, ps, []proto.Startup_Subsystem{
proto.Startup_ENVBOX,
proto.Startup_ENVBUILDER,
proto.Startup_EXECTRACE,
})
}
func TestProtoFromLifecycle(t *testing.T) {
t.Parallel()
now := dbtime.Now()
for _, s := range codersdk.WorkspaceAgentLifecycleOrder {
sr := agentsdk.PostLifecycleRequest{State: s, ChangedAt: now}
pr, err := agentsdk.ProtoFromLifecycle(sr)
require.NoError(t, err)
require.Equal(t, now, pr.ChangedAt.AsTime())
state, err := agentsdk.LifecycleStateFromProto(pr.State)
require.NoError(t, err)
require.Equal(t, s, state)
}
}
func TestProtoFromMetadataResult(t *testing.T) {
t.Parallel()
now := dbtime.Now()
result := codersdk.WorkspaceAgentMetadataResult{
CollectedAt: now,
Age: 4,
Value: "lemons",
Error: "rats",
}
pr := agentsdk.ProtoFromMetadataResult(result)
require.NotNil(t, pr)
require.Equal(t, now, pr.CollectedAt.AsTime())
require.EqualValues(t, 4, pr.Age)
require.Equal(t, "lemons", pr.Value)
require.Equal(t, "rats", pr.Error)
result2 := agentsdk.MetadataResultFromProto(pr)
require.Equal(t, result, result2)
}
func TestMetadataFromProto(t *testing.T) {
t.Parallel()
now := dbtime.Now()
pmd := &proto.Metadata{
Key: "a flat",
Result: &proto.WorkspaceAgentMetadata_Result{
CollectedAt: timestamppb.New(now),
Age: 88,
Value: "lemons",
Error: "rats",
},
}
smd := agentsdk.MetadataFromProto(pmd)
require.Equal(t, "a flat", smd.Key)
require.Equal(t, now, smd.CollectedAt)
require.EqualValues(t, 88, smd.Age)
require.Equal(t, "lemons", smd.Value)
require.Equal(t, "rats", smd.Error)
}
func TestSecretsRoundTrip(t *testing.T) {
t.Parallel()
secrets := []agentsdk.WorkspaceSecret{
{
EnvName: "GITHUB_TOKEN",
FilePath: "",
Value: []byte("ghp_xxxx"),
},
{
EnvName: "",
FilePath: "~/.aws/credentials",
Value: []byte("[default]\naws_access_key_id=AKIA..."),
},
{
EnvName: "BOTH_ENV",
FilePath: "/etc/both",
Value: []byte("both-value"),
},
}
protoSecrets := agentsdk.ProtoFromSecrets(secrets)
require.Len(t, protoSecrets, 3)
require.Equal(t, "GITHUB_TOKEN", protoSecrets[0].EnvName)
require.Equal(t, "", protoSecrets[0].FilePath)
require.Equal(t, []byte("ghp_xxxx"), protoSecrets[0].Value)
require.Equal(t, "", protoSecrets[1].EnvName)
require.Equal(t, "~/.aws/credentials", protoSecrets[1].FilePath)
require.Equal(t, []byte("[default]\naws_access_key_id=AKIA..."), protoSecrets[1].Value)
require.Equal(t, "BOTH_ENV", protoSecrets[2].EnvName)
require.Equal(t, "/etc/both", protoSecrets[2].FilePath)
require.Equal(t, []byte("both-value"), protoSecrets[2].Value)
roundTripped := agentsdk.SecretsFromProto(protoSecrets)
require.Equal(t, secrets, roundTripped)
}