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.
171 lines
5.2 KiB
Go
171 lines
5.2 KiB
Go
package provisionersdk_test
|
|
|
|
import (
|
|
"context"
|
|
"net"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"go.uber.org/goleak"
|
|
"storj.io/drpc/drpcconn"
|
|
|
|
"github.com/coder/coder/v2/codersdk/drpcsdk"
|
|
"github.com/coder/coder/v2/provisionersdk"
|
|
"github.com/coder/coder/v2/provisionersdk/proto"
|
|
"github.com/coder/coder/v2/testutil"
|
|
)
|
|
|
|
func TestMain(m *testing.M) {
|
|
goleak.VerifyTestMain(m, testutil.GoleakOptions...)
|
|
}
|
|
|
|
func TestProvisionerSDK(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("ServeListener", func(t *testing.T) {
|
|
t.Parallel()
|
|
client, server := drpcsdk.MemTransportPipe()
|
|
defer client.Close()
|
|
defer server.Close()
|
|
|
|
ctx, cancelFunc := context.WithCancel(context.Background())
|
|
defer cancelFunc()
|
|
go func() {
|
|
err := provisionersdk.Serve(ctx, unimplementedServer{}, &provisionersdk.ServeOptions{
|
|
Listener: server,
|
|
WorkDirectory: t.TempDir(),
|
|
})
|
|
assert.NoError(t, err)
|
|
}()
|
|
|
|
api := proto.NewDRPCProvisionerClient(client)
|
|
s, err := api.Session(ctx)
|
|
require.NoError(t, err)
|
|
err = s.Send(&proto.Request{Type: &proto.Request_Config{Config: &proto.Config{}}})
|
|
require.NoError(t, err)
|
|
|
|
err = s.Send(&proto.Request{Type: &proto.Request_Init{Init: &proto.InitRequest{}}})
|
|
require.NoError(t, err)
|
|
_, err = s.Recv()
|
|
require.NoError(t, err)
|
|
|
|
err = s.Send(&proto.Request{Type: &proto.Request_Parse{Parse: &proto.ParseRequest{}}})
|
|
require.NoError(t, err)
|
|
msg, err := s.Recv()
|
|
require.NoError(t, err)
|
|
require.Equal(t, "unimplemented", msg.GetParse().GetError())
|
|
|
|
err = s.Send(&proto.Request{Type: &proto.Request_Plan{Plan: &proto.PlanRequest{}}})
|
|
require.NoError(t, err)
|
|
msg, err = s.Recv()
|
|
require.NoError(t, err)
|
|
// Plan has no error so that we're allowed to run Apply
|
|
require.Equal(t, "", msg.GetPlan().GetError())
|
|
|
|
err = s.Send(&proto.Request{Type: &proto.Request_Apply{Apply: &proto.ApplyRequest{}}})
|
|
require.NoError(t, err)
|
|
msg, err = s.Recv()
|
|
require.NoError(t, err)
|
|
require.Equal(t, "unimplemented", msg.GetApply().GetError())
|
|
})
|
|
|
|
t.Run("ServeClosedPipe", func(t *testing.T) {
|
|
t.Parallel()
|
|
client, server := drpcsdk.MemTransportPipe()
|
|
_ = client.Close()
|
|
_ = server.Close()
|
|
|
|
err := provisionersdk.Serve(context.Background(), unimplementedServer{}, &provisionersdk.ServeOptions{
|
|
Listener: server,
|
|
WorkDirectory: t.TempDir(),
|
|
})
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("ServeConn", func(t *testing.T) {
|
|
t.Parallel()
|
|
client, server := net.Pipe()
|
|
defer client.Close()
|
|
defer server.Close()
|
|
|
|
ctx, cancelFunc := context.WithTimeout(context.Background(), testutil.WaitMedium)
|
|
defer cancelFunc()
|
|
srvErr := make(chan error, 1)
|
|
go func() {
|
|
err := provisionersdk.Serve(ctx, unimplementedServer{}, &provisionersdk.ServeOptions{
|
|
Conn: server,
|
|
WorkDirectory: t.TempDir(),
|
|
})
|
|
srvErr <- err
|
|
}()
|
|
|
|
api := proto.NewDRPCProvisionerClient(drpcconn.NewWithOptions(client, drpcconn.Options{
|
|
Manager: drpcsdk.DefaultDRPCOptions(nil),
|
|
}))
|
|
s, err := api.Session(ctx)
|
|
require.NoError(t, err)
|
|
err = s.Send(&proto.Request{Type: &proto.Request_Config{Config: &proto.Config{}}})
|
|
require.NoError(t, err)
|
|
|
|
err = s.Send(&proto.Request{Type: &proto.Request_Init{Init: &proto.InitRequest{}}})
|
|
require.NoError(t, err)
|
|
_, err = s.Recv()
|
|
require.NoError(t, err)
|
|
|
|
err = s.Send(&proto.Request{Type: &proto.Request_Parse{Parse: &proto.ParseRequest{}}})
|
|
require.NoError(t, err)
|
|
msg, err := s.Recv()
|
|
require.NoError(t, err)
|
|
require.Equal(t, "unimplemented", msg.GetParse().GetError())
|
|
|
|
err = s.Send(&proto.Request{Type: &proto.Request_Plan{Plan: &proto.PlanRequest{}}})
|
|
require.NoError(t, err)
|
|
msg, err = s.Recv()
|
|
require.NoError(t, err)
|
|
// Plan has no error so that we're allowed to run Apply
|
|
require.Equal(t, "", msg.GetPlan().GetError())
|
|
|
|
err = s.Send(&proto.Request{Type: &proto.Request_Apply{Apply: &proto.ApplyRequest{}}})
|
|
require.NoError(t, err)
|
|
msg, err = s.Recv()
|
|
require.NoError(t, err)
|
|
require.Equal(t, "unimplemented", msg.GetApply().GetError())
|
|
|
|
// Check provisioner closes when the connection does
|
|
err = s.Close()
|
|
require.NoError(t, err)
|
|
err = api.DRPCConn().Close()
|
|
require.NoError(t, err)
|
|
select {
|
|
case <-ctx.Done():
|
|
t.Fatal("timeout waiting for provisioner")
|
|
case err = <-srvErr:
|
|
require.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
|
|
var _ provisionersdk.Server = unimplementedServer{}
|
|
|
|
type unimplementedServer struct{}
|
|
|
|
func (unimplementedServer) Init(s *provisionersdk.Session, r *provisionersdk.InitRequest, canceledOrComplete <-chan struct{}) *proto.InitComplete {
|
|
return &proto.InitComplete{}
|
|
}
|
|
|
|
func (unimplementedServer) Graph(s *provisionersdk.Session, r *proto.GraphRequest, canceledOrComplete <-chan struct{}) *proto.GraphComplete {
|
|
return &proto.GraphComplete{Error: "unimplemented"}
|
|
}
|
|
|
|
func (unimplementedServer) Parse(_ *provisionersdk.Session, _ *proto.ParseRequest, _ <-chan struct{}) *proto.ParseComplete {
|
|
return &proto.ParseComplete{Error: "unimplemented"}
|
|
}
|
|
|
|
func (unimplementedServer) Plan(_ *provisionersdk.Session, _ *proto.PlanRequest, _ <-chan struct{}) *proto.PlanComplete {
|
|
return &proto.PlanComplete{}
|
|
}
|
|
|
|
func (unimplementedServer) Apply(_ *provisionersdk.Session, _ *proto.ApplyRequest, _ <-chan struct{}) *proto.ApplyComplete {
|
|
return &proto.ApplyComplete{Error: "unimplemented"}
|
|
}
|