From 682238d384dc6068fdd4a825a2e8220dd50d2af7 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Fri, 4 Feb 2022 16:51:54 -0600 Subject: [PATCH] feat: Add echo provisioner (#162) This replaces the cdr-basic provisioner type with "echo". It reads binary data from the directory and returns the responses in order. This is used to test project and workspace job logic. --- coderd/coderdtest/coderdtest.go | 20 ++- coderd/projecthistory_test.go | 25 ++-- coderd/projects.go | 2 +- coderd/projects_test.go | 42 +++---- coderd/provisionerdaemons.go | 2 +- coderd/workspacehistory_test.go | 51 +++----- coderd/workspacehistorylogs_test.go | 40 +++--- coderd/workspaces_test.go | 8 +- codersdk/projects_test.go | 12 +- codersdk/workspaces_test.go | 24 ++-- database/dump.sql | 4 +- database/migrations/000002_projects.up.sql | 2 +- database/models.go | 2 +- provisioner/echo/serve.go | 138 +++++++++++++++++++++ provisioner/echo/serve_test.go | 123 ++++++++++++++++++ 15 files changed, 366 insertions(+), 129 deletions(-) create mode 100644 provisioner/echo/serve.go create mode 100644 provisioner/echo/serve_test.go diff --git a/coderd/coderdtest/coderdtest.go b/coderd/coderdtest/coderdtest.go index d064c82dcf..86a50fabd0 100644 --- a/coderd/coderdtest/coderdtest.go +++ b/coderd/coderdtest/coderdtest.go @@ -20,7 +20,7 @@ import ( "github.com/coder/coder/database" "github.com/coder/coder/database/databasefake" "github.com/coder/coder/database/postgres" - "github.com/coder/coder/provisioner/terraform" + "github.com/coder/coder/provisioner/echo" "github.com/coder/coder/provisionerd" "github.com/coder/coder/provisionersdk" "github.com/coder/coder/provisionersdk/proto" @@ -64,21 +64,19 @@ func (s *Server) RandomInitialUser(t *testing.T) coderd.CreateInitialUserRequest return req } -// AddProvisionerd launches a new provisionerd instance! +// AddProvisionerd launches a new provisionerd instance with the +// test provisioner registered. func (s *Server) AddProvisionerd(t *testing.T) io.Closer { - tfClient, tfServer := provisionersdk.TransportPipe() + echoClient, echoServer := provisionersdk.TransportPipe() ctx, cancelFunc := context.WithCancel(context.Background()) t.Cleanup(func() { - _ = tfClient.Close() - _ = tfServer.Close() + _ = echoClient.Close() + _ = echoServer.Close() cancelFunc() }) go func() { - err := terraform.Serve(ctx, &terraform.ServeOptions{ - ServeOptions: &provisionersdk.ServeOptions{ - Listener: tfServer, - }, - Logger: slogtest.Make(t, nil).Named("terraform-provisioner").Leveled(slog.LevelDebug), + err := echo.Serve(ctx, &provisionersdk.ServeOptions{ + Listener: echoServer, }) require.NoError(t, err) }() @@ -88,7 +86,7 @@ func (s *Server) AddProvisionerd(t *testing.T) io.Closer { PollInterval: 50 * time.Millisecond, UpdateInterval: 50 * time.Millisecond, Provisioners: provisionerd.Provisioners{ - string(database.ProvisionerTypeTerraform): proto.NewDRPCProvisionerClient(provisionersdk.Conn(tfClient)), + string(database.ProvisionerTypeEcho): proto.NewDRPCProvisionerClient(provisionersdk.Conn(echoClient)), }, WorkDirectory: t.TempDir(), }) diff --git a/coderd/projecthistory_test.go b/coderd/projecthistory_test.go index f3a1922b0e..a0cf932886 100644 --- a/coderd/projecthistory_test.go +++ b/coderd/projecthistory_test.go @@ -11,6 +11,8 @@ import ( "github.com/coder/coder/coderd" "github.com/coder/coder/coderd/coderdtest" "github.com/coder/coder/database" + "github.com/coder/coder/provisioner/echo" + "github.com/coder/coder/provisionersdk/proto" ) func TestProjectHistory(t *testing.T) { @@ -22,7 +24,7 @@ func TestProjectHistory(t *testing.T) { user := server.RandomInitialUser(t) project, err := server.Client.CreateProject(context.Background(), user.Organization, coderd.CreateProjectRequest{ Name: "someproject", - Provisioner: database.ProvisionerTypeTerraform, + Provisioner: database.ProvisionerTypeEcho, }) require.NoError(t, err) versions, err := server.Client.ListProjectHistory(context.Background(), user.Organization, project.Name) @@ -36,21 +38,18 @@ func TestProjectHistory(t *testing.T) { user := server.RandomInitialUser(t) project, err := server.Client.CreateProject(context.Background(), user.Organization, coderd.CreateProjectRequest{ Name: "someproject", - Provisioner: database.ProvisionerTypeTerraform, + Provisioner: database.ProvisionerTypeEcho, }) require.NoError(t, err) - var buffer bytes.Buffer - writer := tar.NewWriter(&buffer) - err = writer.WriteHeader(&tar.Header{ - Name: "file", - Size: 1 << 10, - }) - require.NoError(t, err) - _, err = writer.Write(make([]byte, 1<<10)) + data, err := echo.Tar([]*proto.Parse_Response{{ + Type: &proto.Parse_Response_Complete{ + Complete: &proto.Parse_Complete{}, + }, + }}, nil) require.NoError(t, err) history, err := server.Client.CreateProjectHistory(context.Background(), user.Organization, project.Name, coderd.CreateProjectHistoryRequest{ StorageMethod: database.ProjectStorageMethodInlineArchive, - StorageSource: buffer.Bytes(), + StorageSource: data, }) require.NoError(t, err) versions, err := server.Client.ListProjectHistory(context.Background(), user.Organization, project.Name) @@ -67,7 +66,7 @@ func TestProjectHistory(t *testing.T) { user := server.RandomInitialUser(t) project, err := server.Client.CreateProject(context.Background(), user.Organization, coderd.CreateProjectRequest{ Name: "someproject", - Provisioner: database.ProvisionerTypeTerraform, + Provisioner: database.ProvisionerTypeEcho, }) require.NoError(t, err) var buffer bytes.Buffer @@ -92,7 +91,7 @@ func TestProjectHistory(t *testing.T) { user := server.RandomInitialUser(t) project, err := server.Client.CreateProject(context.Background(), user.Organization, coderd.CreateProjectRequest{ Name: "someproject", - Provisioner: database.ProvisionerTypeTerraform, + Provisioner: database.ProvisionerTypeEcho, }) require.NoError(t, err) _, err = server.Client.CreateProjectHistory(context.Background(), user.Organization, project.Name, coderd.CreateProjectHistoryRequest{ diff --git a/coderd/projects.go b/coderd/projects.go index e0ed677fa6..991d20f0e7 100644 --- a/coderd/projects.go +++ b/coderd/projects.go @@ -22,7 +22,7 @@ type Project database.Project // CreateProjectRequest enables callers to create a new Project. type CreateProjectRequest struct { Name string `json:"name" validate:"username,required"` - Provisioner database.ProvisionerType `json:"provisioner" validate:"oneof=terraform cdr-basic,required"` + Provisioner database.ProvisionerType `json:"provisioner" validate:"oneof=terraform echo,required"` } // Lists all projects the authenticated user has access to. diff --git a/coderd/projects_test.go b/coderd/projects_test.go index 5e96b78aad..14ed1af4f0 100644 --- a/coderd/projects_test.go +++ b/coderd/projects_test.go @@ -1,8 +1,6 @@ package coderd_test import ( - "archive/tar" - "bytes" "context" "testing" "time" @@ -12,6 +10,8 @@ import ( "github.com/coder/coder/coderd" "github.com/coder/coder/coderd/coderdtest" "github.com/coder/coder/database" + "github.com/coder/coder/provisioner/echo" + "github.com/coder/coder/provisionersdk/proto" ) func TestProjects(t *testing.T) { @@ -23,7 +23,7 @@ func TestProjects(t *testing.T) { user := server.RandomInitialUser(t) _, err := server.Client.CreateProject(context.Background(), user.Organization, coderd.CreateProjectRequest{ Name: "someproject", - Provisioner: database.ProvisionerTypeTerraform, + Provisioner: database.ProvisionerTypeEcho, }) require.NoError(t, err) }) @@ -34,12 +34,12 @@ func TestProjects(t *testing.T) { user := server.RandomInitialUser(t) _, err := server.Client.CreateProject(context.Background(), user.Organization, coderd.CreateProjectRequest{ Name: "someproject", - Provisioner: database.ProvisionerTypeTerraform, + Provisioner: database.ProvisionerTypeEcho, }) require.NoError(t, err) _, err = server.Client.CreateProject(context.Background(), user.Organization, coderd.CreateProjectRequest{ Name: "someproject", - Provisioner: database.ProvisionerTypeTerraform, + Provisioner: database.ProvisionerTypeEcho, }) require.Error(t, err) }) @@ -59,7 +59,7 @@ func TestProjects(t *testing.T) { user := server.RandomInitialUser(t) _, err := server.Client.CreateProject(context.Background(), user.Organization, coderd.CreateProjectRequest{ Name: "someproject", - Provisioner: database.ProvisionerTypeTerraform, + Provisioner: database.ProvisionerTypeEcho, }) require.NoError(t, err) // Ensure global query works. @@ -89,7 +89,7 @@ func TestProjects(t *testing.T) { user := server.RandomInitialUser(t) project, err := server.Client.CreateProject(context.Background(), user.Organization, coderd.CreateProjectRequest{ Name: "someproject", - Provisioner: database.ProvisionerTypeTerraform, + Provisioner: database.ProvisionerTypeEcho, }) require.NoError(t, err) _, err = server.Client.Project(context.Background(), user.Organization, project.Name) @@ -102,7 +102,7 @@ func TestProjects(t *testing.T) { user := server.RandomInitialUser(t) project, err := server.Client.CreateProject(context.Background(), user.Organization, coderd.CreateProjectRequest{ Name: "someproject", - Provisioner: database.ProvisionerTypeTerraform, + Provisioner: database.ProvisionerTypeEcho, }) require.NoError(t, err) _, err = server.Client.ProjectParameters(context.Background(), user.Organization, project.Name) @@ -115,7 +115,7 @@ func TestProjects(t *testing.T) { user := server.RandomInitialUser(t) project, err := server.Client.CreateProject(context.Background(), user.Organization, coderd.CreateProjectRequest{ Name: "someproject", - Provisioner: database.ProvisionerTypeTerraform, + Provisioner: database.ProvisionerTypeEcho, }) require.NoError(t, err) _, err = server.Client.CreateProjectParameter(context.Background(), user.Organization, project.Name, coderd.CreateParameterValueRequest{ @@ -135,24 +135,22 @@ func TestProjects(t *testing.T) { _ = server.AddProvisionerd(t) project, err := server.Client.CreateProject(context.Background(), user.Organization, coderd.CreateProjectRequest{ Name: "someproject", - Provisioner: database.ProvisionerTypeTerraform, + Provisioner: database.ProvisionerTypeEcho, }) require.NoError(t, err) - var buffer bytes.Buffer - writer := tar.NewWriter(&buffer) - content := `variable "example" { - default = "hi" -}` - err = writer.WriteHeader(&tar.Header{ - Name: "main.tf", - Size: int64(len(content)), - }) - require.NoError(t, err) - _, err = writer.Write([]byte(content)) + data, err := echo.Tar([]*proto.Parse_Response{{ + Type: &proto.Parse_Response_Complete{ + Complete: &proto.Parse_Complete{ + ParameterSchemas: []*proto.ParameterSchema{{ + Name: "example", + }}, + }, + }, + }}, nil) require.NoError(t, err) history, err := server.Client.CreateProjectHistory(context.Background(), user.Organization, project.Name, coderd.CreateProjectHistoryRequest{ StorageMethod: database.ProjectStorageMethodInlineArchive, - StorageSource: buffer.Bytes(), + StorageSource: data, }) require.NoError(t, err) require.Eventually(t, func() bool { diff --git a/coderd/provisionerdaemons.go b/coderd/provisionerdaemons.go index ac9cb7f3cb..445f9ed171 100644 --- a/coderd/provisionerdaemons.go +++ b/coderd/provisionerdaemons.go @@ -65,7 +65,7 @@ func (api *api) provisionerDaemonsServe(rw http.ResponseWriter, r *http.Request) ID: uuid.New(), CreatedAt: database.Now(), Name: namesgenerator.GetRandomName(1), - Provisioners: []database.ProvisionerType{database.ProvisionerTypeCdrBasic, database.ProvisionerTypeTerraform}, + Provisioners: []database.ProvisionerType{database.ProvisionerTypeEcho, database.ProvisionerTypeTerraform}, }) if err != nil { _ = conn.Close(websocket.StatusInternalError, fmt.Sprintf("insert provisioner daemon:% s", err)) diff --git a/coderd/workspacehistory_test.go b/coderd/workspacehistory_test.go index 49a41a4d25..d65e82fd10 100644 --- a/coderd/workspacehistory_test.go +++ b/coderd/workspacehistory_test.go @@ -1,8 +1,6 @@ package coderd_test import ( - "archive/tar" - "bytes" "context" "testing" "time" @@ -14,6 +12,7 @@ import ( "github.com/coder/coder/coderd/coderdtest" "github.com/coder/coder/codersdk" "github.com/coder/coder/database" + "github.com/coder/coder/provisioner/echo" ) func TestWorkspaceHistory(t *testing.T) { @@ -22,7 +21,7 @@ func TestWorkspaceHistory(t *testing.T) { setupProjectAndWorkspace := func(t *testing.T, client *codersdk.Client, user coderd.CreateInitialUserRequest) (coderd.Project, coderd.Workspace) { project, err := client.CreateProject(context.Background(), user.Organization, coderd.CreateProjectRequest{ Name: "banana", - Provisioner: database.ProvisionerTypeTerraform, + Provisioner: database.ProvisionerTypeEcho, }) require.NoError(t, err) workspace, err := client.CreateWorkspace(context.Background(), "", coderd.CreateWorkspaceRequest{ @@ -33,24 +32,10 @@ func TestWorkspaceHistory(t *testing.T) { return project, workspace } - setupProjectHistory := func(t *testing.T, client *codersdk.Client, user coderd.CreateInitialUserRequest, project coderd.Project, files map[string]string) coderd.ProjectHistory { - var buffer bytes.Buffer - writer := tar.NewWriter(&buffer) - for path, content := range files { - err := writer.WriteHeader(&tar.Header{ - Name: path, - Size: int64(len(content)), - }) - require.NoError(t, err) - _, err = writer.Write([]byte(content)) - require.NoError(t, err) - } - err := writer.Flush() - require.NoError(t, err) - + setupProjectHistory := func(t *testing.T, client *codersdk.Client, user coderd.CreateInitialUserRequest, project coderd.Project, data []byte) coderd.ProjectHistory { projectHistory, err := client.CreateProjectHistory(context.Background(), user.Organization, project.Name, coderd.CreateProjectHistoryRequest{ StorageMethod: database.ProjectStorageMethodInlineArchive, - StorageSource: buffer.Bytes(), + StorageSource: data, }) require.NoError(t, err) require.Eventually(t, func() bool { @@ -71,9 +56,9 @@ func TestWorkspaceHistory(t *testing.T) { history, err := server.Client.ListWorkspaceHistory(context.Background(), "", workspace.Name) require.NoError(t, err) require.Len(t, history, 0) - projectVersion := setupProjectHistory(t, server.Client, user, project, map[string]string{ - "example": "file", - }) + data, err := echo.Tar(echo.ParseComplete, echo.ProvisionComplete) + require.NoError(t, err) + projectVersion := setupProjectHistory(t, server.Client, user, project, data) _, err = server.Client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{ ProjectHistoryID: projectVersion.ID, Transition: database.WorkspaceTransitionCreate, @@ -92,9 +77,9 @@ func TestWorkspaceHistory(t *testing.T) { project, workspace := setupProjectAndWorkspace(t, server.Client, user) _, err := server.Client.WorkspaceHistory(context.Background(), "", workspace.Name, "") require.Error(t, err) - projectHistory := setupProjectHistory(t, server.Client, user, project, map[string]string{ - "some": "file", - }) + data, err := echo.Tar(echo.ParseComplete, echo.ProvisionComplete) + require.NoError(t, err) + projectHistory := setupProjectHistory(t, server.Client, user, project, data) _, err = server.Client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{ ProjectHistoryID: projectHistory.ID, Transition: database.WorkspaceTransitionCreate, @@ -110,10 +95,10 @@ func TestWorkspaceHistory(t *testing.T) { user := server.RandomInitialUser(t) _ = server.AddProvisionerd(t) project, workspace := setupProjectAndWorkspace(t, server.Client, user) - projectHistory := setupProjectHistory(t, server.Client, user, project, map[string]string{ - "main.tf": `resource "null_resource" "example" {}`, - }) - _, err := server.Client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{ + data, err := echo.Tar(echo.ParseComplete, echo.ProvisionComplete) + require.NoError(t, err) + projectHistory := setupProjectHistory(t, server.Client, user, project, data) + _, err = server.Client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{ ProjectHistoryID: projectHistory.ID, Transition: database.WorkspaceTransitionCreate, }) @@ -135,11 +120,11 @@ func TestWorkspaceHistory(t *testing.T) { user := server.RandomInitialUser(t) _ = server.AddProvisionerd(t) project, workspace := setupProjectAndWorkspace(t, server.Client, user) - projectHistory := setupProjectHistory(t, server.Client, user, project, map[string]string{ - "some": "content", - }) + data, err := echo.Tar(echo.ParseComplete, echo.ProvisionComplete) + require.NoError(t, err) + projectHistory := setupProjectHistory(t, server.Client, user, project, data) - _, err := server.Client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{ + _, err = server.Client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{ ProjectHistoryID: projectHistory.ID, Transition: database.WorkspaceTransitionCreate, }) diff --git a/coderd/workspacehistorylogs_test.go b/coderd/workspacehistorylogs_test.go index bbaa1e6be6..310cd62153 100644 --- a/coderd/workspacehistorylogs_test.go +++ b/coderd/workspacehistorylogs_test.go @@ -1,8 +1,6 @@ package coderd_test import ( - "archive/tar" - "bytes" "context" "testing" "time" @@ -13,6 +11,8 @@ import ( "github.com/coder/coder/coderd/coderdtest" "github.com/coder/coder/codersdk" "github.com/coder/coder/database" + "github.com/coder/coder/provisioner/echo" + "github.com/coder/coder/provisionersdk/proto" ) func TestWorkspaceHistoryLogs(t *testing.T) { @@ -21,7 +21,7 @@ func TestWorkspaceHistoryLogs(t *testing.T) { setupProjectAndWorkspace := func(t *testing.T, client *codersdk.Client, user coderd.CreateInitialUserRequest) (coderd.Project, coderd.Workspace) { project, err := client.CreateProject(context.Background(), user.Organization, coderd.CreateProjectRequest{ Name: "banana", - Provisioner: database.ProvisionerTypeTerraform, + Provisioner: database.ProvisionerTypeEcho, }) require.NoError(t, err) workspace, err := client.CreateWorkspace(context.Background(), "", coderd.CreateWorkspaceRequest{ @@ -32,24 +32,10 @@ func TestWorkspaceHistoryLogs(t *testing.T) { return project, workspace } - setupProjectHistory := func(t *testing.T, client *codersdk.Client, user coderd.CreateInitialUserRequest, project coderd.Project, files map[string]string) coderd.ProjectHistory { - var buffer bytes.Buffer - writer := tar.NewWriter(&buffer) - for path, content := range files { - err := writer.WriteHeader(&tar.Header{ - Name: path, - Size: int64(len(content)), - }) - require.NoError(t, err) - _, err = writer.Write([]byte(content)) - require.NoError(t, err) - } - err := writer.Flush() - require.NoError(t, err) - + setupProjectHistory := func(t *testing.T, client *codersdk.Client, user coderd.CreateInitialUserRequest, project coderd.Project, data []byte) coderd.ProjectHistory { projectHistory, err := client.CreateProjectHistory(context.Background(), user.Organization, project.Name, coderd.CreateProjectHistoryRequest{ StorageMethod: database.ProjectStorageMethodInlineArchive, - StorageSource: buffer.Bytes(), + StorageSource: data, }) require.NoError(t, err) require.Eventually(t, func() bool { @@ -64,9 +50,19 @@ func TestWorkspaceHistoryLogs(t *testing.T) { user := server.RandomInitialUser(t) _ = server.AddProvisionerd(t) project, workspace := setupProjectAndWorkspace(t, server.Client, user) - projectHistory := setupProjectHistory(t, server.Client, user, project, map[string]string{ - "main.tf": `resource "null_resource" "test" {}`, - }) + data, err := echo.Tar(echo.ParseComplete, []*proto.Provision_Response{{ + Type: &proto.Provision_Response_Log{ + Log: &proto.Log{ + Output: "test", + }, + }, + }, { + Type: &proto.Provision_Response_Complete{ + Complete: &proto.Provision_Complete{}, + }, + }}) + require.NoError(t, err) + projectHistory := setupProjectHistory(t, server.Client, user, project, data) workspaceHistory, err := server.Client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{ ProjectHistoryID: projectHistory.ID, diff --git a/coderd/workspaces_test.go b/coderd/workspaces_test.go index 2ff3518136..2ee817899c 100644 --- a/coderd/workspaces_test.go +++ b/coderd/workspaces_test.go @@ -28,7 +28,7 @@ func TestWorkspaces(t *testing.T) { setupProjectAndWorkspace := func(t *testing.T, client *codersdk.Client, user coderd.CreateInitialUserRequest) (coderd.Project, coderd.Workspace) { project, err := client.CreateProject(context.Background(), user.Organization, coderd.CreateProjectRequest{ Name: "banana", - Provisioner: database.ProvisionerTypeTerraform, + Provisioner: database.ProvisionerTypeEcho, }) require.NoError(t, err) workspace, err := client.CreateWorkspace(context.Background(), "", coderd.CreateWorkspaceRequest{ @@ -55,7 +55,7 @@ func TestWorkspaces(t *testing.T) { user := server.RandomInitialUser(t) project, err := server.Client.CreateProject(context.Background(), user.Organization, coderd.CreateProjectRequest{ Name: "banana", - Provisioner: database.ProvisionerTypeTerraform, + Provisioner: database.ProvisionerTypeEcho, }) require.NoError(t, err) workspaces, err := server.Client.WorkspacesByProject(context.Background(), user.Organization, project.Name) @@ -79,7 +79,7 @@ func TestWorkspaces(t *testing.T) { user := server.RandomInitialUser(t) project, err := server.Client.CreateProject(context.Background(), user.Organization, coderd.CreateProjectRequest{ Name: "banana", - Provisioner: database.ProvisionerTypeTerraform, + Provisioner: database.ProvisionerTypeEcho, }) require.NoError(t, err) _, err = server.Client.CreateWorkspace(context.Background(), "", coderd.CreateWorkspaceRequest{ @@ -106,7 +106,7 @@ func TestWorkspaces(t *testing.T) { initial := server.RandomInitialUser(t) project, err := server.Client.CreateProject(context.Background(), initial.Organization, coderd.CreateProjectRequest{ Name: "banana", - Provisioner: database.ProvisionerTypeTerraform, + Provisioner: database.ProvisionerTypeEcho, }) require.NoError(t, err) _, err = server.Client.CreateUser(context.Background(), coderd.CreateUserRequest{ diff --git a/codersdk/projects_test.go b/codersdk/projects_test.go index 9ccc211e2d..0645438c24 100644 --- a/codersdk/projects_test.go +++ b/codersdk/projects_test.go @@ -46,7 +46,7 @@ func TestProjects(t *testing.T) { user := server.RandomInitialUser(t) _, err := server.Client.CreateProject(context.Background(), user.Organization, coderd.CreateProjectRequest{ Name: "bananas", - Provisioner: database.ProvisionerTypeTerraform, + Provisioner: database.ProvisionerTypeEcho, }) require.NoError(t, err) }) @@ -64,7 +64,7 @@ func TestProjects(t *testing.T) { user := server.RandomInitialUser(t) _, err := server.Client.CreateProject(context.Background(), user.Organization, coderd.CreateProjectRequest{ Name: "bananas", - Provisioner: database.ProvisionerTypeTerraform, + Provisioner: database.ProvisionerTypeEcho, }) require.NoError(t, err) _, err = server.Client.Project(context.Background(), user.Organization, "bananas") @@ -84,7 +84,7 @@ func TestProjects(t *testing.T) { user := server.RandomInitialUser(t) project, err := server.Client.CreateProject(context.Background(), user.Organization, coderd.CreateProjectRequest{ Name: "bananas", - Provisioner: database.ProvisionerTypeTerraform, + Provisioner: database.ProvisionerTypeEcho, }) require.NoError(t, err) _, err = server.Client.ListProjectHistory(context.Background(), user.Organization, project.Name) @@ -107,7 +107,7 @@ func TestProjects(t *testing.T) { user := server.RandomInitialUser(t) project, err := server.Client.CreateProject(context.Background(), user.Organization, coderd.CreateProjectRequest{ Name: "bananas", - Provisioner: database.ProvisionerTypeTerraform, + Provisioner: database.ProvisionerTypeEcho, }) require.NoError(t, err) var buffer bytes.Buffer @@ -135,7 +135,7 @@ func TestProjects(t *testing.T) { user := server.RandomInitialUser(t) project, err := server.Client.CreateProject(context.Background(), user.Organization, coderd.CreateProjectRequest{ Name: "someproject", - Provisioner: database.ProvisionerTypeTerraform, + Provisioner: database.ProvisionerTypeEcho, }) require.NoError(t, err) params, err := server.Client.ProjectParameters(context.Background(), user.Organization, project.Name) @@ -150,7 +150,7 @@ func TestProjects(t *testing.T) { user := server.RandomInitialUser(t) project, err := server.Client.CreateProject(context.Background(), user.Organization, coderd.CreateProjectRequest{ Name: "someproject", - Provisioner: database.ProvisionerTypeTerraform, + Provisioner: database.ProvisionerTypeEcho, }) require.NoError(t, err) param, err := server.Client.CreateProjectParameter(context.Background(), user.Organization, project.Name, coderd.CreateParameterValueRequest{ diff --git a/codersdk/workspaces_test.go b/codersdk/workspaces_test.go index 4b5e64d346..b030b37414 100644 --- a/codersdk/workspaces_test.go +++ b/codersdk/workspaces_test.go @@ -34,11 +34,11 @@ func TestWorkspaces(t *testing.T) { user := server.RandomInitialUser(t) project, err := server.Client.CreateProject(context.Background(), user.Organization, coderd.CreateProjectRequest{ Name: "tomato", - Provisioner: database.ProvisionerTypeTerraform, + Provisioner: database.ProvisionerTypeEcho, }) require.NoError(t, err) _, err = server.Client.CreateWorkspace(context.Background(), "", coderd.CreateWorkspaceRequest{ - Name: "wooow", + Name: "wow", ProjectID: project.ID, }) require.NoError(t, err) @@ -52,11 +52,11 @@ func TestWorkspaces(t *testing.T) { user := server.RandomInitialUser(t) project, err := server.Client.CreateProject(context.Background(), user.Organization, coderd.CreateProjectRequest{ Name: "tomato", - Provisioner: database.ProvisionerTypeTerraform, + Provisioner: database.ProvisionerTypeEcho, }) require.NoError(t, err) _, err = server.Client.CreateWorkspace(context.Background(), "", coderd.CreateWorkspaceRequest{ - Name: "wooow", + Name: "wow", ProjectID: project.ID, }) require.NoError(t, err) @@ -84,11 +84,11 @@ func TestWorkspaces(t *testing.T) { user := server.RandomInitialUser(t) project, err := server.Client.CreateProject(context.Background(), user.Organization, coderd.CreateProjectRequest{ Name: "tomato", - Provisioner: database.ProvisionerTypeTerraform, + Provisioner: database.ProvisionerTypeEcho, }) require.NoError(t, err) workspace, err := server.Client.CreateWorkspace(context.Background(), "", coderd.CreateWorkspaceRequest{ - Name: "wooow", + Name: "wow", ProjectID: project.ID, }) require.NoError(t, err) @@ -109,11 +109,11 @@ func TestWorkspaces(t *testing.T) { user := server.RandomInitialUser(t) project, err := server.Client.CreateProject(context.Background(), user.Organization, coderd.CreateProjectRequest{ Name: "tomato", - Provisioner: database.ProvisionerTypeTerraform, + Provisioner: database.ProvisionerTypeEcho, }) require.NoError(t, err) workspace, err := server.Client.CreateWorkspace(context.Background(), "", coderd.CreateWorkspaceRequest{ - Name: "wooow", + Name: "wow", ProjectID: project.ID, }) require.NoError(t, err) @@ -134,11 +134,11 @@ func TestWorkspaces(t *testing.T) { user := server.RandomInitialUser(t) project, err := server.Client.CreateProject(context.Background(), user.Organization, coderd.CreateProjectRequest{ Name: "tomato", - Provisioner: database.ProvisionerTypeTerraform, + Provisioner: database.ProvisionerTypeEcho, }) require.NoError(t, err) workspace, err := server.Client.CreateWorkspace(context.Background(), "", coderd.CreateWorkspaceRequest{ - Name: "wooow", + Name: "wow", ProjectID: project.ID, }) require.NoError(t, err) @@ -152,11 +152,11 @@ func TestWorkspaces(t *testing.T) { user := server.RandomInitialUser(t) project, err := server.Client.CreateProject(context.Background(), user.Organization, coderd.CreateProjectRequest{ Name: "tomato", - Provisioner: database.ProvisionerTypeTerraform, + Provisioner: database.ProvisionerTypeEcho, }) require.NoError(t, err) workspace, err := server.Client.CreateWorkspace(context.Background(), "", coderd.CreateWorkspaceRequest{ - Name: "wooow", + Name: "wow", ProjectID: project.ID, }) require.NoError(t, err) diff --git a/database/dump.sql b/database/dump.sql index 2aa3ae2792..7f174528af 100644 --- a/database/dump.sql +++ b/database/dump.sql @@ -52,8 +52,8 @@ CREATE TYPE provisioner_job_type AS ENUM ( ); CREATE TYPE provisioner_type AS ENUM ( - 'terraform', - 'cdr-basic' + 'echo', + 'terraform' ); CREATE TYPE userstatus AS ENUM ( diff --git a/database/migrations/000002_projects.up.sql b/database/migrations/000002_projects.up.sql index 62b7468558..0149ec8033 100644 --- a/database/migrations/000002_projects.up.sql +++ b/database/migrations/000002_projects.up.sql @@ -1,4 +1,4 @@ -CREATE TYPE provisioner_type AS ENUM ('terraform', 'cdr-basic'); +CREATE TYPE provisioner_type AS ENUM ('echo', 'terraform'); -- Project defines infrastructure that your software project -- requires for development. diff --git a/database/models.go b/database/models.go index dd7005a6c1..f0c0458e5c 100644 --- a/database/models.go +++ b/database/models.go @@ -191,8 +191,8 @@ func (e *ProvisionerJobType) Scan(src interface{}) error { type ProvisionerType string const ( + ProvisionerTypeEcho ProvisionerType = "echo" ProvisionerTypeTerraform ProvisionerType = "terraform" - ProvisionerTypeCdrBasic ProvisionerType = "cdr-basic" ) func (e *ProvisionerType) Scan(src interface{}) error { diff --git a/provisioner/echo/serve.go b/provisioner/echo/serve.go new file mode 100644 index 0000000000..12172b6c58 --- /dev/null +++ b/provisioner/echo/serve.go @@ -0,0 +1,138 @@ +package echo + +import ( + "archive/tar" + "bytes" + "context" + "fmt" + "os" + "path/filepath" + + "golang.org/x/xerrors" + + protobuf "google.golang.org/protobuf/proto" + + "github.com/coder/coder/provisionersdk" + "github.com/coder/coder/provisionersdk/proto" +) + +var ( + // ParseComplete is a helper to indicate an empty parse completion. + ParseComplete = []*proto.Parse_Response{{ + Type: &proto.Parse_Response_Complete{ + Complete: &proto.Parse_Complete{}, + }, + }} + // ProvisionComplete is a helper to indicate an empty provision completion. + ProvisionComplete = []*proto.Provision_Response{{ + Type: &proto.Provision_Response_Complete{ + Complete: &proto.Provision_Complete{}, + }, + }} +) + +// Serve starts the echo provisioner. +func Serve(ctx context.Context, options *provisionersdk.ServeOptions) error { + return provisionersdk.Serve(ctx, &echo{}, options) +} + +// The echo provisioner serves as a dummy provisioner primarily +// used for testing. It echos responses from JSON files in the +// format %d.protobuf. It's used for testing. +type echo struct { +} + +// Parse reads requests from the provided directory to stream responses. +func (*echo) Parse(request *proto.Parse_Request, stream proto.DRPCProvisioner_ParseStream) error { + for index := 0; ; index++ { + path := filepath.Join(request.Directory, fmt.Sprintf("%d.parse.protobuf", index)) + _, err := os.Stat(path) + if err != nil { + break + } + data, err := os.ReadFile(path) + if err != nil { + return xerrors.Errorf("read file %q: %w", path, err) + } + var response proto.Parse_Response + err = protobuf.Unmarshal(data, &response) + if err != nil { + return xerrors.Errorf("unmarshal: %w", err) + } + err = stream.Send(&response) + if err != nil { + return err + } + } + return nil +} + +// Provision reads requests from the provided directory to stream responses. +func (*echo) Provision(request *proto.Provision_Request, stream proto.DRPCProvisioner_ProvisionStream) error { + for index := 0; ; index++ { + path := filepath.Join(request.Directory, fmt.Sprintf("%d.provision.protobuf", index)) + _, err := os.Stat(path) + if err != nil { + break + } + data, err := os.ReadFile(path) + if err != nil { + return xerrors.Errorf("read file %q: %w", path, err) + } + var response proto.Provision_Response + err = protobuf.Unmarshal(data, &response) + if err != nil { + return xerrors.Errorf("unmarshal: %w", err) + } + err = stream.Send(&response) + if err != nil { + return err + } + } + return nil +} + +// Tar returns a tar archive of responses to provisioner operations. +func Tar(parseResponses []*proto.Parse_Response, provisionResponses []*proto.Provision_Response) ([]byte, error) { + var buffer bytes.Buffer + writer := tar.NewWriter(&buffer) + for index, response := range parseResponses { + data, err := protobuf.Marshal(response) + if err != nil { + return nil, err + } + err = writer.WriteHeader(&tar.Header{ + Name: fmt.Sprintf("%d.parse.protobuf", index), + Size: int64(len(data)), + }) + if err != nil { + return nil, err + } + _, err = writer.Write(data) + if err != nil { + return nil, err + } + } + for index, response := range provisionResponses { + data, err := protobuf.Marshal(response) + if err != nil { + return nil, err + } + err = writer.WriteHeader(&tar.Header{ + Name: fmt.Sprintf("%d.provision.protobuf", index), + Size: int64(len(data)), + }) + if err != nil { + return nil, err + } + _, err = writer.Write(data) + if err != nil { + return nil, err + } + } + err := writer.Flush() + if err != nil { + return nil, err + } + return buffer.Bytes(), nil +} diff --git a/provisioner/echo/serve_test.go b/provisioner/echo/serve_test.go new file mode 100644 index 0000000000..ce8a1e078b --- /dev/null +++ b/provisioner/echo/serve_test.go @@ -0,0 +1,123 @@ +package echo_test + +import ( + "archive/tar" + "bytes" + "context" + "io" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/coder/coder/provisioner/echo" + "github.com/coder/coder/provisionersdk" + "github.com/coder/coder/provisionersdk/proto" +) + +func TestEcho(t *testing.T) { + t.Parallel() + + // Create an in-memory provisioner to communicate with. + client, server := provisionersdk.TransportPipe() + ctx, cancelFunc := context.WithCancel(context.Background()) + t.Cleanup(func() { + _ = client.Close() + _ = server.Close() + cancelFunc() + }) + go func() { + err := echo.Serve(ctx, &provisionersdk.ServeOptions{ + Listener: server, + }) + require.NoError(t, err) + }() + api := proto.NewDRPCProvisionerClient(provisionersdk.Conn(client)) + + t.Run("Parse", func(t *testing.T) { + t.Parallel() + + responses := []*proto.Parse_Response{{ + Type: &proto.Parse_Response_Log{ + Log: &proto.Log{ + Output: "log-output", + }, + }, + }, { + Type: &proto.Parse_Response_Complete{ + Complete: &proto.Parse_Complete{ + ParameterSchemas: []*proto.ParameterSchema{{ + Name: "parameter-schema", + }}, + }, + }, + }} + data, err := echo.Tar(responses, nil) + require.NoError(t, err) + client, err := api.Parse(ctx, &proto.Parse_Request{ + Directory: unpackTar(t, data), + }) + 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.Equal(t, responses[1].GetComplete().ParameterSchemas[0].Name, + complete.GetComplete().ParameterSchemas[0].Name) + }) + + t.Run("Provision", func(t *testing.T) { + t.Parallel() + + responses := []*proto.Provision_Response{{ + Type: &proto.Provision_Response_Log{ + Log: &proto.Log{ + Output: "log-output", + }, + }, + }, { + Type: &proto.Provision_Response_Complete{ + Complete: &proto.Provision_Complete{ + Resources: []*proto.Resource{{ + Name: "resource", + }}, + }, + }, + }} + data, err := echo.Tar(nil, responses) + require.NoError(t, err) + client, err := api.Provision(ctx, &proto.Provision_Request{ + Directory: unpackTar(t, data), + }) + 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.Equal(t, responses[1].GetComplete().Resources[0].Name, + complete.GetComplete().Resources[0].Name) + }) +} + +func unpackTar(t *testing.T, data []byte) string { + directory := t.TempDir() + reader := tar.NewReader(bytes.NewReader(data)) + for { + header, err := reader.Next() + if err != nil { + break + } + // #nosec + path := filepath.Join(directory, header.Name) + file, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, 0600) + require.NoError(t, err) + _, err = io.CopyN(file, reader, 1<<20) + require.ErrorIs(t, err, io.EOF) + err = file.Close() + require.NoError(t, err) + } + return directory +}