feat: support template bundles as zip archives (#11839)

This commit is contained in:
Marcin Tojek
2024-01-31 14:49:55 +01:00
committed by GitHub
parent b25deaae20
commit 13cbca679e
8 changed files with 248 additions and 18 deletions

View File

@ -1,6 +1,9 @@
package coderd
import (
"archive/tar"
"archive/zip"
"bytes"
"crypto/sha256"
"database/sql"
"encoding/hex"
@ -9,6 +12,7 @@ import (
"io"
"net/http"
"cdr.dev/slog"
"github.com/go-chi/chi/v5"
"github.com/google/uuid"
@ -21,6 +25,9 @@ import (
const (
tarMimeType = "application/x-tar"
zipMimeType = "application/zip"
httpFileMaxBytes = 10 * (10 << 20)
)
// @Summary Upload file
@ -30,7 +37,7 @@ const (
// @Produce json
// @Accept application/x-tar
// @Tags Files
// @Param Content-Type header string true "Content-Type must be `application/x-tar`" default(application/x-tar)
// @Param Content-Type header string true "Content-Type must be `application/x-tar` or `application/zip`" default(application/x-tar)
// @Param file formData file true "File to be uploaded"
// @Success 201 {object} codersdk.UploadResponse
// @Router /files [post]
@ -39,9 +46,8 @@ func (api *API) postFile(rw http.ResponseWriter, r *http.Request) {
apiKey := httpmw.APIKey(r)
contentType := r.Header.Get("Content-Type")
switch contentType {
case tarMimeType:
case tarMimeType, zipMimeType:
default:
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: fmt.Sprintf("Unsupported content type header %q.", contentType),
@ -49,7 +55,7 @@ func (api *API) postFile(rw http.ResponseWriter, r *http.Request) {
return
}
r.Body = http.MaxBytesReader(rw, r.Body, 10*(10<<20))
r.Body = http.MaxBytesReader(rw, r.Body, httpFileMaxBytes)
data, err := io.ReadAll(r.Body)
if err != nil {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
@ -58,6 +64,28 @@ func (api *API) postFile(rw http.ResponseWriter, r *http.Request) {
})
return
}
if contentType == zipMimeType {
zipReader, err := zip.NewReader(bytes.NewReader(data), int64(len(data)))
if err != nil {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "Incomplete .zip archive file.",
Detail: err.Error(),
})
return
}
data, err = CreateTarFromZip(zipReader)
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error processing .zip archive.",
Detail: err.Error(),
})
return
}
contentType = tarMimeType
}
hashBytes := sha256.Sum256(data)
hash := hex.EncodeToString(hashBytes[:])
file, err := api.Database.GetFileByHashAndCreator(ctx, database.GetFileByHashAndCreatorParams{
@ -108,7 +136,10 @@ func (api *API) postFile(rw http.ResponseWriter, r *http.Request) {
// @Success 200
// @Router /files/{fileID} [get]
func (api *API) fileByID(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var (
ctx = r.Context()
format = r.URL.Query().Get("format")
)
fileID := chi.URLParam(r, "fileID")
if fileID == "" {
@ -139,7 +170,29 @@ func (api *API) fileByID(rw http.ResponseWriter, r *http.Request) {
return
}
rw.Header().Set("Content-Type", file.Mimetype)
rw.WriteHeader(http.StatusOK)
_, _ = rw.Write(file.Data)
switch format {
case codersdk.FormatZip:
if file.Mimetype != codersdk.ContentTypeTar {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "Only .tar files can be converted to .zip format",
Detail: err.Error(),
})
return
}
rw.Header().Set("Content-Type", codersdk.ContentTypeZip)
rw.WriteHeader(http.StatusOK)
err = WriteZipArchive(rw, tar.NewReader(bytes.NewReader(file.Data)))
if err != nil {
api.Logger.Error(ctx, "invalid .zip archive", slog.F("file_id", fileID), slog.F("mimetype", file.Mimetype), slog.Error(err))
}
case "": // no format? no conversion
rw.Header().Set("Content-Type", file.Mimetype)
_, _ = rw.Write(file.Data)
default:
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "Unsupported conversion format.",
Detail: err.Error(),
})
}
}