Skip to content

feat: add template export functionality to UI #17890

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions coderd/coderd.go
Original file line number Diff line number Diff line change
Expand Up @@ -1114,6 +1114,7 @@ func New(options *Options) *API {
r.Get("/", api.template)
r.Delete("/", api.deleteTemplate)
r.Patch("/", api.patchTemplateMeta)
r.Get("/export", api.exportTemplate)
r.Route("/versions", func(r chi.Router) {
r.Post("/archive", api.postArchiveTemplateVersions)
r.Get("/", api.templateVersionsByTemplate)
Expand Down
110 changes: 110 additions & 0 deletions coderd/templates.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package coderd

import (
"archive/tar"
"bytes"
"context"
"database/sql"
"errors"
"fmt"
"io"
"net/http"
"sort"
"time"
Expand Down Expand Up @@ -32,6 +35,7 @@
"github.com/coder/coder/v2/coderd/workspacestats"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/examples"
"compress/gzip"
)

// Returns a single template.
Expand Down Expand Up @@ -1100,3 +1104,109 @@
}
return append(owners, templateAdmins...), nil
}

// @Summary Export template by ID
// @ID export-template-by-id
// @Security CoderSessionToken
// @Produce application/x-gzip
// @Tags Templates
// @Param template path string true "Template ID" format(uuid)
// @Success 200 {file} binary "Template archive"
// @Router /templates/{template}/export [get]
func (api *API) exportTemplate(rw http.ResponseWriter, r *http.Request) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this new endpoint necessary? coder templates pull just calls api/v2/files/{fileID}.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we could use the files endpoint for this feature.

ctx := r.Context()
template := httpmw.TemplateParam(r)

// Get the latest version of the template
version, err := api.Database.GetTemplateVersionByTemplateIDAndName(ctx, database.GetTemplateVersionByTemplateIDAndNameParams{
TemplateID: template.ID,

Check failure on line 1122 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / lint

cannot use template.ID (variable of array type "github.com/google/uuid".UUID) as "github.com/google/uuid".NullUUID value in struct literal

Check failure on line 1122 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / lint

cannot use template.ID (variable of array type "github.com/google/uuid".UUID) as "github.com/google/uuid".NullUUID value in struct literal

Check failure on line 1122 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / lint

cannot use template.ID (variable of array type "github.com/google/uuid".UUID) as "github.com/google/uuid".NullUUID value in struct literal

Check failure on line 1122 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go (windows-2022)

cannot use template.ID (variable of array type "github.com/google/uuid".UUID) as "github.com/google/uuid".NullUUID value in struct literal

Check failure on line 1122 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go (windows-2022)

cannot use template.ID (variable of array type "github.com/google/uuid".UUID) as "github.com/google/uuid".NullUUID value in struct literal

Check failure on line 1122 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go (windows-2022)

cannot use template.ID (variable of array type "github.com/google/uuid".UUID) as "github.com/google/uuid".NullUUID value in struct literal

Check failure on line 1122 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go (windows-2022)

cannot use template.ID (variable of array type "github.com/google/uuid".UUID) as "github.com/google/uuid".NullUUID value in struct literal

Check failure on line 1122 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go (windows-2022)

cannot use template.ID (variable of array type "github.com/google/uuid".UUID) as "github.com/google/uuid".NullUUID value in struct literal

Check failure on line 1122 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / gen

cannot use template.ID (variable of array type "github.com/google/uuid".UUID) as "github.com/google/uuid".NullUUID value in struct literal

Check failure on line 1122 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-cli (macos-latest)

cannot use template.ID (variable of array type "github.com/google/uuid".UUID) as "github.com/google/uuid".NullUUID value in struct literal

Check failure on line 1122 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-cli (macos-latest)

cannot use template.ID (variable of array type "github.com/google/uuid".UUID) as "github.com/google/uuid".NullUUID value in struct literal

Check failure on line 1122 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go-pg-16

cannot use template.ID (variable of array type "github.com/google/uuid".UUID) as "github.com/google/uuid".NullUUID value in struct literal

Check failure on line 1122 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go-pg-16

cannot use template.ID (variable of array type "github.com/google/uuid".UUID) as "github.com/google/uuid".NullUUID value in struct literal

Check failure on line 1122 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go-pg-16

cannot use template.ID (variable of array type "github.com/google/uuid".UUID) as "github.com/google/uuid".NullUUID value in struct literal

Check failure on line 1122 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go-pg-16

cannot use template.ID (variable of array type "github.com/google/uuid".UUID) as "github.com/google/uuid".NullUUID value in struct literal

Check failure on line 1122 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go-pg-16

cannot use template.ID (variable of array type "github.com/google/uuid".UUID) as "github.com/google/uuid".NullUUID value in struct literal

Check failure on line 1122 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go-pg (ubuntu-latest)

cannot use template.ID (variable of array type "github.com/google/uuid".UUID) as "github.com/google/uuid".NullUUID value in struct literal

Check failure on line 1122 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go-pg (ubuntu-latest)

cannot use template.ID (variable of array type "github.com/google/uuid".UUID) as "github.com/google/uuid".NullUUID value in struct literal

Check failure on line 1122 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go-pg (ubuntu-latest)

cannot use template.ID (variable of array type "github.com/google/uuid".UUID) as "github.com/google/uuid".NullUUID value in struct literal

Check failure on line 1122 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go-pg (ubuntu-latest)

cannot use template.ID (variable of array type "github.com/google/uuid".UUID) as "github.com/google/uuid".NullUUID value in struct literal

Check failure on line 1122 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go-pg (ubuntu-latest)

cannot use template.ID (variable of array type "github.com/google/uuid".UUID) as "github.com/google/uuid".NullUUID value in struct literal

Check failure on line 1122 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go (ubuntu-latest)

cannot use template.ID (variable of array type "github.com/google/uuid".UUID) as "github.com/google/uuid".NullUUID value in struct literal

Check failure on line 1122 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go (ubuntu-latest)

cannot use template.ID (variable of array type "github.com/google/uuid".UUID) as "github.com/google/uuid".NullUUID value in struct literal

Check failure on line 1122 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go (ubuntu-latest)

cannot use template.ID (variable of array type "github.com/google/uuid".UUID) as "github.com/google/uuid".NullUUID value in struct literal

Check failure on line 1122 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go (ubuntu-latest)

cannot use template.ID (variable of array type "github.com/google/uuid".UUID) as "github.com/google/uuid".NullUUID value in struct literal

Check failure on line 1122 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go (ubuntu-latest)

cannot use template.ID (variable of array type "github.com/google/uuid".UUID) as "github.com/google/uuid".NullUUID value in struct literal

Check failure on line 1122 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go (macos-latest)

cannot use template.ID (variable of array type "github.com/google/uuid".UUID) as "github.com/google/uuid".NullUUID value in struct literal

Check failure on line 1122 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go (macos-latest)

cannot use template.ID (variable of array type "github.com/google/uuid".UUID) as "github.com/google/uuid".NullUUID value in struct literal

Check failure on line 1122 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go (macos-latest)

cannot use template.ID (variable of array type "github.com/google/uuid".UUID) as "github.com/google/uuid".NullUUID value in struct literal

Check failure on line 1122 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go (macos-latest)

cannot use template.ID (variable of array type "github.com/google/uuid".UUID) as "github.com/google/uuid".NullUUID value in struct literal

Check failure on line 1122 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go (macos-latest)

cannot use template.ID (variable of array type "github.com/google/uuid".UUID) as "github.com/google/uuid".NullUUID value in struct literal

Check failure on line 1122 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go-race

cannot use template.ID (variable of array type "github.com/google/uuid".UUID) as "github.com/google/uuid".NullUUID value in struct literal

Check failure on line 1122 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go-race

cannot use template.ID (variable of array type "github.com/google/uuid".UUID) as "github.com/google/uuid".NullUUID value in struct literal

Check failure on line 1122 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go-race

cannot use template.ID (variable of array type "github.com/google/uuid".UUID) as "github.com/google/uuid".NullUUID value in struct literal

Check failure on line 1122 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go-race

cannot use template.ID (variable of array type "github.com/google/uuid".UUID) as "github.com/google/uuid".NullUUID value in struct literal

Check failure on line 1122 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go-race

cannot use template.ID (variable of array type "github.com/google/uuid".UUID) as "github.com/google/uuid".NullUUID value in struct literal

Check failure on line 1122 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go-race-pg

cannot use template.ID (variable of array type "github.com/google/uuid".UUID) as "github.com/google/uuid".NullUUID value in struct literal

Check failure on line 1122 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go-race-pg

cannot use template.ID (variable of array type "github.com/google/uuid".UUID) as "github.com/google/uuid".NullUUID value in struct literal

Check failure on line 1122 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go-race-pg

cannot use template.ID (variable of array type "github.com/google/uuid".UUID) as "github.com/google/uuid".NullUUID value in struct literal

Check failure on line 1122 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go-race-pg

cannot use template.ID (variable of array type "github.com/google/uuid".UUID) as "github.com/google/uuid".NullUUID value in struct literal

Check failure on line 1122 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go-race-pg

cannot use template.ID (variable of array type "github.com/google/uuid".UUID) as "github.com/google/uuid".NullUUID value in struct literal
Name: template.ActiveVersionID.String(),
})
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Failed to get template version.",
Detail: err.Error(),
})
return
}

