Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
bcdaf74
chore: refactor provisioner to handle more discrete terraform actions
Emyrk Nov 25, 2025
a148996
chore: initial init work, does not work for template import yet
Emyrk Nov 25, 2025
f9fb825
init step broken out
Emyrk Nov 25, 2025
8176ece
chore: echo provisioner, graph, and init
Emyrk Nov 25, 2025
c89d824
graph step in executor
Emyrk Nov 25, 2025
cbac23e
formatting
Emyrk Nov 25, 2025
94aa0f7
fix echo provisioner
Emyrk Nov 25, 2025
d454ca1
fmt
Emyrk Nov 25, 2025
2ac5bcf
fix dynamic param test
Emyrk Nov 25, 2025
3c94ed9
fix tests
Emyrk Nov 25, 2025
0e00edf
solve more tests
Emyrk Nov 25, 2025
d15327a
solve more tests
Emyrk Nov 25, 2025
121f9f8
more test fixes
Emyrk Nov 26, 2025
974dff9
fmt
Emyrk Nov 26, 2025
3e0c099
test fixes
Emyrk Nov 26, 2025
41f9dd5
test fixes
Emyrk Nov 26, 2025
4373086
make fmt
Emyrk Nov 26, 2025
8ef0f89
provisioner test fixes
Emyrk Nov 26, 2025
346183a
scaletest fixes
Emyrk Nov 26, 2025
c8c6e3d
test fixes
Emyrk Nov 26, 2025
7bf9707
provisioner test fixes
Emyrk Nov 26, 2025
11815cb
make gen
Emyrk Nov 26, 2025
ef7b33e
e2e fixes
Emyrk Nov 26, 2025
a422514
fmt
Emyrk Nov 26, 2025
c79f45e
graph is at the end now
Emyrk Nov 26, 2025
1c76f44
test
Emyrk Nov 26, 2025
de93769
ci push
Emyrk Nov 26, 2025
0a69503
make gen
Emyrk Nov 26, 2025
cf5acd3
more test fixes
Emyrk Nov 26, 2025
b6c2671
more test fixes
Emyrk Nov 26, 2025
54109a4
fix linting
Emyrk Nov 26, 2025
9630d94
fix linting
Emyrk Nov 26, 2025
7ac45f3
timing fixes
Emyrk Nov 26, 2025
3d3b293
linting
Emyrk Nov 26, 2025
25aa046
more tests
Emyrk Nov 26, 2025
9e19ed4
more tests
Emyrk Nov 26, 2025
9e7fb7e
more tests
Emyrk Nov 26, 2025
07c65a9
more tests
Emyrk Nov 26, 2025
b93321e
more tests
Emyrk Dec 1, 2025
9a6ab44
more tests
Emyrk Dec 1, 2025
7494a9b
more tests
Emyrk Dec 1, 2025
07c8383
fix provisioner test
Emyrk Dec 1, 2025
14ee7f2
fix script envs
Emyrk Dec 1, 2025
feb63df
fix script envs
Emyrk Dec 1, 2025
f978207
more fixes to provisioning test
Emyrk Dec 1, 2025
08078c6
flip asserts
Emyrk Dec 1, 2025
bcf9ff6
add logs to timing test
Emyrk Dec 1, 2025
336231e
linting
Emyrk Dec 1, 2025
bee6934
fix race condition
Emyrk Dec 1, 2025
9906c0a
add e2e echos
Emyrk Dec 1, 2025
63fd15f
add e2e echos
Emyrk Dec 1, 2025
3d2c059
e2e changes
Emyrk Dec 1, 2025
32da8c7
init timing arrays
Emyrk Dec 1, 2025
e86c6e2
init timing arrays
Emyrk Dec 1, 2025
99e3fd9
fix serialization
Emyrk Dec 1, 2025
7add31c
revert playwright change
Emyrk Dec 1, 2025
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
Prev Previous commit
Next Next commit
init step broken out
  • Loading branch information
Emyrk committed Dec 2, 2025
commit f9fb825bfae25ca79a780ec3a1caad5c28d7bc1e
49 changes: 45 additions & 4 deletions provisionerd/runner/init.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package runner

