Ctrl K
ComunidadGuías…

codersdk/agentsdk/convert_test.go

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

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