// Create a buffer to store our archive
var buf bytes.Buffer

// Create gzip writer
gw := gzip.NewWriter(&buf)
tw := tar.NewWriter(gw)

// Add template files to archive
files := []struct {
Name string
Content string
}{
{
Name: "main.tf",
Content: version.Provisioner,

Check failure on line 1147 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / lint

version.Provisioner undefined (type database.TemplateVersion has no field or method Provisioner)) (typecheck)

Check failure on line 1147 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / lint

version.Provisioner undefined (type database.TemplateVersion has no field or method Provisioner)) (typecheck)

Check failure on line 1147 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / lint

version.Provisioner undefined (type database.TemplateVersion has no field or method Provisioner)) (typecheck)

Check failure on line 1147 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go (windows-2022)

version.Provisioner undefined (type database.TemplateVersion has no field or method Provisioner)

Check failure on line 1147 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go (windows-2022)

version.Provisioner undefined (type database.TemplateVersion has no field or method Provisioner)

Check failure on line 1147 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go (windows-2022)

version.Provisioner undefined (type database.TemplateVersion has no field or method Provisioner)

Check failure on line 1147 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go (windows-2022)

version.Provisioner undefined (type database.TemplateVersion has no field or method Provisioner)

Check failure on line 1147 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go (windows-2022)