import (
"bytes"
"context"
"time"

Expand All @@ -22,17 +23,18 @@ func (r *Runner) init(ctx context.Context, omitModules bool, templateArchive []b
return nil, r.failedJobf("send init request: %v", err)
}

var moduleFilesUpload *sdkproto.DataBuilder
for {
msg, err := r.session.Recv()
if err != nil {
return nil, r.failedJobf("receive init response: %v", err)
}
switch msgType := msg.Type.(type) {
case *sdkproto.Response_Log:
r.logProvisionerJobLog(context.Background(), msgType.Log.Level, "workspace provisioner job logged",
r.logProvisionerJobLog(context.Background(), msgType.Log.Level, "terraform initialization",
slog.F("level", msgType.Log.Level),
slog.F("output", msgType.Log.Output),
slog.F("workspace_build_id", r.job.GetWorkspaceBuild().WorkspaceBuildId),
//slog.F("workspace_build_id", r.job.GetWorkspaceBuild().WorkspaceBuildId),
)

r.queueLog(ctx, &proto.Log{
Expand All @@ -43,10 +45,49 @@ func (r *Runner) init(ctx context.Context, omitModules bool, templateArchive []b
Stage: "Initializing Terraform Directory",
})
case *sdkproto.Response_DataUpload:
continue // Only for template imports
if omitModules {
return nil, r.failedJobf("received unexpected module files data upload when omitModules is true")
}
c := msgType.DataUpload
if c.UploadType != sdkproto.DataUploadType_UPLOAD_TYPE_MODULE_FILES {
return nil, r.failedJobf("invalid data upload type: %q", c.UploadType)
}

if moduleFilesUpload != nil {
return nil, r.failedJobf("multiple module data uploads received, only expect 1")
}

moduleFilesUpload, err = sdkproto.NewDataBuilder(c)
if err != nil {
return nil, r.failedJobf("create data builder: %w", err)
}
case *sdkproto.Response_ChunkPiece:
continue // Only for template imports
if omitModules {
return nil, r.failedJobf("received unexpected module files data upload when omitModules is true")
}
c := msgType.ChunkPiece
if moduleFilesUpload == nil {
return nil, r.failedJobf("received chunk piece before module files data upload")
}

_, err := moduleFilesUpload.Add(c)
if err != nil {
return nil, r.failedJobf("module files, add chunk piece: %w", err)
}
case *sdkproto.Response_Init:
if moduleFilesUpload != nil {
// If files were uploaded in multiple chunks, put them back together.
moduleFilesData, err := moduleFilesUpload.Complete()
if err != nil {
return nil, r.failedJobf("complete module files data upload: %w", err)
}

if !bytes.Equal(msgType.Init.ModuleFilesHash, moduleFilesUpload.Hash) {
return nil, r.failedJobf("module files hash mismatch, uploaded: %x, expected: %x", moduleFilesUpload.Hash, msgType.Init.ModuleFilesHash)
}
msgType.Init.ModuleFiles = moduleFilesData
}

return msgType.Init, nil
default:
return nil, r.failedJobf("unexpected init response type %T", msg.Type)
Expand Down
81 changes: 28 additions & 53 deletions provisionerd/runner/runner.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package runner

import (
"bytes"
"context"
"encoding/json"
"errors"
Expand Down Expand Up @@ -524,6 +523,18 @@ func (r *Runner) runTemplateImport(ctx context.Context) (*proto.CompletedJob, *p
return nil, failedJob
}

// Initialize the Terraform working directory
initResp, failedInit := r.init(ctx, false, r.job.GetTemplateSourceArchive())
if failedInit != nil {
return nil, failedInit
}
if initResp == nil {
return nil, r.failedJobf("template import init returned nil response")
}
if initResp.Error != "" {
return nil, r.failedJobf("template import init error: %s", initResp.Error)
}

// Parse parameters and update the job with the parameter specs
r.queueLog(ctx, &proto.Log{
Source: proto.LogSource_PROVISIONER_DAEMON,
Expand Down Expand Up @@ -560,7 +571,7 @@ func (r *Runner) runTemplateImport(ctx context.Context) (*proto.CompletedJob, *p
CoderUrl: r.job.GetTemplateImport().Metadata.CoderUrl,
WorkspaceOwnerGroups: r.job.GetTemplateImport().Metadata.WorkspaceOwnerGroups,
WorkspaceTransition: sdkproto.WorkspaceTransition_START,
}, false)
})
if err != nil {
return nil, r.failedJobf("template import provision for start: %s", err)
}
Expand All @@ -576,8 +587,7 @@ func (r *Runner) runTemplateImport(ctx context.Context) (*proto.CompletedJob, *p
CoderUrl: r.job.GetTemplateImport().Metadata.CoderUrl,
WorkspaceOwnerGroups: r.job.GetTemplateImport().Metadata.WorkspaceOwnerGroups,
WorkspaceTransition: sdkproto.WorkspaceTransition_STOP,
}, true, // Modules downloaded on the start provision
)
})
if err != nil {
return nil, r.failedJobf("template import provision for stop: %s", err)
}
Expand All @@ -601,8 +611,7 @@ func (r *Runner) runTemplateImport(ctx context.Context) (*proto.CompletedJob, *p
StopModules: stopProvision.Modules,
Presets: startProvision.Presets,
Plan: startProvision.Plan,
// ModuleFiles are not on the stopProvision. So grab from the startProvision.
ModuleFiles: startProvision.ModuleFiles,
ModuleFiles: initResp.ModuleFiles,
// ModuleFileHash will be populated if the file is uploaded async
ModuleFilesHash: []byte{},
HasAiTasks: startProvision.HasAITasks,
Expand Down Expand Up @@ -669,16 +678,15 @@ type templateImportProvision struct {
Modules []*sdkproto.Module
Presets []*sdkproto.Preset
Plan json.RawMessage
ModuleFiles []byte
HasAITasks bool
HasExternalAgents bool
}

// Performs a dry-run provision when importing a template.
// This is used to detect resources that would be provisioned for a workspace in various states.
// It doesn't define values for rich parameters as they're unknown during template import.
func (r *Runner) runTemplateImportProvision(ctx context.Context, variableValues []*sdkproto.VariableValue, metadata *sdkproto.Metadata, omitModules bool) (*templateImportProvision, error) {
return r.runTemplateImportProvisionWithRichParameters(ctx, variableValues, nil, metadata, omitModules)
func (r *Runner) runTemplateImportProvision(ctx context.Context, variableValues []*sdkproto.VariableValue, metadata *sdkproto.Metadata) (*templateImportProvision, error) {
return r.runTemplateImportProvisionWithRichParameters(ctx, variableValues, nil, metadata)
}

// Performs a dry-run provision with provided rich parameters.
Expand All @@ -688,7 +696,6 @@ func (r *Runner) runTemplateImportProvisionWithRichParameters(
variableValues []*sdkproto.VariableValue,
richParameterValues []*sdkproto.RichParameterValue,
metadata *sdkproto.Metadata,
omitModules bool,
) (*templateImportProvision, error) {
ctx, span := r.startTrace(ctx, tracing.FuncName())
defer span.End()
Expand All @@ -708,7 +715,6 @@ func (r *Runner) runTemplateImportProvisionWithRichParameters(
// Template import has no previous values
PreviousParameterValues: make([]*sdkproto.RichParameterValue, 0),
VariableValues: variableValues,
//OmitModuleFiles: omitModules,
}}})
if err != nil {
return nil, xerrors.Errorf("start provision: %w", err)
Expand All @@ -730,7 +736,6 @@ func (r *Runner) runTemplateImportProvisionWithRichParameters(
}
}()

var moduleFilesUpload *sdkproto.DataBuilder
for {
msg, err := r.session.Recv()
if err != nil {
Expand All @@ -750,30 +755,6 @@ func (r *Runner) runTemplateImportProvisionWithRichParameters(
Output: msgType.Log.Output,
Stage: stage,
})
case *sdkproto.Response_DataUpload:
c := msgType.DataUpload
if c.UploadType != sdkproto.DataUploadType_UPLOAD_TYPE_MODULE_FILES {
return nil, xerrors.Errorf("invalid data upload type: %q", c.UploadType)
}

if moduleFilesUpload != nil {
return nil, xerrors.New("multiple module data uploads received, only expect 1")
}

moduleFilesUpload, err = sdkproto.NewDataBuilder(c)
if err != nil {
return nil, xerrors.Errorf("create data builder: %w", err)
}
case *sdkproto.Response_ChunkPiece:
c := msgType.ChunkPiece
if moduleFilesUpload == nil {
return nil, xerrors.New("received chunk piece before module files data upload")
}

_, err := moduleFilesUpload.Add(c)
if err != nil {
return nil, xerrors.Errorf("module files, add chunk piece: %w", err)
}
case *sdkproto.Response_Plan:
c := msgType.Plan
if c.Error != "" {
Expand All @@ -784,34 +765,18 @@ func (r *Runner) runTemplateImportProvisionWithRichParameters(
return nil, xerrors.New(c.Error)
}

if moduleFilesUpload != nil && len(c.ModuleFiles) > 0 {
return nil, xerrors.New("module files were uploaded and module files were returned in the plan response. Only one of these should be set")
}

r.logger.Info(context.Background(), "parse dry-run provision successful",
slog.F("resource_count", len(c.Resources)),
slog.F("resources", resourceNames(c.Resources)),
)

moduleFilesData := c.ModuleFiles
if moduleFilesUpload != nil {
uploadData, err := moduleFilesUpload.Complete()
if err != nil {
return nil, xerrors.Errorf("module files, complete upload: %w", err)
}
moduleFilesData = uploadData
if !bytes.Equal(c.ModuleFilesHash, moduleFilesUpload.Hash) {
return nil, xerrors.Errorf("module files hash mismatch, uploaded: %x, expected: %x", moduleFilesUpload.Hash, c.ModuleFilesHash)
}
}
return &templateImportProvision{
Resources: c.Resources,
Parameters: c.Parameters,
ExternalAuthProviders: c.ExternalAuthProviders,
Modules: c.Modules,
Presets: c.Presets,
Plan: c.Plan,
ModuleFiles: moduleFilesData,
HasAITasks: c.HasAiTasks,
HasExternalAgents: c.HasExternalAgents,
}, nil
Expand Down Expand Up @@ -861,12 +826,22 @@ func (r *Runner) runTemplateDryRun(ctx context.Context) (*proto.CompletedJob, *p
return nil, failedJob
}

initResp, failedJob := r.init(ctx, false, r.job.GetTemplateSourceArchive())
if failedJob != nil {
return nil, failedJob
}
if initResp == nil {
return nil, r.failedJobf("template dry-run init returned nil response")
}
if initResp.Error != "" {
return nil, r.failedJobf("template dry-run init error: %s", initResp.Error)
}

// Run the template import provision task since it's already a dry run.
provision, err := r.runTemplateImportProvisionWithRichParameters(ctx,
r.job.GetTemplateDryRun().GetVariableValues(),
r.job.GetTemplateDryRun().GetRichParameterValues(),
metadata,
false,
)
if err != nil {
return nil, r.failedJobf("run dry-run provision job: %s", err)
Expand Down
3 changes: 2 additions & 1 deletion provisionersdk/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/codersdk/drpcsdk"
"github.com/coder/coder/v2/provisionersdk/tfpath"
"github.com/coder/coder/v2/provisionersdk/tfpath/x"

protobuf "google.golang.org/protobuf/proto"

Expand Down Expand Up @@ -56,7 +57,7 @@ func (p *protoServer) Session(stream proto.DRPCProvisioner_SessionStream) error
}

if p.opts.Experiments.Enabled(codersdk.ExperimentTerraformWorkspace) {
//s.Files = x.SessionDir(p.opts.WorkDirectory, sessID, config)
s.Files = x.SessionDir(p.opts.WorkDirectory, sessID, config)
}

// Cleanup any previously left stale sessions.
Expand Down
8 changes: 3 additions & 5 deletions provisionersdk/tfpath/x/tfpath.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,9 @@ func (td Layout) Cleanup(ctx context.Context, logger slog.Logger, fs afero.Fs) {
slog.F("path", path), slog.Error(err))
}

func (td Layout) ExtractArchive(ctx context.Context, logger slog.Logger, fs afero.Fs, cfg *proto.Config) error {
func (td Layout) ExtractArchive(ctx context.Context, logger slog.Logger, fs afero.Fs, archive []byte) error {
logger.Info(ctx, "unpacking template source archive",
slog.F("size_bytes", len(cfg.TemplateSourceArchive)),
slog.F("size_bytes", len(archive)),
)

err := fs.MkdirAll(td.WorkDirectory(), 0o700)
Expand All @@ -163,9 +163,7 @@ func (td Layout) ExtractArchive(ctx context.Context, logger slog.Logger, fs afer
return xerrors.Errorf("select terraform workspace: %w", err)
}

reader := tar.NewReader(bytes.NewBuffer(cfg.TemplateSourceArchive))
// for safety, nil out the reference on Config, since the reader now owns it.
cfg.TemplateSourceArchive = nil
reader := tar.NewReader(bytes.NewBuffer(archive))
for {
header, err := reader.Next()
if err != nil {
Expand Down