mirror of
https://github.com/coder/coder.git
synced 2025-07-03 16:13:58 +00:00
feat: support template bundles as zip archives (#11839)
This commit is contained in:
@ -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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user