version.Provisioner undefined (type database.TemplateVersion has no field or method Provisioner)

Check failure on line 1147 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / gen

version.Provisioner undefined (type database.TemplateVersion has no field or method Provisioner)

Check failure on line 1147 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-cli (macos-latest)

version.Provisioner undefined (type database.TemplateVersion has no field or method Provisioner)

Check failure on line 1147 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-cli (macos-latest)

version.Provisioner undefined (type database.TemplateVersion has no field or method Provisioner)

Check failure on line 1147 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go-pg-16

version.Provisioner undefined (type database.TemplateVersion has no field or method Provisioner)

Check failure on line 1147 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go-pg-16

version.Provisioner undefined (type database.TemplateVersion has no field or method Provisioner)

Check failure on line 1147 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go-pg-16

version.Provisioner undefined (type database.TemplateVersion has no field or method Provisioner)

Check failure on line 1147 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go-pg-16

version.Provisioner undefined (type database.TemplateVersion has no field or method Provisioner)

Check failure on line 1147 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go-pg-16

version.Provisioner undefined (type database.TemplateVersion has no field or method Provisioner)

Check failure on line 1147 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go-pg (ubuntu-latest)

version.Provisioner undefined (type database.TemplateVersion has no field or method Provisioner)

Check failure on line 1147 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go-pg (ubuntu-latest)

version.Provisioner undefined (type database.TemplateVersion has no field or method Provisioner)

Check failure on line 1147 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go-pg (ubuntu-latest)

version.Provisioner undefined (type database.TemplateVersion has no field or method Provisioner)

Check failure on line 1147 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go-pg (ubuntu-latest)

version.Provisioner undefined (type database.TemplateVersion has no field or method Provisioner)

Check failure on line 1147 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go-pg (ubuntu-latest)

version.Provisioner undefined (type database.TemplateVersion has no field or method Provisioner)

Check failure on line 1147 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go (ubuntu-latest)

version.Provisioner undefined (type database.TemplateVersion has no field or method Provisioner)

Check failure on line 1147 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go (ubuntu-latest)

version.Provisioner undefined (type database.TemplateVersion has no field or method Provisioner)

Check failure on line 1147 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go (ubuntu-latest)

version.Provisioner undefined (type database.TemplateVersion has no field or method Provisioner)

