package echo_test import ( "context" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/coder/coder/v2/provisioner/echo" "github.com/coder/coder/v2/provisionersdk" "github.com/coder/coder/v2/provisionersdk/proto" "github.com/coder/coder/v2/testutil" ) func TestEcho(t *testing.T) { t.Parallel() workdir := t.TempDir() // Create an in-memory provisioner to communicate with. client, server := provisionersdk.MemTransportPipe() ctx, cancelFunc := context.WithCancel(context.Background()) t.Cleanup(func() { _ = client.Close() _ = server.Close() cancelFunc() }) go func() { err := echo.Serve(ctx, &provisionersdk.ServeOptions{ Listener: server, WorkDirectory: workdir, }) assert.NoError(t, err) }() api := proto.NewDRPCProvisionerClient(client) t.Run("Parse", func(t *testing.T) { t.Parallel() ctx, cancel := context.WithTimeout(ctx, testutil.WaitShort) defer cancel() responses := []*proto.Response{ { Type: &proto.Response_Log{ Log: &proto.Log{ Output: "log-output", }, }, }, { Type: &proto.Response_Parse{ Parse: &proto.ParseComplete{}, }, }, } data, err := echo.Tar(&echo.Responses{ Parse: responses, }) require.NoError(t, err) client, err := api.Session(ctx) require.NoError(t, err) defer func() { err := client.Close() require.NoError(t, err) }() err = client.Send(&proto.Request{Type: &proto.Request_Config{Config: &proto.Config{ TemplateSourceArchive: data, }}}) require.NoError(t, err) err = client.Send(&proto.Request{Type: &proto.Request_Parse{Parse: &proto.ParseRequest{}}}) require.NoError(t, err) log, err := client.Recv() require.NoError(t, err) require.Equal(t, responses[0].GetLog().Output, log.GetLog().Output) complete, err := client.Recv() require.NoError(t, err) require.NotNil(t, complete) }) t.Run("Provision", func(t *testing.T) { t.Parallel() ctx, cancel := context.WithTimeout(ctx, testutil.WaitShort) defer cancel() planResponses := []*proto.Response{ { Type: &proto.Response_Log{ Log: &proto.Log{ Level: proto.LogLevel_INFO, Output: "log-output", }, }, }, { Type: &proto.Response_Plan{ Plan: &proto.PlanComplete{ Resources: []*proto.Resource{{ Name: "resource", }}, }, }, }, } applyResponses := []*proto.Response{ { Type: &proto.Response_Log{ Log: &proto.Log{ Level: proto.LogLevel_INFO, Output: "log-output", }, }, }, { Type: &proto.Response_Apply{ Apply: &proto.ApplyComplete{ Resources: []*proto.Resource{{ Name: "resource", }}, }, }, }, } data, err := echo.Tar(&echo.Responses{ ProvisionPlan: planResponses, ProvisionApply: applyResponses, }) require.NoError(t, err) client, err := api.Session(ctx) require.NoError(t, err) defer func() { err := client.Close() require.NoError(t, err) }() err = client.Send(&proto.Request{Type: &proto.Request_Config{Config: &proto.Config{ TemplateSourceArchive: data, }}}) require.NoError(t, err) err = client.Send(&proto.Request{Type: &proto.Request_Plan{Plan: &proto.PlanRequest{}}}) require.NoError(t, err) log, err := client.Recv() require.NoError(t, err) require.Equal(t, planResponses[0].GetLog().Output, log.GetLog().Output) complete, err := client.Recv() require.NoError(t, err) require.Equal(t, planResponses[1].GetPlan().Resources[0].Name, complete.GetPlan().Resources[0].Name) err = client.Send(&proto.Request{Type: &proto.Request_Apply{Apply: &proto.ApplyRequest{}}}) require.NoError(t, err) log, err = client.Recv() require.NoError(t, err) require.Equal(t, applyResponses[0].GetLog().Output, log.GetLog().Output) complete, err = client.Recv() require.NoError(t, err) require.Equal(t, applyResponses[1].GetApply().Resources[0].Name, complete.GetApply().Resources[0].Name) }) t.Run("ProvisionStop", func(t *testing.T) { t.Parallel() // Stop responses should be returned when the workspace is being stopped. data, err := echo.Tar(&echo.Responses{ ProvisionApply: applyCompleteResource("DEFAULT"), ProvisionPlan: planCompleteResource("DEFAULT"), ProvisionPlanMap: map[proto.WorkspaceTransition][]*proto.Response{ proto.WorkspaceTransition_STOP: planCompleteResource("STOP"), }, ProvisionApplyMap: map[proto.WorkspaceTransition][]*proto.Response{ proto.WorkspaceTransition_STOP: applyCompleteResource("STOP"), }, }) require.NoError(t, err) client, err := api.Session(ctx) require.NoError(t, err) defer func() { err := client.Close() require.NoError(t, err) }() err = client.Send(&proto.Request{Type: &proto.Request_Config{Config: &proto.Config{ TemplateSourceArchive: data, }}}) require.NoError(t, err) // Do stop. err = client.Send(&proto.Request{ Type: &proto.Request_Plan{ Plan: &proto.PlanRequest{ Metadata: &proto.Metadata{ WorkspaceTransition: proto.WorkspaceTransition_STOP, }, }, }, }) require.NoError(t, err) complete, err := client.Recv() require.NoError(t, err) require.Equal(t, "STOP", complete.GetPlan().Resources[0].Name, ) // Do start. err = client.Send(&proto.Request{ Type: &proto.Request_Plan{ Plan: &proto.PlanRequest{ Metadata: &proto.Metadata{ WorkspaceTransition: proto.WorkspaceTransition_START, }, }, }, }) require.NoError(t, err) complete, err = client.Recv() require.NoError(t, err) require.Equal(t, "DEFAULT", complete.GetPlan().Resources[0].Name, ) }) t.Run("ProvisionWithLogLevel", func(t *testing.T) { t.Parallel() ctx, cancel := context.WithTimeout(ctx, testutil.WaitShort) defer cancel() responses := []*proto.Response{{ Type: &proto.Response_Log{ Log: &proto.Log{ Level: proto.LogLevel_TRACE, Output: "log-output-trace", }, }, }, { Type: &proto.Response_Log{ Log: &proto.Log{ Level: proto.LogLevel_INFO, Output: "log-output-info", }, }, }, { Type: &proto.Response_Apply{ Apply: &proto.ApplyComplete{ Resources: []*proto.Resource{{ Name: "resource", }}, }, }, }} data, err := echo.Tar(&echo.Responses{ ProvisionPlan: echo.PlanComplete, ProvisionApply: responses, }) require.NoError(t, err) client, err := api.Session(ctx) require.NoError(t, err) defer func() { err := client.Close() require.NoError(t, err) }() err = client.Send(&proto.Request{Type: &proto.Request_Config{Config: &proto.Config{ TemplateSourceArchive: data, ProvisionerLogLevel: "debug", }}}) require.NoError(t, err) // Plan is required before apply err = client.Send(&proto.Request{Type: &proto.Request_Plan{Plan: &proto.PlanRequest{}}}) require.NoError(t, err) complete, err := client.Recv() require.NoError(t, err) require.NotNil(t, complete.GetPlan()) err = client.Send(&proto.Request{Type: &proto.Request_Apply{Apply: &proto.ApplyRequest{}}}) require.NoError(t, err) log, err := client.Recv() require.NoError(t, err) // Skip responses[0] as it's trace level require.Equal(t, responses[1].GetLog().Output, log.GetLog().Output) complete, err = client.Recv() require.NoError(t, err) require.Equal(t, responses[2].GetApply().Resources[0].Name, complete.GetApply().Resources[0].Name) }) } func planCompleteResource(name string) []*proto.Response { return []*proto.Response{{ Type: &proto.Response_Plan{ Plan: &proto.PlanComplete{ Resources: []*proto.Resource{{ Name: name, }}, }, }, }} } func applyCompleteResource(name string) []*proto.Response { return []*proto.Response{{ Type: &proto.Response_Apply{ Apply: &proto.ApplyComplete{ Resources: []*proto.Resource{{ Name: name, }}, }, }, }} }