From 2d495c22dceee786187f6d9bff8509db9d5219e1 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Thu, 7 Mar 2024 12:25:34 +0000 Subject: [PATCH 1/2] feat(support): add template info to support bundle --- cli/support.go | 26 +++++++++++------ cli/support_test.go | 18 ++++++++++-- support/support.go | 63 ++++++++++++++++++++++++++++++++++++++--- support/support_test.go | 24 +++++++++++++++- 4 files changed, 116 insertions(+), 15 deletions(-) diff --git a/cli/support.go b/cli/support.go index e2d3019950ead..ac0e2041cb6ae 100644 --- a/cli/support.go +++ b/cli/support.go @@ -3,6 +3,7 @@ package cli import ( "archive/zip" "bytes" + "encoding/base64" "encoding/json" "fmt" "os" @@ -137,14 +138,17 @@ func findAgent(agentName string, haystack []codersdk.WorkspaceResource) (*coders func writeBundle(src *support.Bundle, dest *zip.Writer) error { for k, v := range map[string]any{ - "deployment/buildinfo.json": src.Deployment.BuildInfo, - "deployment/config.json": src.Deployment.Config, - "deployment/experiments.json": src.Deployment.Experiments, - "deployment/health.json": src.Deployment.HealthReport, - "network/netcheck_local.json": src.Network.NetcheckLocal, - "network/netcheck_remote.json": src.Network.NetcheckRemote, - "workspace/workspace.json": src.Workspace.Workspace, - "workspace/agent.json": src.Workspace.Agent, + "deployment/buildinfo.json": src.Deployment.BuildInfo, + "deployment/config.json": src.Deployment.Config, + "deployment/experiments.json": src.Deployment.Experiments, + "deployment/health.json": src.Deployment.HealthReport, + "network/netcheck_local.json": src.Network.NetcheckLocal, + "network/netcheck_remote.json": src.Network.NetcheckRemote, + "workspace/workspace.json": src.Workspace.Workspace, + "workspace/agent.json": src.Workspace.Agent, + "workspace/template.json": src.Workspace.Template, + "workspace/template_version.json": src.Workspace.TemplateVersion, + "workspace/parameters.json": src.Workspace.Parameters, } { f, err := dest.Create(k) if err != nil { @@ -157,11 +161,17 @@ func writeBundle(src *support.Bundle, dest *zip.Writer) error { } } + templateVersionBytes, err := base64.StdEncoding.DecodeString(src.Workspace.TemplateFileBase64) + if err != nil { + return xerrors.Errorf("decode template zip from base64") + } + for k, v := range map[string]string{ "network/coordinator_debug.html": src.Network.CoordinatorDebug, "network/tailnet_debug.html": src.Network.TailnetDebug, "workspace/build_logs.txt": humanizeBuildLogs(src.Workspace.BuildLogs), "workspace/agent_startup_logs.txt": humanizeAgentLogs(src.Workspace.AgentStartupLogs), + "workspace/template_file.zip": string(templateVersionBytes), "logs.txt": strings.Join(src.Logs, "\n"), } { f, err := dest.Create(k) diff --git a/cli/support_test.go b/cli/support_test.go index 41e2f6df6d98d..1782c26d45809 100644 --- a/cli/support_test.go +++ b/cli/support_test.go @@ -114,7 +114,6 @@ func assertBundleContents(t *testing.T, path string) { require.NoError(t, err, "open zip file") defer r.Close() for _, f := range r.File { - require.NotZero(t, f.UncompressedSize64, "file %q should not be empty", f.Name) switch f.Name { case "deployment/buildinfo.json": var v codersdk.BuildInfoResponse @@ -156,11 +155,26 @@ func assertBundleContents(t *testing.T, path string) { case "workspace/agent_startup_logs.txt": bs := readBytesFromZip(t, f) require.Contains(t, string(bs), "started up") + case "workspace/template.json": + var v codersdk.Template + decodeJSONFromZip(t, f, &v) + require.NotEmpty(t, v, "workspace template should not be empty") + case "workspace/template_version.json": + var v codersdk.TemplateVersion + decodeJSONFromZip(t, f, &v) + require.NotEmpty(t, v, "workspace template version should not be empty") + case "workspace/parameters.json": + var v []codersdk.TemplateVersionParameter + decodeJSONFromZip(t, f, &v) + require.NotNil(t, v, "workspace parameters should not be nil") + case "workspace/template_file.zip": + bs := readBytesFromZip(t, f) + require.NotNil(t, bs, "template file should not be nil") case "logs.txt": bs := readBytesFromZip(t, f) require.NotEmpty(t, bs, "logs should not be empty") default: - require.Fail(t, "unexpected file in bundle", f.Name) + require.Failf(t, "unexpected file in bundle: %q", f.Name) } } } diff --git a/support/support.go b/support/support.go index 0a1e31a968c31..fcd260ecdc7c1 100644 --- a/support/support.go +++ b/support/support.go @@ -2,6 +2,7 @@ package support import ( "context" + "encoding/base64" "io" "net/http" "strings" @@ -42,10 +43,14 @@ type Network struct { } type Workspace struct { - Workspace codersdk.Workspace `json:"workspace"` - BuildLogs []codersdk.ProvisionerJobLog `json:"build_logs"` - Agent codersdk.WorkspaceAgent `json:"agent"` - AgentStartupLogs []codersdk.WorkspaceAgentLog `json:"startup_logs"` + Workspace codersdk.Workspace `json:"workspace"` + Parameters []codersdk.WorkspaceBuildParameter `json:"parameters"` + Template codersdk.Template `json:"template"` + TemplateVersion codersdk.TemplateVersion `json:"template_version"` + TemplateFileBase64 string `json:"template_file_base64"` + BuildLogs []codersdk.ProvisionerJobLog `json:"build_logs"` + Agent codersdk.WorkspaceAgent `json:"agent"` + AgentStartupLogs []codersdk.WorkspaceAgentLog `json:"startup_logs"` } // Deps is a set of dependencies for discovering information @@ -229,6 +234,56 @@ func WorkspaceInfo(ctx context.Context, client *codersdk.Client, log slog.Logger return nil }) + eg.Go(func() error { + if w.Workspace.TemplateActiveVersionID == uuid.Nil { + return xerrors.Errorf("workspace has nil template active version id") + } + tv, err := client.TemplateVersion(ctx, w.Workspace.TemplateActiveVersionID) + if err != nil { + return xerrors.Errorf("fetch template active version id") + } + w.TemplateVersion = tv + + if tv.Job.FileID == uuid.Nil { + return xerrors.Errorf("template file id is nil") + } + raw, ctype, err := client.DownloadWithFormat(ctx, tv.Job.FileID, codersdk.FormatZip) + if err != nil { + return err + } + if ctype != codersdk.ContentTypeZip { + return xerrors.Errorf("expected content-type %s, got %s", codersdk.ContentTypeZip, ctype) + } + + b64encoded := base64.StdEncoding.EncodeToString(raw) + w.TemplateFileBase64 = b64encoded + return nil + }) + + eg.Go(func() error { + if w.Workspace.TemplateID == uuid.Nil { + return xerrors.Errorf("workspace has nil version id") + } + tpl, err := client.Template(ctx, w.Workspace.TemplateID) + if err != nil { + return xerrors.Errorf("fetch template") + } + w.Template = tpl + return nil + }) + + eg.Go(func() error { + if ws.LatestBuild.ID == uuid.Nil { + return xerrors.Errorf("workspace has nil latest build id") + } + params, err := client.WorkspaceBuildParameters(ctx, ws.LatestBuild.ID) + if err != nil { + return xerrors.Errorf("fetch workspace build parameters: %w", err) + } + w.Parameters = params + return nil + }) + if err := eg.Wait(); err != nil { log.Error(ctx, "fetch workspace information", slog.Error(err)) } diff --git a/support/support_test.go b/support/support_test.go index b1132dbdeb45f..c2427fadb88af 100644 --- a/support/support_test.go +++ b/support/support_test.go @@ -1,6 +1,7 @@ package support_test import ( + "bytes" "context" "io" "net/http" @@ -59,6 +60,10 @@ func TestRun(t *testing.T) { require.NotEmpty(t, bun.Workspace.BuildLogs) require.NotNil(t, bun.Workspace.Agent) require.NotEmpty(t, bun.Workspace.AgentStartupLogs) + require.NotEmpty(t, bun.Workspace.Template) + require.NotEmpty(t, bun.Workspace.TemplateVersion) + require.NotEmpty(t, bun.Workspace.TemplateFileBase64) + require.NotNil(t, bun.Workspace.Parameters) require.NotEmpty(t, bun.Logs) }) @@ -136,10 +141,27 @@ func assertSanitizedDeploymentConfig(t *testing.T, dc *codersdk.DeploymentConfig } func setupWorkspaceAndAgent(ctx context.Context, t *testing.T, client *codersdk.Client, db database.Store, user codersdk.CreateFirstUserResponse) (codersdk.Workspace, codersdk.WorkspaceAgent) { + // This is a valid zip file + zipBytes := make([]byte, 22) + zipBytes[0] = 80 + zipBytes[1] = 75 + zipBytes[2] = 0o5 + zipBytes[3] = 0o6 + uploadRes, err := client.Upload(ctx, codersdk.ContentTypeZip, bytes.NewReader(zipBytes)) + require.NoError(t, err) + + tv := dbfake.TemplateVersion(t, db). + FileID(uploadRes.ID). + Seed(database.TemplateVersion{ + OrganizationID: user.OrganizationID, + CreatedBy: user.UserID, + }). + Do() wbr := dbfake.WorkspaceBuild(t, db, database.Workspace{ OrganizationID: user.OrganizationID, OwnerID: user.UserID, - }).WithAgent().Do() + TemplateID: tv.Template.ID, + }).Resource().WithAgent().Do() ws, err := client.Workspace(ctx, wbr.Workspace.ID) require.NoError(t, err) agt := ws.LatestBuild.Resources[0].Agents[0] From 8e4e34c51f6f2114bed4c03b85edc4347d6ee26a Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Thu, 7 Mar 2024 14:12:05 +0000 Subject: [PATCH 2/2] fixup! feat(support): add template info to support bundle --- cli/support_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/support_test.go b/cli/support_test.go index 1782c26d45809..66ea44a949b36 100644 --- a/cli/support_test.go +++ b/cli/support_test.go @@ -164,7 +164,7 @@ func assertBundleContents(t *testing.T, path string) { decodeJSONFromZip(t, f, &v) require.NotEmpty(t, v, "workspace template version should not be empty") case "workspace/parameters.json": - var v []codersdk.TemplateVersionParameter + var v []codersdk.WorkspaceBuildParameter decodeJSONFromZip(t, f, &v) require.NotNil(t, v, "workspace parameters should not be nil") case "workspace/template_file.zip":