mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
60b3fd0783
# What this does Dynamic parameters caches the `./terraform/modules` directory for parameter usage. What this PR does is send over this archive to the provisioner when building workspaces. This allow terraform to skip downloading modules from their registries, a step that takes seconds. <img width="1223" height="429" alt="Screenshot From 2025-12-29 12-57-52" src="https://github.com/user-attachments/assets/16066e0a-ac79-4296-819d-924f4b0418dc" /> # Wire protocol The wire protocol reuses the same mechanism used to download the modules `provisoner -> coder`. It splits up large archives into multiple protobuf messages so larger archives can be sent under the message size limit. # 🚨 Behavior Change (Breaking Change) 🚨 **Before this PR** modules were downloaded on every workspace build. This means unpinned modules always fetched the latest version **After this PR** modules are cached at template import time, and their versions are effectively pinned for all subsequent workspace builds.
86 lines
2.5 KiB
Go
86 lines
2.5 KiB
Go
package provisionersdk
|
|
|
|
import (
|
|
"io"
|
|
|
|
"golang.org/x/xerrors"
|
|
|
|
sdkproto "github.com/coder/coder/v2/provisionersdk/proto"
|
|
)
|
|
|
|
// HandleReceivingDataUpload can download a multi-part file from a proto stream.
|
|
// The stream is expected to be closed by the caller.
|
|
func HandleReceivingDataUpload(stream interface {
|
|
Recv() (*sdkproto.FileUpload, error)
|
|
},
|
|
) (*sdkproto.DataBuilder, error) {
|
|
var file *sdkproto.DataBuilder
|
|
UploadFileStream:
|
|
for {
|
|
msg, err := stream.Recv()
|
|
if err != nil {
|
|
if xerrors.Is(err, io.EOF) {
|
|
// Do not return an EOF here, as it is a "retryable error" in the client context.
|
|
// This failure indicates the download stream was closed prematurely, and it is a
|
|
// fatal error.
|
|
return nil, xerrors.Errorf("stream closed before file download complete")
|
|
}
|
|
return nil, xerrors.Errorf("receive file download: %w", err)
|
|
}
|
|
|
|
switch typed := msg.Type.(type) {
|
|
case *sdkproto.FileUpload_Error:
|
|
return nil, xerrors.Errorf("download file: %s", typed.Error.Error)
|
|
case *sdkproto.FileUpload_DataUpload:
|
|
if file != nil {
|
|
return nil, xerrors.New("unexpected file download while waiting for file completion")
|
|
}
|
|
|
|
file, err = sdkproto.NewDataBuilder(&sdkproto.DataUpload{
|
|
UploadType: typed.DataUpload.UploadType,
|
|
DataHash: typed.DataUpload.DataHash,
|
|
FileSize: typed.DataUpload.FileSize,
|
|
Chunks: typed.DataUpload.Chunks,
|
|
})
|
|
if err != nil {
|
|
return nil, xerrors.Errorf("unable to create file download: %w", err)
|
|
}
|
|
|
|
if file.IsDone() {
|
|
// If a file is 0 bytes, we can consider it done immediately.
|
|
// This should never really happen in practice, but we handle it gracefully.
|
|
break UploadFileStream
|
|
}
|
|
case *sdkproto.FileUpload_ChunkPiece:
|
|
if file == nil {
|
|
return nil, xerrors.New("unexpected chunk piece while waiting for file upload")
|
|
}
|
|
|
|
done, err := file.Add(&sdkproto.ChunkPiece{
|
|
Data: typed.ChunkPiece.Data,
|
|
FullDataHash: typed.ChunkPiece.FullDataHash,
|
|
PieceIndex: typed.ChunkPiece.PieceIndex,
|
|
})
|
|
if err != nil {
|
|
return nil, xerrors.Errorf("unable to add a chunk piece: %w", err)
|
|
}
|
|
|
|
if done {
|
|
break UploadFileStream
|
|
}
|
|
default:
|
|
// This should never happen
|
|
return nil, xerrors.Errorf("received unknown file upload message type: %T", msg.Type)
|
|
}
|
|
}
|
|
|
|
// This needs to be called again by the caller to retrieve the final payload.
|
|
// It is called here to do a hash check and ensure the file is correct.
|
|
_, err := file.Complete()
|
|
if err != nil {
|
|
return nil, xerrors.Errorf("complete file upload: %w", err)
|
|
}
|
|
|
|
return file, nil
|
|
}
|