Check failure on line 1147 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go (ubuntu-latest)

version.Provisioner undefined (type database.TemplateVersion has no field or method Provisioner)

Check failure on line 1147 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go (ubuntu-latest)

version.Provisioner undefined (type database.TemplateVersion has no field or method Provisioner)

Check failure on line 1147 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go (macos-latest)

version.Provisioner undefined (type database.TemplateVersion has no field or method Provisioner)

Check failure on line 1147 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go (macos-latest)

version.Provisioner undefined (type database.TemplateVersion has no field or method Provisioner)

Check failure on line 1147 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go (macos-latest)

version.Provisioner undefined (type database.TemplateVersion has no field or method Provisioner)

Check failure on line 1147 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go (macos-latest)

version.Provisioner undefined (type database.TemplateVersion has no field or method Provisioner)

Check failure on line 1147 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go (macos-latest)

version.Provisioner undefined (type database.TemplateVersion has no field or method Provisioner)

Check failure on line 1147 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go-race

version.Provisioner undefined (type database.TemplateVersion has no field or method Provisioner)

Check failure on line 1147 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go-race

version.Provisioner undefined (type database.TemplateVersion has no field or method Provisioner)

Check failure on line 1147 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go-race

version.Provisioner undefined (type database.TemplateVersion has no field or method Provisioner)

Check failure on line 1147 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go-race

version.Provisioner undefined (type database.TemplateVersion has no field or method Provisioner)

Check failure on line 1147 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go-race

version.Provisioner undefined (type database.TemplateVersion has no field or method Provisioner)

Check failure on line 1147 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go-race-pg

version.Provisioner undefined (type database.TemplateVersion has no field or method Provisioner)

Check failure on line 1147 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go-race-pg

version.Provisioner undefined (type database.TemplateVersion has no field or method Provisioner)

Check failure on line 1147 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go-race-pg

version.Provisioner undefined (type database.TemplateVersion has no field or method Provisioner)

Check failure on line 1147 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go-race-pg

version.Provisioner undefined (type database.TemplateVersion has no field or method Provisioner)

Check failure on line 1147 in coderd/templates.go

View workflow job for this annotation

GitHub Actions / test-go-race-pg

version.Provisioner undefined (type database.TemplateVersion has no field or method Provisioner)
},
{
Name: "README.md",
Content: template.Description,
},
}

for _, file := range files {
hdr := &tar.Header{
Name: file.Name,
Mode: 0644,
Size: int64(len(file.Content)),
ModTime: time.Now(),
Format: tar.FormatPAX,
}

if err := tw.WriteHeader(hdr); err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Failed to write tar header.",
Detail: err.Error(),
})
return
}

if _, err := tw.Write([]byte(file.Content)); err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Failed to write file content.",
Detail: err.Error(),
})
return
}
}

// Close tar writer
if err := tw.Close(); err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Failed to close tar writer.",
Detail: err.Error(),
})
return
}

// Close gzip writer
if err := gw.Close(); err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Failed to close gzip writer.",
Detail: err.Error(),
})
return
}

// Set response headers
rw.Header().Set("Content-Type", "application/x-gzip")
rw.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s.tar.gz", template.Name))
rw.Header().Set("Content-Length", fmt.Sprintf("%d", buf.Len()))

// Write the archive to the response
if _, err := io.Copy(rw, &buf); err != nil {
api.Logger.Error(ctx, "failed to write template archive to response",
slog.Error(err),
slog.F("template_id", template.ID),
)
return
}
}
11 changes: 11 additions & 0 deletions site/src/pages/TemplatePage/TemplatePageHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import EditIcon from "@mui/icons-material/EditOutlined";
import DownloadIcon from "@mui/icons-material/Download";
import Button from "@mui/material/Button";
import { workspaces } from "api/queries/workspaces";
import type {
Expand Down Expand Up @@ -102,6 +103,16 @@ const TemplateMenu: FC<TemplateMenuProps> = ({
<CopyIcon className="size-icon-sm" />
Duplicate&hellip;
</DropdownMenuItem>

<DropdownMenuItem
onClick={() => {
window.location.href = `/api/v2/templates/${templateId}/export`;
}}
>
<DownloadIcon className="size-icon-sm" />
Export template
</DropdownMenuItem>

<DropdownMenuSeparator />
<DropdownMenuItem
className="text-content-destructive focus:text-content-destructive"
Expand Down
Loading