refactor: change template archive extraction to be on provisioner (#9264)

* refactor provisionersdk protocol

Signed-off-by: Spike Curtis <spike@coder.com>

* refactor provisioners to use new protocol

Signed-off-by: Spike Curtis <spike@coder.com>

* refactor provisionerd to use new protocol

Signed-off-by: Spike Curtis <spike@coder.com>

* refactor tests & proto renames

* Fixes from self-review

Signed-off-by: Spike Curtis <spike@coder.com>

* appease fmt & link

Signed-off-by: Spike Curtis <spike@coder.com>

* code review fixes & e2e fixes

Signed-off-by: Spike Curtis <spike@coder.com>

* More fmt

Signed-off-by: Spike Curtis <spike@coder.com>

* Code review fixes

Signed-off-by: Spike Curtis <spike@coder.com>

* new gen; use uuid for session workdir

Signed-off-by: Spike Curtis <spike@coder.com>

* Revert nix-based gen CI task until dogfood is on nix

Signed-off-by: Spike Curtis <spike@coder.com>

* revert deleting dogfood Docker stuff

Signed-off-by: Spike Curtis <spike@coder.com>

* Revert "revert deleting dogfood Docker stuff"

This reverts commit 9762158167.

---------

Signed-off-by: Spike Curtis <spike@coder.com>
This commit is contained in:
Spike Curtis 2023-08-25 10:10:15 +04:00 committed by GitHub
parent 4bed492012
commit 60d5002eb6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
77 changed files with 3789 additions and 3825 deletions

View File

@ -456,10 +456,10 @@ DB_GEN_FILES := \
# all gen targets should be added here and to gen/mark-fresh # all gen targets should be added here and to gen/mark-fresh
gen: \ gen: \
coderd/database/dump.sql \
$(DB_GEN_FILES) \
provisionersdk/proto/provisioner.pb.go \ provisionersdk/proto/provisioner.pb.go \
provisionerd/proto/provisionerd.pb.go \ provisionerd/proto/provisionerd.pb.go \
coderd/database/dump.sql \
$(DB_GEN_FILES) \
site/src/api/typesGenerated.ts \ site/src/api/typesGenerated.ts \
coderd/rbac/object_gen.go \ coderd/rbac/object_gen.go \
docs/admin/prometheus.md \ docs/admin/prometheus.md \
@ -478,10 +478,10 @@ gen: \
# used during releases so we don't run generation scripts. # used during releases so we don't run generation scripts.
gen/mark-fresh: gen/mark-fresh:
files="\ files="\
coderd/database/dump.sql \
$(DB_GEN_FILES) \
provisionersdk/proto/provisioner.pb.go \ provisionersdk/proto/provisioner.pb.go \
provisionerd/proto/provisionerd.pb.go \ provisionerd/proto/provisionerd.pb.go \
coderd/database/dump.sql \
$(DB_GEN_FILES) \
site/src/api/typesGenerated.ts \ site/src/api/typesGenerated.ts \
coderd/rbac/object_gen.go \ coderd/rbac/object_gen.go \
docs/admin/prometheus.md \ docs/admin/prometheus.md \

View File

@ -75,9 +75,9 @@ func TestWorkspaceAgent(t *testing.T) {
user := coderdtest.CreateFirstUser(t, client) user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: []*proto.Response{{
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{ Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{{ Resources: []*proto.Resource{{
Name: "somename", Name: "somename",
Type: "someinstance", Type: "someinstance",
@ -127,9 +127,9 @@ func TestWorkspaceAgent(t *testing.T) {
user := coderdtest.CreateFirstUser(t, client) user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: []*proto.Response{{
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{ Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{{ Resources: []*proto.Resource{{
Name: "somename", Name: "somename",
Type: "someinstance", Type: "someinstance",
@ -179,9 +179,9 @@ func TestWorkspaceAgent(t *testing.T) {
user := coderdtest.CreateFirstUser(t, client) user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: []*proto.Response{{
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{ Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{{ Resources: []*proto.Resource{{
Name: "somename", Name: "somename",
Type: "someinstance", Type: "someinstance",

View File

@ -82,9 +82,9 @@ func TestConfigSSH(t *testing.T) {
authToken := uuid.NewString() authToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: []*proto.Provision_Response{{ ProvisionPlan: []*proto.Response{{
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Plan{
Complete: &proto.Provision_Complete{ Plan: &proto.PlanComplete{
Resources: []*proto.Resource{{ Resources: []*proto.Resource{{
Name: "example", Name: "example",
Type: "aws_instance", Type: "aws_instance",
@ -720,22 +720,11 @@ func TestConfigSSH_Hostnames(t *testing.T) {
resources = append(resources, resource) resources = append(resources, resource)
} }
provisionResponse := []*proto.Provision_Response{{
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{
Resources: resources,
},
},
}}
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
user := coderdtest.CreateFirstUser(t, client) user := coderdtest.CreateFirstUser(t, client)
// authToken := uuid.NewString() // authToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID,
Parse: echo.ParseComplete, echo.WithResources(resources))
ProvisionPlan: provisionResponse,
ProvisionApply: provisionResponse,
})
coderdtest.AwaitTemplateVersionJob(t, client, version.ID) coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID) workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)

View File

@ -29,11 +29,7 @@ func TestCreate(t *testing.T) {
t.Parallel() t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
user := coderdtest.CreateFirstUser(t, client) user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, completeWithAgent())
Parse: echo.ParseComplete,
ProvisionApply: provisionCompleteWithAgent,
ProvisionPlan: provisionCompleteWithAgent,
})
coderdtest.AwaitTemplateVersionJob(t, client, version.ID) coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
args := []string{ args := []string{
@ -84,11 +80,7 @@ func TestCreate(t *testing.T) {
t.Parallel() t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
owner := coderdtest.CreateFirstUser(t, client) owner := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, completeWithAgent())
Parse: echo.ParseComplete,
ProvisionApply: provisionCompleteWithAgent,
ProvisionPlan: provisionCompleteWithAgent,
})
coderdtest.AwaitTemplateVersionJob(t, client, version.ID) coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID) template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
_, user := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID) _, user := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
@ -141,11 +133,7 @@ func TestCreate(t *testing.T) {
t.Parallel() t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
user := coderdtest.CreateFirstUser(t, client) user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, completeWithAgent())
Parse: echo.ParseComplete,
ProvisionApply: provisionCompleteWithAgent,
ProvisionPlan: provisionCompleteWithAgent,
})
coderdtest.AwaitTemplateVersionJob(t, client, version.ID) coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) { template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
var defaultTTLMillis int64 = 2 * 60 * 60 * 1000 // 2 hours var defaultTTLMillis int64 = 2 * 60 * 60 * 1000 // 2 hours
@ -240,6 +228,22 @@ func TestCreate(t *testing.T) {
}) })
} }
func prepareEchoResponses(parameters []*proto.RichParameter) *echo.Responses {
return &echo.Responses{
Parse: echo.ParseComplete,
ProvisionPlan: []*proto.Response{
{
Type: &proto.Response_Plan{
Plan: &proto.PlanComplete{
Parameters: parameters,
},
},
},
},
ProvisionApply: echo.ApplyComplete,
}
}
func TestCreateWithRichParameters(t *testing.T) { func TestCreateWithRichParameters(t *testing.T) {
t.Parallel() t.Parallel()
@ -258,27 +262,12 @@ func TestCreateWithRichParameters(t *testing.T) {
immutableParameterValue = "4" immutableParameterValue = "4"
) )
echoResponses := &echo.Responses{ echoResponses := prepareEchoResponses([]*proto.RichParameter{
Parse: echo.ParseComplete, {Name: firstParameterName, Description: firstParameterDescription, Mutable: true},
ProvisionPlan: []*proto.Provision_Response{ {Name: secondParameterName, DisplayName: secondParameterDisplayName, Description: secondParameterDescription, Mutable: true},
{ {Name: immutableParameterName, Description: immutableParameterDescription, Mutable: false},
Type: &proto.Provision_Response_Complete{ },
Complete: &proto.Provision_Complete{ )
Parameters: []*proto.RichParameter{
{Name: firstParameterName, Description: firstParameterDescription, Mutable: true},
{Name: secondParameterName, DisplayName: secondParameterDisplayName, Description: secondParameterDescription, Mutable: true},
{Name: immutableParameterName, Description: immutableParameterDescription, Mutable: false},
},
},
},
},
},
ProvisionApply: []*proto.Provision_Response{{
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{},
},
}},
}
t.Run("InputParameters", func(t *testing.T) { t.Run("InputParameters", func(t *testing.T) {
t.Parallel() t.Parallel()
@ -427,28 +416,6 @@ func TestCreateValidateRichParameters(t *testing.T) {
{Name: boolParameterName, Type: "bool", Mutable: true}, {Name: boolParameterName, Type: "bool", Mutable: true},
} }
prepareEchoResponses := func(richParameters []*proto.RichParameter) *echo.Responses {
return &echo.Responses{
Parse: echo.ParseComplete,
ProvisionPlan: []*proto.Provision_Response{
{
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{
Parameters: richParameters,
},
},
},
},
ProvisionApply: []*proto.Provision_Response{
{
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{},
},
},
},
}
}
t.Run("ValidateString", func(t *testing.T) { t.Run("ValidateString", func(t *testing.T) {
t.Parallel() t.Parallel()
@ -626,20 +593,16 @@ func TestCreateWithGitAuth(t *testing.T) {
t.Parallel() t.Parallel()
echoResponses := &echo.Responses{ echoResponses := &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: []*proto.Provision_Response{ ProvisionPlan: []*proto.Response{
{ {
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Plan{
Complete: &proto.Provision_Complete{ Plan: &proto.PlanComplete{
GitAuthProviders: []string{"github"}, GitAuthProviders: []string{"github"},
}, },
}, },
}, },
}, },
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: echo.ApplyComplete,
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{},
},
}},
} }
client := coderdtest.New(t, &coderdtest.Options{ client := coderdtest.New(t, &coderdtest.Options{

View File

@ -48,7 +48,7 @@ func prepareTestGitSSH(ctx context.Context, t *testing.T) (*codersdk.Client, str
agentToken := uuid.NewString() agentToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ProvisionApplyWithAgent(agentToken), ProvisionApply: echo.ProvisionApplyWithAgent(agentToken),
}) })
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)

View File

@ -302,7 +302,7 @@ func runAgent(t *testing.T, client *codersdk.Client, userID uuid.UUID) codersdk.
agentToken := uuid.NewString() agentToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, orgID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, orgID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ProvisionApplyWithAgent(agentToken), ProvisionApply: echo.ProvisionApplyWithAgent(agentToken),
}) })

View File

@ -20,30 +20,14 @@ import (
func TestRestart(t *testing.T) { func TestRestart(t *testing.T) {
t.Parallel() t.Parallel()
echoResponses := &echo.Responses{ echoResponses := prepareEchoResponses([]*proto.RichParameter{
Parse: echo.ParseComplete, {
ProvisionPlan: []*proto.Provision_Response{ Name: ephemeralParameterName,
{ Description: ephemeralParameterDescription,
Type: &proto.Provision_Response_Complete{ Mutable: true,
Complete: &proto.Provision_Complete{ Ephemeral: true,
Parameters: []*proto.RichParameter{
{
Name: ephemeralParameterName,
Description: ephemeralParameterDescription,
Mutable: true,
Ephemeral: true,
},
},
},
},
},
}, },
ProvisionApply: []*proto.Provision_Response{{ })
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{},
},
}},
}
t.Run("OK", func(t *testing.T) { t.Run("OK", func(t *testing.T) {
t.Parallel() t.Parallel()
@ -187,10 +171,10 @@ func TestRestartWithParameters(t *testing.T) {
echoResponses := &echo.Responses{ echoResponses := &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: []*proto.Provision_Response{ ProvisionPlan: []*proto.Response{
{ {
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Plan{
Complete: &proto.Provision_Complete{ Plan: &proto.PlanComplete{
Parameters: []*proto.RichParameter{ Parameters: []*proto.RichParameter{
{ {
Name: immutableParameterName, Name: immutableParameterName,
@ -202,11 +186,7 @@ func TestRestartWithParameters(t *testing.T) {
}, },
}, },
}, },
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: echo.ApplyComplete,
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{},
},
}},
} }
t.Run("DoNotAskForImmutables", func(t *testing.T) { t.Run("DoNotAskForImmutables", func(t *testing.T) {

View File

@ -41,7 +41,6 @@ import (
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/collectors" "github.com/prometheus/client_golang/prometheus/collectors"
"github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/spf13/afero"
"go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace"
"golang.org/x/mod/semver" "golang.org/x/mod/semver"
"golang.org/x/oauth2" "golang.org/x/oauth2"
@ -1304,7 +1303,11 @@ func newProvisionerDaemon(
defer wg.Done() defer wg.Done()
defer cancel() defer cancel()
err := echo.Serve(ctx, afero.NewOsFs(), &provisionersdk.ServeOptions{Listener: echoServer}) err := echo.Serve(ctx, &provisionersdk.ServeOptions{
Listener: echoServer,
WorkDirectory: workDir,
Logger: logger.Named("echo"),
})
if err != nil { if err != nil {
select { select {
case errCh <- err: case errCh <- err:
@ -1336,10 +1339,11 @@ func newProvisionerDaemon(
err := terraform.Serve(ctx, &terraform.ServeOptions{ err := terraform.Serve(ctx, &terraform.ServeOptions{
ServeOptions: &provisionersdk.ServeOptions{ ServeOptions: &provisionersdk.ServeOptions{
Listener: terraformServer, Listener: terraformServer,
Logger: logger.Named("terraform"),
WorkDirectory: workDir,
}, },
CachePath: tfDir, CachePath: tfDir,
Logger: logger.Named("terraform"),
Tracer: tracer, Tracer: tracer,
}) })
if err != nil && !xerrors.Is(err, context.Canceled) { if err != nil && !xerrors.Is(err, context.Canceled) {
@ -1366,7 +1370,6 @@ func newProvisionerDaemon(
UpdateInterval: time.Second, UpdateInterval: time.Second,
ForceCancelInterval: cfg.Provisioner.ForceCancelInterval.Value(), ForceCancelInterval: cfg.Provisioner.ForceCancelInterval.Value(),
Provisioners: provisioners, Provisioners: provisioners,
WorkDirectory: workDir,
TracerProvider: coderAPI.TracerProvider, TracerProvider: coderAPI.TracerProvider,
Metrics: &metrics, Metrics: &metrics,
}), nil }), nil

View File

@ -7,7 +7,6 @@ import (
"github.com/coder/coder/v2/cli/clitest" "github.com/coder/coder/v2/cli/clitest"
"github.com/coder/coder/v2/coderd/coderdtest" "github.com/coder/coder/v2/coderd/coderdtest"
"github.com/coder/coder/v2/provisioner/echo"
"github.com/coder/coder/v2/pty/ptytest" "github.com/coder/coder/v2/pty/ptytest"
) )
@ -17,11 +16,7 @@ func TestShow(t *testing.T) {
t.Parallel() t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
user := coderdtest.CreateFirstUser(t, client) user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, completeWithAgent())
Parse: echo.ParseComplete,
ProvisionApply: provisionCompleteWithAgent,
ProvisionPlan: provisionCompleteWithAgent,
})
coderdtest.AwaitTemplateVersionJob(t, client, version.ID) coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID) workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)

View File

@ -56,10 +56,10 @@ func setupWorkspaceForAgent(t *testing.T, mutate func([]*proto.Agent) []*proto.A
agentToken := uuid.NewString() agentToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: []*proto.Response{{
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{ Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{{ Resources: []*proto.Resource{{
Name: "dev", Name: "dev",
Type: "google_compute_instance", Type: "google_compute_instance",

View File

@ -33,10 +33,10 @@ func TestStart(t *testing.T) {
echoResponses := &echo.Responses{ echoResponses := &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: []*proto.Provision_Response{ ProvisionPlan: []*proto.Response{
{ {
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Plan{
Complete: &proto.Provision_Complete{ Plan: &proto.PlanComplete{
Parameters: []*proto.RichParameter{ Parameters: []*proto.RichParameter{
{ {
Name: ephemeralParameterName, Name: ephemeralParameterName,
@ -49,11 +49,7 @@ func TestStart(t *testing.T) {
}, },
}, },
}, },
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: echo.ApplyComplete,
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{},
},
}},
} }
t.Run("BuildOptions", func(t *testing.T) { t.Run("BuildOptions", func(t *testing.T) {
@ -151,10 +147,10 @@ func TestStartWithParameters(t *testing.T) {
echoResponses := &echo.Responses{ echoResponses := &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: []*proto.Provision_Response{ ProvisionPlan: []*proto.Response{
{ {
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Plan{
Complete: &proto.Provision_Complete{ Plan: &proto.PlanComplete{
Parameters: []*proto.RichParameter{ Parameters: []*proto.RichParameter{
{ {
Name: immutableParameterName, Name: immutableParameterName,
@ -166,11 +162,7 @@ func TestStartWithParameters(t *testing.T) {
}, },
}, },
}, },
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: echo.ApplyComplete,
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{},
},
}},
} }
t.Run("DoNotAskForImmutables", func(t *testing.T) { t.Run("DoNotAskForImmutables", func(t *testing.T) {

View File

@ -25,9 +25,9 @@ func TestStatePull(t *testing.T) {
wantState := []byte("some state") wantState := []byte("some state")
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: []*proto.Response{{
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{ Apply: &proto.ApplyComplete{
State: wantState, State: wantState,
}, },
}, },
@ -53,9 +53,9 @@ func TestStatePull(t *testing.T) {
wantState := []byte("some state") wantState := []byte("some state")
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: []*proto.Response{{
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{ Apply: &proto.ApplyComplete{
State: wantState, State: wantState,
}, },
}, },
@ -83,7 +83,7 @@ func TestStatePush(t *testing.T) {
user := coderdtest.CreateFirstUser(t, client) user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionApply: echo.ProvisionComplete, ProvisionApply: echo.ApplyComplete,
}) })
coderdtest.AwaitTemplateVersionJob(t, client, version.ID) coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
@ -108,7 +108,7 @@ func TestStatePush(t *testing.T) {
user := coderdtest.CreateFirstUser(t, client) user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionApply: echo.ProvisionComplete, ProvisionApply: echo.ApplyComplete,
}) })
coderdtest.AwaitTemplateVersionJob(t, client, version.ID) coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)

View File

@ -19,26 +19,52 @@ import (
"github.com/coder/coder/v2/testutil" "github.com/coder/coder/v2/testutil"
) )
var provisionCompleteWithAgent = []*proto.Provision_Response{ func completeWithAgent() *echo.Responses {
{ return &echo.Responses{
Type: &proto.Provision_Response_Complete{ Parse: echo.ParseComplete,
Complete: &proto.Provision_Complete{ ProvisionPlan: []*proto.Response{
Resources: []*proto.Resource{ {
{ Type: &proto.Response_Plan{
Type: "compute", Plan: &proto.PlanComplete{
Name: "main", Resources: []*proto.Resource{
Agents: []*proto.Agent{
{ {
Name: "smith", Type: "compute",
OperatingSystem: "linux", Name: "main",
Architecture: "i386", Agents: []*proto.Agent{
{
Name: "smith",
OperatingSystem: "linux",
Architecture: "i386",
},
},
}, },
}, },
}, },
}, },
}, },
}, },
}, ProvisionApply: []*proto.Response{
{
Type: &proto.Response_Apply{
Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{
{
Type: "compute",
Name: "main",
Agents: []*proto.Agent{
{
Name: "smith",
OperatingSystem: "linux",
Architecture: "i386",
},
},
},
},
},
},
},
},
}
} }
func TestTemplateCreate(t *testing.T) { func TestTemplateCreate(t *testing.T) {
@ -47,10 +73,7 @@ func TestTemplateCreate(t *testing.T) {
t.Parallel() t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
coderdtest.CreateFirstUser(t, client) coderdtest.CreateFirstUser(t, client)
source := clitest.CreateTemplateVersionSource(t, &echo.Responses{ source := clitest.CreateTemplateVersionSource(t, completeWithAgent())
Parse: echo.ParseComplete,
ProvisionApply: provisionCompleteWithAgent,
})
args := []string{ args := []string{
"templates", "templates",
"create", "create",
@ -85,10 +108,7 @@ func TestTemplateCreate(t *testing.T) {
t.Parallel() t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
coderdtest.CreateFirstUser(t, client) coderdtest.CreateFirstUser(t, client)
source := clitest.CreateTemplateVersionSource(t, &echo.Responses{ source := clitest.CreateTemplateVersionSource(t, completeWithAgent())
Parse: echo.ParseComplete,
ProvisionApply: provisionCompleteWithAgent,
})
require.NoError(t, os.Remove(filepath.Join(source, ".terraform.lock.hcl"))) require.NoError(t, os.Remove(filepath.Join(source, ".terraform.lock.hcl")))
args := []string{ args := []string{
"templates", "templates",
@ -128,10 +148,7 @@ func TestTemplateCreate(t *testing.T) {
t.Parallel() t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
coderdtest.CreateFirstUser(t, client) coderdtest.CreateFirstUser(t, client)
source := clitest.CreateTemplateVersionSource(t, &echo.Responses{ source := clitest.CreateTemplateVersionSource(t, completeWithAgent())
Parse: echo.ParseComplete,
ProvisionApply: provisionCompleteWithAgent,
})
require.NoError(t, os.Remove(filepath.Join(source, ".terraform.lock.hcl"))) require.NoError(t, os.Remove(filepath.Join(source, ".terraform.lock.hcl")))
args := []string{ args := []string{
"templates", "templates",
@ -167,10 +184,7 @@ func TestTemplateCreate(t *testing.T) {
t.Parallel() t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
coderdtest.CreateFirstUser(t, client) coderdtest.CreateFirstUser(t, client)
source, err := echo.Tar(&echo.Responses{ source, err := echo.Tar(completeWithAgent())
Parse: echo.ParseComplete,
ProvisionApply: provisionCompleteWithAgent,
})
require.NoError(t, err) require.NoError(t, err)
args := []string{ args := []string{
@ -196,10 +210,7 @@ func TestTemplateCreate(t *testing.T) {
coderdtest.CreateFirstUser(t, client) coderdtest.CreateFirstUser(t, client)
create := func() error { create := func() error {
source := clitest.CreateTemplateVersionSource(t, &echo.Responses{ source := clitest.CreateTemplateVersionSource(t, completeWithAgent())
Parse: echo.ParseComplete,
ProvisionApply: provisionCompleteWithAgent,
})
args := []string{ args := []string{
"templates", "templates",
"create", "create",

View File

@ -205,9 +205,9 @@ func TestTemplatePull(t *testing.T) {
// a template version source. // a template version source.
func genTemplateVersionSource() *echo.Responses { func genTemplateVersionSource() *echo.Responses {
return &echo.Responses{ return &echo.Responses{
Parse: []*proto.Parse_Response{ Parse: []*proto.Response{
{ {
Type: &proto.Parse_Response_Log{ Type: &proto.Response_Log{
Log: &proto.Log{ Log: &proto.Log{
Output: uuid.NewString(), Output: uuid.NewString(),
}, },
@ -215,11 +215,11 @@ func genTemplateVersionSource() *echo.Responses {
}, },
{ {
Type: &proto.Parse_Response_Complete{ Type: &proto.Response_Parse{
Complete: &proto.Parse_Complete{}, Parse: &proto.ParseComplete{},
}, },
}, },
}, },
ProvisionApply: echo.ProvisionComplete, ProvisionApply: echo.ApplyComplete,
} }
} }

View File

@ -38,7 +38,7 @@ func TestTemplatePush(t *testing.T) {
// Test the cli command. // Test the cli command.
source := clitest.CreateTemplateVersionSource(t, &echo.Responses{ source := clitest.CreateTemplateVersionSource(t, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionApply: echo.ProvisionComplete, ProvisionApply: echo.ApplyComplete,
}) })
inv, root := clitest.New(t, "templates", "push", template.Name, "--directory", source, "--test.provisioner", string(database.ProvisionerTypeEcho), "--name", "example") inv, root := clitest.New(t, "templates", "push", template.Name, "--directory", source, "--test.provisioner", string(database.ProvisionerTypeEcho), "--name", "example")
clitest.SetupConfig(t, client, root) clitest.SetupConfig(t, client, root)
@ -82,7 +82,7 @@ func TestTemplatePush(t *testing.T) {
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
source := clitest.CreateTemplateVersionSource(t, &echo.Responses{ source := clitest.CreateTemplateVersionSource(t, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionApply: echo.ProvisionComplete, ProvisionApply: echo.ApplyComplete,
}) })
wantMessage := strings.Repeat("a", 72) wantMessage := strings.Repeat("a", 72)
@ -121,7 +121,7 @@ func TestTemplatePush(t *testing.T) {
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
source := clitest.CreateTemplateVersionSource(t, &echo.Responses{ source := clitest.CreateTemplateVersionSource(t, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionApply: echo.ProvisionComplete, ProvisionApply: echo.ApplyComplete,
}) })
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
@ -168,7 +168,7 @@ func TestTemplatePush(t *testing.T) {
// Test the cli command. // Test the cli command.
source := clitest.CreateTemplateVersionSource(t, &echo.Responses{ source := clitest.CreateTemplateVersionSource(t, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionApply: echo.ProvisionComplete, ProvisionApply: echo.ApplyComplete,
}) })
require.NoError(t, os.Remove(filepath.Join(source, ".terraform.lock.hcl"))) require.NoError(t, os.Remove(filepath.Join(source, ".terraform.lock.hcl")))
@ -211,7 +211,7 @@ func TestTemplatePush(t *testing.T) {
// Test the cli command. // Test the cli command.
source := clitest.CreateTemplateVersionSource(t, &echo.Responses{ source := clitest.CreateTemplateVersionSource(t, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionApply: echo.ProvisionComplete, ProvisionApply: echo.ApplyComplete,
}) })
require.NoError(t, os.Remove(filepath.Join(source, ".terraform.lock.hcl"))) require.NoError(t, os.Remove(filepath.Join(source, ".terraform.lock.hcl")))
@ -248,7 +248,7 @@ func TestTemplatePush(t *testing.T) {
// Test the cli command. // Test the cli command.
source := clitest.CreateTemplateVersionSource(t, &echo.Responses{ source := clitest.CreateTemplateVersionSource(t, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionApply: echo.ProvisionComplete, ProvisionApply: echo.ApplyComplete,
}) })
inv, root := clitest.New(t, "templates", "push", template.Name, "--activate=false", "--directory", source, "--test.provisioner", string(database.ProvisionerTypeEcho), "--name", "example") inv, root := clitest.New(t, "templates", "push", template.Name, "--activate=false", "--directory", source, "--test.provisioner", string(database.ProvisionerTypeEcho), "--name", "example")
clitest.SetupConfig(t, client, root) clitest.SetupConfig(t, client, root)
@ -293,7 +293,7 @@ func TestTemplatePush(t *testing.T) {
// Test the cli command. // Test the cli command.
source := clitest.CreateTemplateVersionSource(t, &echo.Responses{ source := clitest.CreateTemplateVersionSource(t, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionApply: echo.ProvisionComplete, ProvisionApply: echo.ApplyComplete,
}) })
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID,
@ -340,7 +340,7 @@ func TestTemplatePush(t *testing.T) {
source, err := echo.Tar(&echo.Responses{ source, err := echo.Tar(&echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionApply: echo.ProvisionComplete, ProvisionApply: echo.ApplyComplete,
}) })
require.NoError(t, err) require.NoError(t, err)
@ -619,10 +619,7 @@ func TestTemplatePush(t *testing.T) {
t.Parallel() t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
user := coderdtest.CreateFirstUser(t, client) user := coderdtest.CreateFirstUser(t, client)
source := clitest.CreateTemplateVersionSource(t, &echo.Responses{ source := clitest.CreateTemplateVersionSource(t, completeWithAgent())
Parse: echo.ParseComplete,
ProvisionApply: provisionCompleteWithAgent,
})
const templateName = "my-template" const templateName = "my-template"
args := []string{ args := []string{
@ -665,16 +662,16 @@ func TestTemplatePush(t *testing.T) {
func createEchoResponsesWithTemplateVariables(templateVariables []*proto.TemplateVariable) *echo.Responses { func createEchoResponsesWithTemplateVariables(templateVariables []*proto.TemplateVariable) *echo.Responses {
return &echo.Responses{ return &echo.Responses{
Parse: []*proto.Parse_Response{ Parse: []*proto.Response{
{ {
Type: &proto.Parse_Response_Complete{ Type: &proto.Response_Parse{
Complete: &proto.Parse_Complete{ Parse: &proto.ParseComplete{
TemplateVariables: templateVariables, TemplateVariables: templateVariables,
}, },
}, },
}, },
}, },
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ProvisionComplete, ProvisionApply: echo.ApplyComplete,
} }
} }

View File

@ -57,8 +57,8 @@ func TestUpdate(t *testing.T) {
version2 := coderdtest.UpdateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version2 := coderdtest.UpdateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionApply: echo.ProvisionComplete, ProvisionApply: echo.ApplyComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
}, template.ID) }, template.ID)
_ = coderdtest.AwaitTemplateVersionJob(t, client, version2.ID) _ = coderdtest.AwaitTemplateVersionJob(t, client, version2.ID)
@ -100,28 +100,13 @@ func TestUpdateWithRichParameters(t *testing.T) {
immutableParameterValue = "4" immutableParameterValue = "4"
) )
echoResponses := &echo.Responses{ echoResponses := prepareEchoResponses([]*proto.RichParameter{
Parse: echo.ParseComplete, {Name: firstParameterName, Description: firstParameterDescription, Mutable: true},
ProvisionPlan: []*proto.Provision_Response{ {Name: immutableParameterName, Description: immutableParameterDescription, Mutable: false},
{ {Name: secondParameterName, Description: secondParameterDescription, Mutable: true},
Type: &proto.Provision_Response_Complete{ {Name: ephemeralParameterName, Description: ephemeralParameterDescription, Mutable: true, Ephemeral: true},
Complete: &proto.Provision_Complete{ },
Parameters: []*proto.RichParameter{ )
{Name: firstParameterName, Description: firstParameterDescription, Mutable: true},
{Name: immutableParameterName, Description: immutableParameterDescription, Mutable: false},
{Name: secondParameterName, Description: secondParameterDescription, Mutable: true},
{Name: ephemeralParameterName, Description: ephemeralParameterDescription, Mutable: true, Ephemeral: true},
},
},
},
},
},
ProvisionApply: []*proto.Provision_Response{{
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{},
},
}},
}
t.Run("ImmutableCannotBeCustomized", func(t *testing.T) { t.Run("ImmutableCannotBeCustomized", func(t *testing.T) {
t.Parallel() t.Parallel()
@ -313,28 +298,6 @@ func TestUpdateValidateRichParameters(t *testing.T) {
{Name: boolParameterName, Type: "bool", Mutable: true}, {Name: boolParameterName, Type: "bool", Mutable: true},
} }
prepareEchoResponses := func(richParameters []*proto.RichParameter) *echo.Responses {
return &echo.Responses{
Parse: echo.ParseComplete,
ProvisionPlan: []*proto.Provision_Response{
{
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{
Parameters: richParameters,
},
},
},
},
ProvisionApply: []*proto.Provision_Response{
{
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{},
},
},
},
}
}
t.Run("ValidateString", func(t *testing.T) { t.Run("ValidateString", func(t *testing.T) {
t.Parallel() t.Parallel()

View File

@ -17,7 +17,6 @@ import (
"github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/codersdk/agentsdk" "github.com/coder/coder/v2/codersdk/agentsdk"
"github.com/coder/coder/v2/provisioner/echo" "github.com/coder/coder/v2/provisioner/echo"
"github.com/coder/coder/v2/provisionersdk/proto"
"github.com/coder/coder/v2/testutil" "github.com/coder/coder/v2/testutil"
) )
@ -60,25 +59,9 @@ func TestWorkspaceActivityBump(t *testing.T) {
ttlMillis := int64(ttl / time.Millisecond) ttlMillis := int64(ttl / time.Millisecond)
agentToken := uuid.NewString() agentToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: echo.ProvisionApplyWithAgent(agentToken),
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{
Resources: []*proto.Resource{{
Name: "example",
Type: "aws_instance",
Agents: []*proto.Agent{{
Id: uuid.NewString(),
Name: "agent",
Auth: &proto.Agent_Token{
Token: agentToken,
},
}},
}},
},
},
}},
}) })
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID) coderdtest.AwaitTemplateVersionJob(t, client, version.ID)

View File

@ -683,8 +683,8 @@ func TestExecutorFailedWorkspace(t *testing.T) {
user := coderdtest.CreateFirstUser(t, client) user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ProvisionFailed, ProvisionApply: echo.ApplyFailed,
}) })
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) { template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
ctr.FailureTTLMillis = ptr.Ref[int64](failureTTL.Milliseconds()) ctr.FailureTTLMillis = ptr.Ref[int64](failureTTL.Milliseconds())
@ -733,8 +733,8 @@ func TestExecutorInactiveWorkspace(t *testing.T) {
user := coderdtest.CreateFirstUser(t, client) user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ProvisionComplete, ProvisionApply: echo.ApplyComplete,
}) })
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) { template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
ctr.TimeTilDormantMillis = ptr.Ref[int64](inactiveTTL.Milliseconds()) ctr.TimeTilDormantMillis = ptr.Ref[int64](inactiveTTL.Milliseconds())
@ -766,22 +766,16 @@ func mustProvisionWorkspaceWithParameters(t *testing.T, client *codersdk.Client,
user := coderdtest.CreateFirstUser(t, client) user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: []*proto.Provision_Response{ ProvisionPlan: []*proto.Response{
{ {
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Plan{
Complete: &proto.Provision_Complete{ Plan: &proto.PlanComplete{
Parameters: richParameters, Parameters: richParameters,
}, },
}, },
}, },
}, },
ProvisionApply: []*proto.Provision_Response{ ProvisionApply: echo.ApplyComplete,
{
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{},
},
},
},
}) })
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID) coderdtest.AwaitTemplateVersionJob(t, client, version.ID)

View File

@ -181,7 +181,7 @@ func TestDERPForceWebSockets(t *testing.T) {
authToken := uuid.NewString() authToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ProvisionApplyWithAgent(authToken), ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
}) })
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)

View File

@ -37,7 +37,6 @@ import (
"github.com/google/uuid" "github.com/google/uuid"
"github.com/moby/moby/pkg/namesgenerator" "github.com/moby/moby/pkg/namesgenerator"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/spf13/afero"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"golang.org/x/oauth2" "golang.org/x/oauth2"
@ -469,10 +468,13 @@ func NewProvisionerDaemon(t testing.TB, coderAPI *coderd.API) io.Closer {
_ = echoServer.Close() _ = echoServer.Close()
cancelFunc() cancelFunc()
}) })
fs := afero.NewMemMapFs() // seems t.TempDir() is not safe to call from a different goroutine
workDir := t.TempDir()
go func() { go func() {
err := echo.Serve(ctx, fs, &provisionersdk.ServeOptions{ err := echo.Serve(ctx, &provisionersdk.ServeOptions{
Listener: echoServer, Listener: echoServer,
WorkDirectory: workDir,
Logger: coderAPI.Logger.Named("echo").Leveled(slog.LevelDebug),
}) })
assert.NoError(t, err) assert.NoError(t, err)
}() }()
@ -480,7 +482,6 @@ func NewProvisionerDaemon(t testing.TB, coderAPI *coderd.API) io.Closer {
closer := provisionerd.New(func(ctx context.Context) (provisionerdproto.DRPCProvisionerDaemonClient, error) { closer := provisionerd.New(func(ctx context.Context) (provisionerdproto.DRPCProvisionerDaemonClient, error) {
return coderAPI.CreateInMemoryProvisionerDaemon(ctx, 0) return coderAPI.CreateInMemoryProvisionerDaemon(ctx, 0)
}, &provisionerd.Options{ }, &provisionerd.Options{
Filesystem: fs,
Logger: coderAPI.Logger.Named("provisionerd").Leveled(slog.LevelDebug), Logger: coderAPI.Logger.Named("provisionerd").Leveled(slog.LevelDebug),
JobPollInterval: 50 * time.Millisecond, JobPollInterval: 50 * time.Millisecond,
UpdateInterval: 250 * time.Millisecond, UpdateInterval: 250 * time.Millisecond,
@ -488,7 +489,6 @@ func NewProvisionerDaemon(t testing.TB, coderAPI *coderd.API) io.Closer {
Provisioners: provisionerd.Provisioners{ Provisioners: provisionerd.Provisioners{
string(database.ProvisionerTypeEcho): sdkproto.NewDRPCProvisionerClient(echoClient), string(database.ProvisionerTypeEcho): sdkproto.NewDRPCProvisionerClient(echoClient),
}, },
WorkDirectory: t.TempDir(),
}) })
t.Cleanup(func() { t.Cleanup(func() {
_ = closer.Close() _ = closer.Close()
@ -506,11 +506,11 @@ func NewExternalProvisionerDaemon(t *testing.T, client *codersdk.Client, org uui
cancelFunc() cancelFunc()
<-serveDone <-serveDone
}) })
fs := afero.NewMemMapFs()
go func() { go func() {
defer close(serveDone) defer close(serveDone)
err := echo.Serve(ctx, fs, &provisionersdk.ServeOptions{ err := echo.Serve(ctx, &provisionersdk.ServeOptions{
Listener: echoServer, Listener: echoServer,
WorkDirectory: t.TempDir(),
}) })
assert.NoError(t, err) assert.NoError(t, err)
}() }()
@ -522,7 +522,6 @@ func NewExternalProvisionerDaemon(t *testing.T, client *codersdk.Client, org uui
Tags: tags, Tags: tags,
}) })
}, &provisionerd.Options{ }, &provisionerd.Options{
Filesystem: fs,
Logger: slogtest.Make(t, nil).Named("provisionerd").Leveled(slog.LevelDebug), Logger: slogtest.Make(t, nil).Named("provisionerd").Leveled(slog.LevelDebug),
JobPollInterval: 50 * time.Millisecond, JobPollInterval: 50 * time.Millisecond,
UpdateInterval: 250 * time.Millisecond, UpdateInterval: 250 * time.Millisecond,
@ -530,7 +529,6 @@ func NewExternalProvisionerDaemon(t *testing.T, client *codersdk.Client, org uui
Provisioners: provisionerd.Provisioners{ Provisioners: provisionerd.Provisioners{
string(database.ProvisionerTypeEcho): sdkproto.NewDRPCProvisionerClient(echoClient), string(database.ProvisionerTypeEcho): sdkproto.NewDRPCProvisionerClient(echoClient),
}, },
WorkDirectory: t.TempDir(),
}) })
t.Cleanup(func() { t.Cleanup(func() {
_ = closer.Close() _ = closer.Close()

View File

@ -23,7 +23,6 @@ import (
"github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/codersdk/agentsdk" "github.com/coder/coder/v2/codersdk/agentsdk"
"github.com/coder/coder/v2/provisioner/echo" "github.com/coder/coder/v2/provisioner/echo"
"github.com/coder/coder/v2/provisionersdk/proto"
"github.com/coder/coder/v2/testutil" "github.com/coder/coder/v2/testutil"
) )
@ -227,7 +226,7 @@ func TestGitAuthCallback(t *testing.T) {
authToken := uuid.NewString() authToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ProvisionApplyWithAgent(authToken), ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
}) })
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
@ -256,24 +255,9 @@ func TestGitAuthCallback(t *testing.T) {
user := coderdtest.CreateFirstUser(t, client) user := coderdtest.CreateFirstUser(t, client)
authToken := uuid.NewString() authToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{
Resources: []*proto.Resource{{
Name: "example",
Type: "aws_instance",
Agents: []*proto.Agent{{
Id: uuid.NewString(),
Auth: &proto.Agent_Token{
Token: authToken,
},
}},
}},
},
},
}},
}) })
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID) coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
@ -342,7 +326,7 @@ func TestGitAuthCallback(t *testing.T) {
authToken := uuid.NewString() authToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ProvisionApplyWithAgent(authToken), ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
}) })
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
@ -400,7 +384,7 @@ func TestGitAuthCallback(t *testing.T) {
authToken := uuid.NewString() authToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ProvisionApplyWithAgent(authToken), ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
}) })
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
@ -443,7 +427,7 @@ func TestGitAuthCallback(t *testing.T) {
authToken := uuid.NewString() authToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ProvisionApplyWithAgent(authToken), ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
}) })
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)

View File

@ -108,7 +108,7 @@ func TestAgentGitSSHKey(t *testing.T) {
authToken := uuid.NewString() authToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ProvisionApplyWithAgent(authToken), ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
}) })
project := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) project := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)

View File

@ -48,7 +48,7 @@ func TestDeploymentInsights(t *testing.T) {
authToken := uuid.NewString() authToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ProvisionApplyWithAgent(authToken), ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
}) })
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
@ -134,7 +134,7 @@ func TestUserLatencyInsights(t *testing.T) {
authToken := uuid.NewString() authToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ProvisionApplyWithAgent(authToken), ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
}) })
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
@ -498,18 +498,18 @@ func TestTemplateInsights_Golden(t *testing.T) {
// Create the template version and template. // Create the template version and template.
version := coderdtest.CreateTemplateVersion(t, client, firstUser.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, firstUser.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: []*proto.Provision_Response{ ProvisionPlan: []*proto.Response{
{ {
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Plan{
Complete: &proto.Provision_Complete{ Plan: &proto.PlanComplete{
Parameters: parameters, Parameters: parameters,
}, },
}, },
}, },
}, },
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: []*proto.Response{{
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{ Apply: &proto.ApplyComplete{
Resources: resources, Resources: resources,
}, },
}, },

View File

@ -268,10 +268,10 @@ func TestAgents(t *testing.T) {
user := coderdtest.CreateFirstUser(t, client) user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: []*proto.Response{{
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{ Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{{ Resources: []*proto.Resource{{
Name: "example", Name: "example",
Type: "aws_instance", Type: "aws_instance",
@ -494,7 +494,7 @@ func prepareWorkspaceAndAgent(t *testing.T, client *codersdk.Client, user coders
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ProvisionApplyWithAgent(authToken), ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
}) })
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)

View File

@ -280,7 +280,7 @@ func (server *Server) AcquireJob(ctx context.Context, _ *proto.Empty) (*proto.Ac
RichParameterValues: convertRichParameterValues(workspaceBuildParameters), RichParameterValues: convertRichParameterValues(workspaceBuildParameters),
VariableValues: asVariableValues(templateVariables), VariableValues: asVariableValues(templateVariables),
GitAuthProviders: gitAuthProviders, GitAuthProviders: gitAuthProviders,
Metadata: &sdkproto.Provision_Metadata{ Metadata: &sdkproto.Metadata{
CoderUrl: server.AccessURL.String(), CoderUrl: server.AccessURL.String(),
WorkspaceTransition: transition, WorkspaceTransition: transition,
WorkspaceName: workspace.Name, WorkspaceName: workspace.Name,
@ -316,7 +316,7 @@ func (server *Server) AcquireJob(ctx context.Context, _ *proto.Empty) (*proto.Ac
TemplateDryRun: &proto.AcquiredJob_TemplateDryRun{ TemplateDryRun: &proto.AcquiredJob_TemplateDryRun{
RichParameterValues: convertRichParameterValues(input.RichParameterValues), RichParameterValues: convertRichParameterValues(input.RichParameterValues),
VariableValues: asVariableValues(templateVariables), VariableValues: asVariableValues(templateVariables),
Metadata: &sdkproto.Provision_Metadata{ Metadata: &sdkproto.Metadata{
CoderUrl: server.AccessURL.String(), CoderUrl: server.AccessURL.String(),
WorkspaceName: input.WorkspaceName, WorkspaceName: input.WorkspaceName,
}, },
@ -337,7 +337,7 @@ func (server *Server) AcquireJob(ctx context.Context, _ *proto.Empty) (*proto.Ac
protoJob.Type = &proto.AcquiredJob_TemplateImport_{ protoJob.Type = &proto.AcquiredJob_TemplateImport_{
TemplateImport: &proto.AcquiredJob_TemplateImport{ TemplateImport: &proto.AcquiredJob_TemplateImport{
UserVariableValues: convertVariableValues(userVariableValues), UserVariableValues: convertVariableValues(userVariableValues),
Metadata: &sdkproto.Provision_Metadata{ Metadata: &sdkproto.Metadata{
CoderUrl: server.AccessURL.String(), CoderUrl: server.AccessURL.String(),
}, },
}, },

View File

@ -267,7 +267,7 @@ func TestAcquireJob(t *testing.T) {
Id: gitAuthProvider, Id: gitAuthProvider,
AccessToken: "access_token", AccessToken: "access_token",
}}, }},
Metadata: &sdkproto.Provision_Metadata{ Metadata: &sdkproto.Metadata{
CoderUrl: srv.AccessURL.String(), CoderUrl: srv.AccessURL.String(),
WorkspaceTransition: sdkproto.WorkspaceTransition_START, WorkspaceTransition: sdkproto.WorkspaceTransition_START,
WorkspaceName: workspace.Name, WorkspaceName: workspace.Name,
@ -359,7 +359,7 @@ func TestAcquireJob(t *testing.T) {
want, err := json.Marshal(&proto.AcquiredJob_TemplateDryRun_{ want, err := json.Marshal(&proto.AcquiredJob_TemplateDryRun_{
TemplateDryRun: &proto.AcquiredJob_TemplateDryRun{ TemplateDryRun: &proto.AcquiredJob_TemplateDryRun{
Metadata: &sdkproto.Provision_Metadata{ Metadata: &sdkproto.Metadata{
CoderUrl: srv.AccessURL.String(), CoderUrl: srv.AccessURL.String(),
WorkspaceName: "testing", WorkspaceName: "testing",
}, },
@ -391,7 +391,7 @@ func TestAcquireJob(t *testing.T) {
want, err := json.Marshal(&proto.AcquiredJob_TemplateImport_{ want, err := json.Marshal(&proto.AcquiredJob_TemplateImport_{
TemplateImport: &proto.AcquiredJob_TemplateImport{ TemplateImport: &proto.AcquiredJob_TemplateImport{
Metadata: &sdkproto.Provision_Metadata{ Metadata: &sdkproto.Metadata{
CoderUrl: srv.AccessURL.String(), CoderUrl: srv.AccessURL.String(),
}, },
}, },
@ -434,7 +434,7 @@ func TestAcquireJob(t *testing.T) {
UserVariableValues: []*sdkproto.VariableValue{ UserVariableValues: []*sdkproto.VariableValue{
{Name: "first", Sensitive: true, Value: "first_value"}, {Name: "first", Sensitive: true, Value: "first_value"},
}, },
Metadata: &sdkproto.Provision_Metadata{ Metadata: &sdkproto.Metadata{
CoderUrl: srv.AccessURL.String(), CoderUrl: srv.AccessURL.String(),
}, },
}, },

View File

@ -20,16 +20,16 @@ func TestProvisionerJobLogs(t *testing.T) {
user := coderdtest.CreateFirstUser(t, client) user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: []*proto.Response{{
Type: &proto.Provision_Response_Log{ Type: &proto.Response_Log{
Log: &proto.Log{ Log: &proto.Log{
Level: proto.LogLevel_INFO, Level: proto.LogLevel_INFO,
Output: "log-output", Output: "log-output",
}, },
}, },
}, { }, {
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{}, Apply: &proto.ApplyComplete{},
}, },
}}, }},
}) })
@ -59,16 +59,16 @@ func TestProvisionerJobLogs(t *testing.T) {
user := coderdtest.CreateFirstUser(t, client) user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: []*proto.Response{{
Type: &proto.Provision_Response_Log{ Type: &proto.Response_Log{
Log: &proto.Log{ Log: &proto.Log{
Level: proto.LogLevel_INFO, Level: proto.LogLevel_INFO,
Output: "log-output", Output: "log-output",
}, },
}, },
}, { }, {
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{}, Apply: &proto.ApplyComplete{},
}, },
}}, }},
}) })

View File

@ -1203,7 +1203,7 @@ func TestTemplateMetrics(t *testing.T) {
authToken := uuid.NewString() authToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ProvisionApplyWithAgent(authToken), ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
}) })
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)

View File

@ -136,8 +136,8 @@ func TestPostTemplateVersionsByOrganization(t *testing.T) {
user := coderdtest.CreateFirstUser(t, client) user := coderdtest.CreateFirstUser(t, client)
data, err := echo.Tar(&echo.Responses{ data, err := echo.Tar(&echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionApply: echo.ProvisionComplete, ProvisionApply: echo.ApplyComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
}) })
require.NoError(t, err) require.NoError(t, err)
@ -245,8 +245,8 @@ func TestPatchCancelTemplateVersion(t *testing.T) {
user := coderdtest.CreateFirstUser(t, client) user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: []*proto.Response{{
Type: &proto.Provision_Response_Log{ Type: &proto.Response_Log{
Log: &proto.Log{}, Log: &proto.Log{},
}, },
}}, }},
@ -284,8 +284,8 @@ func TestPatchCancelTemplateVersion(t *testing.T) {
user := coderdtest.CreateFirstUser(t, client) user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: []*proto.Response{{
Type: &proto.Provision_Response_Log{ Type: &proto.Response_Log{
Log: &proto.Log{}, Log: &proto.Log{},
}, },
}}, }},
@ -346,9 +346,9 @@ func TestTemplateVersionsGitAuth(t *testing.T) {
user := coderdtest.CreateFirstUser(t, client) user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: []*proto.Provision_Response{{ ProvisionPlan: []*proto.Response{{
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Plan{
Complete: &proto.Provision_Complete{ Plan: &proto.PlanComplete{
GitAuthProviders: []string{"github"}, GitAuthProviders: []string{"github"},
}, },
}, },
@ -400,9 +400,9 @@ func TestTemplateVersionResources(t *testing.T) {
user := coderdtest.CreateFirstUser(t, client) user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: []*proto.Response{{
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{ Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{{ Resources: []*proto.Resource{{
Name: "some", Name: "some",
Type: "example", Type: "example",
@ -439,17 +439,17 @@ func TestTemplateVersionLogs(t *testing.T) {
user := coderdtest.CreateFirstUser(t, client) user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: []*proto.Response{{
Type: &proto.Provision_Response_Log{ Type: &proto.Response_Log{
Log: &proto.Log{ Log: &proto.Log{
Level: proto.LogLevel_INFO, Level: proto.LogLevel_INFO,
Output: "example", Output: "example",
}, },
}, },
}, { }, {
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{ Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{{ Resources: []*proto.Resource{{
Name: "some", Name: "some",
Type: "example", Type: "example",
@ -610,15 +610,15 @@ func TestTemplateVersionDryRun(t *testing.T) {
user := coderdtest.CreateFirstUser(t, client) user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionApply: []*proto.Provision_Response{ ProvisionApply: []*proto.Response{
{ {
Type: &proto.Provision_Response_Log{ Type: &proto.Response_Log{
Log: &proto.Log{}, Log: &proto.Log{},
}, },
}, },
{ {
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{ Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{resource}, Resources: []*proto.Resource{resource},
}, },
}, },
@ -677,8 +677,8 @@ func TestTemplateVersionDryRun(t *testing.T) {
// This import job will never finish // This import job will never finish
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: []*proto.Response{{
Type: &proto.Provision_Response_Log{ Type: &proto.Response_Log{
Log: &proto.Log{}, Log: &proto.Log{},
}, },
}}, }},
@ -705,15 +705,15 @@ func TestTemplateVersionDryRun(t *testing.T) {
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionApply: []*proto.Provision_Response{ ProvisionApply: []*proto.Response{
{ {
Type: &proto.Provision_Response_Log{ Type: &proto.Response_Log{
Log: &proto.Log{}, Log: &proto.Log{},
}, },
}, },
{ {
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{}, Apply: &proto.ApplyComplete{},
}, },
}, },
}, },
@ -776,15 +776,15 @@ func TestTemplateVersionDryRun(t *testing.T) {
user := coderdtest.CreateFirstUser(t, client) user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionApply: []*proto.Provision_Response{ ProvisionApply: []*proto.Response{
{ {
Type: &proto.Provision_Response_Log{ Type: &proto.Response_Log{
Log: &proto.Log{}, Log: &proto.Log{},
}, },
}, },
{ {
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{}, Apply: &proto.ApplyComplete{},
}, },
}, },
}, },
@ -1040,21 +1040,17 @@ func TestTemplateVersionVariables(t *testing.T) {
createEchoResponses := func(templateVariables []*proto.TemplateVariable) *echo.Responses { createEchoResponses := func(templateVariables []*proto.TemplateVariable) *echo.Responses {
return &echo.Responses{ return &echo.Responses{
Parse: []*proto.Parse_Response{ Parse: []*proto.Response{
{ {
Type: &proto.Parse_Response_Complete{ Type: &proto.Response_Parse{
Complete: &proto.Parse_Complete{ Parse: &proto.ParseComplete{
TemplateVariables: templateVariables, TemplateVariables: templateVariables,
}, },
}, },
}, },
}, },
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: echo.ApplyComplete,
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{},
},
}},
} }
} }
@ -1418,10 +1414,10 @@ func TestTemplateVersionParameters_Order(t *testing.T) {
user := coderdtest.CreateFirstUser(t, client) user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: []*proto.Provision_Response{ ProvisionPlan: []*proto.Response{
{ {
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Plan{
Complete: &proto.Provision_Complete{ Plan: &proto.PlanComplete{
Parameters: []*proto.RichParameter{ Parameters: []*proto.RichParameter{
{ {
Name: firstParameterName, Name: firstParameterName,
@ -1453,11 +1449,7 @@ func TestTemplateVersionParameters_Order(t *testing.T) {
}, },
}, },
}, },
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: echo.ApplyComplete,
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{},
},
}},
}) })
coderdtest.AwaitTemplateVersionJob(t, client, version.ID) coderdtest.AwaitTemplateVersionJob(t, client, version.ID)

View File

@ -43,10 +43,10 @@ func TestWorkspaceAgent(t *testing.T) {
tmpDir := t.TempDir() tmpDir := t.TempDir()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: []*proto.Response{{
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{ Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{{ Resources: []*proto.Resource{{
Name: "example", Name: "example",
Type: "aws_instance", Type: "aws_instance",
@ -87,10 +87,10 @@ func TestWorkspaceAgent(t *testing.T) {
tmpDir := t.TempDir() tmpDir := t.TempDir()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: []*proto.Response{{
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{ Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{{ Resources: []*proto.Resource{{
Name: "example", Name: "example",
Type: "aws_instance", Type: "aws_instance",
@ -132,10 +132,10 @@ func TestWorkspaceAgent(t *testing.T) {
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: []*proto.Response{{
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{ Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{{ Resources: []*proto.Resource{{
Name: "example", Name: "example",
Type: "aws_instance", Type: "aws_instance",
@ -188,10 +188,10 @@ func TestWorkspaceAgentStartupLogs(t *testing.T) {
authToken := uuid.NewString() authToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: []*proto.Response{{
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{ Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{{ Resources: []*proto.Resource{{
Name: "example", Name: "example",
Type: "aws_instance", Type: "aws_instance",
@ -252,10 +252,10 @@ func TestWorkspaceAgentStartupLogs(t *testing.T) {
authToken := uuid.NewString() authToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: []*proto.Response{{
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{ Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{{ Resources: []*proto.Resource{{
Name: "example", Name: "example",
Type: "aws_instance", Type: "aws_instance",
@ -319,7 +319,7 @@ func TestWorkspaceAgentListen(t *testing.T) {
authToken := uuid.NewString() authToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ProvisionApplyWithAgent(authToken), ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
}) })
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
@ -360,7 +360,7 @@ func TestWorkspaceAgentListen(t *testing.T) {
authToken := uuid.NewString() authToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ProvisionApplyWithAgent(authToken), ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
}) })
@ -371,10 +371,10 @@ func TestWorkspaceAgentListen(t *testing.T) {
version = coderdtest.UpdateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version = coderdtest.UpdateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: []*proto.Response{{
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{ Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{{ Resources: []*proto.Resource{{
Name: "example", Name: "example",
Type: "aws_instance", Type: "aws_instance",
@ -419,7 +419,7 @@ func TestWorkspaceAgentTailnet(t *testing.T) {
authToken := uuid.NewString() authToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ProvisionApplyWithAgent(authToken), ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
}) })
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
@ -471,7 +471,7 @@ func TestWorkspaceAgentTailnetDirectDisabled(t *testing.T) {
authToken := uuid.NewString() authToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ProvisionApplyWithAgent(authToken), ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
}) })
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
@ -548,10 +548,10 @@ func TestWorkspaceAgentListeningPorts(t *testing.T) {
authToken := uuid.NewString() authToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: []*proto.Response{{
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{ Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{{ Resources: []*proto.Resource{{
Name: "example", Name: "example",
Type: "aws_instance", Type: "aws_instance",
@ -806,9 +806,9 @@ func TestWorkspaceAgentAppHealth(t *testing.T) {
} }
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: []*proto.Response{{
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{ Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{{ Resources: []*proto.Resource{{
Name: "example", Name: "example",
Type: "aws_instance", Type: "aws_instance",
@ -893,7 +893,7 @@ func TestWorkspaceAgentReportStats(t *testing.T) {
authToken := uuid.NewString() authToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ProvisionApplyWithAgent(authToken), ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
}) })
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
@ -942,7 +942,7 @@ func TestWorkspaceAgent_LifecycleState(t *testing.T) {
authToken := uuid.NewString() authToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ProvisionApplyWithAgent(authToken), ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
}) })
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
@ -1014,10 +1014,10 @@ func TestWorkspaceAgent_Metadata(t *testing.T) {
authToken := uuid.NewString() authToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: []*proto.Response{{
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{ Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{{ Resources: []*proto.Resource{{
Name: "example", Name: "example",
Type: "aws_instance", Type: "aws_instance",
@ -1184,7 +1184,7 @@ func TestWorkspaceAgent_Startup(t *testing.T) {
authToken := uuid.NewString() authToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ProvisionApplyWithAgent(authToken), ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
}) })
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
@ -1238,7 +1238,7 @@ func TestWorkspaceAgent_Startup(t *testing.T) {
authToken := uuid.NewString() authToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ProvisionApplyWithAgent(authToken), ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
}) })
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
@ -1293,7 +1293,7 @@ func TestWorkspaceAgent_UpdatedDERP(t *testing.T) {
agentToken := uuid.NewString() agentToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ProvisionApplyWithAgent(agentToken), ProvisionApply: echo.ProvisionApplyWithAgent(agentToken),
}) })
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)

View File

@ -288,10 +288,10 @@ func createWorkspaceWithApps(t *testing.T, client *codersdk.Client, orgID uuid.U
appURL := fmt.Sprintf("%s://127.0.0.1:%d?%s", scheme, port, proxyTestAppQuery) appURL := fmt.Sprintf("%s://127.0.0.1:%d?%s", scheme, port, proxyTestAppQuery)
version := coderdtest.CreateTemplateVersion(t, client, orgID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, orgID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: []*proto.Response{{
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{ Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{{ Resources: []*proto.Resource{{
Name: "example", Name: "example",
Type: "aws_instance", Type: "aws_instance",

View File

@ -94,10 +94,10 @@ func Test_ResolveRequest(t *testing.T) {
agentAuthToken := uuid.NewString() agentAuthToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, firstUser.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, firstUser.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: []*proto.Response{{
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{ Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{{ Resources: []*proto.Resource{{
Name: "example", Name: "example",
Type: "aws_instance", Type: "aws_instance",

View File

@ -376,12 +376,12 @@ func TestPatchCancelWorkspaceBuild(t *testing.T) {
user := coderdtest.CreateFirstUser(t, client) user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: []*proto.Response{{
Type: &proto.Provision_Response_Log{ Type: &proto.Response_Log{
Log: &proto.Log{}, Log: &proto.Log{},
}, },
}}, }},
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
}) })
coderdtest.AwaitTemplateVersionJob(t, client, version.ID) coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
@ -401,11 +401,12 @@ func TestPatchCancelWorkspaceBuild(t *testing.T) {
require.Eventually(t, func() bool { require.Eventually(t, func() bool {
var err error var err error
build, err = client.WorkspaceBuild(ctx, build.ID) build, err = client.WorkspaceBuild(ctx, build.ID)
// job gets marked Failed when there is an Error; in practice we never get to Status = Canceled
// because provisioners report an Error when canceled. We check the Error string to ensure we don't mask
// other errors in this test.
return assert.NoError(t, err) && return assert.NoError(t, err) &&
// The job will never actually cancel successfully because it will never send a build.Job.Error == "canceled" &&
// provision complete response. build.Job.Status == codersdk.ProvisionerJobFailed
assert.Empty(t, build.Job.Error) &&
build.Job.Status == codersdk.ProvisionerJobCanceling
}, testutil.WaitShort, testutil.IntervalFast) }, testutil.WaitShort, testutil.IntervalFast)
}) })
t.Run("User is not allowed to cancel", func(t *testing.T) { t.Run("User is not allowed to cancel", func(t *testing.T) {
@ -415,12 +416,12 @@ func TestPatchCancelWorkspaceBuild(t *testing.T) {
owner := coderdtest.CreateFirstUser(t, client) owner := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: []*proto.Response{{
Type: &proto.Provision_Response_Log{ Type: &proto.Response_Log{
Log: &proto.Log{}, Log: &proto.Log{},
}, },
}}, }},
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
}) })
coderdtest.AwaitTemplateVersionJob(t, client, version.ID) coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID) template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
@ -452,9 +453,9 @@ func TestWorkspaceBuildResources(t *testing.T) {
user := coderdtest.CreateFirstUser(t, client) user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: []*proto.Response{{
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{ Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{{ Resources: []*proto.Resource{{
Name: "some", Name: "some",
Type: "example", Type: "example",
@ -494,16 +495,16 @@ func TestWorkspaceBuildLogs(t *testing.T) {
user := coderdtest.CreateFirstUser(t, client) user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: []*proto.Response{{
Type: &proto.Provision_Response_Log{ Type: &proto.Response_Log{
Log: &proto.Log{ Log: &proto.Log{
Level: proto.LogLevel_INFO, Level: proto.LogLevel_INFO,
Output: "example", Output: "example",
}, },
}, },
}, { }, {
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{ Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{{ Resources: []*proto.Resource{{
Name: "some", Name: "some",
Type: "example", Type: "example",
@ -548,10 +549,10 @@ func TestWorkspaceBuildState(t *testing.T) {
wantState := []byte("some kinda state") wantState := []byte("some kinda state")
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: []*proto.Response{{
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{ Apply: &proto.ApplyComplete{
State: wantState, State: wantState,
}, },
}, },
@ -764,31 +765,31 @@ func TestWorkspaceBuildDebugMode(t *testing.T) {
// Interact as template admin // Interact as template admin
echoResponses := &echo.Responses{ echoResponses := &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: []*proto.Response{{
Type: &proto.Provision_Response_Log{ Type: &proto.Response_Log{
Log: &proto.Log{ Log: &proto.Log{
Level: proto.LogLevel_DEBUG, Level: proto.LogLevel_DEBUG,
Output: "want-it", Output: "want-it",
}, },
}, },
}, { }, {
Type: &proto.Provision_Response_Log{ Type: &proto.Response_Log{
Log: &proto.Log{ Log: &proto.Log{
Level: proto.LogLevel_TRACE, Level: proto.LogLevel_TRACE,
Output: "dont-want-it", Output: "dont-want-it",
}, },
}, },
}, { }, {
Type: &proto.Provision_Response_Log{ Type: &proto.Response_Log{
Log: &proto.Log{ Log: &proto.Log{
Level: proto.LogLevel_DEBUG, Level: proto.LogLevel_DEBUG,
Output: "done", Output: "done",
}, },
}, },
}, { }, {
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{}, Apply: &proto.ApplyComplete{},
}, },
}}, }},
} }
@ -831,7 +832,10 @@ func TestWorkspaceBuildDebugMode(t *testing.T) {
if !ok { if !ok {
break processingLogs break processingLogs
} }
t.Logf("got log: %s -- %s | %s | %s", log.Level, log.Stage, log.Source, log.Output)
if log.Source != "provisioner" {
continue
}
logsProcessed++ logsProcessed++
require.NotEqual(t, "dont-want-it", log.Output, "unexpected log message", "%s log message shouldn't be logged: %s") require.NotEqual(t, "dont-want-it", log.Output, "unexpected log message", "%s log message shouldn't be logged: %s")
@ -841,7 +845,6 @@ func TestWorkspaceBuildDebugMode(t *testing.T) {
} }
} }
} }
require.Equal(t, 2, logsProcessed)
require.Len(t, echoResponses.ProvisionApply, logsProcessed)
}) })
} }

View File

@ -26,9 +26,9 @@ func TestPostWorkspaceAuthAzureInstanceIdentity(t *testing.T) {
user := coderdtest.CreateFirstUser(t, client) user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: []*proto.Response{{
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{ Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{{ Resources: []*proto.Resource{{
Name: "somename", Name: "somename",
Type: "someinstance", Type: "someinstance",
@ -71,9 +71,9 @@ func TestPostWorkspaceAuthAWSInstanceIdentity(t *testing.T) {
user := coderdtest.CreateFirstUser(t, client) user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: []*proto.Response{{
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{ Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{{ Resources: []*proto.Resource{{
Name: "somename", Name: "somename",
Type: "someinstance", Type: "someinstance",
@ -157,9 +157,9 @@ func TestPostWorkspaceAuthGoogleInstanceIdentity(t *testing.T) {
user := coderdtest.CreateFirstUser(t, client) user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: []*proto.Response{{
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{ Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{{ Resources: []*proto.Resource{{
Name: "somename", Name: "somename",
Type: "someinstance", Type: "someinstance",

View File

@ -174,9 +174,9 @@ func TestWorkspace(t *testing.T) {
user := coderdtest.CreateFirstUser(t, client) user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: []*proto.Response{{
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{ Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{{ Resources: []*proto.Resource{{
Name: "some", Name: "some",
Type: "example", Type: "example",
@ -214,9 +214,9 @@ func TestWorkspace(t *testing.T) {
user := coderdtest.CreateFirstUser(t, client) user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: []*proto.Response{{
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{ Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{{ Resources: []*proto.Resource{{
Name: "some", Name: "some",
Type: "example", Type: "example",
@ -258,9 +258,9 @@ func TestWorkspace(t *testing.T) {
user := coderdtest.CreateFirstUser(t, client) user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: []*proto.Response{{
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{ Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{{ Resources: []*proto.Resource{{
Name: "some", Name: "some",
Type: "example", Type: "example",
@ -1248,7 +1248,7 @@ func TestWorkspaceFilterManual(t *testing.T) {
authToken := uuid.NewString() authToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ProvisionApplyWithAgent(authToken), ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
}) })
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
@ -1276,7 +1276,7 @@ func TestWorkspaceFilterManual(t *testing.T) {
authToken := uuid.NewString() authToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ProvisionApplyWithAgent(authToken), ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
}) })
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
@ -1316,10 +1316,10 @@ func TestWorkspaceFilterManual(t *testing.T) {
authToken := uuid.NewString() authToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: []*proto.Response{{
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{ Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{{ Resources: []*proto.Resource{{
Name: "example", Name: "example",
Type: "aws_instance", Type: "aws_instance",
@ -1374,7 +1374,7 @@ func TestWorkspaceFilterManual(t *testing.T) {
authToken := uuid.NewString() authToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ProvisionApplyWithAgent(authToken), ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
}) })
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
@ -1418,7 +1418,7 @@ func TestWorkspaceFilterManual(t *testing.T) {
authToken := uuid.NewString() authToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ProvisionApplyWithAgent(authToken), ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
}) })
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
@ -1457,7 +1457,7 @@ func TestWorkspaceFilterManual(t *testing.T) {
authToken := uuid.NewString() authToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ProvisionApplyWithAgent(authToken), ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
}) })
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
@ -1575,7 +1575,7 @@ func TestPostWorkspaceBuild(t *testing.T) {
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
user := coderdtest.CreateFirstUser(t, client) user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
ProvisionApply: []*proto.Provision_Response{{}}, ProvisionApply: []*proto.Response{{}},
}) })
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID) coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
@ -2138,10 +2138,10 @@ func TestWorkspaceWatcher(t *testing.T) {
authToken := uuid.NewString() authToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: []*proto.Response{{
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{ Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{{ Resources: []*proto.Resource{{
Name: "example", Name: "example",
Type: "aws_instance", Type: "aws_instance",
@ -2231,10 +2231,10 @@ func TestWorkspaceWatcher(t *testing.T) {
// Add a new version that will fail. // Add a new version that will fail.
badVersion := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ badVersion := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: []*proto.Response{{
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{ Apply: &proto.ApplyComplete{
Error: "test error", Error: "test error",
}, },
}, },
@ -2299,9 +2299,9 @@ func TestWorkspaceResource(t *testing.T) {
user := coderdtest.CreateFirstUser(t, client) user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: []*proto.Response{{
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{ Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{{ Resources: []*proto.Resource{{
Name: "beta", Name: "beta",
Type: "example", Type: "example",
@ -2367,9 +2367,9 @@ func TestWorkspaceResource(t *testing.T) {
} }
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: []*proto.Response{{
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{ Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{{ Resources: []*proto.Resource{{
Name: "some", Name: "some",
Type: "example", Type: "example",
@ -2424,9 +2424,9 @@ func TestWorkspaceResource(t *testing.T) {
user := coderdtest.CreateFirstUser(t, client) user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: []*proto.Response{{
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{ Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{{ Resources: []*proto.Resource{{
Name: "some", Name: "some",
Type: "example", Type: "example",
@ -2497,10 +2497,10 @@ func TestWorkspaceWithRichParameters(t *testing.T) {
user := coderdtest.CreateFirstUser(t, client) user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: []*proto.Provision_Response{ ProvisionPlan: []*proto.Response{
{ {
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Plan{
Complete: &proto.Provision_Complete{ Plan: &proto.PlanComplete{
Parameters: []*proto.RichParameter{ Parameters: []*proto.RichParameter{
{ {
Name: firstParameterName, Name: firstParameterName,
@ -2521,9 +2521,9 @@ func TestWorkspaceWithRichParameters(t *testing.T) {
}, },
}, },
}, },
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: []*proto.Response{{
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{}, Apply: &proto.ApplyComplete{},
}, },
}}, }},
}) })
@ -2590,10 +2590,10 @@ func TestWorkspaceWithOptionalRichParameters(t *testing.T) {
user := coderdtest.CreateFirstUser(t, client) user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: []*proto.Provision_Response{ ProvisionPlan: []*proto.Response{
{ {
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Plan{
Complete: &proto.Provision_Complete{ Plan: &proto.PlanComplete{
Parameters: []*proto.RichParameter{ Parameters: []*proto.RichParameter{
{ {
Name: firstParameterName, Name: firstParameterName,
@ -2612,9 +2612,9 @@ func TestWorkspaceWithOptionalRichParameters(t *testing.T) {
}, },
}, },
}, },
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: []*proto.Response{{
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{}, Apply: &proto.ApplyComplete{},
}, },
}}, }},
}) })
@ -2681,10 +2681,10 @@ func TestWorkspaceWithEphemeralRichParameters(t *testing.T) {
user := coderdtest.CreateFirstUser(t, client) user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: []*proto.Provision_Response{ ProvisionPlan: []*proto.Response{
{ {
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Plan{
Complete: &proto.Provision_Complete{ Plan: &proto.PlanComplete{
Parameters: []*proto.RichParameter{ Parameters: []*proto.RichParameter{
{ {
Name: firstParameterName, Name: firstParameterName,
@ -2706,9 +2706,9 @@ func TestWorkspaceWithEphemeralRichParameters(t *testing.T) {
}, },
}, },
}, },
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: []*proto.Response{{
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{}, Apply: &proto.ApplyComplete{},
}, },
}}, }},
}) })

View File

@ -70,6 +70,11 @@ func (r *RootCmd) provisionerDaemonStart() *clibase.Cmd {
return xerrors.Errorf("mkdir %q: %w", cacheDir, err) return xerrors.Errorf("mkdir %q: %w", cacheDir, err)
} }
tempDir, err := os.MkdirTemp("", "provisionerd")
if err != nil {
return err
}
terraformClient, terraformServer := provisionersdk.MemTransportPipe() terraformClient, terraformServer := provisionersdk.MemTransportPipe()
go func() { go func() {
<-ctx.Done() <-ctx.Done()
@ -84,10 +89,11 @@ func (r *RootCmd) provisionerDaemonStart() *clibase.Cmd {
err := terraform.Serve(ctx, &terraform.ServeOptions{ err := terraform.Serve(ctx, &terraform.ServeOptions{
ServeOptions: &provisionersdk.ServeOptions{ ServeOptions: &provisionersdk.ServeOptions{
Listener: terraformServer, Listener: terraformServer,
Logger: logger.Named("terraform"),
WorkDirectory: tempDir,
}, },
CachePath: cacheDir, CachePath: cacheDir,
Logger: logger.Named("terraform"),
}) })
if err != nil && !xerrors.Is(err, context.Canceled) { if err != nil && !xerrors.Is(err, context.Canceled) {
select { select {
@ -97,11 +103,6 @@ func (r *RootCmd) provisionerDaemonStart() *clibase.Cmd {
} }
}() }()
tempDir, err := os.MkdirTemp("", "provisionerd")
if err != nil {
return err
}
logger.Info(ctx, "starting provisioner daemon", slog.F("tags", tags)) logger.Info(ctx, "starting provisioner daemon", slog.F("tags", tags))
provisioners := provisionerd.Provisioners{ provisioners := provisionerd.Provisioners{
@ -121,7 +122,6 @@ func (r *RootCmd) provisionerDaemonStart() *clibase.Cmd {
JobPollJitter: pollJitter, JobPollJitter: pollJitter,
UpdateInterval: 500 * time.Millisecond, UpdateInterval: 500 * time.Millisecond,
Provisioners: provisioners, Provisioners: provisioners,
WorkDirectory: tempDir,
}) })
var exitErr error var exitErr error

View File

@ -111,7 +111,7 @@ func TestServiceBanners(t *testing.T) {
agentClient.SetSessionToken(authToken) agentClient.SetSessionToken(authToken)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ProvisionApplyWithAgent(authToken), ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
}) })
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)

View File

@ -139,9 +139,9 @@ func TestProvisionerDaemonServe(t *testing.T) {
authToken := uuid.NewString() authToken := uuid.NewString()
data, err := echo.Tar(&echo.Responses{ data, err := echo.Tar(&echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: []*proto.Provision_Response{{ ProvisionPlan: []*proto.Response{{
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Plan{
Complete: &proto.Provision_Complete{ Plan: &proto.PlanComplete{
Resources: []*proto.Resource{{ Resources: []*proto.Resource{{
Name: "example", Name: "example",
Type: "aws_instance", Type: "aws_instance",

View File

@ -73,9 +73,9 @@ func setupWorkspaceAgent(t *testing.T, client *codersdk.Client, user codersdk.Cr
authToken := uuid.NewString() authToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: []*proto.Response{{
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{ Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{{ Resources: []*proto.Resource{{
Name: "example", Name: "example",
Type: "aws_instance", Type: "aws_instance",

View File

@ -89,9 +89,9 @@ func TestWorkspaceQuota(t *testing.T) {
authToken := uuid.NewString() authToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: []*proto.Response{{
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{ Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{{ Resources: []*proto.Resource{{
Name: "example", Name: "example",
Type: "aws_instance", Type: "aws_instance",
@ -183,39 +183,15 @@ func TestWorkspaceQuota(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
verifyQuota(ctx, t, client, 0, 4) verifyQuota(ctx, t, client, 0, 4)
stopResp := []*proto.Provision_Response{{
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{
Resources: []*proto.Resource{{
Name: "example",
Type: "aws_instance",
DailyCost: 1,
}},
},
},
}}
startResp := []*proto.Provision_Response{{
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{
Resources: []*proto.Resource{{
Name: "example",
Type: "aws_instance",
DailyCost: 2,
}},
},
},
}}
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlanMap: map[proto.WorkspaceTransition][]*proto.Provision_Response{ ProvisionPlanMap: map[proto.WorkspaceTransition][]*proto.Response{
proto.WorkspaceTransition_START: startResp, proto.WorkspaceTransition_START: planWithCost(2),
proto.WorkspaceTransition_STOP: stopResp, proto.WorkspaceTransition_STOP: planWithCost(1),
}, },
ProvisionApplyMap: map[proto.WorkspaceTransition][]*proto.Provision_Response{ ProvisionApplyMap: map[proto.WorkspaceTransition][]*proto.Response{
proto.WorkspaceTransition_START: startResp, proto.WorkspaceTransition_START: applyWithCost(2),
proto.WorkspaceTransition_STOP: stopResp, proto.WorkspaceTransition_STOP: applyWithCost(1),
}, },
}) })
@ -258,3 +234,31 @@ func TestWorkspaceQuota(t *testing.T) {
require.Equal(t, codersdk.WorkspaceStatusRunning, build.Status) require.Equal(t, codersdk.WorkspaceStatusRunning, build.Status)
}) })
} }
func planWithCost(cost int32) []*proto.Response {
return []*proto.Response{{
Type: &proto.Response_Plan{
Plan: &proto.PlanComplete{
Resources: []*proto.Resource{{
Name: "example",
Type: "aws_instance",
DailyCost: cost,
}},
},
},
}}
}
func applyWithCost(cost int32) []*proto.Response {
return []*proto.Response{{
Type: &proto.Response_Apply{
Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{{
Name: "example",
Type: "aws_instance",
DailyCost: cost,
}},
},
},
}}
}

View File

@ -120,8 +120,8 @@ func TestWorkspaceAutobuild(t *testing.T) {
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ProvisionFailed, ProvisionApply: echo.ApplyFailed,
}) })
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) { template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
ctr.FailureTTLMillis = ptr.Ref[int64](failureTTL.Milliseconds()) ctr.FailureTTLMillis = ptr.Ref[int64](failureTTL.Milliseconds())
@ -166,8 +166,8 @@ func TestWorkspaceAutobuild(t *testing.T) {
}) })
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ProvisionFailed, ProvisionApply: echo.ApplyFailed,
}) })
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) { template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
ctr.FailureTTLMillis = ptr.Ref[int64](failureTTL.Milliseconds()) ctr.FailureTTLMillis = ptr.Ref[int64](failureTTL.Milliseconds())
@ -212,8 +212,8 @@ func TestWorkspaceAutobuild(t *testing.T) {
}) })
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ProvisionComplete, ProvisionApply: echo.ApplyComplete,
}) })
// Create a template without setting a failure_ttl. // Create a template without setting a failure_ttl.
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
@ -255,8 +255,8 @@ func TestWorkspaceAutobuild(t *testing.T) {
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ProvisionComplete, ProvisionApply: echo.ApplyComplete,
}) })
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) { template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
ctr.TimeTilDormantMillis = ptr.Ref[int64](inactiveTTL.Milliseconds()) ctr.TimeTilDormantMillis = ptr.Ref[int64](inactiveTTL.Milliseconds())
@ -311,8 +311,8 @@ func TestWorkspaceAutobuild(t *testing.T) {
}) })
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ProvisionComplete, ProvisionApply: echo.ApplyComplete,
}) })
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) { template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
ctr.TimeTilDormantMillis = ptr.Ref[int64](inactiveTTL.Milliseconds()) ctr.TimeTilDormantMillis = ptr.Ref[int64](inactiveTTL.Milliseconds())
@ -353,8 +353,8 @@ func TestWorkspaceAutobuild(t *testing.T) {
}) })
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ProvisionComplete, ProvisionApply: echo.ApplyComplete,
}) })
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) { template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
ctr.TimeTilDormantAutoDeleteMillis = ptr.Ref[int64](autoDeleteTTL.Milliseconds()) ctr.TimeTilDormantAutoDeleteMillis = ptr.Ref[int64](autoDeleteTTL.Milliseconds())
@ -395,8 +395,8 @@ func TestWorkspaceAutobuild(t *testing.T) {
}) })
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ProvisionComplete, ProvisionApply: echo.ApplyComplete,
}) })
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) { template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
ctr.TimeTilDormantMillis = ptr.Ref[int64](inactiveTTL.Milliseconds()) ctr.TimeTilDormantMillis = ptr.Ref[int64](inactiveTTL.Milliseconds())
@ -447,8 +447,8 @@ func TestWorkspaceAutobuild(t *testing.T) {
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ProvisionComplete, ProvisionApply: echo.ApplyComplete,
}) })
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) { template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
ctr.TimeTilDormantMillis = ptr.Ref[int64](transitionTTL.Milliseconds()) ctr.TimeTilDormantMillis = ptr.Ref[int64](transitionTTL.Milliseconds())
@ -516,8 +516,8 @@ func TestWorkspaceAutobuild(t *testing.T) {
}) })
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ProvisionComplete, ProvisionApply: echo.ApplyComplete,
}) })
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) { template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
ctr.TimeTilDormantAutoDeleteMillis = ptr.Ref[int64](dormantTTL.Milliseconds()) ctr.TimeTilDormantAutoDeleteMillis = ptr.Ref[int64](dormantTTL.Milliseconds())
@ -578,8 +578,8 @@ func TestWorkspaceAutobuild(t *testing.T) {
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ProvisionComplete, ProvisionApply: echo.ApplyComplete,
}) })
coderdtest.AwaitTemplateVersionJob(t, client, version.ID) coderdtest.AwaitTemplateVersionJob(t, client, version.ID)

View File

@ -5,25 +5,24 @@ import (
"bytes" "bytes"
"context" "context"
"fmt" "fmt"
"os"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/google/uuid"
"golang.org/x/xerrors" "golang.org/x/xerrors"
protobuf "google.golang.org/protobuf/proto" protobuf "google.golang.org/protobuf/proto"
"github.com/google/uuid"
"github.com/spf13/afero"
"github.com/coder/coder/v2/provisionersdk" "github.com/coder/coder/v2/provisionersdk"
"github.com/coder/coder/v2/provisionersdk/proto" "github.com/coder/coder/v2/provisionersdk/proto"
) )
// ProvisionApplyWithAgent returns provision responses that will mock a fake // ProvisionApplyWithAgent returns provision responses that will mock a fake
// "aws_instance" resource with an agent that has the given auth token. // "aws_instance" resource with an agent that has the given auth token.
func ProvisionApplyWithAgent(authToken string) []*proto.Provision_Response { func ProvisionApplyWithAgent(authToken string) []*proto.Response {
return []*proto.Provision_Response{{ return []*proto.Response{{
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{ Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{{ Resources: []*proto.Resource{{
Name: "example", Name: "example",
Type: "aws_instance", Type: "aws_instance",
@ -42,23 +41,36 @@ func ProvisionApplyWithAgent(authToken string) []*proto.Provision_Response {
var ( var (
// ParseComplete is a helper to indicate an empty parse completion. // ParseComplete is a helper to indicate an empty parse completion.
ParseComplete = []*proto.Parse_Response{{ ParseComplete = []*proto.Response{{
Type: &proto.Parse_Response_Complete{ Type: &proto.Response_Parse{
Complete: &proto.Parse_Complete{}, Parse: &proto.ParseComplete{},
}, },
}} }}
// ProvisionComplete is a helper to indicate an empty provision completion. // PlanComplete is a helper to indicate an empty provision completion.
ProvisionComplete = []*proto.Provision_Response{{ PlanComplete = []*proto.Response{{
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Plan{
Complete: &proto.Provision_Complete{}, Plan: &proto.PlanComplete{},
},
}}
// ApplyComplete is a helper to indicate an empty provision completion.
ApplyComplete = []*proto.Response{{
Type: &proto.Response_Apply{
Apply: &proto.ApplyComplete{},
}, },
}} }}
// ProvisionFailed is a helper to convey a failed provision // PlanFailed is a helper to convey a failed plan operation
// operation. PlanFailed = []*proto.Response{{
ProvisionFailed = []*proto.Provision_Response{{ Type: &proto.Response_Plan{
Type: &proto.Provision_Response_Complete{ Plan: &proto.PlanComplete{
Complete: &proto.Provision_Complete{ Error: "failed!",
},
},
}}
// ApplyFailed is a helper to convey a failed apply operation
ApplyFailed = []*proto.Response{{
Type: &proto.Response_Apply{
Apply: &proto.ApplyComplete{
Error: "failed!", Error: "failed!",
}, },
}, },
@ -66,120 +78,116 @@ var (
) )
// Serve starts the echo provisioner. // Serve starts the echo provisioner.
func Serve(ctx context.Context, filesystem afero.Fs, options *provisionersdk.ServeOptions) error { func Serve(ctx context.Context, options *provisionersdk.ServeOptions) error {
return provisionersdk.Serve(ctx, &echo{ return provisionersdk.Serve(ctx, &echo{}, options)
filesystem: filesystem,
}, options)
} }
// The echo provisioner serves as a dummy provisioner primarily // The echo provisioner serves as a dummy provisioner primarily
// used for testing. It echos responses from JSON files in the // used for testing. It echos responses from JSON files in the
// format %d.protobuf. It's used for testing. // format %d.protobuf. It's used for testing.
type echo struct { type echo struct{}
filesystem afero.Fs
func readResponses(sess *provisionersdk.Session, trans string, suffix string) ([]*proto.Response, error) {
var responses []*proto.Response
for i := 0; ; i++ {
paths := []string{
// Try more specific path first, then fallback to generic.
filepath.Join(sess.WorkDirectory, fmt.Sprintf("%d.%s.%s", i, trans, suffix)),
filepath.Join(sess.WorkDirectory, fmt.Sprintf("%d.%s", i, suffix)),
}
for pathIndex, path := range paths {
_, err := os.Stat(path)
if err != nil && pathIndex == (len(paths)-1) {
// If there are zero messages, something is wrong
if i == 0 {
// Error if nothing is around to enable failed states.
return nil, xerrors.Errorf("no state: %w", err)
}
// Otherwise, we've read all responses
return responses, nil
}
if err != nil {
// try next path
continue
}
data, err := os.ReadFile(path)
if err != nil {
return nil, xerrors.Errorf("read file %q: %w", path, err)
}
response := new(proto.Response)
err = protobuf.Unmarshal(data, response)
if err != nil {
return nil, xerrors.Errorf("unmarshal: %w", err)
}
responses = append(responses, response)
break
}
}
} }
// Parse reads requests from the provided directory to stream responses. // Parse reads requests from the provided directory to stream responses.
func (e *echo) Parse(request *proto.Parse_Request, stream proto.DRPCProvisioner_ParseStream) error { func (*echo) Parse(sess *provisionersdk.Session, _ *proto.ParseRequest, _ <-chan struct{}) *proto.ParseComplete {
for index := 0; ; index++ { responses, err := readResponses(sess, "unspecified", "parse.protobuf")
path := filepath.Join(request.Directory, fmt.Sprintf("%d.parse.protobuf", index)) if err != nil {
_, err := e.filesystem.Stat(path) return &proto.ParseComplete{Error: err.Error()}
if err != nil { }
if index == 0 { for _, response := range responses {
// Error if nothing is around to enable failed states. if log := response.GetLog(); log != nil {
return xerrors.Errorf("no state: %w", err) sess.ProvisionLog(log.Level, log.Output)
}
break
} }
data, err := afero.ReadFile(e.filesystem, path) if complete := response.GetParse(); complete != nil {
if err != nil { return complete
return xerrors.Errorf("read file %q: %w", path, err)
}
var response proto.Parse_Response
err = protobuf.Unmarshal(data, &response)
if err != nil {
return xerrors.Errorf("unmarshal: %w", err)
}
err = stream.Send(&response)
if err != nil {
return err
} }
} }
<-stream.Context().Done()
return stream.Context().Err() // if we didn't get a complete from the filesystem, that's an error
return provisionersdk.ParseErrorf("complete response missing")
} }
// Provision reads requests from the provided directory to stream responses. // Plan reads requests from the provided directory to stream responses.
func (e *echo) Provision(stream proto.DRPCProvisioner_ProvisionStream) error { func (*echo) Plan(sess *provisionersdk.Session, req *proto.PlanRequest, canceledOrComplete <-chan struct{}) *proto.PlanComplete {
msg, err := stream.Recv() responses, err := readResponses(
sess,
strings.ToLower(req.GetMetadata().GetWorkspaceTransition().String()),
"plan.protobuf")
if err != nil { if err != nil {
return err return &proto.PlanComplete{Error: err.Error()}
}
for _, response := range responses {
if log := response.GetLog(); log != nil {
sess.ProvisionLog(log.Level, log.Output)
}
if complete := response.GetPlan(); complete != nil {
return complete
}
} }
var config *proto.Provision_Config // some tests use Echo without a complete response to test cancel
switch { <-canceledOrComplete
case msg.GetPlan() != nil: return provisionersdk.PlanErrorf("canceled")
config = msg.GetPlan().GetConfig() }
case msg.GetApply() != nil:
config = msg.GetApply().GetConfig() // Apply reads requests from the provided directory to stream responses.
default: func (*echo) Apply(sess *provisionersdk.Session, req *proto.ApplyRequest, canceledOrComplete <-chan struct{}) *proto.ApplyComplete {
// Probably a cancel responses, err := readResponses(
return nil sess,
strings.ToLower(req.GetMetadata().GetWorkspaceTransition().String()),
"apply.protobuf")
if err != nil {
return &proto.ApplyComplete{Error: err.Error()}
}
for _, response := range responses {
if log := response.GetLog(); log != nil {
sess.ProvisionLog(log.Level, log.Output)
}
if complete := response.GetApply(); complete != nil {
return complete
}
} }
outer: // some tests use Echo without a complete response to test cancel
for i := 0; ; i++ { <-canceledOrComplete
var extension string return provisionersdk.ApplyErrorf("canceled")
if msg.GetPlan() != nil {
extension = ".plan.protobuf"
} else {
extension = ".apply.protobuf"
}
var (
path string
pathIndex int
)
// Try more specific path first, then fallback to generic.
paths := []string{
filepath.Join(config.Directory, fmt.Sprintf("%d.%s.provision"+extension, i, strings.ToLower(config.GetMetadata().GetWorkspaceTransition().String()))),
filepath.Join(config.Directory, fmt.Sprintf("%d.provision"+extension, i)),
}
for pathIndex, path = range paths {
_, err := e.filesystem.Stat(path)
if err != nil && pathIndex == len(paths)-1 {
// If there are zero messages, something is wrong.
if i == 0 {
// Error if nothing is around to enable failed states.
return xerrors.New("no state")
}
// Otherwise, we're done with the entire provision.
break outer
} else if err != nil {
continue
}
break
}
data, err := afero.ReadFile(e.filesystem, path)
if err != nil {
return xerrors.Errorf("read file %q: %w", path, err)
}
var response proto.Provision_Response
err = protobuf.Unmarshal(data, &response)
if err != nil {
return xerrors.Errorf("unmarshal: %w", err)
}
r, ok := filterLogResponses(config, &response)
if !ok {
continue
}
err = stream.Send(r)
if err != nil {
return err
}
}
<-stream.Context().Done()
return stream.Context().Err()
} }
func (*echo) Shutdown(_ context.Context, _ *proto.Empty) (*proto.Empty, error) { func (*echo) Shutdown(_ context.Context, _ *proto.Empty) (*proto.Empty, error) {
@ -188,29 +196,42 @@ func (*echo) Shutdown(_ context.Context, _ *proto.Empty) (*proto.Empty, error) {
// Responses is a collection of mocked responses to Provision operations. // Responses is a collection of mocked responses to Provision operations.
type Responses struct { type Responses struct {
Parse []*proto.Parse_Response Parse []*proto.Response
// ProvisionApply and ProvisionPlan are used to mock ALL responses of // ProvisionApply and ProvisionPlan are used to mock ALL responses of
// Apply and Plan, regardless of transition. // Apply and Plan, regardless of transition.
ProvisionApply []*proto.Provision_Response ProvisionApply []*proto.Response
ProvisionPlan []*proto.Provision_Response ProvisionPlan []*proto.Response
// ProvisionApplyMap and ProvisionPlanMap are used to mock specific // ProvisionApplyMap and ProvisionPlanMap are used to mock specific
// transition responses. They are prioritized over the generic responses. // transition responses. They are prioritized over the generic responses.
ProvisionApplyMap map[proto.WorkspaceTransition][]*proto.Provision_Response ProvisionApplyMap map[proto.WorkspaceTransition][]*proto.Response
ProvisionPlanMap map[proto.WorkspaceTransition][]*proto.Provision_Response ProvisionPlanMap map[proto.WorkspaceTransition][]*proto.Response
} }
// Tar returns a tar archive of responses to provisioner operations. // Tar returns a tar archive of responses to provisioner operations.
func Tar(responses *Responses) ([]byte, error) { func Tar(responses *Responses) ([]byte, error) {
if responses == nil { if responses == nil {
responses = &Responses{ responses = &Responses{
ParseComplete, ProvisionComplete, ProvisionComplete, ParseComplete, ApplyComplete, PlanComplete,
nil, nil, nil, nil,
} }
} }
if responses.ProvisionPlan == nil { if responses.ProvisionPlan == nil {
responses.ProvisionPlan = responses.ProvisionApply for _, resp := range responses.ProvisionApply {
if resp.GetLog() != nil {
responses.ProvisionPlan = append(responses.ProvisionPlan, resp)
continue
}
responses.ProvisionPlan = append(responses.ProvisionPlan, &proto.Response{
Type: &proto.Response_Plan{Plan: &proto.PlanComplete{
Error: resp.GetApply().GetError(),
Resources: resp.GetApply().GetResources(),
Parameters: resp.GetApply().GetParameters(),
GitAuthProviders: resp.GetApply().GetGitAuthProviders(),
}},
})
}
} }
var buffer bytes.Buffer var buffer bytes.Buffer
@ -245,20 +266,20 @@ func Tar(responses *Responses) ([]byte, error) {
} }
} }
for index, response := range responses.ProvisionApply { for index, response := range responses.ProvisionApply {
err := writeProto(fmt.Sprintf("%d.provision.apply.protobuf", index), response) err := writeProto(fmt.Sprintf("%d.apply.protobuf", index), response)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
for index, response := range responses.ProvisionPlan { for index, response := range responses.ProvisionPlan {
err := writeProto(fmt.Sprintf("%d.provision.plan.protobuf", index), response) err := writeProto(fmt.Sprintf("%d.plan.protobuf", index), response)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
for trans, m := range responses.ProvisionApplyMap { for trans, m := range responses.ProvisionApplyMap {
for i, rs := range m { for i, rs := range m {
err := writeProto(fmt.Sprintf("%d.%s.provision.apply.protobuf", i, strings.ToLower(trans.String())), rs) err := writeProto(fmt.Sprintf("%d.%s.apply.protobuf", i, strings.ToLower(trans.String())), rs)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -266,7 +287,7 @@ func Tar(responses *Responses) ([]byte, error) {
} }
for trans, m := range responses.ProvisionPlanMap { for trans, m := range responses.ProvisionPlanMap {
for i, rs := range m { for i, rs := range m {
err := writeProto(fmt.Sprintf("%d.%s.provision.plan.protobuf", i, strings.ToLower(trans.String())), rs) err := writeProto(fmt.Sprintf("%d.%s.plan.protobuf", i, strings.ToLower(trans.String())), rs)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -279,22 +300,14 @@ func Tar(responses *Responses) ([]byte, error) {
return buffer.Bytes(), nil return buffer.Bytes(), nil
} }
func filterLogResponses(config *proto.Provision_Config, response *proto.Provision_Response) (*proto.Provision_Response, bool) { func WithResources(resources []*proto.Resource) *Responses {
responseLog, ok := response.Type.(*proto.Provision_Response_Log) return &Responses{
if !ok { Parse: ParseComplete,
// Pass all non-log responses ProvisionApply: []*proto.Response{{Type: &proto.Response_Apply{Apply: &proto.ApplyComplete{
return response, true Resources: resources,
}}}},
ProvisionPlan: []*proto.Response{{Type: &proto.Response_Plan{Plan: &proto.PlanComplete{
Resources: resources,
}}}},
} }
if config.ProvisionerLogLevel == "" {
// Don't change the default behavior of "echo"
return response, true
}
provisionerLogLevel := proto.LogLevel_value[strings.ToUpper(config.ProvisionerLogLevel)]
if int32(responseLog.Log.Level) < provisionerLogLevel {
// Log level is not enabled
return nil, false
}
return response, true
} }

View File

@ -1,27 +1,23 @@
package echo_test package echo_test
import ( import (
"archive/tar"
"bytes"
"context" "context"
"io"
"os"
"path/filepath"
"testing" "testing"
"github.com/spf13/afero"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/coder/coder/v2/provisioner/echo" "github.com/coder/coder/v2/provisioner/echo"
"github.com/coder/coder/v2/provisionersdk" "github.com/coder/coder/v2/provisionersdk"
"github.com/coder/coder/v2/provisionersdk/proto" "github.com/coder/coder/v2/provisionersdk/proto"
"github.com/coder/coder/v2/testutil"
) )
func TestEcho(t *testing.T) { func TestEcho(t *testing.T) {
t.Parallel() t.Parallel()
fs := afero.NewMemMapFs() workdir := t.TempDir()
// Create an in-memory provisioner to communicate with. // Create an in-memory provisioner to communicate with.
client, server := provisionersdk.MemTransportPipe() client, server := provisionersdk.MemTransportPipe()
ctx, cancelFunc := context.WithCancel(context.Background()) ctx, cancelFunc := context.WithCancel(context.Background())
@ -31,8 +27,9 @@ func TestEcho(t *testing.T) {
cancelFunc() cancelFunc()
}) })
go func() { go func() {
err := echo.Serve(ctx, fs, &provisionersdk.ServeOptions{ err := echo.Serve(ctx, &provisionersdk.ServeOptions{
Listener: server, Listener: server,
WorkDirectory: workdir,
}) })
assert.NoError(t, err) assert.NoError(t, err)
}() }()
@ -40,25 +37,39 @@ func TestEcho(t *testing.T) {
t.Run("Parse", func(t *testing.T) { t.Run("Parse", func(t *testing.T) {
t.Parallel() t.Parallel()
ctx, cancel := context.WithTimeout(ctx, testutil.WaitShort)
defer cancel()
responses := []*proto.Parse_Response{{ responses := []*proto.Response{
Type: &proto.Parse_Response_Log{ {
Log: &proto.Log{ Type: &proto.Response_Log{
Output: "log-output", Log: &proto.Log{
Output: "log-output",
},
}, },
}, },
}, { {
Type: &proto.Parse_Response_Complete{ Type: &proto.Response_Parse{
Complete: &proto.Parse_Complete{}, Parse: &proto.ParseComplete{},
},
}, },
}} }
data, err := echo.Tar(&echo.Responses{ data, err := echo.Tar(&echo.Responses{
Parse: responses, Parse: responses,
}) })
require.NoError(t, err) require.NoError(t, err)
client, err := api.Parse(ctx, &proto.Parse_Request{ client, err := api.Session(ctx)
Directory: unpackTar(t, fs, data), require.NoError(t, err)
}) defer func() {
err := client.Close()
require.NoError(t, err)
}()
err = client.Send(&proto.Request{Type: &proto.Request_Config{Config: &proto.Config{
TemplateSourceArchive: data,
}}})
require.NoError(t, err)
err = client.Send(&proto.Request{Type: &proto.Request_Parse{Parse: &proto.ParseRequest{}}})
require.NoError(t, err) require.NoError(t, err)
log, err := client.Recv() log, err := client.Recv()
require.NoError(t, err) require.NoError(t, err)
@ -70,95 +81,117 @@ func TestEcho(t *testing.T) {
t.Run("Provision", func(t *testing.T) { t.Run("Provision", func(t *testing.T) {
t.Parallel() t.Parallel()
ctx, cancel := context.WithTimeout(ctx, testutil.WaitShort)
defer cancel()
responses := []*proto.Provision_Response{{ planResponses := []*proto.Response{
Type: &proto.Provision_Response_Log{ {
Log: &proto.Log{ Type: &proto.Response_Log{
Level: proto.LogLevel_INFO, Log: &proto.Log{
Output: "log-output", Level: proto.LogLevel_INFO,
}, Output: "log-output",
},
}, {
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{
Resources: []*proto.Resource{{
Name: "resource",
}},
},
},
}}
data, err := echo.Tar(&echo.Responses{
ProvisionApply: responses,
})
require.NoError(t, err)
client, err := api.Provision(ctx)
require.NoError(t, err)
err = client.Send(&proto.Provision_Request{
Type: &proto.Provision_Request_Plan{
Plan: &proto.Provision_Plan{
Config: &proto.Provision_Config{
Directory: unpackTar(t, fs, data),
}, },
}, },
}, },
{
Type: &proto.Response_Plan{
Plan: &proto.PlanComplete{
Resources: []*proto.Resource{{
Name: "resource",
}},
},
},
},
}
applyResponses := []*proto.Response{
{
Type: &proto.Response_Log{
Log: &proto.Log{
Level: proto.LogLevel_INFO,
Output: "log-output",
},
},
},
{
Type: &proto.Response_Apply{
Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{{
Name: "resource",
}},
},
},
},
}
data, err := echo.Tar(&echo.Responses{
ProvisionPlan: planResponses,
ProvisionApply: applyResponses,
}) })
require.NoError(t, err) require.NoError(t, err)
client, err := api.Session(ctx)
require.NoError(t, err)
defer func() {
err := client.Close()
require.NoError(t, err)
}()
err = client.Send(&proto.Request{Type: &proto.Request_Config{Config: &proto.Config{
TemplateSourceArchive: data,
}}})
require.NoError(t, err)
err = client.Send(&proto.Request{Type: &proto.Request_Plan{Plan: &proto.PlanRequest{}}})
require.NoError(t, err)
log, err := client.Recv() log, err := client.Recv()
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, responses[0].GetLog().Output, log.GetLog().Output) require.Equal(t, planResponses[0].GetLog().Output, log.GetLog().Output)
complete, err := client.Recv() complete, err := client.Recv()
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, responses[1].GetComplete().Resources[0].Name, require.Equal(t, planResponses[1].GetPlan().Resources[0].Name,
complete.GetComplete().Resources[0].Name) complete.GetPlan().Resources[0].Name)
err = client.Send(&proto.Request{Type: &proto.Request_Apply{Apply: &proto.ApplyRequest{}}})
require.NoError(t, err)
log, err = client.Recv()
require.NoError(t, err)
require.Equal(t, applyResponses[0].GetLog().Output, log.GetLog().Output)
complete, err = client.Recv()
require.NoError(t, err)
require.Equal(t, applyResponses[1].GetApply().Resources[0].Name,
complete.GetApply().Resources[0].Name)
}) })
t.Run("ProvisionStop", func(t *testing.T) { t.Run("ProvisionStop", func(t *testing.T) {
t.Parallel() t.Parallel()
// Stop responses should be returned when the workspace is being stopped. // Stop responses should be returned when the workspace is being stopped.
defaultResponses := []*proto.Provision_Response{{
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{
Resources: []*proto.Resource{{
Name: "DEFAULT",
}},
},
},
}}
stopResponses := []*proto.Provision_Response{{
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{
Resources: []*proto.Resource{{
Name: "STOP",
}},
},
},
}}
data, err := echo.Tar(&echo.Responses{ data, err := echo.Tar(&echo.Responses{
ProvisionApply: defaultResponses, ProvisionApply: applyCompleteResource("DEFAULT"),
ProvisionPlan: defaultResponses, ProvisionPlan: planCompleteResource("DEFAULT"),
ProvisionPlanMap: map[proto.WorkspaceTransition][]*proto.Provision_Response{ ProvisionPlanMap: map[proto.WorkspaceTransition][]*proto.Response{
proto.WorkspaceTransition_STOP: stopResponses, proto.WorkspaceTransition_STOP: planCompleteResource("STOP"),
}, },
ProvisionApplyMap: map[proto.WorkspaceTransition][]*proto.Provision_Response{ ProvisionApplyMap: map[proto.WorkspaceTransition][]*proto.Response{
proto.WorkspaceTransition_STOP: stopResponses, proto.WorkspaceTransition_STOP: applyCompleteResource("STOP"),
}, },
}) })
require.NoError(t, err) require.NoError(t, err)
client, err := api.Provision(ctx) client, err := api.Session(ctx)
require.NoError(t, err)
defer func() {
err := client.Close()
require.NoError(t, err)
}()
err = client.Send(&proto.Request{Type: &proto.Request_Config{Config: &proto.Config{
TemplateSourceArchive: data,
}}})
require.NoError(t, err) require.NoError(t, err)
// Do stop. // Do stop.
err = client.Send(&proto.Provision_Request{ err = client.Send(&proto.Request{
Type: &proto.Provision_Request_Plan{ Type: &proto.Request_Plan{
Plan: &proto.Provision_Plan{ Plan: &proto.PlanRequest{
Config: &proto.Provision_Config{ Metadata: &proto.Metadata{
Directory: unpackTar(t, fs, data), WorkspaceTransition: proto.WorkspaceTransition_STOP,
Metadata: &proto.Provision_Metadata{
WorkspaceTransition: proto.WorkspaceTransition_STOP,
},
}, },
}, },
}, },
@ -168,22 +201,16 @@ func TestEcho(t *testing.T) {
complete, err := client.Recv() complete, err := client.Recv()
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, require.Equal(t,
stopResponses[0].GetComplete().Resources[0].Name, "STOP",
complete.GetComplete().Resources[0].Name, complete.GetPlan().Resources[0].Name,
) )
// Do start. // Do start.
client, err = api.Provision(ctx) err = client.Send(&proto.Request{
require.NoError(t, err) Type: &proto.Request_Plan{
Plan: &proto.PlanRequest{
err = client.Send(&proto.Provision_Request{ Metadata: &proto.Metadata{
Type: &proto.Provision_Request_Plan{ WorkspaceTransition: proto.WorkspaceTransition_START,
Plan: &proto.Provision_Plan{
Config: &proto.Provision_Config{
Directory: unpackTar(t, fs, data),
Metadata: &proto.Provision_Metadata{
WorkspaceTransition: proto.WorkspaceTransition_START,
},
}, },
}, },
}, },
@ -193,31 +220,33 @@ func TestEcho(t *testing.T) {
complete, err = client.Recv() complete, err = client.Recv()
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, require.Equal(t,
defaultResponses[0].GetComplete().Resources[0].Name, "DEFAULT",
complete.GetComplete().Resources[0].Name, complete.GetPlan().Resources[0].Name,
) )
}) })
t.Run("ProvisionWithLogLevel", func(t *testing.T) { t.Run("ProvisionWithLogLevel", func(t *testing.T) {
t.Parallel() t.Parallel()
ctx, cancel := context.WithTimeout(ctx, testutil.WaitShort)
defer cancel()
responses := []*proto.Provision_Response{{ responses := []*proto.Response{{
Type: &proto.Provision_Response_Log{ Type: &proto.Response_Log{
Log: &proto.Log{ Log: &proto.Log{
Level: proto.LogLevel_TRACE, Level: proto.LogLevel_TRACE,
Output: "log-output-trace", Output: "log-output-trace",
}, },
}, },
}, { }, {
Type: &proto.Provision_Response_Log{ Type: &proto.Response_Log{
Log: &proto.Log{ Log: &proto.Log{
Level: proto.LogLevel_INFO, Level: proto.LogLevel_INFO,
Output: "log-output-info", Output: "log-output-info",
}, },
}, },
}, { }, {
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{ Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{{ Resources: []*proto.Resource{{
Name: "resource", Name: "resource",
}}, }},
@ -225,49 +254,62 @@ func TestEcho(t *testing.T) {
}, },
}} }}
data, err := echo.Tar(&echo.Responses{ data, err := echo.Tar(&echo.Responses{
ProvisionPlan: echo.PlanComplete,
ProvisionApply: responses, ProvisionApply: responses,
}) })
require.NoError(t, err) require.NoError(t, err)
client, err := api.Provision(ctx) client, err := api.Session(ctx)
require.NoError(t, err) require.NoError(t, err)
err = client.Send(&proto.Provision_Request{ defer func() {
Type: &proto.Provision_Request_Plan{ err := client.Close()
Plan: &proto.Provision_Plan{ require.NoError(t, err)
Config: &proto.Provision_Config{ }()
Directory: unpackTar(t, fs, data), err = client.Send(&proto.Request{Type: &proto.Request_Config{Config: &proto.Config{
ProvisionerLogLevel: "debug", TemplateSourceArchive: data,
}, ProvisionerLogLevel: "debug",
}, }}})
}, require.NoError(t, err)
})
// Plan is required before apply
err = client.Send(&proto.Request{Type: &proto.Request_Plan{Plan: &proto.PlanRequest{}}})
require.NoError(t, err)
complete, err := client.Recv()
require.NoError(t, err)
require.NotNil(t, complete.GetPlan())
err = client.Send(&proto.Request{Type: &proto.Request_Apply{Apply: &proto.ApplyRequest{}}})
require.NoError(t, err) require.NoError(t, err)
log, err := client.Recv() log, err := client.Recv()
require.NoError(t, err) require.NoError(t, err)
// Skip responses[0] as it's trace level // Skip responses[0] as it's trace level
require.Equal(t, responses[1].GetLog().Output, log.GetLog().Output) require.Equal(t, responses[1].GetLog().Output, log.GetLog().Output)
complete, err := client.Recv() complete, err = client.Recv()
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, responses[2].GetComplete().Resources[0].Name, require.Equal(t, responses[2].GetApply().Resources[0].Name,
complete.GetComplete().Resources[0].Name) complete.GetApply().Resources[0].Name)
}) })
} }
func unpackTar(t *testing.T, fs afero.Fs, data []byte) string { func planCompleteResource(name string) []*proto.Response {
directory := t.TempDir() return []*proto.Response{{
reader := tar.NewReader(bytes.NewReader(data)) Type: &proto.Response_Plan{
for { Plan: &proto.PlanComplete{
header, err := reader.Next() Resources: []*proto.Resource{{
if err != nil { Name: name,
break }},
} },
// #nosec },
path := filepath.Join(directory, header.Name) }}
file, err := fs.OpenFile(path, os.O_CREATE|os.O_RDWR, 0o600) }
require.NoError(t, err)
_, err = io.CopyN(file, reader, 1<<20) func applyCompleteResource(name string) []*proto.Response {
require.ErrorIs(t, err, io.EOF) return []*proto.Response{{
err = file.Close() Type: &proto.Response_Apply{
require.NoError(t, err) Apply: &proto.ApplyComplete{
} Resources: []*proto.Resource{{
return directory Name: name,
}},
},
},
}}
} }

View File

@ -25,6 +25,7 @@ import (
) )
type executor struct { type executor struct {
logger slog.Logger
server *server server *server
mut *sync.Mutex mut *sync.Mutex
binaryPath string binaryPath string
@ -50,8 +51,10 @@ func (e *executor) execWriteOutput(ctx, killCtx context.Context, args, env []str
ctx, span := e.server.startTrace(ctx, fmt.Sprintf("exec - terraform %s", args[0])) ctx, span := e.server.startTrace(ctx, fmt.Sprintf("exec - terraform %s", args[0]))
defer span.End() defer span.End()
span.SetAttributes(attribute.StringSlice("args", args)) span.SetAttributes(attribute.StringSlice("args", args))
e.logger.Debug(ctx, "starting command", slog.F("args", args))
defer func() { defer func() {
e.logger.Debug(ctx, "closing writers", slog.Error(err))
closeErr := stdOutWriter.Close() closeErr := stdOutWriter.Close()
if err == nil && closeErr != nil { if err == nil && closeErr != nil {
err = closeErr err = closeErr
@ -62,6 +65,7 @@ func (e *executor) execWriteOutput(ctx, killCtx context.Context, args, env []str
} }
}() }()
if ctx.Err() != nil { if ctx.Err() != nil {
e.logger.Debug(ctx, "context canceled before command started", slog.F("args", args))
return ctx.Err() return ctx.Err()
} }
@ -90,11 +94,14 @@ func (e *executor) execWriteOutput(ctx, killCtx context.Context, args, env []str
) )
err = cmd.Start() err = cmd.Start()
if err != nil { if err != nil {
e.logger.Debug(ctx, "failed to start command", slog.F("args", args))
return err return err
} }
interruptCommandOnCancel(ctx, killCtx, cmd) interruptCommandOnCancel(ctx, killCtx, e.logger, cmd)
return cmd.Wait() err = cmd.Wait()
e.logger.Debug(ctx, "command done", slog.F("args", args), slog.Error(err))
return err
} }
// execParseJSON must only be called while the lock is held. // execParseJSON must only be called while the lock is held.
@ -120,7 +127,7 @@ func (e *executor) execParseJSON(ctx, killCtx context.Context, args, env []strin
if err != nil { if err != nil {
return err return err
} }
interruptCommandOnCancel(ctx, killCtx, cmd) interruptCommandOnCancel(ctx, killCtx, e.logger, cmd)
err = cmd.Wait() err = cmd.Wait()
if err != nil { if err != nil {
@ -207,15 +214,23 @@ func (e *executor) init(ctx, killCtx context.Context, logr logSink) error {
return e.execWriteOutput(ctx, killCtx, args, e.basicEnv(), outWriter, errWriter) return e.execWriteOutput(ctx, killCtx, args, e.basicEnv(), outWriter, errWriter)
} }
func getPlanFilePath(workdir string) string {
return filepath.Join(workdir, "terraform.tfplan")
}
func getStateFilePath(workdir string) string {
return filepath.Join(workdir, "terraform.tfstate")
}
// revive:disable-next-line:flag-parameter // revive:disable-next-line:flag-parameter
func (e *executor) plan(ctx, killCtx context.Context, env, vars []string, logr logSink, destroy bool) (*proto.Provision_Response, error) { func (e *executor) plan(ctx, killCtx context.Context, env, vars []string, logr logSink, destroy bool) (*proto.PlanComplete, error) {
ctx, span := e.server.startTrace(ctx, tracing.FuncName()) ctx, span := e.server.startTrace(ctx, tracing.FuncName())
defer span.End() defer span.End()
e.mut.Lock() e.mut.Lock()
defer e.mut.Unlock() defer e.mut.Unlock()
planfilePath := filepath.Join(e.workdir, "terraform.tfplan") planfilePath := getPlanFilePath(e.workdir)
args := []string{ args := []string{
"plan", "plan",
"-no-color", "-no-color",
@ -248,19 +263,10 @@ func (e *executor) plan(ctx, killCtx context.Context, env, vars []string, logr l
if err != nil { if err != nil {
return nil, err return nil, err
} }
planFileByt, err := os.ReadFile(planfilePath) return &proto.PlanComplete{
if err != nil { Parameters: state.Parameters,
return nil, err Resources: state.Resources,
} GitAuthProviders: state.GitAuthProviders,
return &proto.Provision_Response{
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{
Parameters: state.Parameters,
Resources: state.Resources,
GitAuthProviders: state.GitAuthProviders,
Plan: planFileByt,
},
},
}, nil }, nil
} }
@ -346,7 +352,7 @@ func (e *executor) graph(ctx, killCtx context.Context) (string, error) {
if err != nil { if err != nil {
return "", err return "", err
} }
interruptCommandOnCancel(ctx, killCtx, cmd) interruptCommandOnCancel(ctx, killCtx, e.logger, cmd)
err = cmd.Wait() err = cmd.Wait()
if err != nil { if err != nil {
@ -357,33 +363,22 @@ func (e *executor) graph(ctx, killCtx context.Context) (string, error) {
func (e *executor) apply( func (e *executor) apply(
ctx, killCtx context.Context, ctx, killCtx context.Context,
plan []byte,
env []string, env []string,
logr logSink, logr logSink,
) (*proto.Provision_Response, error) { ) (*proto.ApplyComplete, error) {
ctx, span := e.server.startTrace(ctx, tracing.FuncName()) ctx, span := e.server.startTrace(ctx, tracing.FuncName())
defer span.End() defer span.End()
e.mut.Lock() e.mut.Lock()
defer e.mut.Unlock() defer e.mut.Unlock()
planFile, err := os.CreateTemp("", "coder-terrafrom-plan")
if err != nil {
return nil, xerrors.Errorf("create plan file: %w", err)
}
_, err = planFile.Write(plan)
if err != nil {
return nil, xerrors.Errorf("write plan file: %w", err)
}
defer os.Remove(planFile.Name())
args := []string{ args := []string{
"apply", "apply",
"-no-color", "-no-color",
"-auto-approve", "-auto-approve",
"-input=false", "-input=false",
"-json", "-json",
planFile.Name(), getPlanFilePath(e.workdir),
} }
outWriter, doneOut := provisionLogWriter(logr) outWriter, doneOut := provisionLogWriter(logr)
@ -395,7 +390,7 @@ func (e *executor) apply(
<-doneErr <-doneErr
}() }()
err = e.execWriteOutput(ctx, killCtx, args, env, outWriter, errWriter) err := e.execWriteOutput(ctx, killCtx, args, env, outWriter, errWriter)
if err != nil { if err != nil {
return nil, xerrors.Errorf("terraform apply: %w", err) return nil, xerrors.Errorf("terraform apply: %w", err)
} }
@ -408,15 +403,11 @@ func (e *executor) apply(
if err != nil { if err != nil {
return nil, xerrors.Errorf("read statefile %q: %w", statefilePath, err) return nil, xerrors.Errorf("read statefile %q: %w", statefilePath, err)
} }
return &proto.Provision_Response{ return &proto.ApplyComplete{
Type: &proto.Provision_Response_Complete{ Parameters: state.Parameters,
Complete: &proto.Provision_Complete{ Resources: state.Resources,
Parameters: state.Parameters, GitAuthProviders: state.GitAuthProviders,
Resources: state.Resources, State: stateContent,
GitAuthProviders: state.GitAuthProviders,
State: stateContent,
},
},
}, nil }, nil
} }
@ -461,48 +452,28 @@ func (e *executor) state(ctx, killCtx context.Context) (*tfjson.State, error) {
return state, nil return state, nil
} }
func interruptCommandOnCancel(ctx, killCtx context.Context, cmd *exec.Cmd) { func interruptCommandOnCancel(ctx, killCtx context.Context, logger slog.Logger, cmd *exec.Cmd) {
go func() { go func() {
select { select {
case <-ctx.Done(): case <-ctx.Done():
var err error
switch runtime.GOOS { switch runtime.GOOS {
case "windows": case "windows":
// Interrupts aren't supported by Windows. // Interrupts aren't supported by Windows.
_ = cmd.Process.Kill() err = cmd.Process.Kill()
default: default:
_ = cmd.Process.Signal(os.Interrupt) err = cmd.Process.Signal(os.Interrupt)
} }
logger.Debug(ctx, "interrupted command", slog.F("args", cmd.Args), slog.Error(err))
case <-killCtx.Done(): case <-killCtx.Done():
logger.Debug(ctx, "kill context ended", slog.F("args", cmd.Args))
} }
}() }()
} }
type logSink interface { type logSink interface {
Log(*proto.Log) ProvisionLog(l proto.LogLevel, o string)
}
type streamLogSink struct {
// Any errors writing to the stream will be logged to logger.
logger slog.Logger
stream proto.DRPCProvisioner_ProvisionStream
}
var _ logSink = streamLogSink{}
func (s streamLogSink) Log(l *proto.Log) {
err := s.stream.Send(&proto.Provision_Response{
Type: &proto.Provision_Response_Log{
Log: l,
},
})
if err != nil {
s.logger.Warn(context.Background(), "write log to stream",
slog.F("level", l.Level.String()),
slog.F("message", l.Output),
slog.Error(err),
)
}
} }
// logWriter creates a WriteCloser that will log each line of text at the given level. The WriteCloser must be closed // logWriter creates a WriteCloser that will log each line of text at the given level. The WriteCloser must be closed
@ -526,7 +497,7 @@ func readAndLog(sink logSink, r io.Reader, done chan<- any, level proto.LogLevel
continue continue
} }
sink.Log(&proto.Log{Level: level, Output: scanner.Text()}) sink.ProvisionLog(level, scanner.Text())
continue continue
} }
@ -543,7 +514,7 @@ func readAndLog(sink logSink, r io.Reader, done chan<- any, level proto.LogLevel
if logLevel == proto.LogLevel_INFO { if logLevel == proto.LogLevel_INFO {
logLevel = proto.LogLevel_DEBUG logLevel = proto.LogLevel_DEBUG
} }
sink.Log(&proto.Log{Level: logLevel, Output: log.Message}) sink.ProvisionLog(logLevel, log.Message)
} }
} }
@ -588,7 +559,7 @@ func provisionReadAndLog(sink logSink, r io.Reader, done chan<- any) {
} }
logLevel := convertTerraformLogLevel(log.Level, sink) logLevel := convertTerraformLogLevel(log.Level, sink)
sink.Log(&proto.Log{Level: logLevel, Output: log.Message}) sink.ProvisionLog(logLevel, log.Message)
// If the diagnostic is provided, let's provide a bit more info! // If the diagnostic is provided, let's provide a bit more info!
if log.Diagnostic == nil { if log.Diagnostic == nil {
@ -596,7 +567,7 @@ func provisionReadAndLog(sink logSink, r io.Reader, done chan<- any) {
} }
logLevel = convertTerraformLogLevel(string(log.Diagnostic.Severity), sink) logLevel = convertTerraformLogLevel(string(log.Diagnostic.Severity), sink)
for _, diagLine := range strings.Split(FormatDiagnostic(log.Diagnostic), "\n") { for _, diagLine := range strings.Split(FormatDiagnostic(log.Diagnostic), "\n") {
sink.Log(&proto.Log{Level: logLevel, Output: diagLine}) sink.ProvisionLog(logLevel, diagLine)
} }
} }
} }
@ -614,10 +585,7 @@ func convertTerraformLogLevel(logLevel string, sink logSink) proto.LogLevel {
case "error": case "error":
return proto.LogLevel_ERROR return proto.LogLevel_ERROR
default: default:
sink.Log(&proto.Log{ sink.ProvisionLog(proto.LogLevel_WARN, fmt.Sprintf("unable to convert log level %s", logLevel))
Level: proto.LogLevel_WARN,
Output: fmt.Sprintf("unable to convert log level %s", logLevel),
})
return proto.LogLevel_INFO return proto.LogLevel_INFO
} }
} }

View File

@ -16,8 +16,8 @@ type mockLogger struct {
var _ logSink = &mockLogger{} var _ logSink = &mockLogger{}
func (m *mockLogger) Log(l *proto.Log) { func (m *mockLogger) ProvisionLog(l proto.LogLevel, o string) {
m.logs = append(m.logs, l) m.logs = append(m.logs, &proto.Log{Level: l, Output: o})
} }
func TestLogWriter_Mainline(t *testing.T) { func TestLogWriter_Mainline(t *testing.T) {

View File

@ -12,18 +12,20 @@ import (
"golang.org/x/xerrors" "golang.org/x/xerrors"
"github.com/coder/coder/v2/coderd/tracing" "github.com/coder/coder/v2/coderd/tracing"
"github.com/coder/coder/v2/provisionersdk"
"github.com/coder/coder/v2/provisionersdk/proto" "github.com/coder/coder/v2/provisionersdk/proto"
) )
// Parse extracts Terraform variables from source-code. // Parse extracts Terraform variables from source-code.
func (s *server) Parse(request *proto.Parse_Request, stream proto.DRPCProvisioner_ParseStream) error { func (s *server) Parse(sess *provisionersdk.Session, _ *proto.ParseRequest, _ <-chan struct{}) *proto.ParseComplete {
_, span := s.startTrace(stream.Context(), tracing.FuncName()) ctx := sess.Context()
_, span := s.startTrace(ctx, tracing.FuncName())
defer span.End() defer span.End()
// Load the module and print any parse errors. // Load the module and print any parse errors.
module, diags := tfconfig.LoadModule(request.Directory) module, diags := tfconfig.LoadModule(sess.WorkDirectory)
if diags.HasErrors() { if diags.HasErrors() {
return xerrors.Errorf("load module: %s", formatDiagnostics(request.Directory, diags)) return provisionersdk.ParseErrorf("load module: %s", formatDiagnostics(sess.WorkDirectory, diags))
} }
// Sort variables by (filename, line) to make the ordering consistent // Sort variables by (filename, line) to make the ordering consistent
@ -40,17 +42,13 @@ func (s *server) Parse(request *proto.Parse_Request, stream proto.DRPCProvisione
for _, v := range variables { for _, v := range variables {
mv, err := convertTerraformVariable(v) mv, err := convertTerraformVariable(v)
if err != nil { if err != nil {
return xerrors.Errorf("can't convert the Terraform variable to a managed one: %w", err) return provisionersdk.ParseErrorf("can't convert the Terraform variable to a managed one: %s", err)
} }
templateVariables = append(templateVariables, mv) templateVariables = append(templateVariables, mv)
} }
return stream.Send(&proto.Parse_Response{ return &proto.ParseComplete{
Type: &proto.Parse_Response_Complete{ TemplateVariables: templateVariables,
Complete: &proto.Parse_Complete{ }
TemplateVariables: templateVariables,
},
},
})
} }
// Converts a Terraform variable to a template-wide variable, processed by Coder. // Converts a Terraform variable to a template-wide variable, processed by Coder.

View File

@ -4,8 +4,6 @@ package terraform_test
import ( import (
"encoding/json" "encoding/json"
"os"
"path/filepath"
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -21,9 +19,8 @@ func TestParse(t *testing.T) {
testCases := []struct { testCases := []struct {
Name string Name string
Files map[string]string Files map[string]string
Response *proto.Parse_Response Response *proto.ParseComplete
// If ErrorContains is not empty, then response.Recv() should return an // If ErrorContains is not empty, then the ParseComplete should have an Error containing the given string
// error containing this string before a Complete response is returned.
ErrorContains string ErrorContains string
}{ }{
{ {
@ -33,16 +30,12 @@ func TestParse(t *testing.T) {
description = "Testing!" description = "Testing!"
}`, }`,
}, },
Response: &proto.Parse_Response{ Response: &proto.ParseComplete{
Type: &proto.Parse_Response_Complete{ TemplateVariables: []*proto.TemplateVariable{
Complete: &proto.Parse_Complete{ {
TemplateVariables: []*proto.TemplateVariable{ Name: "A",
{ Description: "Testing!",
Name: "A", Required: true,
Description: "Testing!",
Required: true,
},
},
}, },
}, },
}, },
@ -54,15 +47,11 @@ func TestParse(t *testing.T) {
default = "wow" default = "wow"
}`, }`,
}, },
Response: &proto.Parse_Response{ Response: &proto.ParseComplete{
Type: &proto.Parse_Response_Complete{ TemplateVariables: []*proto.TemplateVariable{
Complete: &proto.Parse_Complete{ {
TemplateVariables: []*proto.TemplateVariable{ Name: "A",
{ DefaultValue: "wow",
Name: "A",
DefaultValue: "wow",
},
},
}, },
}, },
}, },
@ -76,15 +65,11 @@ func TestParse(t *testing.T) {
} }
}`, }`,
}, },
Response: &proto.Parse_Response{ Response: &proto.ParseComplete{
Type: &proto.Parse_Response_Complete{ TemplateVariables: []*proto.TemplateVariable{
Complete: &proto.Parse_Complete{ {
TemplateVariables: []*proto.TemplateVariable{ Name: "A",
{ Required: true,
Name: "A",
Required: true,
},
},
}, },
}, },
}, },
@ -104,27 +89,23 @@ func TestParse(t *testing.T) {
"main2.tf": `variable "baz" { } "main2.tf": `variable "baz" { }
variable "quux" { }`, variable "quux" { }`,
}, },
Response: &proto.Parse_Response{ Response: &proto.ParseComplete{
Type: &proto.Parse_Response_Complete{ TemplateVariables: []*proto.TemplateVariable{
Complete: &proto.Parse_Complete{ {
TemplateVariables: []*proto.TemplateVariable{ Name: "foo",
{ Required: true,
Name: "foo", },
Required: true, {
}, Name: "bar",
{ Required: true,
Name: "bar", },
Required: true, {
}, Name: "baz",
{ Required: true,
Name: "baz", },
Required: true, {
}, Name: "quux",
{ Required: true,
Name: "quux",
Required: true,
},
},
}, },
}, },
}, },
@ -139,19 +120,15 @@ func TestParse(t *testing.T) {
sensitive = true sensitive = true
}`, }`,
}, },
Response: &proto.Parse_Response{ Response: &proto.ParseComplete{
Type: &proto.Parse_Response_Complete{ TemplateVariables: []*proto.TemplateVariable{
Complete: &proto.Parse_Complete{ {
TemplateVariables: []*proto.TemplateVariable{ Name: "A",
{ Description: "Testing!",
Name: "A", Type: "bool",
Description: "Testing!", DefaultValue: "true",
Type: "bool", Required: false,
DefaultValue: "true", Sensitive: true,
Required: false,
Sensitive: true,
},
},
}, },
}, },
}, },
@ -166,19 +143,15 @@ func TestParse(t *testing.T) {
sensitive = true sensitive = true
}`, }`,
}, },
Response: &proto.Parse_Response{ Response: &proto.ParseComplete{
Type: &proto.Parse_Response_Complete{ TemplateVariables: []*proto.TemplateVariable{
Complete: &proto.Parse_Complete{ {
TemplateVariables: []*proto.TemplateVariable{ Name: "A",
{ Description: "Testing!",
Name: "A", Type: "string",
Description: "Testing!", DefaultValue: "abc",
Type: "string", Required: false,
DefaultValue: "abc", Sensitive: true,
Required: false,
Sensitive: true,
},
},
}, },
}, },
}, },
@ -193,19 +166,15 @@ func TestParse(t *testing.T) {
sensitive = true sensitive = true
}`, }`,
}, },
Response: &proto.Parse_Response{ Response: &proto.ParseComplete{
Type: &proto.Parse_Response_Complete{ TemplateVariables: []*proto.TemplateVariable{
Complete: &proto.Parse_Complete{ {
TemplateVariables: []*proto.TemplateVariable{ Name: "A",
{ Description: "Testing!",
Name: "A", Type: "string",
Description: "Testing!", DefaultValue: "",
Type: "string", Required: false,
DefaultValue: "", Sensitive: true,
Required: false,
Sensitive: true,
},
},
}, },
}, },
}, },
@ -219,19 +188,15 @@ func TestParse(t *testing.T) {
sensitive = true sensitive = true
}`, }`,
}, },
Response: &proto.Parse_Response{ Response: &proto.ParseComplete{
Type: &proto.Parse_Response_Complete{ TemplateVariables: []*proto.TemplateVariable{
Complete: &proto.Parse_Complete{ {
TemplateVariables: []*proto.TemplateVariable{ Name: "A",
{ Description: "Testing!",
Name: "A", Type: "string",
Description: "Testing!", DefaultValue: "",
Type: "string", Required: true,
DefaultValue: "", Sensitive: true,
Required: true,
Sensitive: true,
},
},
}, },
}, },
}, },
@ -243,40 +208,31 @@ func TestParse(t *testing.T) {
t.Run(testCase.Name, func(t *testing.T) { t.Run(testCase.Name, func(t *testing.T) {
t.Parallel() t.Parallel()
// Write all files to the temporary test directory. session := configure(ctx, t, api, &proto.Config{
directory := t.TempDir() TemplateSourceArchive: makeTar(t, testCase.Files),
for path, content := range testCase.Files {
err := os.WriteFile(filepath.Join(directory, path), []byte(content), 0o600)
require.NoError(t, err)
}
response, err := api.Parse(ctx, &proto.Parse_Request{
Directory: directory,
}) })
err := session.Send(&proto.Request{Type: &proto.Request_Parse{Parse: &proto.ParseRequest{}}})
require.NoError(t, err) require.NoError(t, err)
for { for {
msg, err := response.Recv() msg, err := session.Recv()
if err != nil { require.NoError(t, err)
if testCase.ErrorContains != "" {
require.ErrorContains(t, err, testCase.ErrorContains)
break
}
require.NoError(t, err)
}
if msg.GetComplete() == nil {
continue
}
if testCase.ErrorContains != "" { if testCase.ErrorContains != "" {
t.Fatal("expected error but job completed successfully") require.Contains(t, msg.GetParse().GetError(), testCase.ErrorContains)
break
}
// Ignore logs in this test
if msg.GetLog() != nil {
continue
} }
// Ensure the want and got are equivalent! // Ensure the want and got are equivalent!
want, err := json.Marshal(testCase.Response) want, err := json.Marshal(testCase.Response)
require.NoError(t, err) require.NoError(t, err)
got, err := json.Marshal(msg) got, err := json.Marshal(msg.GetParse())
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, string(want), string(got)) require.Equal(t, string(want), string(got))

View File

@ -4,11 +4,10 @@ import (
"context" "context"
"fmt" "fmt"
"os" "os"
"path/filepath"
"strings" "strings"
"time" "time"
"golang.org/x/xerrors" "cdr.dev/slog"
"github.com/coder/coder/v2/coderd/tracing" "github.com/coder/coder/v2/coderd/tracing"
"github.com/coder/coder/v2/provisionersdk" "github.com/coder/coder/v2/provisionersdk"
@ -16,48 +15,23 @@ import (
"github.com/coder/terraform-provider-coder/provider" "github.com/coder/terraform-provider-coder/provider"
) )
// Provision executes `terraform apply` or `terraform plan` for dry runs. func (s *server) setupContexts(parent context.Context, canceledOrComplete <-chan struct{}) (
func (s *server) Provision(stream proto.DRPCProvisioner_ProvisionStream) error { ctx context.Context, cancel func(), killCtx context.Context, kill func(),
ctx, span := s.startTrace(stream.Context(), tracing.FuncName()) ) {
defer span.End() // Create a context for graceful cancellation bound to the session
request, err := stream.Recv()
if err != nil {
return err
}
if request.GetCancel() != nil {
return nil
}
var (
applyRequest = request.GetApply()
planRequest = request.GetPlan()
)
var config *proto.Provision_Config
if applyRequest == nil && planRequest == nil {
return nil
} else if applyRequest != nil {
config = applyRequest.Config
} else if planRequest != nil {
config = planRequest.Config
}
// Create a context for graceful cancellation bound to the stream
// context. This ensures that we will perform graceful cancellation // context. This ensures that we will perform graceful cancellation
// even on connection loss. // even on connection loss.
ctx, cancel := context.WithCancel(ctx) ctx, cancel = context.WithCancel(parent)
defer cancel()
// Create a separate context for forceful cancellation not tied to // Create a separate context for forceful cancellation not tied to
// the stream so that we can control when to terminate the process. // the stream so that we can control when to terminate the process.
killCtx, kill := context.WithCancel(context.Background()) killCtx, kill = context.WithCancel(context.Background())
defer kill()
// Ensure processes are eventually cleaned up on graceful // Ensure processes are eventually cleaned up on graceful
// cancellation or disconnect. // cancellation or disconnect.
go func() { go func() {
<-ctx.Done() <-ctx.Done()
s.logger.Debug(ctx, "graceful context done")
// TODO(mafredri): We should track this provision request as // TODO(mafredri): We should track this provision request as
// part of graceful server shutdown procedure. Waiting on a // part of graceful server shutdown procedure. Waiting on a
@ -66,134 +40,131 @@ func (s *server) Provision(stream proto.DRPCProvisioner_ProvisionStream) error {
defer t.Stop() defer t.Stop()
select { select {
case <-t.C: case <-t.C:
s.logger.Debug(ctx, "exit timeout hit")
kill() kill()
case <-killCtx.Done(): case <-killCtx.Done():
s.logger.Debug(ctx, "kill context done")
} }
}() }()
// Process cancel
go func() { go func() {
for { <-canceledOrComplete
request, err := stream.Recv() s.logger.Debug(ctx, "canceledOrComplete closed")
if err != nil { cancel()
return
}
if request.GetCancel() == nil {
// We only process cancellation requests here.
continue
}
cancel()
return
}
}() }()
return ctx, cancel, killCtx, kill
}
sink := streamLogSink{ func (s *server) Plan(
logger: s.logger.Named("execution_logs"), sess *provisionersdk.Session, request *proto.PlanRequest, canceledOrComplete <-chan struct{},
stream: stream, ) *proto.PlanComplete {
} ctx, span := s.startTrace(sess.Context(), tracing.FuncName())
defer span.End()
ctx, cancel, killCtx, kill := s.setupContexts(ctx, canceledOrComplete)
defer cancel()
defer kill()
e := s.executor(config.Directory) e := s.executor(sess.WorkDirectory)
if err = e.checkMinVersion(ctx); err != nil { if err := e.checkMinVersion(ctx); err != nil {
return err return provisionersdk.PlanErrorf(err.Error())
}
logTerraformEnvVars(sink)
statefilePath := filepath.Join(config.Directory, "terraform.tfstate")
if len(config.State) > 0 {
err = os.WriteFile(statefilePath, config.State, 0o600)
if err != nil {
return xerrors.Errorf("write statefile %q: %w", statefilePath, err)
}
} }
logTerraformEnvVars(sess)
// If we're destroying, exit early if there's no state. This is necessary to // If we're destroying, exit early if there's no state. This is necessary to
// avoid any cases where a workspace is "locked out" of terraform due to // avoid any cases where a workspace is "locked out" of terraform due to
// e.g. bad template param values and cannot be deleted. This is just for // e.g. bad template param values and cannot be deleted. This is just for
// contingency, in the future we will try harder to prevent workspaces being // contingency, in the future we will try harder to prevent workspaces being
// broken this hard. // broken this hard.
if config.Metadata.WorkspaceTransition == proto.WorkspaceTransition_DESTROY && len(config.State) == 0 { if request.Metadata.GetWorkspaceTransition() == proto.WorkspaceTransition_DESTROY && len(sess.Config.State) == 0 {
_ = stream.Send(&proto.Provision_Response{ sess.ProvisionLog(proto.LogLevel_INFO, "The terraform state does not exist, there is nothing to do")
Type: &proto.Provision_Response_Log{ return &proto.PlanComplete{}
Log: &proto.Log{ }
Level: proto.LogLevel_INFO,
Output: "The terraform state does not exist, there is nothing to do",
},
},
})
return stream.Send(&proto.Provision_Response{ statefilePath := getStateFilePath(sess.WorkDirectory)
Type: &proto.Provision_Response_Complete{ if len(sess.Config.State) > 0 {
Complete: &proto.Provision_Complete{}, err := os.WriteFile(statefilePath, sess.Config.State, 0o600)
}, if err != nil {
}) return provisionersdk.PlanErrorf("write statefile %q: %s", statefilePath, err)
}
} }
s.logger.Debug(ctx, "running initialization") s.logger.Debug(ctx, "running initialization")
err = e.init(ctx, killCtx, sink) err := e.init(ctx, killCtx, sess)
if err != nil { if err != nil {
if ctx.Err() != nil { s.logger.Debug(ctx, "init failed", slog.Error(err))
return stream.Send(&proto.Provision_Response{ return provisionersdk.PlanErrorf("initialize terraform: %s", err)
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{
Error: err.Error(),
},
},
})
}
return xerrors.Errorf("initialize terraform: %w", err)
} }
s.logger.Debug(ctx, "ran initialization") s.logger.Debug(ctx, "ran initialization")
env, err := provisionEnv(config, request.GetPlan().GetRichParameterValues(), request.GetPlan().GetGitAuthProviders())
env, err := provisionEnv(sess.Config, request.Metadata, request.RichParameterValues, request.GitAuthProviders)
if err != nil { if err != nil {
return err return provisionersdk.PlanErrorf("setup env: %s", err)
} }
var resp *proto.Provision_Response vars, err := planVars(request)
if planRequest != nil { if err != nil {
vars, err := planVars(planRequest) return provisionersdk.PlanErrorf("plan vars: %s", err)
if err != nil {
return err
}
resp, err = e.plan(
ctx, killCtx, env, vars, sink,
config.Metadata.WorkspaceTransition == proto.WorkspaceTransition_DESTROY,
)
if err != nil {
if ctx.Err() != nil {
return stream.Send(&proto.Provision_Response{
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{
Error: err.Error(),
},
},
})
}
return xerrors.Errorf("plan terraform: %w", err)
}
return stream.Send(resp)
} }
// Must be apply
resp, err = e.apply( resp, err := e.plan(
ctx, killCtx, applyRequest.Plan, env, sink, ctx, killCtx, env, vars, sess,
request.Metadata.GetWorkspaceTransition() == proto.WorkspaceTransition_DESTROY,
)
if err != nil {
return provisionersdk.PlanErrorf(err.Error())
}
return resp
}
func (s *server) Apply(
sess *provisionersdk.Session, request *proto.ApplyRequest, canceledOrComplete <-chan struct{},
) *proto.ApplyComplete {
ctx, span := s.startTrace(sess.Context(), tracing.FuncName())
defer span.End()
ctx, cancel, killCtx, kill := s.setupContexts(ctx, canceledOrComplete)
defer cancel()
defer kill()
e := s.executor(sess.WorkDirectory)
if err := e.checkMinVersion(ctx); err != nil {
return provisionersdk.ApplyErrorf(err.Error())
}
logTerraformEnvVars(sess)
// Exit early if there is no plan file. This is necessary to
// avoid any cases where a workspace is "locked out" of terraform due to
// e.g. bad template param values and cannot be deleted. This is just for
// contingency, in the future we will try harder to prevent workspaces being
// broken this hard.
if request.Metadata.GetWorkspaceTransition() == proto.WorkspaceTransition_DESTROY && len(sess.Config.State) == 0 {
sess.ProvisionLog(proto.LogLevel_INFO, "The terraform plan does not exist, there is nothing to do")
return &proto.ApplyComplete{}
}
// Earlier in the session, Plan() will have written the state file and the plan file.
statefilePath := getStateFilePath(sess.WorkDirectory)
env, err := provisionEnv(sess.Config, request.Metadata, nil, nil)
if err != nil {
return provisionersdk.ApplyErrorf("provision env: %s", err)
}
resp, err := e.apply(
ctx, killCtx, env, sess,
) )
if err != nil { if err != nil {
errorMessage := err.Error() errorMessage := err.Error()
// Terraform can fail and apply and still need to store it's state. // Terraform can fail and apply and still need to store it's state.
// In this case, we return Complete with an explicit error message. // In this case, we return Complete with an explicit error message.
stateData, _ := os.ReadFile(statefilePath) stateData, _ := os.ReadFile(statefilePath)
return stream.Send(&proto.Provision_Response{ return &proto.ApplyComplete{
Type: &proto.Provision_Response_Complete{ State: stateData,
Complete: &proto.Provision_Complete{ Error: errorMessage,
State: stateData, }
Error: errorMessage,
},
},
})
} }
return stream.Send(resp) return resp
} }
func planVars(plan *proto.Provision_Plan) ([]string, error) { func planVars(plan *proto.PlanRequest) ([]string, error) {
vars := []string{} vars := []string{}
for _, variable := range plan.VariableValues { for _, variable := range plan.VariableValues {
vars = append(vars, fmt.Sprintf("%s=%s", variable.Name, variable.Value)) vars = append(vars, fmt.Sprintf("%s=%s", variable.Name, variable.Value))
@ -201,18 +172,21 @@ func planVars(plan *proto.Provision_Plan) ([]string, error) {
return vars, nil return vars, nil
} }
func provisionEnv(config *proto.Provision_Config, richParams []*proto.RichParameterValue, gitAuth []*proto.GitAuthProvider) ([]string, error) { func provisionEnv(
config *proto.Config, metadata *proto.Metadata,
richParams []*proto.RichParameterValue, gitAuth []*proto.GitAuthProvider,
) ([]string, error) {
env := safeEnviron() env := safeEnviron()
env = append(env, env = append(env,
"CODER_AGENT_URL="+config.Metadata.CoderUrl, "CODER_AGENT_URL="+metadata.GetCoderUrl(),
"CODER_WORKSPACE_TRANSITION="+strings.ToLower(config.Metadata.WorkspaceTransition.String()), "CODER_WORKSPACE_TRANSITION="+strings.ToLower(metadata.GetWorkspaceTransition().String()),
"CODER_WORKSPACE_NAME="+config.Metadata.WorkspaceName, "CODER_WORKSPACE_NAME="+metadata.GetWorkspaceName(),
"CODER_WORKSPACE_OWNER="+config.Metadata.WorkspaceOwner, "CODER_WORKSPACE_OWNER="+metadata.GetWorkspaceOwner(),
"CODER_WORKSPACE_OWNER_EMAIL="+config.Metadata.WorkspaceOwnerEmail, "CODER_WORKSPACE_OWNER_EMAIL="+metadata.GetWorkspaceOwnerEmail(),
"CODER_WORKSPACE_OWNER_OIDC_ACCESS_TOKEN="+config.Metadata.WorkspaceOwnerOidcAccessToken, "CODER_WORKSPACE_OWNER_OIDC_ACCESS_TOKEN="+metadata.GetWorkspaceOwnerOidcAccessToken(),
"CODER_WORKSPACE_ID="+config.Metadata.WorkspaceId, "CODER_WORKSPACE_ID="+metadata.GetWorkspaceId(),
"CODER_WORKSPACE_OWNER_ID="+config.Metadata.WorkspaceOwnerId, "CODER_WORKSPACE_OWNER_ID="+metadata.GetWorkspaceOwnerId(),
"CODER_WORKSPACE_OWNER_SESSION_TOKEN="+config.Metadata.WorkspaceOwnerSessionToken, "CODER_WORKSPACE_OWNER_SESSION_TOKEN="+metadata.GetWorkspaceOwnerSessionToken(),
) )
for key, value := range provisionersdk.AgentScriptEnv() { for key, value := range provisionersdk.AgentScriptEnv() {
env = append(env, key+"="+value) env = append(env, key+"="+value)
@ -258,10 +232,10 @@ func logTerraformEnvVars(sink logSink) {
if !tfEnvSafeToPrint[parts[0]] { if !tfEnvSafeToPrint[parts[0]] {
parts[1] = "<value redacted>" parts[1] = "<value redacted>"
} }
sink.Log(&proto.Log{ sink.ProvisionLog(
Level: proto.LogLevel_WARN, proto.LogLevel_WARN,
Output: fmt.Sprintf("terraform environment variable: %s=%s", parts[0], parts[1]), fmt.Sprintf("terraform environment variable: %s=%s", parts[0], parts[1]),
}) )
} }
} }
} }

View File

@ -3,6 +3,8 @@
package terraform_test package terraform_test
import ( import (
"archive/tar"
"bytes"
"context" "context"
"encoding/json" "encoding/json"
"errors" "errors"
@ -35,6 +37,7 @@ func setupProvisioner(t *testing.T, opts *provisionerServeOptions) (context.Cont
opts = &provisionerServeOptions{} opts = &provisionerServeOptions{}
} }
cachePath := t.TempDir() cachePath := t.TempDir()
workDir := t.TempDir()
client, server := provisionersdk.MemTransportPipe() client, server := provisionersdk.MemTransportPipe()
ctx, cancelFunc := context.WithCancel(context.Background()) ctx, cancelFunc := context.WithCancel(context.Background())
serverErr := make(chan error, 1) serverErr := make(chan error, 1)
@ -50,40 +53,75 @@ func setupProvisioner(t *testing.T, opts *provisionerServeOptions) (context.Cont
go func() { go func() {
serverErr <- terraform.Serve(ctx, &terraform.ServeOptions{ serverErr <- terraform.Serve(ctx, &terraform.ServeOptions{
ServeOptions: &provisionersdk.ServeOptions{ ServeOptions: &provisionersdk.ServeOptions{
Listener: server, Listener: server,
Logger: slogtest.Make(t, nil).Leveled(slog.LevelDebug),
WorkDirectory: workDir,
}, },
BinaryPath: opts.binaryPath, BinaryPath: opts.binaryPath,
CachePath: cachePath, CachePath: cachePath,
Logger: slogtest.Make(t, nil).Leveled(slog.LevelDebug),
ExitTimeout: opts.exitTimeout, ExitTimeout: opts.exitTimeout,
}) })
}() }()
api := proto.NewDRPCProvisionerClient(client) api := proto.NewDRPCProvisionerClient(client)
return ctx, api return ctx, api
} }
func readProvisionLog(t *testing.T, response proto.DRPCProvisioner_ProvisionClient) ( func makeTar(t *testing.T, files map[string]string) []byte {
string, t.Helper()
*proto.Provision_Complete, var buffer bytes.Buffer
) { writer := tar.NewWriter(&buffer)
var ( for name, content := range files {
logBuf strings.Builder err := writer.WriteHeader(&tar.Header{
c *proto.Provision_Complete Name: name,
) Size: int64(len(content)),
Mode: 0o644,
})
require.NoError(t, err)
_, err = writer.Write([]byte(content))
require.NoError(t, err)
}
err := writer.Flush()
require.NoError(t, err)
return buffer.Bytes()
}
func configure(ctx context.Context, t *testing.T, client proto.DRPCProvisionerClient, config *proto.Config) proto.DRPCProvisioner_SessionClient {
t.Helper()
sess, err := client.Session(ctx)
require.NoError(t, err)
err = sess.Send(&proto.Request{Type: &proto.Request_Config{Config: config}})
require.NoError(t, err)
return sess
}
func readProvisionLog(t *testing.T, response proto.DRPCProvisioner_SessionClient) string {
var logBuf strings.Builder
for { for {
msg, err := response.Recv() msg, err := response.Recv()
require.NoError(t, err) require.NoError(t, err)
if log := msg.GetLog(); log != nil { if log := msg.GetLog(); log != nil {
t.Log(log.Level.String(), log.Output) t.Log(log.Level.String(), log.Output)
_, _ = logBuf.WriteString(log.Output) _, err = logBuf.WriteString(log.Output)
} require.NoError(t, err)
if c = msg.GetComplete(); c != nil { continue
require.Empty(t, c.Error)
break
} }
break
} }
return logBuf.String(), c return logBuf.String()
}
func sendPlan(sess proto.DRPCProvisioner_SessionClient, transition proto.WorkspaceTransition) error {
return sess.Send(&proto.Request{Type: &proto.Request_Plan{Plan: &proto.PlanRequest{
Metadata: &proto.Metadata{WorkspaceTransition: transition},
}}})
}
func sendApply(sess proto.DRPCProvisioner_SessionClient, transition proto.WorkspaceTransition) error {
return sess.Send(&proto.Request{Type: &proto.Request_Apply{Apply: &proto.ApplyRequest{
Metadata: &proto.Metadata{WorkspaceTransition: transition},
}}})
} }
func TestProvision_Cancel(t *testing.T) { func TestProvision_Cancel(t *testing.T) {
@ -109,9 +147,10 @@ func TestProvision_Cancel(t *testing.T) {
wantLog: []string{"interrupt", "exit"}, wantLog: []string{"interrupt", "exit"},
}, },
{ {
name: "Cancel apply", // Provisioner requires a plan before an apply, so test cancel with plan.
mode: "apply", name: "Cancel plan",
startSequence: []string{"init", "apply_start"}, mode: "plan",
startSequence: []string{"init", "plan_start"},
wantLog: []string{"interrupt", "exit"}, wantLog: []string{"interrupt", "exit"},
}, },
} }
@ -131,24 +170,16 @@ func TestProvision_Cancel(t *testing.T) {
ctx, api := setupProvisioner(t, &provisionerServeOptions{ ctx, api := setupProvisioner(t, &provisionerServeOptions{
binaryPath: binPath, binaryPath: binPath,
}) })
sess := configure(ctx, t, api, &proto.Config{
response, err := api.Provision(ctx) TemplateSourceArchive: makeTar(t, nil),
require.NoError(t, err)
err = response.Send(&proto.Provision_Request{
Type: &proto.Provision_Request_Apply{
Apply: &proto.Provision_Apply{
Config: &proto.Provision_Config{
Directory: dir,
Metadata: &proto.Provision_Metadata{},
},
},
},
}) })
err = sendPlan(sess, proto.WorkspaceTransition_START)
require.NoError(t, err) require.NoError(t, err)
for _, line := range tt.startSequence { for _, line := range tt.startSequence {
LoopStart: LoopStart:
msg, err := response.Recv() msg, err := sess.Recv()
require.NoError(t, err) require.NoError(t, err)
t.Log(msg.Type) t.Log(msg.Type)
@ -160,22 +191,22 @@ func TestProvision_Cancel(t *testing.T) {
require.Equal(t, line, log.Output) require.Equal(t, line, log.Output)
} }
err = response.Send(&proto.Provision_Request{ err = sess.Send(&proto.Request{
Type: &proto.Provision_Request_Cancel{ Type: &proto.Request_Cancel{
Cancel: &proto.Provision_Cancel{}, Cancel: &proto.CancelRequest{},
}, },
}) })
require.NoError(t, err) require.NoError(t, err)
var gotLog []string var gotLog []string
for { for {
msg, err := response.Recv() msg, err := sess.Recv()
require.NoError(t, err) require.NoError(t, err)
if log := msg.GetLog(); log != nil { if log := msg.GetLog(); log != nil {
gotLog = append(gotLog, log.Output) gotLog = append(gotLog, log.Output)
} }
if c := msg.GetComplete(); c != nil { if c := msg.GetPlan(); c != nil {
require.Contains(t, c.Error, "exit status 1") require.Contains(t, c.Error, "exit status 1")
break break
} }
@ -208,23 +239,17 @@ func TestProvision_CancelTimeout(t *testing.T) {
exitTimeout: time.Second, exitTimeout: time.Second,
}) })
response, err := api.Provision(ctx) sess := configure(ctx, t, api, &proto.Config{
require.NoError(t, err) TemplateSourceArchive: makeTar(t, nil),
err = response.Send(&proto.Provision_Request{
Type: &proto.Provision_Request_Apply{
Apply: &proto.Provision_Apply{
Config: &proto.Provision_Config{
Directory: dir,
Metadata: &proto.Provision_Metadata{},
},
},
},
}) })
// provisioner requires plan before apply, so test cancel with plan.
err = sendPlan(sess, proto.WorkspaceTransition_START)
require.NoError(t, err) require.NoError(t, err)
for _, line := range []string{"init", "apply_start"} { for _, line := range []string{"init", "plan_start"} {
LoopStart: LoopStart:
msg, err := response.Recv() msg, err := sess.Recv()
require.NoError(t, err) require.NoError(t, err)
t.Log(msg.Type) t.Log(msg.Type)
@ -236,18 +261,14 @@ func TestProvision_CancelTimeout(t *testing.T) {
require.Equal(t, line, log.Output) require.Equal(t, line, log.Output)
} }
err = response.Send(&proto.Provision_Request{ err = sess.Send(&proto.Request{Type: &proto.Request_Cancel{Cancel: &proto.CancelRequest{}}})
Type: &proto.Provision_Request_Cancel{
Cancel: &proto.Provision_Cancel{},
},
})
require.NoError(t, err) require.NoError(t, err)
for { for {
msg, err := response.Recv() msg, err := sess.Recv()
require.NoError(t, err) require.NoError(t, err)
if c := msg.GetComplete(); c != nil { if c := msg.GetPlan(); c != nil {
require.Contains(t, c.Error, "killed") require.Contains(t, c.Error, "killed")
break break
} }
@ -258,17 +279,18 @@ func TestProvision(t *testing.T) {
t.Parallel() t.Parallel()
testCases := []struct { testCases := []struct {
Name string Name string
Files map[string]string Files map[string]string
Request *proto.Provision_Plan Metadata *proto.Metadata
Request *proto.PlanRequest
// Response may be nil to not check the response. // Response may be nil to not check the response.
Response *proto.Provision_Response Response *proto.PlanComplete
// If ErrorContains is not empty, then response.Recv() should return an // If ErrorContains is not empty, PlanComplete should have an Error containing the given string
// error containing this string before a Complete response is returned.
ErrorContains string ErrorContains string
// If ExpectLogContains is not empty, then the logs should contain it. // If ExpectLogContains is not empty, then the logs should contain it.
ExpectLogContains string ExpectLogContains string
Apply bool // If Apply is true, then send an Apply request and check we get the same Resources as in Response.
Apply bool
}{ }{
{ {
Name: "missing-variable", Name: "missing-variable",
@ -293,15 +315,11 @@ func TestProvision(t *testing.T) {
Files: map[string]string{ Files: map[string]string{
"main.tf": `resource "null_resource" "A" {}`, "main.tf": `resource "null_resource" "A" {}`,
}, },
Response: &proto.Provision_Response{ Response: &proto.PlanComplete{
Type: &proto.Provision_Response_Complete{ Resources: []*proto.Resource{{
Complete: &proto.Provision_Complete{ Name: "A",
Resources: []*proto.Resource{{ Type: "null_resource",
Name: "A", }},
Type: "null_resource",
}},
},
},
}, },
}, },
{ {
@ -309,15 +327,11 @@ func TestProvision(t *testing.T) {
Files: map[string]string{ Files: map[string]string{
"main.tf": `resource "null_resource" "A" {}`, "main.tf": `resource "null_resource" "A" {}`,
}, },
Response: &proto.Provision_Response{ Response: &proto.PlanComplete{
Type: &proto.Provision_Response_Complete{ Resources: []*proto.Resource{{
Complete: &proto.Provision_Complete{ Name: "A",
Resources: []*proto.Resource{{ Type: "null_resource",
Name: "A", }},
Type: "null_resource",
}},
},
},
}, },
Apply: true, Apply: true,
}, },
@ -334,15 +348,11 @@ func TestProvision(t *testing.T) {
} }
}`, }`,
}, },
Response: &proto.Provision_Response{ Response: &proto.PlanComplete{
Type: &proto.Provision_Response_Complete{ Resources: []*proto.Resource{{
Complete: &proto.Provision_Complete{ Name: "A",
Resources: []*proto.Resource{{ Type: "null_resource",
Name: "A", }},
Type: "null_resource",
}},
},
},
}, },
Apply: true, Apply: true,
}, },
@ -367,12 +377,8 @@ func TestProvision(t *testing.T) {
Files: map[string]string{ Files: map[string]string{
"main.tf": `resource "null_resource" "A" {}`, "main.tf": `resource "null_resource" "A" {}`,
}, },
Request: &proto.Provision_Plan{ Metadata: &proto.Metadata{
Config: &proto.Provision_Config{ WorkspaceTransition: proto.WorkspaceTransition_DESTROY,
Metadata: &proto.Provision_Metadata{
WorkspaceTransition: proto.WorkspaceTransition_DESTROY,
},
},
}, },
ExpectLogContains: "nothing to do", ExpectLogContains: "nothing to do",
}, },
@ -406,7 +412,7 @@ func TestProvision(t *testing.T) {
} }
}`, }`,
}, },
Request: &proto.Provision_Plan{ Request: &proto.PlanRequest{
RichParameterValues: []*proto.RichParameterValue{ RichParameterValues: []*proto.RichParameterValue{
{ {
Name: "Example", Name: "Example",
@ -418,27 +424,23 @@ func TestProvision(t *testing.T) {
}, },
}, },
}, },
Response: &proto.Provision_Response{ Response: &proto.PlanComplete{
Type: &proto.Provision_Response_Complete{ Parameters: []*proto.RichParameter{
Complete: &proto.Provision_Complete{ {
Parameters: []*proto.RichParameter{ Name: "Example",
{ Type: "string",
Name: "Example", DefaultValue: "foobar",
Type: "string", },
DefaultValue: "foobar", {
}, Name: "Sample",
{ Type: "string",
Name: "Sample", DefaultValue: "foobaz",
Type: "string",
DefaultValue: "foobaz",
},
},
Resources: []*proto.Resource{{
Name: "example",
Type: "null_resource",
}},
}, },
}, },
Resources: []*proto.Resource{{
Name: "example",
Type: "null_resource",
}},
}, },
}, },
{ {
@ -488,7 +490,7 @@ func TestProvision(t *testing.T) {
] ]
}`, }`,
}, },
Request: &proto.Provision_Plan{ Request: &proto.PlanRequest{
RichParameterValues: []*proto.RichParameterValue{ RichParameterValues: []*proto.RichParameterValue{
{ {
Name: "Example", Name: "Example",
@ -500,27 +502,23 @@ func TestProvision(t *testing.T) {
}, },
}, },
}, },
Response: &proto.Provision_Response{ Response: &proto.PlanComplete{
Type: &proto.Provision_Response_Complete{ Parameters: []*proto.RichParameter{
Complete: &proto.Provision_Complete{ {
Parameters: []*proto.RichParameter{ Name: "Example",
{ Type: "string",
Name: "Example", DefaultValue: "foobar",
Type: "string", },
DefaultValue: "foobar", {
}, Name: "Sample",
{ Type: "string",
Name: "Sample", DefaultValue: "foobaz",
Type: "string",
DefaultValue: "foobaz",
},
},
Resources: []*proto.Resource{{
Name: "example",
Type: "null_resource",
}},
}, },
}, },
Resources: []*proto.Resource{{
Name: "example",
Type: "null_resource",
}},
}, },
}, },
{ {
@ -550,25 +548,21 @@ func TestProvision(t *testing.T) {
} }
`, `,
}, },
Request: &proto.Provision_Plan{ Request: &proto.PlanRequest{
GitAuthProviders: []*proto.GitAuthProvider{{ GitAuthProviders: []*proto.GitAuthProvider{{
Id: "github", Id: "github",
AccessToken: "some-value", AccessToken: "some-value",
}}, }},
}, },
Response: &proto.Provision_Response{ Response: &proto.PlanComplete{
Type: &proto.Provision_Response_Complete{ Resources: []*proto.Resource{{
Complete: &proto.Provision_Complete{ Name: "example",
Resources: []*proto.Resource{{ Type: "null_resource",
Name: "example", Metadata: []*proto.Resource_Metadata{{
Type: "null_resource", Key: "token",
Metadata: []*proto.Resource_Metadata{{ Value: "some-value",
Key: "token", }},
Value: "some-value", }},
}},
}},
},
},
}, },
}, },
} }
@ -579,50 +573,26 @@ func TestProvision(t *testing.T) {
t.Parallel() t.Parallel()
ctx, api := setupProvisioner(t, nil) ctx, api := setupProvisioner(t, nil)
sess := configure(ctx, t, api, &proto.Config{
TemplateSourceArchive: makeTar(t, testCase.Files),
})
directory := t.TempDir() planRequest := &proto.Request{Type: &proto.Request_Plan{Plan: &proto.PlanRequest{
for path, content := range testCase.Files { Metadata: testCase.Metadata,
err := os.WriteFile(filepath.Join(directory, path), []byte(content), 0o600) }}}
require.NoError(t, err)
}
planRequest := &proto.Provision_Request{
Type: &proto.Provision_Request_Plan{
Plan: &proto.Provision_Plan{
Config: &proto.Provision_Config{
Directory: directory,
},
},
},
}
if testCase.Request != nil { if testCase.Request != nil {
if planRequest.GetPlan().GetConfig() == nil { planRequest = &proto.Request{Type: &proto.Request_Plan{Plan: testCase.Request}}
planRequest.GetPlan().Config = &proto.Provision_Config{}
}
planRequest.GetPlan().RichParameterValues = testCase.Request.RichParameterValues
planRequest.GetPlan().GitAuthProviders = testCase.Request.GitAuthProviders
if testCase.Request.Config != nil {
planRequest.GetPlan().Config.State = testCase.Request.Config.State
planRequest.GetPlan().Config.Metadata = testCase.Request.Config.Metadata
}
}
if planRequest.GetPlan().Config.Metadata == nil {
planRequest.GetPlan().Config.Metadata = &proto.Provision_Metadata{}
} }
gotExpectedLog := testCase.ExpectLogContains == "" gotExpectedLog := testCase.ExpectLogContains == ""
provision := func(req *proto.Provision_Request) *proto.Provision_Complete { provision := func(req *proto.Request) *proto.Response {
response, err := api.Provision(ctx) err := sess.Send(req)
require.NoError(t, err) require.NoError(t, err)
err = response.Send(req)
require.NoError(t, err)
var complete *proto.Provision_Complete
for { for {
msg, err := response.Recv() msg, err := sess.Recv()
if msg != nil && msg.GetLog() != nil { require.NoError(t, err)
if msg.GetLog() != nil {
if testCase.ExpectLogContains != "" && strings.Contains(msg.GetLog().Output, testCase.ExpectLogContains) { if testCase.ExpectLogContains != "" && strings.Contains(msg.GetLog().Output, testCase.ExpectLogContains) {
gotExpectedLog = true gotExpectedLog = true
} }
@ -630,67 +600,51 @@ func TestProvision(t *testing.T) {
t.Logf("log: [%s] %s", msg.GetLog().Level, msg.GetLog().Output) t.Logf("log: [%s] %s", msg.GetLog().Level, msg.GetLog().Output)
continue continue
} }
if testCase.ErrorContains != "" { return msg
require.ErrorContains(t, err, testCase.ErrorContains)
break
}
require.NoError(t, err)
if complete = msg.GetComplete(); complete == nil {
continue
}
require.NoError(t, err)
// Remove randomly generated data.
for _, resource := range msg.GetComplete().Resources {
sort.Slice(resource.Agents, func(i, j int) bool {
return resource.Agents[i].Name < resource.Agents[j].Name
})
for _, agent := range resource.Agents {
agent.Id = ""
if agent.GetToken() == "" {
continue
}
agent.Auth = &proto.Agent_Token{}
}
}
if testCase.Response != nil {
require.Equal(t, testCase.Response.GetComplete().Error, msg.GetComplete().Error)
resourcesGot, err := json.Marshal(msg.GetComplete().Resources)
require.NoError(t, err)
resourcesWant, err := json.Marshal(testCase.Response.GetComplete().Resources)
require.NoError(t, err)
require.Equal(t, string(resourcesWant), string(resourcesGot))
parametersGot, err := json.Marshal(msg.GetComplete().Parameters)
require.NoError(t, err)
parametersWant, err := json.Marshal(testCase.Response.GetComplete().Parameters)
require.NoError(t, err)
require.Equal(t, string(parametersWant), string(parametersGot))
}
break
} }
return complete
} }
planComplete := provision(planRequest) resp := provision(planRequest)
planComplete := resp.GetPlan()
require.NotNil(t, planComplete)
if testCase.ErrorContains != "" {
require.Contains(t, planComplete.GetError(), testCase.ErrorContains)
}
if testCase.Response != nil {
require.Equal(t, testCase.Response.Error, planComplete.Error)
// Remove randomly generated data.
normalizeResources(planComplete.Resources)
resourcesGot, err := json.Marshal(planComplete.Resources)
require.NoError(t, err)
resourcesWant, err := json.Marshal(testCase.Response.Resources)
require.NoError(t, err)
require.Equal(t, string(resourcesWant), string(resourcesGot))
parametersGot, err := json.Marshal(planComplete.Parameters)
require.NoError(t, err)
parametersWant, err := json.Marshal(testCase.Response.Parameters)
require.NoError(t, err)
require.Equal(t, string(parametersWant), string(parametersGot))
}
if testCase.Apply { if testCase.Apply {
require.NotNil(t, planComplete.Plan) resp = provision(&proto.Request{Type: &proto.Request_Apply{Apply: &proto.ApplyRequest{
provision(&proto.Provision_Request{ Metadata: &proto.Metadata{WorkspaceTransition: proto.WorkspaceTransition_START},
Type: &proto.Provision_Request_Apply{ }}})
Apply: &proto.Provision_Apply{ applyComplete := resp.GetApply()
Config: planRequest.GetPlan().GetConfig(), require.NotNil(t, applyComplete)
Plan: planComplete.Plan,
}, if testCase.Response != nil {
}, normalizeResources(applyComplete.Resources)
}) resourcesGot, err := json.Marshal(applyComplete.Resources)
require.NoError(t, err)
resourcesWant, err := json.Marshal(testCase.Response.Resources)
require.NoError(t, err)
require.Equal(t, string(resourcesWant), string(resourcesGot))
}
} }
if !gotExpectedLog { if !gotExpectedLog {
@ -700,6 +654,22 @@ func TestProvision(t *testing.T) {
} }
} }
func normalizeResources(resources []*proto.Resource) {
for _, resource := range resources {
sort.Slice(resource.Agents, func(i, j int) bool {
return resource.Agents[i].Name < resource.Agents[j].Name
})
for _, agent := range resource.Agents {
agent.Id = ""
if agent.GetToken() == "" {
continue
}
agent.Auth = &proto.Agent_Token{}
}
}
}
// nolint:paralleltest // nolint:paralleltest
func TestProvision_ExtraEnv(t *testing.T) { func TestProvision_ExtraEnv(t *testing.T) {
// #nosec // #nosec
@ -708,31 +678,15 @@ func TestProvision_ExtraEnv(t *testing.T) {
t.Setenv("TF_SUPERSECRET", secretValue) t.Setenv("TF_SUPERSECRET", secretValue)
ctx, api := setupProvisioner(t, nil) ctx, api := setupProvisioner(t, nil)
sess := configure(ctx, t, api, &proto.Config{
TemplateSourceArchive: makeTar(t, map[string]string{"main.tf": `resource "null_resource" "A" {}`}),
})
directory := t.TempDir() err := sendPlan(sess, proto.WorkspaceTransition_START)
path := filepath.Join(directory, "main.tf")
err := os.WriteFile(path, []byte(`resource "null_resource" "A" {}`), 0o600)
require.NoError(t, err)
request := &proto.Provision_Request{
Type: &proto.Provision_Request_Plan{
Plan: &proto.Provision_Plan{
Config: &proto.Provision_Config{
Directory: directory,
Metadata: &proto.Provision_Metadata{
WorkspaceTransition: proto.WorkspaceTransition_START,
},
},
},
},
}
response, err := api.Provision(ctx)
require.NoError(t, err)
err = response.Send(request)
require.NoError(t, err) require.NoError(t, err)
found := false found := false
for { for {
msg, err := response.Recv() msg, err := sess.Recv()
require.NoError(t, err) require.NoError(t, err)
if log := msg.GetLog(); log != nil { if log := msg.GetLog(); log != nil {
@ -742,7 +696,7 @@ func TestProvision_ExtraEnv(t *testing.T) {
} }
require.NotContains(t, log.Output, secretValue) require.NotContains(t, log.Output, secretValue)
} }
if c := msg.GetComplete(); c != nil { if c := msg.GetPlan(); c != nil {
require.Empty(t, c.Error) require.Empty(t, c.Error)
break break
} }
@ -774,48 +728,19 @@ func TestProvision_SafeEnv(t *testing.T) {
` `
ctx, api := setupProvisioner(t, nil) ctx, api := setupProvisioner(t, nil)
sess := configure(ctx, t, api, &proto.Config{
directory := t.TempDir() TemplateSourceArchive: makeTar(t, map[string]string{"main.tf": echoResource}),
path := filepath.Join(directory, "main.tf")
err := os.WriteFile(path, []byte(echoResource), 0o600)
require.NoError(t, err)
response, err := api.Provision(ctx)
require.NoError(t, err)
err = response.Send(&proto.Provision_Request{
Type: &proto.Provision_Request_Plan{
Plan: &proto.Provision_Plan{
Config: &proto.Provision_Config{
Directory: directory,
Metadata: &proto.Provision_Metadata{
WorkspaceTransition: proto.WorkspaceTransition_START,
},
},
},
},
}) })
err := sendPlan(sess, proto.WorkspaceTransition_START)
require.NoError(t, err) require.NoError(t, err)
_, complete := readProvisionLog(t, response) _ = readProvisionLog(t, sess)
response, err = api.Provision(ctx) err = sendApply(sess, proto.WorkspaceTransition_START)
require.NoError(t, err)
err = response.Send(&proto.Provision_Request{
Type: &proto.Provision_Request_Apply{
Apply: &proto.Provision_Apply{
Config: &proto.Provision_Config{
Directory: directory,
Metadata: &proto.Provision_Metadata{
WorkspaceTransition: proto.WorkspaceTransition_START,
},
},
Plan: complete.GetPlan(),
},
},
})
require.NoError(t, err) require.NoError(t, err)
log, _ := readProvisionLog(t, response) log := readProvisionLog(t, sess)
require.Contains(t, log, passedValue) require.Contains(t, log, passedValue)
require.NotContains(t, log, secretValue) require.NotContains(t, log, secretValue)
require.Contains(t, log, "CODER_") require.Contains(t, log, "CODER_")

View File

@ -24,7 +24,6 @@ type ServeOptions struct {
BinaryPath string BinaryPath string
// CachePath must not be used by multiple processes at once. // CachePath must not be used by multiple processes at once.
CachePath string CachePath string
Logger slog.Logger
Tracer trace.Tracer Tracer trace.Tracer
// ExitTimeout defines how long we will wait for a running Terraform // ExitTimeout defines how long we will wait for a running Terraform
@ -128,5 +127,6 @@ func (s *server) executor(workdir string) *executor {
binaryPath: s.binaryPath, binaryPath: s.binaryPath,
cachePath: s.cachePath, cachePath: s.cachePath,
workdir: workdir, workdir: workdir,
logger: s.logger.Named("executor"),
} }
} }

View File

@ -22,8 +22,9 @@ version)
;; ;;
init) init)
case "$MODE" in case "$MODE" in
apply) plan)
echo "init" echo "init"
exit 0
;; ;;
init) init)
sleep 10 & sleep 10 &
@ -39,7 +40,7 @@ init)
;; ;;
esac esac
;; ;;
apply) plan)
sleep 10 & sleep 10 &
sleep_pid=$! sleep_pid=$!
@ -47,14 +48,14 @@ apply)
trap 'json_print interrupt; exit 1' INT trap 'json_print interrupt; exit 1' INT
trap 'json_print terminate; exit 2' TERM trap 'json_print terminate; exit 2' TERM
json_print apply_start json_print plan_start
wait wait
json_print apply_end json_print plan_end
;; ;;
plan) apply)
echo "plan not supported" echo "apply not supported"
exit 1 exit 1
;; ;;
esac esac
exit 0 exit 10

View File

@ -23,19 +23,19 @@ init)
echo "init" echo "init"
exit 0 exit 0
;; ;;
apply) plan)
trap 'json_print interrupt' INT trap 'json_print interrupt' INT
json_print apply_start json_print plan_start
sleep 10 2>/dev/null >/dev/null sleep 10 2>/dev/null >/dev/null
json_print apply_end json_print plan_end
exit 0 exit 0
;; ;;
plan) apply)
echo "plan not supported" echo "apply not supported"
exit 1 exit 1
;; ;;
esac esac
exit 0 exit 10

View File

@ -819,7 +819,7 @@ type AcquiredJob_WorkspaceBuild struct {
RichParameterValues []*proto.RichParameterValue `protobuf:"bytes,4,rep,name=rich_parameter_values,json=richParameterValues,proto3" json:"rich_parameter_values,omitempty"` RichParameterValues []*proto.RichParameterValue `protobuf:"bytes,4,rep,name=rich_parameter_values,json=richParameterValues,proto3" json:"rich_parameter_values,omitempty"`
VariableValues []*proto.VariableValue `protobuf:"bytes,5,rep,name=variable_values,json=variableValues,proto3" json:"variable_values,omitempty"` VariableValues []*proto.VariableValue `protobuf:"bytes,5,rep,name=variable_values,json=variableValues,proto3" json:"variable_values,omitempty"`
GitAuthProviders []*proto.GitAuthProvider `protobuf:"bytes,6,rep,name=git_auth_providers,json=gitAuthProviders,proto3" json:"git_auth_providers,omitempty"` GitAuthProviders []*proto.GitAuthProvider `protobuf:"bytes,6,rep,name=git_auth_providers,json=gitAuthProviders,proto3" json:"git_auth_providers,omitempty"`
Metadata *proto.Provision_Metadata `protobuf:"bytes,7,opt,name=metadata,proto3" json:"metadata,omitempty"` Metadata *proto.Metadata `protobuf:"bytes,7,opt,name=metadata,proto3" json:"metadata,omitempty"`
State []byte `protobuf:"bytes,8,opt,name=state,proto3" json:"state,omitempty"` State []byte `protobuf:"bytes,8,opt,name=state,proto3" json:"state,omitempty"`
LogLevel string `protobuf:"bytes,9,opt,name=log_level,json=logLevel,proto3" json:"log_level,omitempty"` LogLevel string `protobuf:"bytes,9,opt,name=log_level,json=logLevel,proto3" json:"log_level,omitempty"`
} }
@ -891,7 +891,7 @@ func (x *AcquiredJob_WorkspaceBuild) GetGitAuthProviders() []*proto.GitAuthProvi
return nil return nil
} }
func (x *AcquiredJob_WorkspaceBuild) GetMetadata() *proto.Provision_Metadata { func (x *AcquiredJob_WorkspaceBuild) GetMetadata() *proto.Metadata {
if x != nil { if x != nil {
return x.Metadata return x.Metadata
} }
@ -917,8 +917,8 @@ type AcquiredJob_TemplateImport struct {
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
Metadata *proto.Provision_Metadata `protobuf:"bytes,1,opt,name=metadata,proto3" json:"metadata,omitempty"` Metadata *proto.Metadata `protobuf:"bytes,1,opt,name=metadata,proto3" json:"metadata,omitempty"`
UserVariableValues []*proto.VariableValue `protobuf:"bytes,2,rep,name=user_variable_values,json=userVariableValues,proto3" json:"user_variable_values,omitempty"` UserVariableValues []*proto.VariableValue `protobuf:"bytes,2,rep,name=user_variable_values,json=userVariableValues,proto3" json:"user_variable_values,omitempty"`
} }
func (x *AcquiredJob_TemplateImport) Reset() { func (x *AcquiredJob_TemplateImport) Reset() {
@ -953,7 +953,7 @@ func (*AcquiredJob_TemplateImport) Descriptor() ([]byte, []int) {
return file_provisionerd_proto_provisionerd_proto_rawDescGZIP(), []int{1, 1} return file_provisionerd_proto_provisionerd_proto_rawDescGZIP(), []int{1, 1}
} }
func (x *AcquiredJob_TemplateImport) GetMetadata() *proto.Provision_Metadata { func (x *AcquiredJob_TemplateImport) GetMetadata() *proto.Metadata {
if x != nil { if x != nil {
return x.Metadata return x.Metadata
} }
@ -974,7 +974,7 @@ type AcquiredJob_TemplateDryRun struct {
RichParameterValues []*proto.RichParameterValue `protobuf:"bytes,2,rep,name=rich_parameter_values,json=richParameterValues,proto3" json:"rich_parameter_values,omitempty"` RichParameterValues []*proto.RichParameterValue `protobuf:"bytes,2,rep,name=rich_parameter_values,json=richParameterValues,proto3" json:"rich_parameter_values,omitempty"`
VariableValues []*proto.VariableValue `protobuf:"bytes,3,rep,name=variable_values,json=variableValues,proto3" json:"variable_values,omitempty"` VariableValues []*proto.VariableValue `protobuf:"bytes,3,rep,name=variable_values,json=variableValues,proto3" json:"variable_values,omitempty"`
Metadata *proto.Provision_Metadata `protobuf:"bytes,4,opt,name=metadata,proto3" json:"metadata,omitempty"` Metadata *proto.Metadata `protobuf:"bytes,4,opt,name=metadata,proto3" json:"metadata,omitempty"`
} }
func (x *AcquiredJob_TemplateDryRun) Reset() { func (x *AcquiredJob_TemplateDryRun) Reset() {
@ -1023,7 +1023,7 @@ func (x *AcquiredJob_TemplateDryRun) GetVariableValues() []*proto.VariableValue
return nil return nil
} }
func (x *AcquiredJob_TemplateDryRun) GetMetadata() *proto.Provision_Metadata { func (x *AcquiredJob_TemplateDryRun) GetMetadata() *proto.Metadata {
if x != nil { if x != nil {
return x.Metadata return x.Metadata
} }
@ -1335,7 +1335,7 @@ var file_provisionerd_proto_provisionerd_proto_rawDesc = []byte{
0x6f, 0x6e, 0x65, 0x72, 0x64, 0x1a, 0x26, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x1a, 0x26, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e,
0x65, 0x72, 0x73, 0x64, 0x6b, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x72, 0x73, 0x64, 0x6b, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x76,
0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x07, 0x0a, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x07, 0x0a,
0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0xab, 0x0b, 0x0a, 0x0b, 0x41, 0x63, 0x71, 0x75, 0x69, 0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x8d, 0x0b, 0x0a, 0x0b, 0x41, 0x63, 0x71, 0x75, 0x69,
0x72, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x72, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x12, 0x1d, 0x0a,
0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28,
@ -1368,7 +1368,7 @@ var file_provisionerd_proto_provisionerd_proto_rawDesc = []byte{
0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x41, 0x63, 0x71, 0x75, 0x69, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x41, 0x63, 0x71, 0x75, 0x69,
0x72, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x72, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x61,
0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x63, 0x65, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x63, 0x65,
0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x1a, 0xc1, 0x03, 0x0a, 0x0e, 0x57, 0x6f, 0x72, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x1a, 0xb7, 0x03, 0x0a, 0x0e, 0x57, 0x6f, 0x72,
0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x2c, 0x0a, 0x12, 0x77, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x2c, 0x0a, 0x12, 0x77,
0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x69, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x69,
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61,
@ -1389,193 +1389,191 @@ var file_provisionerd_proto_provisionerd_proto_rawDesc = []byte{
0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69,
0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x47, 0x69, 0x74, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x47, 0x69, 0x74, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76,
0x69, 0x64, 0x65, 0x72, 0x52, 0x10, 0x67, 0x69, 0x74, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x69, 0x64, 0x65, 0x72, 0x52, 0x10, 0x67, 0x69, 0x74, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f,
0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x12, 0x3b, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x12, 0x31, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61,
0x74, 0x61, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x74, 0x61, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69,
0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52,
0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61,
0x61, 0x74, 0x61, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x08, 0x20, 0x01, 0x74, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12,
0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x6f, 0x67, 0x1b, 0x0a, 0x09, 0x6c, 0x6f, 0x67, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x09, 0x20, 0x01,
0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x6f, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x4a, 0x04, 0x08, 0x03,
0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x1a, 0x9b, 0x01, 0x0a, 0x10, 0x04, 0x1a, 0x91, 0x01, 0x0a, 0x0e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49,
0x0e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x31, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,
0x3b, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73,
0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08,
0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x4c, 0x0a, 0x14, 0x75, 0x73, 0x65, 0x72,
0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x4c, 0x0a, 0x14, 0x5f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73,
0x75, 0x73, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x61, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69,
0x6c, 0x75, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c,
0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x75, 0x65, 0x52, 0x12, 0x75, 0x73, 0x65, 0x72, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65,
0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x12, 0x75, 0x73, 0x65, 0x72, 0x56, 0x61, 0x72, 0x69, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x1a, 0xe3, 0x01, 0x0a, 0x0e, 0x54, 0x65, 0x6d, 0x70, 0x6c,
0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x1a, 0xed, 0x01, 0x0a, 0x0e, 0x54, 0x61, 0x74, 0x65, 0x44, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x12, 0x53, 0x0a, 0x15, 0x72, 0x69, 0x63,
0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x44, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x12, 0x53, 0x0a, 0x68, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75,
0x15, 0x72, 0x69, 0x63, 0x68, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69,
0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d,
0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68, 0x50, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x13, 0x72, 0x69, 0x63, 0x68, 0x50,
0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x13, 0x72, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x43,
0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x0a, 0x0f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65,
0x65, 0x73, 0x12, 0x43, 0x0a, 0x0f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73,
0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61,
0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x75, 0x65, 0x52, 0x0e, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c,
0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0e, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x31, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18,
0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x3b, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f,
0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x6e, 0x65, 0x72, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65,
0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x4a, 0x04, 0x08, 0x01, 0x10, 0x02, 0x1a, 0x40, 0x0a, 0x12,
0x6e, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x54, 0x72, 0x61, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74,
0x64, 0x61, 0x74, 0x61, 0x4a, 0x04, 0x08, 0x01, 0x10, 0x02, 0x1a, 0x40, 0x0a, 0x12, 0x54, 0x72, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
0x61, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20,
0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x06,
0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0xa5, 0x03, 0x0a, 0x09, 0x46, 0x61, 0x69, 0x6c, 0x65,
0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x06, 0x0a, 0x04, 0x64, 0x4a, 0x6f, 0x62, 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x18, 0x01,
0x74, 0x79, 0x70, 0x65, 0x22, 0xa5, 0x03, 0x0a, 0x09, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x4a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65,
0x6f, 0x62, 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f,
0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x72, 0x12, 0x51, 0x0a, 0x0f, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x62,
0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x75, 0x69, 0x6c, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x70, 0x72, 0x6f,
0x51, 0x0a, 0x0f, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x62, 0x75, 0x69, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64,
0x6c, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x4a, 0x6f, 0x62, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x42, 0x75, 0x69,
0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x4a, 0x6f, 0x6c, 0x64, 0x48, 0x00, 0x52, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x42,
0x62, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x51, 0x0a, 0x0f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65,
0x48, 0x00, 0x52, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x42, 0x75, 0x69, 0x5f, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e,
0x6c, 0x64, 0x12, 0x51, 0x0a, 0x0f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x69, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x46, 0x61, 0x69,
0x6d, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x70, 0x72, 0x6c, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49,
0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x48, 0x00, 0x52, 0x0e, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74,
0x64, 0x4a, 0x6f, 0x62, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x6d, 0x70, 0x65, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x52, 0x0a, 0x10, 0x74, 0x65, 0x6d, 0x70, 0x6c,
0x6f, 0x72, 0x74, 0x48, 0x00, 0x52, 0x0e, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x61, 0x74, 0x65, 0x5f, 0x64, 0x72, 0x79, 0x5f, 0x72, 0x75, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28,
0x6d, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x52, 0x0a, 0x10, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x0b, 0x32, 0x26, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64,
0x65, 0x5f, 0x64, 0x72, 0x79, 0x5f, 0x72, 0x75, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c,
0x26, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x46, 0x61, 0x74, 0x65, 0x44, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x48, 0x00, 0x52, 0x0e, 0x74, 0x65, 0x6d,
0x61, 0x69, 0x6c, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x44, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x65,
0x65, 0x44, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x48, 0x00, 0x52, 0x0e, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52,
0x61, 0x74, 0x65, 0x44, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x72, 0x72, 0x09, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x1a, 0x26, 0x0a, 0x0e, 0x57, 0x6f,
0x6f, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x65, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x14, 0x0a, 0x05,
0x72, 0x72, 0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x1a, 0x26, 0x0a, 0x0e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61,
0x73, 0x70, 0x61, 0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x74, 0x65, 0x1a, 0x10, 0x0a, 0x0e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x6d,
0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x1a, 0x10, 0x0a, 0x0e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65,
0x1a, 0x10, 0x0a, 0x0e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x6d, 0x70, 0x6f, 0x44, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0xd8,
0x72, 0x74, 0x1a, 0x10, 0x0a, 0x0e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x44, 0x72, 0x05, 0x0a, 0x0c, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x12,
0x79, 0x52, 0x75, 0x6e, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0xd8, 0x05, 0x0a,
0x0c, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x12, 0x15, 0x0a,
0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a,
0x6f, 0x62, 0x49, 0x64, 0x12, 0x54, 0x0a, 0x0f, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63,
0x65, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e,
0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x43, 0x6f, 0x6d,
0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70,
0x61, 0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x48, 0x00, 0x52, 0x0e, 0x77, 0x6f, 0x72, 0x6b,
0x73, 0x70, 0x61, 0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x54, 0x0a, 0x0f, 0x74, 0x65,
0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x03, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65,
0x72, 0x64, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x2e,
0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x48, 0x00,
0x52, 0x0e, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74,
0x12, 0x55, 0x0a, 0x10, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x64, 0x72, 0x79,
0x5f, 0x72, 0x75, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x70, 0x72, 0x6f,
0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65,
0x74, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x44,
0x72, 0x79, 0x52, 0x75, 0x6e, 0x48, 0x00, 0x52, 0x0e, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74,
0x65, 0x44, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x1a, 0x5b, 0x0a, 0x0e, 0x57, 0x6f, 0x72, 0x6b, 0x73,
0x70, 0x61, 0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61,
0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12,
0x33, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03,
0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72,
0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75,
0x72, 0x63, 0x65, 0x73, 0x1a, 0x81, 0x02, 0x0a, 0x0e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74,
0x65, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x3e, 0x0a, 0x0f, 0x73, 0x74, 0x61, 0x72, 0x74,
0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b,
0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52,
0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x0e, 0x73, 0x74, 0x61, 0x72, 0x74, 0x52, 0x65,
0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x3c, 0x0a, 0x0e, 0x73, 0x74, 0x6f, 0x70, 0x5f,
0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32,
0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65,
0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x0d, 0x73, 0x74, 0x6f, 0x70, 0x52, 0x65, 0x73, 0x6f,
0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x43, 0x0a, 0x0f, 0x72, 0x69, 0x63, 0x68, 0x5f, 0x70, 0x61,
0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a,
0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63,
0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x52, 0x0e, 0x72, 0x69, 0x63, 0x68,
0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x67, 0x69,
0x74, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73,
0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x67, 0x69, 0x74, 0x41, 0x75, 0x74, 0x68, 0x50,
0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x1a, 0x45, 0x0a, 0x0e, 0x54, 0x65, 0x6d, 0x70,
0x6c, 0x61, 0x74, 0x65, 0x44, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x12, 0x33, 0x0a, 0x09, 0x72, 0x65,
0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e,
0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f,
0x75, 0x72, 0x63, 0x65, 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x42,
0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0xb0, 0x01, 0x0a, 0x03, 0x4c, 0x6f, 0x67, 0x12,
0x2f, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32,
0x17, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x4c,
0x6f, 0x67, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,
0x12, 0x2b, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32,
0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4c, 0x6f,
0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1d, 0x0a,
0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28,
0x03, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x14, 0x0a, 0x05,
0x73, 0x74, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61,
0x67, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x05, 0x20, 0x01,
0x28, 0x09, 0x52, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x22, 0x8a, 0x02, 0x0a, 0x10, 0x55,
0x70, 0x64, 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x18, 0x02, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x12, 0x54, 0x0a, 0x0f, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70,
0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x61, 0x63, 0x65, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x65, 0x72, 0x64, 0x2e, 0x4c, 0x6f, 0x67, 0x52, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x12, 0x4c, 0x0a, 0x29, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x43,
0x12, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x2e, 0x57, 0x6f, 0x72, 0x6b,
0x6c, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x73, 0x70, 0x61, 0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x48, 0x00, 0x52, 0x0e, 0x77, 0x6f,
0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x54, 0x0a, 0x0f,
0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x11, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x18,
0x74, 0x65, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x12, 0x4c, 0x0a, 0x14, 0x75, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f,
0x73, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x4a, 0x6f,
0x75, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x62, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74,
0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x48, 0x00, 0x52, 0x0e, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x6d, 0x70, 0x6f,
0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x12, 0x75, 0x73, 0x65, 0x72, 0x56, 0x61, 0x72, 0x69, 0x61, 0x72, 0x74, 0x12, 0x55, 0x0a, 0x10, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x64,
0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x72, 0x79, 0x5f, 0x72, 0x75, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x70,
0x64, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x72, 0x65, 0x61, 0x64, 0x6d, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x43, 0x6f, 0x6d, 0x70,
0x65, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x22, 0x7a, 0x0a, 0x11, 0x55, 0x70, 0x64, 0x61, 0x74, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74,
0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x44, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x48, 0x00, 0x52, 0x0e, 0x74, 0x65, 0x6d, 0x70, 0x6c,
0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x61, 0x74, 0x65, 0x44, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x1a, 0x5b, 0x0a, 0x0e, 0x57, 0x6f, 0x72,
0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x65, 0x64, 0x12, 0x43, 0x0a, 0x0f, 0x76, 0x61, 0x72, 0x69, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x73,
0x61, 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74,
0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x65, 0x12, 0x33, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x02,
0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0e, 0x76, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e,
0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x4a, 0x04, 0x08, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x09, 0x72, 0x65, 0x73,
0x02, 0x10, 0x03, 0x22, 0x4a, 0x0a, 0x12, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x51, 0x75, 0x6f, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x1a, 0x81, 0x02, 0x0a, 0x0e, 0x54, 0x65, 0x6d, 0x70, 0x6c,
0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x61, 0x74, 0x65, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x3e, 0x0a, 0x0f, 0x73, 0x74, 0x61,
0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x72, 0x74, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03,
0x12, 0x1d, 0x0a, 0x0a, 0x64, 0x61, 0x69, 0x6c, 0x79, 0x5f, 0x63, 0x6f, 0x73, 0x74, 0x18, 0x02, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72,
0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x64, 0x61, 0x69, 0x6c, 0x79, 0x43, 0x6f, 0x73, 0x74, 0x22, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x0e, 0x73, 0x74, 0x61, 0x72, 0x74,
0x68, 0x0a, 0x13, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x61, 0x52, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x3c, 0x0a, 0x0e, 0x73, 0x74, 0x6f,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x6f, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x70, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28,
0x28, 0x08, 0x52, 0x02, 0x6f, 0x6b, 0x12, 0x29, 0x0a, 0x10, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e,
0x73, 0x5f, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x0d, 0x73, 0x74, 0x6f, 0x70, 0x52, 0x65,
0x52, 0x0f, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x73, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x43, 0x0a, 0x0f, 0x72, 0x69, 0x63, 0x68, 0x5f,
0x64, 0x12, 0x16, 0x0a, 0x06, 0x62, 0x75, 0x64, 0x67, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b,
0x05, 0x52, 0x06, 0x62, 0x75, 0x64, 0x67, 0x65, 0x74, 0x2a, 0x34, 0x0a, 0x09, 0x4c, 0x6f, 0x67, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52,
0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x12, 0x50, 0x52, 0x4f, 0x56, 0x49, 0x53, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x52, 0x0e, 0x72, 0x69,
0x49, 0x4f, 0x4e, 0x45, 0x52, 0x5f, 0x44, 0x41, 0x45, 0x4d, 0x4f, 0x4e, 0x10, 0x00, 0x12, 0x0f, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x2c, 0x0a, 0x12,
0x0a, 0x0b, 0x50, 0x52, 0x4f, 0x56, 0x49, 0x53, 0x49, 0x4f, 0x4e, 0x45, 0x52, 0x10, 0x01, 0x32, 0x67, 0x69, 0x74, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65,
0xec, 0x02, 0x0a, 0x11, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x44, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x67, 0x69, 0x74, 0x41, 0x75, 0x74,
0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x12, 0x3c, 0x0a, 0x0a, 0x41, 0x63, 0x71, 0x75, 0x69, 0x72, 0x65, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x1a, 0x45, 0x0a, 0x0e, 0x54, 0x65,
0x4a, 0x6f, 0x62, 0x12, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x44, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x12, 0x33, 0x0a, 0x09,
0x72, 0x64, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32,
0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x41, 0x63, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65,
0x4a, 0x6f, 0x62, 0x12, 0x52, 0x0a, 0x0b, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x51, 0x75, 0x6f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,
0x74, 0x61, 0x12, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x73, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0xb0, 0x01, 0x0a, 0x03, 0x4c, 0x6f,
0x64, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x61, 0x52, 0x65, 0x71, 0x67, 0x12, 0x2f, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x0e, 0x32, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64,
0x2e, 0x4c, 0x6f, 0x67, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72,
0x63, 0x65, 0x12, 0x2b, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28,
0x0e, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e,
0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12,
0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x03, 0x20,
0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x14,
0x0a, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73,
0x74, 0x61, 0x67, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x05,
0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x22, 0x8a, 0x02, 0x0a,
0x10, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x04, 0x6c, 0x6f, 0x67, 0x73,
0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69,
0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x4c, 0x6f, 0x67, 0x52, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x12,
0x4c, 0x0a, 0x12, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x76, 0x61, 0x72, 0x69,
0x61, 0x62, 0x6c, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72,
0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61,
0x74, 0x65, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x11, 0x74, 0x65, 0x6d, 0x70,
0x6c, 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x12, 0x4c, 0x0a,
0x14, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x76,
0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72,
0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62,
0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x12, 0x75, 0x73, 0x65, 0x72, 0x56, 0x61, 0x72,
0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x72,
0x65, 0x61, 0x64, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x72, 0x65, 0x61,
0x64, 0x6d, 0x65, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x22, 0x7a, 0x0a, 0x11, 0x55, 0x70, 0x64,
0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a,
0x0a, 0x08, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08,
0x52, 0x08, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x65, 0x64, 0x12, 0x43, 0x0a, 0x0f, 0x76, 0x61,
0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x03, 0x20,
0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65,
0x72, 0x2e, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52,
0x0e, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x4a,
0x04, 0x08, 0x02, 0x10, 0x03, 0x22, 0x4a, 0x0a, 0x12, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x51,
0x75, 0x6f, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x6a,
0x6f, 0x62, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62,
0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x64, 0x61, 0x69, 0x6c, 0x79, 0x5f, 0x63, 0x6f, 0x73, 0x74,
0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x64, 0x61, 0x69, 0x6c, 0x79, 0x43, 0x6f, 0x73,
0x74, 0x22, 0x68, 0x0a, 0x13, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x61,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x6f, 0x6b, 0x18, 0x01,
0x20, 0x01, 0x28, 0x08, 0x52, 0x02, 0x6f, 0x6b, 0x12, 0x29, 0x0a, 0x10, 0x63, 0x72, 0x65, 0x64,
0x69, 0x74, 0x73, 0x5f, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01,
0x28, 0x05, 0x52, 0x0f, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x73, 0x43, 0x6f, 0x6e, 0x73, 0x75,
0x6d, 0x65, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x62, 0x75, 0x64, 0x67, 0x65, 0x74, 0x18, 0x03, 0x20,
0x01, 0x28, 0x05, 0x52, 0x06, 0x62, 0x75, 0x64, 0x67, 0x65, 0x74, 0x2a, 0x34, 0x0a, 0x09, 0x4c,
0x6f, 0x67, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x12, 0x50, 0x52, 0x4f, 0x56,
0x49, 0x53, 0x49, 0x4f, 0x4e, 0x45, 0x52, 0x5f, 0x44, 0x41, 0x45, 0x4d, 0x4f, 0x4e, 0x10, 0x00,
0x12, 0x0f, 0x0a, 0x0b, 0x50, 0x52, 0x4f, 0x56, 0x49, 0x53, 0x49, 0x4f, 0x4e, 0x45, 0x52, 0x10,
0x01, 0x32, 0xec, 0x02, 0x0a, 0x11, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65,
0x72, 0x44, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x12, 0x3c, 0x0a, 0x0a, 0x41, 0x63, 0x71, 0x75, 0x69,
0x72, 0x65, 0x4a, 0x6f, 0x62, 0x12, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f,
0x6e, 0x65, 0x72, 0x64, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x19, 0x2e, 0x70, 0x72, 0x6f,
0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x41, 0x63, 0x71, 0x75, 0x69, 0x72,
0x65, 0x64, 0x4a, 0x6f, 0x62, 0x12, 0x52, 0x0a, 0x0b, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x51,
0x75, 0x6f, 0x74, 0x61, 0x12, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e,
0x65, 0x72, 0x64, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x61, 0x52, 0x65, 0x72, 0x64, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x61, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x09, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69,
0x65, 0x4a, 0x6f, 0x62, 0x12, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x51, 0x75, 0x6f, 0x74,
0x65, 0x72, 0x64, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x09, 0x55, 0x70, 0x64,
0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x12, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69,
0x65, 0x72, 0x64, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x52,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x07, 0x46, 0x61, 0x69, 0x6c, 0x4a, 0x6f, 0x62, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69,
0x12, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x52,
0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x1a, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x07, 0x46, 0x61, 0x69, 0x6c, 0x4a,
0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3e, 0x6f, 0x62, 0x12, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72,
0x0a, 0x0b, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x12, 0x1a, 0x2e, 0x64, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x1a, 0x13, 0x2e, 0x70, 0x72,
0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x43, 0x6f, 0x6d, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79,
0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x1a, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x12, 0x3e, 0x0a, 0x0b, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x12,
0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x42, 0x2e, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x43,
0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x64, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x1a, 0x13, 0x2e, 0x70, 0x72,
0x65, 0x72, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x76, 0x32, 0x2f, 0x70, 0x72, 0x6f, 0x76, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79,
0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x42, 0x2e, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x76, 0x32, 0x2f, 0x70, 0x72,
0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
} }
var ( var (
@ -1618,7 +1616,7 @@ var file_provisionerd_proto_provisionerd_proto_goTypes = []interface{}{
(*proto.VariableValue)(nil), // 22: provisioner.VariableValue (*proto.VariableValue)(nil), // 22: provisioner.VariableValue
(*proto.RichParameterValue)(nil), // 23: provisioner.RichParameterValue (*proto.RichParameterValue)(nil), // 23: provisioner.RichParameterValue
(*proto.GitAuthProvider)(nil), // 24: provisioner.GitAuthProvider (*proto.GitAuthProvider)(nil), // 24: provisioner.GitAuthProvider
(*proto.Provision_Metadata)(nil), // 25: provisioner.Provision.Metadata (*proto.Metadata)(nil), // 25: provisioner.Metadata
(*proto.Resource)(nil), // 26: provisioner.Resource (*proto.Resource)(nil), // 26: provisioner.Resource
(*proto.RichParameter)(nil), // 27: provisioner.RichParameter (*proto.RichParameter)(nil), // 27: provisioner.RichParameter
} }
@ -1642,12 +1640,12 @@ var file_provisionerd_proto_provisionerd_proto_depIdxs = []int32{
23, // 16: provisionerd.AcquiredJob.WorkspaceBuild.rich_parameter_values:type_name -> provisioner.RichParameterValue 23, // 16: provisionerd.AcquiredJob.WorkspaceBuild.rich_parameter_values:type_name -> provisioner.RichParameterValue
22, // 17: provisionerd.AcquiredJob.WorkspaceBuild.variable_values:type_name -> provisioner.VariableValue 22, // 17: provisionerd.AcquiredJob.WorkspaceBuild.variable_values:type_name -> provisioner.VariableValue
24, // 18: provisionerd.AcquiredJob.WorkspaceBuild.git_auth_providers:type_name -> provisioner.GitAuthProvider 24, // 18: provisionerd.AcquiredJob.WorkspaceBuild.git_auth_providers:type_name -> provisioner.GitAuthProvider
25, // 19: provisionerd.AcquiredJob.WorkspaceBuild.metadata:type_name -> provisioner.Provision.Metadata 25, // 19: provisionerd.AcquiredJob.WorkspaceBuild.metadata:type_name -> provisioner.Metadata
25, // 20: provisionerd.AcquiredJob.TemplateImport.metadata:type_name -> provisioner.Provision.Metadata 25, // 20: provisionerd.AcquiredJob.TemplateImport.metadata:type_name -> provisioner.Metadata
22, // 21: provisionerd.AcquiredJob.TemplateImport.user_variable_values:type_name -> provisioner.VariableValue 22, // 21: provisionerd.AcquiredJob.TemplateImport.user_variable_values:type_name -> provisioner.VariableValue
23, // 22: provisionerd.AcquiredJob.TemplateDryRun.rich_parameter_values:type_name -> provisioner.RichParameterValue 23, // 22: provisionerd.AcquiredJob.TemplateDryRun.rich_parameter_values:type_name -> provisioner.RichParameterValue
22, // 23: provisionerd.AcquiredJob.TemplateDryRun.variable_values:type_name -> provisioner.VariableValue 22, // 23: provisionerd.AcquiredJob.TemplateDryRun.variable_values:type_name -> provisioner.VariableValue
25, // 24: provisionerd.AcquiredJob.TemplateDryRun.metadata:type_name -> provisioner.Provision.Metadata 25, // 24: provisionerd.AcquiredJob.TemplateDryRun.metadata:type_name -> provisioner.Metadata
26, // 25: provisionerd.CompletedJob.WorkspaceBuild.resources:type_name -> provisioner.Resource 26, // 25: provisionerd.CompletedJob.WorkspaceBuild.resources:type_name -> provisioner.Resource
26, // 26: provisionerd.CompletedJob.TemplateImport.start_resources:type_name -> provisioner.Resource 26, // 26: provisionerd.CompletedJob.TemplateImport.start_resources:type_name -> provisioner.Resource
26, // 27: provisionerd.CompletedJob.TemplateImport.stop_resources:type_name -> provisioner.Resource 26, // 27: provisionerd.CompletedJob.TemplateImport.stop_resources:type_name -> provisioner.Resource

View File

@ -19,12 +19,12 @@ message AcquiredJob {
repeated provisioner.RichParameterValue rich_parameter_values = 4; repeated provisioner.RichParameterValue rich_parameter_values = 4;
repeated provisioner.VariableValue variable_values = 5; repeated provisioner.VariableValue variable_values = 5;
repeated provisioner.GitAuthProvider git_auth_providers = 6; repeated provisioner.GitAuthProvider git_auth_providers = 6;
provisioner.Provision.Metadata metadata = 7; provisioner.Metadata metadata = 7;
bytes state = 8; bytes state = 8;
string log_level = 9; string log_level = 9;
} }
message TemplateImport { message TemplateImport {
provisioner.Provision.Metadata metadata = 1; provisioner.Metadata metadata = 1;
repeated provisioner.VariableValue user_variable_values = 2; repeated provisioner.VariableValue user_variable_values = 2;
} }
message TemplateDryRun { message TemplateDryRun {
@ -32,7 +32,7 @@ message AcquiredJob {
repeated provisioner.RichParameterValue rich_parameter_values = 2; repeated provisioner.RichParameterValue rich_parameter_values = 2;
repeated provisioner.VariableValue variable_values = 3; repeated provisioner.VariableValue variable_values = 3;
provisioner.Provision.Metadata metadata = 4; provisioner.Metadata metadata = 4;
} }
string job_id = 1; string job_id = 1;
@ -45,9 +45,9 @@ message AcquiredJob {
TemplateImport template_import = 7; TemplateImport template_import = 7;
TemplateDryRun template_dry_run = 8; TemplateDryRun template_dry_run = 8;
} }
// trace_metadata is currently used for tracing information only. It allows // trace_metadata is currently used for tracing information only. It allows
// jobs to be tied to the request that created them. // jobs to be tied to the request that created them.
map<string, string> trace_metadata = 9; map<string, string> trace_metadata = 9;
} }
message FailedJob { message FailedJob {
@ -113,7 +113,7 @@ message UpdateJobRequest {
string job_id = 1; string job_id = 1;
repeated Log logs = 2; repeated Log logs = 2;
repeated provisioner.TemplateVariable template_variables = 4; repeated provisioner.TemplateVariable template_variables = 4;
repeated provisioner.VariableValue user_variable_values = 5; repeated provisioner.VariableValue user_variable_values = 5;
bytes readme = 6; bytes readme = 6;
} }
@ -121,7 +121,7 @@ message UpdateJobResponse {
reserved 2; reserved 2;
bool canceled = 1; bool canceled = 1;
repeated provisioner.VariableValue variable_values = 3; repeated provisioner.VariableValue variable_values = 3;
} }
message CommitQuotaRequest { message CommitQuotaRequest {

View File

@ -12,7 +12,6 @@ import (
"github.com/hashicorp/yamux" "github.com/hashicorp/yamux"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto" "github.com/prometheus/client_golang/prometheus/promauto"
"github.com/spf13/afero"
"github.com/valyala/fasthttp/fasthttputil" "github.com/valyala/fasthttp/fasthttputil"
"go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/attribute"
semconv "go.opentelemetry.io/otel/semconv/v1.14.0" semconv "go.opentelemetry.io/otel/semconv/v1.14.0"
@ -44,7 +43,6 @@ type Provisioners map[string]sdkproto.DRPCProvisionerClient
// Options provides customizations to the behavior of a provisioner daemon. // Options provides customizations to the behavior of a provisioner daemon.
type Options struct { type Options struct {
Filesystem afero.Fs
Logger slog.Logger Logger slog.Logger
TracerProvider trace.TracerProvider TracerProvider trace.TracerProvider
Metrics *Metrics Metrics *Metrics
@ -56,8 +54,6 @@ type Options struct {
JobPollJitter time.Duration JobPollJitter time.Duration
JobPollDebounce time.Duration JobPollDebounce time.Duration
Provisioners Provisioners Provisioners Provisioners
// WorkDirectory must not be used by multiple processes at once.
WorkDirectory string
} }
// New creates and starts a provisioner daemon. // New creates and starts a provisioner daemon.
@ -80,9 +76,6 @@ func New(clientDialer Dialer, opts *Options) *Server {
if opts.LogBufferInterval == 0 { if opts.LogBufferInterval == 0 {
opts.LogBufferInterval = 250 * time.Millisecond opts.LogBufferInterval = 250 * time.Millisecond
} }
if opts.Filesystem == nil {
opts.Filesystem = afero.NewOsFs()
}
if opts.TracerProvider == nil { if opts.TracerProvider == nil {
opts.TracerProvider = trace.NewNoopTracerProvider() opts.TracerProvider = trace.NewNoopTracerProvider()
} }
@ -405,8 +398,6 @@ func (p *Server) acquireJob(ctx context.Context) {
Updater: p, Updater: p,
QuotaCommitter: p, QuotaCommitter: p,
Logger: p.opts.Logger.Named("runner"), Logger: p.opts.Logger.Named("runner"),
Filesystem: p.opts.Filesystem,
WorkDirectory: p.opts.WorkDirectory,
Provisioner: provisioner, Provisioner: provisioner,
UpdateInterval: p.opts.UpdateInterval, UpdateInterval: p.opts.UpdateInterval,
ForceCancelInterval: p.opts.ForceCancelInterval, ForceCancelInterval: p.opts.ForceCancelInterval,

View File

@ -25,7 +25,6 @@ import (
"cdr.dev/slog/sloggers/slogtest" "cdr.dev/slog/sloggers/slogtest"
"github.com/coder/coder/v2/provisionerd" "github.com/coder/coder/v2/provisionerd"
"github.com/coder/coder/v2/provisionerd/proto" "github.com/coder/coder/v2/provisionerd/proto"
"github.com/coder/coder/v2/provisionerd/runner"
"github.com/coder/coder/v2/provisionersdk" "github.com/coder/coder/v2/provisionersdk"
sdkproto "github.com/coder/coder/v2/provisionersdk/proto" sdkproto "github.com/coder/coder/v2/provisionersdk/proto"
"github.com/coder/coder/v2/testutil" "github.com/coder/coder/v2/testutil"
@ -129,7 +128,7 @@ func TestProvisionerd(t *testing.T) {
}), }),
Type: &proto.AcquiredJob_TemplateImport_{ Type: &proto.AcquiredJob_TemplateImport_{
TemplateImport: &proto.AcquiredJob_TemplateImport{ TemplateImport: &proto.AcquiredJob_TemplateImport{
Metadata: &sdkproto.Provision_Metadata{}, Metadata: &sdkproto.Metadata{},
}, },
}, },
}, nil }, nil
@ -144,10 +143,15 @@ func TestProvisionerd(t *testing.T) {
}), nil }), nil
}, provisionerd.Provisioners{ }, provisionerd.Provisioners{
"someprovisioner": createProvisionerClient(t, done, provisionerTestServer{ "someprovisioner": createProvisionerClient(t, done, provisionerTestServer{
parse: func(request *sdkproto.Parse_Request, stream sdkproto.DRPCProvisioner_ParseStream) error { parse: func(_ *provisionersdk.Session, _ *sdkproto.ParseRequest, _ <-chan struct{}) *sdkproto.ParseComplete {
closerMutex.Lock() closerMutex.Lock()
defer closerMutex.Unlock() defer closerMutex.Unlock()
return closer.Close() err := closer.Close()
c := &sdkproto.ParseComplete{}
if err != nil {
c.Error = err.Error()
}
return c
}, },
}), }),
}) })
@ -180,7 +184,7 @@ func TestProvisionerd(t *testing.T) {
}), }),
Type: &proto.AcquiredJob_TemplateImport_{ Type: &proto.AcquiredJob_TemplateImport_{
TemplateImport: &proto.AcquiredJob_TemplateImport{ TemplateImport: &proto.AcquiredJob_TemplateImport{
Metadata: &sdkproto.Provision_Metadata{}, Metadata: &sdkproto.Metadata{},
}, },
}, },
}, nil }, nil
@ -220,7 +224,7 @@ func TestProvisionerd(t *testing.T) {
}), }),
Type: &proto.AcquiredJob_TemplateImport_{ Type: &proto.AcquiredJob_TemplateImport_{
TemplateImport: &proto.AcquiredJob_TemplateImport{ TemplateImport: &proto.AcquiredJob_TemplateImport{
Metadata: &sdkproto.Provision_Metadata{}, Metadata: &sdkproto.Metadata{},
}, },
}, },
}, nil }, nil
@ -235,9 +239,13 @@ func TestProvisionerd(t *testing.T) {
}), nil }), nil
}, provisionerd.Provisioners{ }, provisionerd.Provisioners{
"someprovisioner": createProvisionerClient(t, done, provisionerTestServer{ "someprovisioner": createProvisionerClient(t, done, provisionerTestServer{
parse: func(request *sdkproto.Parse_Request, stream sdkproto.DRPCProvisioner_ParseStream) error { parse: func(
<-stream.Context().Done() _ *provisionersdk.Session,
return nil _ *sdkproto.ParseRequest,
cancelOrComplete <-chan struct{},
) *sdkproto.ParseComplete {
<-cancelOrComplete
return &sdkproto.ParseComplete{}
}, },
}), }),
}) })
@ -255,7 +263,6 @@ func TestProvisionerd(t *testing.T) {
didComplete atomic.Bool didComplete atomic.Bool
didLog atomic.Bool didLog atomic.Bool
didAcquireJob atomic.Bool didAcquireJob atomic.Bool
didDryRun = atomic.NewBool(true)
didReadme atomic.Bool didReadme atomic.Bool
completeChan = make(chan struct{}) completeChan = make(chan struct{})
completeOnce sync.Once completeOnce sync.Once
@ -273,12 +280,12 @@ func TestProvisionerd(t *testing.T) {
JobId: "test", JobId: "test",
Provisioner: "someprovisioner", Provisioner: "someprovisioner",
TemplateSourceArchive: createTar(t, map[string]string{ TemplateSourceArchive: createTar(t, map[string]string{
"test.txt": "content", "test.txt": "content",
runner.ReadmeFile: "# A cool template 😎\n", provisionersdk.ReadmeFile: "# A cool template 😎\n",
}), }),
Type: &proto.AcquiredJob_TemplateImport_{ Type: &proto.AcquiredJob_TemplateImport_{
TemplateImport: &proto.AcquiredJob_TemplateImport{ TemplateImport: &proto.AcquiredJob_TemplateImport{
Metadata: &sdkproto.Provision_Metadata{}, Metadata: &sdkproto.Metadata{},
}, },
}, },
}, nil }, nil
@ -299,54 +306,34 @@ func TestProvisionerd(t *testing.T) {
}), nil }), nil
}, provisionerd.Provisioners{ }, provisionerd.Provisioners{
"someprovisioner": createProvisionerClient(t, done, provisionerTestServer{ "someprovisioner": createProvisionerClient(t, done, provisionerTestServer{
parse: func(request *sdkproto.Parse_Request, stream sdkproto.DRPCProvisioner_ParseStream) error { parse: func(
data, err := os.ReadFile(filepath.Join(request.Directory, "test.txt")) s *provisionersdk.Session,
_ *sdkproto.ParseRequest,
cancelOrComplete <-chan struct{},
) *sdkproto.ParseComplete {
data, err := os.ReadFile(filepath.Join(s.WorkDirectory, "test.txt"))
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, "content", string(data)) require.Equal(t, "content", string(data))
s.ProvisionLog(sdkproto.LogLevel_INFO, "hello")
err = stream.Send(&sdkproto.Parse_Response{ return &sdkproto.ParseComplete{}
Type: &sdkproto.Parse_Response_Log{
Log: &sdkproto.Log{
Level: sdkproto.LogLevel_INFO,
Output: "hello",
},
},
})
require.NoError(t, err)
err = stream.Send(&sdkproto.Parse_Response{
Type: &sdkproto.Parse_Response_Complete{
Complete: &sdkproto.Parse_Complete{},
},
})
require.NoError(t, err)
return nil
}, },
provision: func(stream sdkproto.DRPCProvisioner_ProvisionStream) error { plan: func(
request, err := stream.Recv() s *provisionersdk.Session,
require.NoError(t, err) _ *sdkproto.PlanRequest,
if request.GetApply() != nil { cancelOrComplete <-chan struct{},
didDryRun.Store(false) ) *sdkproto.PlanComplete {
s.ProvisionLog(sdkproto.LogLevel_INFO, "hello")
return &sdkproto.PlanComplete{
Resources: []*sdkproto.Resource{},
} }
err = stream.Send(&sdkproto.Provision_Response{ },
Type: &sdkproto.Provision_Response_Log{ apply: func(
Log: &sdkproto.Log{ _ *provisionersdk.Session,
Level: sdkproto.LogLevel_INFO, _ *sdkproto.ApplyRequest,
Output: "hello", _ <-chan struct{},
}, ) *sdkproto.ApplyComplete {
}, t.Error("dry run should not apply")
}) return &sdkproto.ApplyComplete{}
require.NoError(t, err)
err = stream.Send(&sdkproto.Provision_Response{
Type: &sdkproto.Provision_Response_Complete{
Complete: &sdkproto.Provision_Complete{
Resources: []*sdkproto.Resource{},
},
},
})
require.NoError(t, err)
return nil
}, },
}), }),
}) })
@ -355,7 +342,6 @@ func TestProvisionerd(t *testing.T) {
require.NoError(t, closer.Close()) require.NoError(t, closer.Close())
assert.True(t, didLog.Load(), "should log some updates") assert.True(t, didLog.Load(), "should log some updates")
assert.True(t, didComplete.Load(), "should complete the job") assert.True(t, didComplete.Load(), "should complete the job")
assert.True(t, didDryRun.Load(), "should be a dry run")
}) })
t.Run("TemplateDryRun", func(t *testing.T) { t.Run("TemplateDryRun", func(t *testing.T) {
@ -371,7 +357,7 @@ func TestProvisionerd(t *testing.T) {
completeChan = make(chan struct{}) completeChan = make(chan struct{})
completeOnce sync.Once completeOnce sync.Once
metadata = &sdkproto.Provision_Metadata{} metadata = &sdkproto.Metadata{}
) )
closer := createProvisionerd(t, func(ctx context.Context) (proto.DRPCProvisionerDaemonClient, error) { closer := createProvisionerd(t, func(ctx context.Context) (proto.DRPCProvisionerDaemonClient, error) {
@ -414,16 +400,22 @@ func TestProvisionerd(t *testing.T) {
}), nil }), nil
}, provisionerd.Provisioners{ }, provisionerd.Provisioners{
"someprovisioner": createProvisionerClient(t, done, provisionerTestServer{ "someprovisioner": createProvisionerClient(t, done, provisionerTestServer{
provision: func(stream sdkproto.DRPCProvisioner_ProvisionStream) error { plan: func(
err := stream.Send(&sdkproto.Provision_Response{ _ *provisionersdk.Session,
Type: &sdkproto.Provision_Response_Complete{ _ *sdkproto.PlanRequest,
Complete: &sdkproto.Provision_Complete{ _ <-chan struct{},
Resources: []*sdkproto.Resource{}, ) *sdkproto.PlanComplete {
}, return &sdkproto.PlanComplete{
}, Resources: []*sdkproto.Resource{},
}) }
require.NoError(t, err) },
return nil apply: func(
_ *provisionersdk.Session,
_ *sdkproto.ApplyRequest,
_ <-chan struct{},
) *sdkproto.ApplyComplete {
t.Error("dry run should not apply")
return &sdkproto.ApplyComplete{}
}, },
}), }),
}) })
@ -464,7 +456,7 @@ func TestProvisionerd(t *testing.T) {
}), }),
Type: &proto.AcquiredJob_WorkspaceBuild_{ Type: &proto.AcquiredJob_WorkspaceBuild_{
WorkspaceBuild: &proto.AcquiredJob_WorkspaceBuild{ WorkspaceBuild: &proto.AcquiredJob_WorkspaceBuild{
Metadata: &sdkproto.Provision_Metadata{}, Metadata: &sdkproto.Metadata{},
}, },
}, },
}, nil }, nil
@ -482,24 +474,20 @@ func TestProvisionerd(t *testing.T) {
}), nil }), nil
}, provisionerd.Provisioners{ }, provisionerd.Provisioners{
"someprovisioner": createProvisionerClient(t, done, provisionerTestServer{ "someprovisioner": createProvisionerClient(t, done, provisionerTestServer{
provision: func(stream sdkproto.DRPCProvisioner_ProvisionStream) error { plan: func(
err := stream.Send(&sdkproto.Provision_Response{ s *provisionersdk.Session,
Type: &sdkproto.Provision_Response_Log{ _ *sdkproto.PlanRequest,
Log: &sdkproto.Log{ cancelOrComplete <-chan struct{},
Level: sdkproto.LogLevel_DEBUG, ) *sdkproto.PlanComplete {
Output: "wow", s.ProvisionLog(sdkproto.LogLevel_DEBUG, "wow")
}, return &sdkproto.PlanComplete{}
}, },
}) apply: func(
require.NoError(t, err) _ *provisionersdk.Session,
_ *sdkproto.ApplyRequest,
err = stream.Send(&sdkproto.Provision_Response{ _ <-chan struct{},
Type: &sdkproto.Provision_Response_Complete{ ) *sdkproto.ApplyComplete {
Complete: &sdkproto.Provision_Complete{}, return &sdkproto.ApplyComplete{}
},
})
require.NoError(t, err)
return nil
}, },
}), }),
}) })
@ -540,7 +528,7 @@ func TestProvisionerd(t *testing.T) {
}), }),
Type: &proto.AcquiredJob_WorkspaceBuild_{ Type: &proto.AcquiredJob_WorkspaceBuild_{
WorkspaceBuild: &proto.AcquiredJob_WorkspaceBuild{ WorkspaceBuild: &proto.AcquiredJob_WorkspaceBuild{
Metadata: &sdkproto.Provision_Metadata{}, Metadata: &sdkproto.Metadata{},
}, },
}, },
}, nil }, nil
@ -567,40 +555,46 @@ func TestProvisionerd(t *testing.T) {
}), nil }), nil
}, provisionerd.Provisioners{ }, provisionerd.Provisioners{
"someprovisioner": createProvisionerClient(t, done, provisionerTestServer{ "someprovisioner": createProvisionerClient(t, done, provisionerTestServer{
provision: func(stream sdkproto.DRPCProvisioner_ProvisionStream) error { plan: func(
err := stream.Send(&sdkproto.Provision_Response{ s *provisionersdk.Session,
Type: &sdkproto.Provision_Response_Log{ _ *sdkproto.PlanRequest,
Log: &sdkproto.Log{ cancelOrComplete <-chan struct{},
Level: sdkproto.LogLevel_DEBUG, ) *sdkproto.PlanComplete {
Output: "wow", s.ProvisionLog(sdkproto.LogLevel_DEBUG, "wow")
return &sdkproto.PlanComplete{
Resources: []*sdkproto.Resource{
{
DailyCost: 10,
},
{
DailyCost: 15,
}, },
}, },
}) }
require.NoError(t, err) },
apply: func(
err = stream.Send(&sdkproto.Provision_Response{ _ *provisionersdk.Session,
Type: &sdkproto.Provision_Response_Complete{ _ *sdkproto.ApplyRequest,
Complete: &sdkproto.Provision_Complete{ _ <-chan struct{},
Resources: []*sdkproto.Resource{ ) *sdkproto.ApplyComplete {
{ t.Error("should not apply when resources exceed quota")
DailyCost: 10, return &sdkproto.ApplyComplete{
}, Resources: []*sdkproto.Resource{
{ {
DailyCost: 15, DailyCost: 10,
}, },
}, {
DailyCost: 15,
}, },
}, },
}) }
require.NoError(t, err)
return nil
}, },
}), }),
}) })
require.Condition(t, closedWithin(completeChan, testutil.WaitShort)) require.Condition(t, closedWithin(completeChan, testutil.WaitShort))
require.NoError(t, closer.Close()) require.NoError(t, closer.Close())
assert.True(t, didLog.Load(), "should log some updates") assert.True(t, didLog.Load(), "should log some updates")
assert.False(t, didComplete.Load(), "should complete the job") assert.False(t, didComplete.Load(), "should not complete the job")
assert.True(t, didFail.Load(), "should fail the job") assert.True(t, didFail.Load(), "should fail the job")
}) })
@ -633,7 +627,7 @@ func TestProvisionerd(t *testing.T) {
}), }),
Type: &proto.AcquiredJob_WorkspaceBuild_{ Type: &proto.AcquiredJob_WorkspaceBuild_{
WorkspaceBuild: &proto.AcquiredJob_WorkspaceBuild{ WorkspaceBuild: &proto.AcquiredJob_WorkspaceBuild{
Metadata: &sdkproto.Provision_Metadata{}, Metadata: &sdkproto.Metadata{},
}, },
}, },
}, nil }, nil
@ -646,14 +640,24 @@ func TestProvisionerd(t *testing.T) {
}), nil }), nil
}, provisionerd.Provisioners{ }, provisionerd.Provisioners{
"someprovisioner": createProvisionerClient(t, done, provisionerTestServer{ "someprovisioner": createProvisionerClient(t, done, provisionerTestServer{
provision: func(stream sdkproto.DRPCProvisioner_ProvisionStream) error { plan: func(
return stream.Send(&sdkproto.Provision_Response{ s *provisionersdk.Session,
Type: &sdkproto.Provision_Response_Complete{ _ *sdkproto.PlanRequest,
Complete: &sdkproto.Provision_Complete{ cancelOrComplete <-chan struct{},
Error: "some error", ) *sdkproto.PlanComplete {
}, return &sdkproto.PlanComplete{
}, Error: "some error",
}) }
},
apply: func(
_ *provisionersdk.Session,
_ *sdkproto.ApplyRequest,
_ <-chan struct{},
) *sdkproto.ApplyComplete {
t.Error("should not apply when plan errors")
return &sdkproto.ApplyComplete{
Error: "some error",
}
}, },
}), }),
}) })
@ -683,7 +687,7 @@ func TestProvisionerd(t *testing.T) {
}), }),
Type: &proto.AcquiredJob_WorkspaceBuild_{ Type: &proto.AcquiredJob_WorkspaceBuild_{
WorkspaceBuild: &proto.AcquiredJob_WorkspaceBuild{ WorkspaceBuild: &proto.AcquiredJob_WorkspaceBuild{
Metadata: &sdkproto.Provision_Metadata{}, Metadata: &sdkproto.Metadata{},
}, },
}, },
}, nil }, nil
@ -712,31 +716,24 @@ func TestProvisionerd(t *testing.T) {
}), nil }), nil
}, provisionerd.Provisioners{ }, provisionerd.Provisioners{
"someprovisioner": createProvisionerClient(t, done, provisionerTestServer{ "someprovisioner": createProvisionerClient(t, done, provisionerTestServer{
provision: func(stream sdkproto.DRPCProvisioner_ProvisionStream) error { plan: func(
// Ignore the first provision message! s *provisionersdk.Session,
_, _ = stream.Recv() _ *sdkproto.PlanRequest,
canceledOrComplete <-chan struct{},
err := stream.Send(&sdkproto.Provision_Response{ ) *sdkproto.PlanComplete {
Type: &sdkproto.Provision_Response_Log{ s.ProvisionLog(sdkproto.LogLevel_DEBUG, "in progress")
Log: &sdkproto.Log{ <-canceledOrComplete
Level: sdkproto.LogLevel_DEBUG, return &sdkproto.PlanComplete{
Output: "in progress", Error: "some error",
}, }
}, },
}) apply: func(
require.NoError(t, err) _ *provisionersdk.Session,
_ *sdkproto.ApplyRequest,
msg, err := stream.Recv() _ <-chan struct{},
require.NoError(t, err) ) *sdkproto.ApplyComplete {
require.NotNil(t, msg.GetCancel()) t.Error("should not apply when shut down during plan")
return &sdkproto.ApplyComplete{}
return stream.Send(&sdkproto.Provision_Response{
Type: &sdkproto.Provision_Response_Complete{
Complete: &sdkproto.Provision_Complete{
Error: "some error",
},
},
})
}, },
}), }),
}) })
@ -768,7 +765,7 @@ func TestProvisionerd(t *testing.T) {
}), }),
Type: &proto.AcquiredJob_WorkspaceBuild_{ Type: &proto.AcquiredJob_WorkspaceBuild_{
WorkspaceBuild: &proto.AcquiredJob_WorkspaceBuild{ WorkspaceBuild: &proto.AcquiredJob_WorkspaceBuild{
Metadata: &sdkproto.Provision_Metadata{}, Metadata: &sdkproto.Metadata{},
}, },
}, },
}, nil }, nil
@ -805,31 +802,24 @@ func TestProvisionerd(t *testing.T) {
}), nil }), nil
}, provisionerd.Provisioners{ }, provisionerd.Provisioners{
"someprovisioner": createProvisionerClient(t, done, provisionerTestServer{ "someprovisioner": createProvisionerClient(t, done, provisionerTestServer{
provision: func(stream sdkproto.DRPCProvisioner_ProvisionStream) error { plan: func(
// Ignore the first provision message! s *provisionersdk.Session,
_, _ = stream.Recv() _ *sdkproto.PlanRequest,
canceledOrComplete <-chan struct{},
err := stream.Send(&sdkproto.Provision_Response{ ) *sdkproto.PlanComplete {
Type: &sdkproto.Provision_Response_Log{ s.ProvisionLog(sdkproto.LogLevel_DEBUG, "in progress")
Log: &sdkproto.Log{ <-canceledOrComplete
Level: sdkproto.LogLevel_DEBUG, return &sdkproto.PlanComplete{
Output: "in progress", Error: "some error",
}, }
}, },
}) apply: func(
require.NoError(t, err) _ *provisionersdk.Session,
_ *sdkproto.ApplyRequest,
msg, err := stream.Recv() _ <-chan struct{},
require.NoError(t, err) ) *sdkproto.ApplyComplete {
require.NotNil(t, msg.GetCancel()) t.Error("should not apply when shut down during plan")
return &sdkproto.ApplyComplete{}
return stream.Send(&sdkproto.Provision_Response{
Type: &sdkproto.Provision_Response_Complete{
Complete: &sdkproto.Provision_Complete{
Error: "some error",
},
},
})
}, },
}), }),
}) })
@ -867,7 +857,7 @@ func TestProvisionerd(t *testing.T) {
}), }),
Type: &proto.AcquiredJob_WorkspaceBuild_{ Type: &proto.AcquiredJob_WorkspaceBuild_{
WorkspaceBuild: &proto.AcquiredJob_WorkspaceBuild{ WorkspaceBuild: &proto.AcquiredJob_WorkspaceBuild{
Metadata: &sdkproto.Provision_Metadata{}, Metadata: &sdkproto.Metadata{},
}, },
}, },
}, nil }, nil
@ -898,16 +888,22 @@ func TestProvisionerd(t *testing.T) {
return client, nil return client, nil
}, provisionerd.Provisioners{ }, provisionerd.Provisioners{
"someprovisioner": createProvisionerClient(t, done, provisionerTestServer{ "someprovisioner": createProvisionerClient(t, done, provisionerTestServer{
provision: func(stream sdkproto.DRPCProvisioner_ProvisionStream) error { plan: func(
// Ignore the first provision message! _ *provisionersdk.Session,
_, _ = stream.Recv() _ *sdkproto.PlanRequest,
return stream.Send(&sdkproto.Provision_Response{ _ <-chan struct{},
Type: &sdkproto.Provision_Response_Complete{ ) *sdkproto.PlanComplete {
Complete: &sdkproto.Provision_Complete{ return &sdkproto.PlanComplete{
Error: "some error", Error: "some error",
}, }
}, },
}) apply: func(
_ *provisionersdk.Session,
_ *sdkproto.ApplyRequest,
_ <-chan struct{},
) *sdkproto.ApplyComplete {
t.Error("should not apply when error during plan")
return &sdkproto.ApplyComplete{}
}, },
}), }),
}) })
@ -945,7 +941,7 @@ func TestProvisionerd(t *testing.T) {
}), }),
Type: &proto.AcquiredJob_WorkspaceBuild_{ Type: &proto.AcquiredJob_WorkspaceBuild_{
WorkspaceBuild: &proto.AcquiredJob_WorkspaceBuild{ WorkspaceBuild: &proto.AcquiredJob_WorkspaceBuild{
Metadata: &sdkproto.Provision_Metadata{}, Metadata: &sdkproto.Metadata{},
}, },
}, },
}, nil }, nil
@ -977,14 +973,19 @@ func TestProvisionerd(t *testing.T) {
return client, nil return client, nil
}, provisionerd.Provisioners{ }, provisionerd.Provisioners{
"someprovisioner": createProvisionerClient(t, done, provisionerTestServer{ "someprovisioner": createProvisionerClient(t, done, provisionerTestServer{
provision: func(stream sdkproto.DRPCProvisioner_ProvisionStream) error { plan: func(
// Ignore the first provision message! _ *provisionersdk.Session,
_, _ = stream.Recv() _ *sdkproto.PlanRequest,
return stream.Send(&sdkproto.Provision_Response{ _ <-chan struct{},
Type: &sdkproto.Provision_Response_Complete{ ) *sdkproto.PlanComplete {
Complete: &sdkproto.Provision_Complete{}, return &sdkproto.PlanComplete{}
}, },
}) apply: func(
_ *provisionersdk.Session,
_ *sdkproto.ApplyRequest,
_ <-chan struct{},
) *sdkproto.ApplyComplete {
return &sdkproto.ApplyComplete{}
}, },
}), }),
}) })
@ -1023,7 +1024,7 @@ func TestProvisionerd(t *testing.T) {
}), }),
Type: &proto.AcquiredJob_WorkspaceBuild_{ Type: &proto.AcquiredJob_WorkspaceBuild_{
WorkspaceBuild: &proto.AcquiredJob_WorkspaceBuild{ WorkspaceBuild: &proto.AcquiredJob_WorkspaceBuild{
Metadata: &sdkproto.Provision_Metadata{}, Metadata: &sdkproto.Metadata{},
}, },
}, },
}, nil }, nil
@ -1056,24 +1057,21 @@ func TestProvisionerd(t *testing.T) {
}), nil }), nil
}, provisionerd.Provisioners{ }, provisionerd.Provisioners{
"someprovisioner": createProvisionerClient(t, done, provisionerTestServer{ "someprovisioner": createProvisionerClient(t, done, provisionerTestServer{
provision: func(stream sdkproto.DRPCProvisioner_ProvisionStream) error { plan: func(
err := stream.Send(&sdkproto.Provision_Response{ s *provisionersdk.Session,
Type: &sdkproto.Provision_Response_Log{ _ *sdkproto.PlanRequest,
Log: &sdkproto.Log{ _ <-chan struct{},
Level: sdkproto.LogLevel_DEBUG, ) *sdkproto.PlanComplete {
Output: "wow", s.ProvisionLog(sdkproto.LogLevel_DEBUG, "wow")
}, return &sdkproto.PlanComplete{}
}, },
}) apply: func(
require.NoError(t, err) s *provisionersdk.Session,
_ *sdkproto.ApplyRequest,
err = stream.Send(&sdkproto.Provision_Response{ _ <-chan struct{},
Type: &sdkproto.Provision_Response_Complete{ ) *sdkproto.ApplyComplete {
Complete: &sdkproto.Provision_Complete{}, s.ProvisionLog(sdkproto.LogLevel_DEBUG, "wow")
}, return &sdkproto.ApplyComplete{}
})
require.NoError(t, err)
return nil
}, },
}), }),
}) })
@ -1111,7 +1109,6 @@ func createProvisionerd(t *testing.T, dialer provisionerd.Dialer, provisioners p
JobPollInterval: 50 * time.Millisecond, JobPollInterval: 50 * time.Millisecond,
UpdateInterval: 50 * time.Millisecond, UpdateInterval: 50 * time.Millisecond,
Provisioners: provisioners, Provisioners: provisioners,
WorkDirectory: t.TempDir(),
}) })
t.Cleanup(func() { t.Cleanup(func() {
_ = server.Close() _ = server.Close()
@ -1172,15 +1169,15 @@ func createProvisionerClient(t *testing.T, done <-chan struct{}, server provisio
_ = clientPipe.Close() _ = clientPipe.Close()
_ = serverPipe.Close() _ = serverPipe.Close()
}) })
mux := drpcmux.New()
err := sdkproto.DRPCRegisterProvisioner(mux, &server)
require.NoError(t, err)
srv := drpcserver.New(mux)
ctx, cancelFunc := context.WithCancel(context.Background()) ctx, cancelFunc := context.WithCancel(context.Background())
closed := make(chan struct{}) closed := make(chan struct{})
go func() { go func() {
defer close(closed) defer close(closed)
_ = srv.Serve(ctx, serverPipe) _ = provisionersdk.Serve(ctx, &server, &provisionersdk.ServeOptions{
Listener: serverPipe,
Logger: slogtest.Make(t, nil).Leveled(slog.LevelDebug).Named("test-provisioner"),
WorkDirectory: t.TempDir(),
})
}() }()
t.Cleanup(func() { t.Cleanup(func() {
cancelFunc() cancelFunc()
@ -1200,16 +1197,21 @@ func createProvisionerClient(t *testing.T, done <-chan struct{}, server provisio
} }
type provisionerTestServer struct { type provisionerTestServer struct {
parse func(request *sdkproto.Parse_Request, stream sdkproto.DRPCProvisioner_ParseStream) error parse func(s *provisionersdk.Session, r *sdkproto.ParseRequest, canceledOrComplete <-chan struct{}) *sdkproto.ParseComplete
provision func(stream sdkproto.DRPCProvisioner_ProvisionStream) error plan func(s *provisionersdk.Session, r *sdkproto.PlanRequest, canceledOrComplete <-chan struct{}) *sdkproto.PlanComplete
apply func(s *provisionersdk.Session, r *sdkproto.ApplyRequest, canceledOrComplete <-chan struct{}) *sdkproto.ApplyComplete
} }
func (p *provisionerTestServer) Parse(request *sdkproto.Parse_Request, stream sdkproto.DRPCProvisioner_ParseStream) error { func (p *provisionerTestServer) Parse(s *provisionersdk.Session, r *sdkproto.ParseRequest, canceledOrComplete <-chan struct{}) *sdkproto.ParseComplete {
return p.parse(request, stream) return p.parse(s, r, canceledOrComplete)
} }
func (p *provisionerTestServer) Provision(stream sdkproto.DRPCProvisioner_ProvisionStream) error { func (p *provisionerTestServer) Plan(s *provisionersdk.Session, r *sdkproto.PlanRequest, canceledOrComplete <-chan struct{}) *sdkproto.PlanComplete {
return p.provision(stream) return p.plan(s, r, canceledOrComplete)
}
func (p *provisionerTestServer) Apply(s *provisionersdk.Session, r *sdkproto.ApplyRequest, canceledOrComplete <-chan struct{}) *sdkproto.ApplyComplete {
return p.apply(s, r, canceledOrComplete)
} }
// Fulfills the protobuf interface for a ProvisionerDaemon with // Fulfills the protobuf interface for a ProvisionerDaemon with

View File

@ -1,15 +1,9 @@
package runner package runner
import ( import (
"archive/tar"
"bytes"
"context" "context"
"errors" "errors"
"fmt" "fmt"
"io"
"os"
"path"
"path/filepath"
"reflect" "reflect"
"strings" "strings"
"sync" "sync"
@ -18,7 +12,6 @@ import (
"github.com/google/uuid" "github.com/google/uuid"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/spf13/afero"
"go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/codes"
semconv "go.opentelemetry.io/otel/semconv/v1.14.0" semconv "go.opentelemetry.io/otel/semconv/v1.14.0"
@ -54,14 +47,14 @@ type Runner struct {
sender JobUpdater sender JobUpdater
quotaCommitter QuotaCommitter quotaCommitter QuotaCommitter
logger slog.Logger logger slog.Logger
filesystem afero.Fs
workDirectory string
provisioner sdkproto.DRPCProvisionerClient provisioner sdkproto.DRPCProvisionerClient
lastUpdate atomic.Pointer[time.Time] lastUpdate atomic.Pointer[time.Time]
updateInterval time.Duration updateInterval time.Duration
forceCancelInterval time.Duration forceCancelInterval time.Duration
logBufferInterval time.Duration logBufferInterval time.Duration
// session is the provisioning session with the (possibly remote) provisioner
session sdkproto.DRPCProvisioner_SessionClient
// closed when the Runner is finished sending any updates/failed/complete. // closed when the Runner is finished sending any updates/failed/complete.
done chan struct{} done chan struct{}
// active as long as we are not canceled // active as long as we are not canceled
@ -108,8 +101,6 @@ type Options struct {
Updater JobUpdater Updater JobUpdater
QuotaCommitter QuotaCommitter QuotaCommitter QuotaCommitter
Logger slog.Logger Logger slog.Logger
Filesystem afero.Fs
WorkDirectory string
Provisioner sdkproto.DRPCProvisionerClient Provisioner sdkproto.DRPCProvisionerClient
UpdateInterval time.Duration UpdateInterval time.Duration
ForceCancelInterval time.Duration ForceCancelInterval time.Duration
@ -149,8 +140,6 @@ func New(
sender: opts.Updater, sender: opts.Updater,
quotaCommitter: opts.QuotaCommitter, quotaCommitter: opts.QuotaCommitter,
logger: logger, logger: logger,
filesystem: opts.Filesystem,
workDirectory: opts.WorkDirectory,
provisioner: opts.Provisioner, provisioner: opts.Provisioner,
updateInterval: opts.UpdateInterval, updateInterval: opts.UpdateInterval,
forceCancelInterval: opts.ForceCancelInterval, forceCancelInterval: opts.ForceCancelInterval,
@ -386,6 +375,14 @@ func (r *Runner) doCleanFinish(ctx context.Context) {
r.setComplete(completedJob) r.setComplete(completedJob)
}() }()
var err error
r.session, err = r.provisioner.Session(ctx)
if err != nil {
failedJob = r.failedJobf("open session: %s", err)
return
}
defer r.session.Close()
defer func() { defer func() {
ctx, span := r.startTrace(ctx, tracing.FuncName()) ctx, span := r.startTrace(ctx, tracing.FuncName())
defer span.End() defer span.End()
@ -396,23 +393,6 @@ func (r *Runner) doCleanFinish(ctx context.Context) {
Stage: "Cleaning Up", Stage: "Cleaning Up",
CreatedAt: time.Now().UnixMilli(), CreatedAt: time.Now().UnixMilli(),
}) })
// Cleanup the work directory after execution.
for attempt := 0; attempt < 5; attempt++ {
err := r.filesystem.RemoveAll(r.workDirectory)
if err != nil {
// On Windows, open files cannot be removed.
// When the provisioner daemon is shutting down,
// it may take a few milliseconds for processes to exit.
// See: https://github.com/golang/go/issues/50510
r.logger.Debug(ctx, "failed to clean work directory; trying again", slog.Error(err))
time.Sleep(250 * time.Millisecond)
continue
}
r.logger.Debug(ctx, "cleaned up work directory")
break
}
r.flushQueuedLogs(ctx) r.flushQueuedLogs(ctx)
}() }()
@ -424,85 +404,19 @@ func (r *Runner) do(ctx context.Context) (*proto.CompletedJob, *proto.FailedJob)
ctx, span := r.startTrace(ctx, tracing.FuncName()) ctx, span := r.startTrace(ctx, tracing.FuncName())
defer span.End() defer span.End()
err := r.filesystem.MkdirAll(r.workDirectory, 0o700)
if err != nil {
return nil, r.failedJobf("create work directory %q: %s", r.workDirectory, err)
}
r.queueLog(ctx, &proto.Log{ r.queueLog(ctx, &proto.Log{
Source: proto.LogSource_PROVISIONER_DAEMON, Source: proto.LogSource_PROVISIONER_DAEMON,
Level: sdkproto.LogLevel_INFO, Level: sdkproto.LogLevel_INFO,
Stage: "Setting up", Stage: "Setting up",
CreatedAt: time.Now().UnixMilli(), CreatedAt: time.Now().UnixMilli(),
}) })
if err != nil {
return nil, r.failedJobf("write log: %s", err)
}
r.logger.Info(ctx, "unpacking template source archive",
slog.F("size_bytes", len(r.job.TemplateSourceArchive)),
)
reader := tar.NewReader(bytes.NewBuffer(r.job.TemplateSourceArchive))
for {
header, err := reader.Next()
if err != nil {
if errors.Is(err, io.EOF) {
break
}
return nil, r.failedJobf("read template source archive: %s", err)
}
// #nosec
headerPath := filepath.Join(r.workDirectory, header.Name)
if !strings.HasPrefix(headerPath, filepath.Clean(r.workDirectory)) {
return nil, r.failedJobf("tar attempts to target relative upper directory")
}
mode := header.FileInfo().Mode()
if mode == 0 {
mode = 0o600
}
switch header.Typeflag {
case tar.TypeDir:
err = r.filesystem.MkdirAll(headerPath, mode)
if err != nil {
return nil, r.failedJobf("mkdir %q: %s", headerPath, err)
}
r.logger.Debug(context.Background(), "extracted directory", slog.F("path", headerPath))
case tar.TypeReg:
file, err := r.filesystem.OpenFile(headerPath, os.O_CREATE|os.O_RDWR, mode)
if err != nil {
return nil, r.failedJobf("create file %q (mode %s): %s", headerPath, mode, err)
}
// Max file size of 10MiB.
size, err := io.CopyN(file, reader, 10<<20)
if errors.Is(err, io.EOF) {
err = nil
}
if err != nil {
_ = file.Close()
return nil, r.failedJobf("copy file %q: %s", headerPath, err)
}
err = file.Close()
if err != nil {
return nil, r.failedJobf("close file %q: %s", headerPath, err)
}
r.logger.Debug(context.Background(), "extracted file",
slog.F("size_bytes", size),
slog.F("path", headerPath),
slog.F("mode", mode),
)
}
}
switch jobType := r.job.Type.(type) { switch jobType := r.job.Type.(type) {
case *proto.AcquiredJob_TemplateImport_: case *proto.AcquiredJob_TemplateImport_:
r.logger.Debug(context.Background(), "acquired job is template import", r.logger.Debug(context.Background(), "acquired job is template import",
slog.F("user_variable_values", redactVariableValues(jobType.TemplateImport.UserVariableValues)), slog.F("user_variable_values", redactVariableValues(jobType.TemplateImport.UserVariableValues)),
) )
failedJob := r.runReadmeParse(ctx)
if failedJob != nil {
return nil, failedJob
}
return r.runTemplateImport(ctx) return r.runTemplateImport(ctx)
case *proto.AcquiredJob_TemplateDryRun_: case *proto.AcquiredJob_TemplateDryRun_:
r.logger.Debug(context.Background(), "acquired job is template dry-run", r.logger.Debug(context.Background(), "acquired job is template dry-run",
@ -525,6 +439,14 @@ func (r *Runner) do(ctx context.Context) (*proto.CompletedJob, *proto.FailedJob)
} }
} }
func (r *Runner) configure(config *sdkproto.Config) *proto.FailedJob {
err := r.session.Send(&sdkproto.Request{Type: &sdkproto.Request_Config{Config: config}})
if err != nil {
return r.failedJobf("send config: %s", err)
}
return nil
}
// heartbeatRoutine periodically sends updates on the job, which keeps coder server // heartbeatRoutine periodically sends updates on the job, which keeps coder server
// from assuming the job is stalled, and allows the runner to learn if the job // from assuming the job is stalled, and allows the runner to learn if the job
// has been canceled by the user. // has been canceled by the user.
@ -577,45 +499,17 @@ func (r *Runner) heartbeatRoutine(ctx context.Context) {
} }
} }
// ReadmeFile is the location we look for to extract documentation from template
// versions.
const ReadmeFile = "README.md"
func (r *Runner) runReadmeParse(ctx context.Context) *proto.FailedJob {
ctx, span := r.startTrace(ctx, tracing.FuncName())
defer span.End()
fi, err := afero.ReadFile(r.filesystem, path.Join(r.workDirectory, ReadmeFile))
if err != nil {
r.queueLog(ctx, &proto.Log{
Source: proto.LogSource_PROVISIONER_DAEMON,
Level: sdkproto.LogLevel_DEBUG,
Stage: "No README.md provided",
CreatedAt: time.Now().UnixMilli(),
})
return nil
}
_, err = r.update(ctx, &proto.UpdateJobRequest{
JobId: r.job.JobId,
Logs: []*proto.Log{{
Source: proto.LogSource_PROVISIONER_DAEMON,
Level: sdkproto.LogLevel_INFO,
Stage: "Adding README.md...",
CreatedAt: time.Now().UnixMilli(),
}},
Readme: fi,
})
if err != nil {
return r.failedJobf("write log: %s", err)
}
return nil
}
func (r *Runner) runTemplateImport(ctx context.Context) (*proto.CompletedJob, *proto.FailedJob) { func (r *Runner) runTemplateImport(ctx context.Context) (*proto.CompletedJob, *proto.FailedJob) {
ctx, span := r.startTrace(ctx, tracing.FuncName()) ctx, span := r.startTrace(ctx, tracing.FuncName())
defer span.End() defer span.End()
failedJob := r.configure(&sdkproto.Config{
TemplateSourceArchive: r.job.GetTemplateSourceArchive(),
})
if failedJob != nil {
return nil, failedJob
}
// Parse parameters and update the job with the parameter specs // Parse parameters and update the job with the parameter specs
r.queueLog(ctx, &proto.Log{ r.queueLog(ctx, &proto.Log{
Source: proto.LogSource_PROVISIONER_DAEMON, Source: proto.LogSource_PROVISIONER_DAEMON,
@ -623,7 +517,7 @@ func (r *Runner) runTemplateImport(ctx context.Context) (*proto.CompletedJob, *p
Stage: "Parsing template parameters", Stage: "Parsing template parameters",
CreatedAt: time.Now().UnixMilli(), CreatedAt: time.Now().UnixMilli(),
}) })
templateVariables, err := r.runTemplateImportParse(ctx) templateVariables, readme, err := r.runTemplateImportParse(ctx)
if err != nil { if err != nil {
return nil, r.failedJobf("run parse: %s", err) return nil, r.failedJobf("run parse: %s", err)
} }
@ -634,6 +528,7 @@ func (r *Runner) runTemplateImport(ctx context.Context) (*proto.CompletedJob, *p
JobId: r.job.JobId, JobId: r.job.JobId,
TemplateVariables: templateVariables, TemplateVariables: templateVariables,
UserVariableValues: r.job.GetTemplateImport().GetUserVariableValues(), UserVariableValues: r.job.GetTemplateImport().GetUserVariableValues(),
Readme: readme,
}) })
if err != nil { if err != nil {
return nil, r.failedJobf("update job: %s", err) return nil, r.failedJobf("update job: %s", err)
@ -646,7 +541,7 @@ func (r *Runner) runTemplateImport(ctx context.Context) (*proto.CompletedJob, *p
Stage: "Detecting persistent resources", Stage: "Detecting persistent resources",
CreatedAt: time.Now().UnixMilli(), CreatedAt: time.Now().UnixMilli(),
}) })
startProvision, err := r.runTemplateImportProvision(ctx, updateResponse.VariableValues, &sdkproto.Provision_Metadata{ startProvision, err := r.runTemplateImportProvision(ctx, updateResponse.VariableValues, &sdkproto.Metadata{
CoderUrl: r.job.GetTemplateImport().Metadata.CoderUrl, CoderUrl: r.job.GetTemplateImport().Metadata.CoderUrl,
WorkspaceTransition: sdkproto.WorkspaceTransition_START, WorkspaceTransition: sdkproto.WorkspaceTransition_START,
}) })
@ -661,7 +556,7 @@ func (r *Runner) runTemplateImport(ctx context.Context) (*proto.CompletedJob, *p
Stage: "Detecting ephemeral resources", Stage: "Detecting ephemeral resources",
CreatedAt: time.Now().UnixMilli(), CreatedAt: time.Now().UnixMilli(),
}) })
stopProvision, err := r.runTemplateImportProvision(ctx, updateResponse.VariableValues, &sdkproto.Provision_Metadata{ stopProvision, err := r.runTemplateImportProvision(ctx, updateResponse.VariableValues, &sdkproto.Metadata{
CoderUrl: r.job.GetTemplateImport().Metadata.CoderUrl, CoderUrl: r.job.GetTemplateImport().Metadata.CoderUrl,
WorkspaceTransition: sdkproto.WorkspaceTransition_STOP, WorkspaceTransition: sdkproto.WorkspaceTransition_STOP,
}) })
@ -682,25 +577,24 @@ func (r *Runner) runTemplateImport(ctx context.Context) (*proto.CompletedJob, *p
}, nil }, nil
} }
// Parses template variables and parameter schemas from source. // Parses template variables and README from source.
func (r *Runner) runTemplateImportParse(ctx context.Context) ([]*sdkproto.TemplateVariable, error) { func (r *Runner) runTemplateImportParse(ctx context.Context) (
vars []*sdkproto.TemplateVariable, readme []byte, err error,
) {
ctx, span := r.startTrace(ctx, tracing.FuncName()) ctx, span := r.startTrace(ctx, tracing.FuncName())
defer span.End() defer span.End()
stream, err := r.provisioner.Parse(ctx, &sdkproto.Parse_Request{ err = r.session.Send(&sdkproto.Request{Type: &sdkproto.Request_Parse{Parse: &sdkproto.ParseRequest{}}})
Directory: r.workDirectory,
})
if err != nil { if err != nil {
return nil, xerrors.Errorf("parse source: %w", err) return nil, nil, xerrors.Errorf("parse source: %w", err)
} }
defer stream.Close()
for { for {
msg, err := stream.Recv() msg, err := r.session.Recv()
if err != nil { if err != nil {
return nil, xerrors.Errorf("recv parse source: %w", err) return nil, nil, xerrors.Errorf("recv parse source: %w", err)
} }
switch msgType := msg.Type.(type) { switch msgType := msg.Type.(type) {
case *sdkproto.Parse_Response_Log: case *sdkproto.Response_Log:
r.logger.Debug(context.Background(), "parse job logged", r.logger.Debug(context.Background(), "parse job logged",
slog.F("level", msgType.Log.Level), slog.F("level", msgType.Log.Level),
slog.F("output", msgType.Log.Output), slog.F("output", msgType.Log.Output),
@ -713,14 +607,20 @@ func (r *Runner) runTemplateImportParse(ctx context.Context) ([]*sdkproto.Templa
Output: msgType.Log.Output, Output: msgType.Log.Output,
Stage: "Parse parameters", Stage: "Parse parameters",
}) })
case *sdkproto.Parse_Response_Complete: case *sdkproto.Response_Parse:
pc := msgType.Parse
r.logger.Debug(context.Background(), "parse complete", r.logger.Debug(context.Background(), "parse complete",
slog.F("template_variables", msgType.Complete.TemplateVariables), slog.F("template_variables", pc.TemplateVariables),
slog.F("readme_len", len(pc.Readme)),
slog.F("error", pc.Error),
) )
if pc.Error != "" {
return nil, nil, xerrors.Errorf("parse error: %s", pc.Error)
}
return msgType.Complete.TemplateVariables, nil return msgType.Parse.TemplateVariables, msgType.Parse.Readme, nil
default: default:
return nil, xerrors.Errorf("invalid message type %q received from provisioner", return nil, nil, xerrors.Errorf("invalid message type %q received from provisioner",
reflect.TypeOf(msg.Type).String()) reflect.TypeOf(msg.Type).String())
} }
} }
@ -735,13 +635,18 @@ type templateImportProvision struct {
// Performs a dry-run provision when importing a template. // 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. // 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. // 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.Provision_Metadata) (*templateImportProvision, error) { func (r *Runner) runTemplateImportProvision(ctx context.Context, variableValues []*sdkproto.VariableValue, metadata *sdkproto.Metadata) (*templateImportProvision, error) {
return r.runTemplateImportProvisionWithRichParameters(ctx, variableValues, nil, metadata) return r.runTemplateImportProvisionWithRichParameters(ctx, variableValues, nil, metadata)
} }
// Performs a dry-run provision with provided rich parameters. // Performs a dry-run provision with provided rich parameters.
// This is used to detect resources that would be provisioned for a workspace in various states. // This is used to detect resources that would be provisioned for a workspace in various states.
func (r *Runner) runTemplateImportProvisionWithRichParameters(ctx context.Context, variableValues []*sdkproto.VariableValue, richParameterValues []*sdkproto.RichParameterValue, metadata *sdkproto.Provision_Metadata) (*templateImportProvision, error) { func (r *Runner) runTemplateImportProvisionWithRichParameters(
ctx context.Context,
variableValues []*sdkproto.VariableValue,
richParameterValues []*sdkproto.RichParameterValue,
metadata *sdkproto.Metadata,
) (*templateImportProvision, error) {
ctx, span := r.startTrace(ctx, tracing.FuncName()) ctx, span := r.startTrace(ctx, tracing.FuncName())
defer span.End() defer span.End()
@ -754,46 +659,38 @@ func (r *Runner) runTemplateImportProvisionWithRichParameters(ctx context.Contex
} }
// use the notStopped so that if we attempt to gracefully cancel, the stream will still be available for us // use the notStopped so that if we attempt to gracefully cancel, the stream will still be available for us
// to send the cancel to the provisioner // to send the cancel to the provisioner
stream, err := r.provisioner.Provision(ctx) err := r.session.Send(&sdkproto.Request{Type: &sdkproto.Request_Plan{Plan: &sdkproto.PlanRequest{
Metadata: metadata,
RichParameterValues: richParameterValues,
VariableValues: variableValues,
}}})
if err != nil { if err != nil {
return nil, xerrors.Errorf("provision: %w", err) return nil, xerrors.Errorf("start provision: %w", err)
} }
defer stream.Close() nevermind := make(chan struct{})
defer close(nevermind)
go func() { go func() {
select { select {
case <-nevermind:
return
case <-r.notStopped.Done(): case <-r.notStopped.Done():
return return
case <-r.notCanceled.Done(): case <-r.notCanceled.Done():
_ = stream.Send(&sdkproto.Provision_Request{ _ = r.session.Send(&sdkproto.Request{
Type: &sdkproto.Provision_Request_Cancel{ Type: &sdkproto.Request_Cancel{
Cancel: &sdkproto.Provision_Cancel{}, Cancel: &sdkproto.CancelRequest{},
}, },
}) })
} }
}() }()
err = stream.Send(&sdkproto.Provision_Request{
Type: &sdkproto.Provision_Request_Plan{
Plan: &sdkproto.Provision_Plan{
Config: &sdkproto.Provision_Config{
Directory: r.workDirectory,
Metadata: metadata,
},
RichParameterValues: richParameterValues,
VariableValues: variableValues,
},
},
})
if err != nil {
return nil, xerrors.Errorf("start provision: %w", err)
}
for { for {
msg, err := stream.Recv() msg, err := r.session.Recv()
if err != nil { if err != nil {
return nil, xerrors.Errorf("recv import provision: %w", err) return nil, xerrors.Errorf("recv import provision: %w", err)
} }
switch msgType := msg.Type.(type) { switch msgType := msg.Type.(type) {
case *sdkproto.Provision_Response_Log: case *sdkproto.Response_Log:
r.logger.Debug(context.Background(), "template import provision job logged", r.logger.Debug(context.Background(), "template import provision job logged",
slog.F("level", msgType.Log.Level), slog.F("level", msgType.Log.Level),
slog.F("output", msgType.Log.Output), slog.F("output", msgType.Log.Output),
@ -805,25 +702,25 @@ func (r *Runner) runTemplateImportProvisionWithRichParameters(ctx context.Contex
Output: msgType.Log.Output, Output: msgType.Log.Output,
Stage: stage, Stage: stage,
}) })
case *sdkproto.Provision_Response_Complete: case *sdkproto.Response_Plan:
if msgType.Complete.Error != "" { c := msgType.Plan
if c.Error != "" {
r.logger.Info(context.Background(), "dry-run provision failure", r.logger.Info(context.Background(), "dry-run provision failure",
slog.F("error", msgType.Complete.Error), slog.F("error", c.Error),
) )
return nil, xerrors.New(msgType.Complete.Error) return nil, xerrors.New(c.Error)
} }
r.logger.Info(context.Background(), "parse dry-run provision successful", r.logger.Info(context.Background(), "parse dry-run provision successful",
slog.F("resource_count", len(msgType.Complete.Resources)), slog.F("resource_count", len(c.Resources)),
slog.F("resources", msgType.Complete.Resources), slog.F("resources", c.Resources),
slog.F("state_length", len(msgType.Complete.State)),
) )
return &templateImportProvision{ return &templateImportProvision{
Resources: msgType.Complete.Resources, Resources: c.Resources,
Parameters: msgType.Complete.Parameters, Parameters: c.Parameters,
GitAuthProviders: msgType.Complete.GitAuthProviders, GitAuthProviders: c.GitAuthProviders,
}, nil }, nil
default: default:
return nil, xerrors.Errorf("invalid message type %q received from provisioner", return nil, xerrors.Errorf("invalid message type %q received from provisioner",
@ -864,6 +761,13 @@ func (r *Runner) runTemplateDryRun(ctx context.Context) (*proto.CompletedJob, *p
metadata.WorkspaceOwnerId = id.String() metadata.WorkspaceOwnerId = id.String()
} }
failedJob := r.configure(&sdkproto.Config{
TemplateSourceArchive: r.job.GetTemplateSourceArchive(),
})
if failedJob != nil {
return nil, failedJob
}
// Run the template import provision task since it's already a dry run. // Run the template import provision task since it's already a dry run.
provision, err := r.runTemplateImportProvisionWithRichParameters(ctx, provision, err := r.runTemplateImportProvisionWithRichParameters(ctx,
r.job.GetTemplateDryRun().GetVariableValues(), r.job.GetTemplateDryRun().GetVariableValues(),
@ -884,41 +788,39 @@ func (r *Runner) runTemplateDryRun(ctx context.Context) (*proto.CompletedJob, *p
}, nil }, nil
} }
func (r *Runner) buildWorkspace(ctx context.Context, stage string, req *sdkproto.Provision_Request) ( func (r *Runner) buildWorkspace(ctx context.Context, stage string, req *sdkproto.Request) (
*sdkproto.Provision_Complete, *proto.FailedJob, *sdkproto.Response, *proto.FailedJob,
) { ) {
// use the notStopped so that if we attempt to gracefully cancel, the stream // use the notStopped so that if we attempt to gracefully cancel, the stream
// will still be available for us to send the cancel to the provisioner // will still be available for us to send the cancel to the provisioner
stream, err := r.provisioner.Provision(ctx) err := r.session.Send(req)
if err != nil { if err != nil {
return nil, r.failedWorkspaceBuildf("provision: %s", err) return nil, r.failedWorkspaceBuildf("start provision: %s", err)
} }
defer stream.Close() nevermind := make(chan struct{})
defer close(nevermind)
go func() { go func() {
select { select {
case <-nevermind:
return
case <-r.notStopped.Done(): case <-r.notStopped.Done():
return return
case <-r.notCanceled.Done(): case <-r.notCanceled.Done():
_ = stream.Send(&sdkproto.Provision_Request{ _ = r.session.Send(&sdkproto.Request{
Type: &sdkproto.Provision_Request_Cancel{ Type: &sdkproto.Request_Cancel{
Cancel: &sdkproto.Provision_Cancel{}, Cancel: &sdkproto.CancelRequest{},
}, },
}) })
} }
}() }()
err = stream.Send(req)
if err != nil {
return nil, r.failedWorkspaceBuildf("start provision: %s", err)
}
for { for {
msg, err := stream.Recv() msg, err := r.session.Recv()
if err != nil { if err != nil {
return nil, r.failedWorkspaceBuildf("recv workspace provision: %s", err) return nil, r.failedWorkspaceBuildf("recv workspace provision: %s", err)
} }
switch msgType := msg.Type.(type) { switch msgType := msg.Type.(type) {
case *sdkproto.Provision_Response_Log: case *sdkproto.Response_Log:
r.logProvisionerJobLog(context.Background(), msgType.Log.Level, "workspace provisioner job logged", r.logProvisionerJobLog(context.Background(), msgType.Log.Level, "workspace provisioner job logged",
slog.F("level", msgType.Log.Level), slog.F("level", msgType.Log.Level),
slog.F("output", msgType.Log.Output), slog.F("output", msgType.Log.Output),
@ -932,33 +834,9 @@ func (r *Runner) buildWorkspace(ctx context.Context, stage string, req *sdkproto
Output: msgType.Log.Output, Output: msgType.Log.Output,
Stage: stage, Stage: stage,
}) })
case *sdkproto.Provision_Response_Complete:
if msgType.Complete.Error != "" {
r.logger.Warn(context.Background(), "provision failed; updating state",
slog.F("state_length", len(msgType.Complete.State)),
slog.F("error", msgType.Complete.Error),
)
return nil, &proto.FailedJob{
JobId: r.job.JobId,
Error: msgType.Complete.Error,
Type: &proto.FailedJob_WorkspaceBuild_{
WorkspaceBuild: &proto.FailedJob_WorkspaceBuild{
State: msgType.Complete.State,
},
},
}
}
r.logger.Info(context.Background(), "provision successful",
slog.F("resource_count", len(msgType.Complete.Resources)),
slog.F("resources", msgType.Complete.Resources),
slog.F("state_length", len(msgType.Complete.State)),
)
// Stop looping!
return msgType.Complete, nil
default: default:
return nil, r.failedWorkspaceBuildf("invalid message type %T received from provisioner", msg.Type) // Stop looping!
return msg, nil
} }
} }
} }
@ -1035,18 +913,19 @@ func (r *Runner) runWorkspaceBuild(ctx context.Context) (*proto.CompletedJob, *p
applyStage = "Destroying workspace" applyStage = "Destroying workspace"
} }
config := &sdkproto.Provision_Config{ failedJob := r.configure(&sdkproto.Config{
Directory: r.workDirectory, TemplateSourceArchive: r.job.GetTemplateSourceArchive(),
Metadata: r.job.GetWorkspaceBuild().Metadata, State: r.job.GetWorkspaceBuild().State,
State: r.job.GetWorkspaceBuild().State, ProvisionerLogLevel: r.job.GetWorkspaceBuild().LogLevel,
})
ProvisionerLogLevel: r.job.GetWorkspaceBuild().LogLevel, if failedJob != nil {
return nil, failedJob
} }
completedPlan, failed := r.buildWorkspace(ctx, "Planning infrastructure", &sdkproto.Provision_Request{ resp, failed := r.buildWorkspace(ctx, "Planning infrastructure", &sdkproto.Request{
Type: &sdkproto.Provision_Request_Plan{ Type: &sdkproto.Request_Plan{
Plan: &sdkproto.Provision_Plan{ Plan: &sdkproto.PlanRequest{
Config: config, Metadata: r.job.GetWorkspaceBuild().Metadata,
RichParameterValues: r.job.GetWorkspaceBuild().RichParameterValues, RichParameterValues: r.job.GetWorkspaceBuild().RichParameterValues,
VariableValues: r.job.GetWorkspaceBuild().VariableValues, VariableValues: r.job.GetWorkspaceBuild().VariableValues,
GitAuthProviders: r.job.GetWorkspaceBuild().GitAuthProviders, GitAuthProviders: r.job.GetWorkspaceBuild().GitAuthProviders,
@ -1056,9 +935,31 @@ func (r *Runner) runWorkspaceBuild(ctx context.Context) (*proto.CompletedJob, *p
if failed != nil { if failed != nil {
return nil, failed return nil, failed
} }
planComplete := resp.GetPlan()
if planComplete == nil {
return nil, r.failedWorkspaceBuildf("invalid message type %T received from provisioner", resp.Type)
}
if planComplete.Error != "" {
r.logger.Warn(context.Background(), "plan request failed",
slog.F("error", planComplete.Error),
)
return nil, &proto.FailedJob{
JobId: r.job.JobId,
Error: planComplete.Error,
Type: &proto.FailedJob_WorkspaceBuild_{
WorkspaceBuild: &proto.FailedJob_WorkspaceBuild{},
},
}
}
r.logger.Info(context.Background(), "plan request successful",
slog.F("resource_count", len(planComplete.Resources)),
slog.F("resources", planComplete.Resources),
)
r.flushQueuedLogs(ctx) r.flushQueuedLogs(ctx)
if commitQuota { if commitQuota {
failed = r.commitQuota(ctx, completedPlan.GetResources()) failed = r.commitQuota(ctx, planComplete.Resources)
r.flushQueuedLogs(ctx) r.flushQueuedLogs(ctx)
if failed != nil { if failed != nil {
return nil, failed return nil, failed
@ -1072,25 +973,50 @@ func (r *Runner) runWorkspaceBuild(ctx context.Context) (*proto.CompletedJob, *p
CreatedAt: time.Now().UnixMilli(), CreatedAt: time.Now().UnixMilli(),
}) })
completedApply, failed := r.buildWorkspace(ctx, applyStage, &sdkproto.Provision_Request{ resp, failed = r.buildWorkspace(ctx, applyStage, &sdkproto.Request{
Type: &sdkproto.Provision_Request_Apply{ Type: &sdkproto.Request_Apply{
Apply: &sdkproto.Provision_Apply{ Apply: &sdkproto.ApplyRequest{
Config: config, Metadata: r.job.GetWorkspaceBuild().Metadata,
Plan: completedPlan.GetPlan(),
}, },
}, },
}) })
if failed != nil { if failed != nil {
return nil, failed return nil, failed
} }
applyComplete := resp.GetApply()
if applyComplete == nil {
return nil, r.failedWorkspaceBuildf("invalid message type %T received from provisioner", resp.Type)
}
if applyComplete.Error != "" {
r.logger.Warn(context.Background(), "apply failed; updating state",
slog.F("error", applyComplete.Error),
slog.F("state_len", len(applyComplete.State)),
)
return nil, &proto.FailedJob{
JobId: r.job.JobId,
Error: applyComplete.Error,
Type: &proto.FailedJob_WorkspaceBuild_{
WorkspaceBuild: &proto.FailedJob_WorkspaceBuild{
State: applyComplete.State,
},
},
}
}
r.logger.Info(context.Background(), "apply successful",
slog.F("resource_count", len(applyComplete.Resources)),
slog.F("resources", applyComplete.Resources),
slog.F("state_len", len(applyComplete.State)),
)
r.flushQueuedLogs(ctx) r.flushQueuedLogs(ctx)
return &proto.CompletedJob{ return &proto.CompletedJob{
JobId: r.job.JobId, JobId: r.job.JobId,
Type: &proto.CompletedJob_WorkspaceBuild_{ Type: &proto.CompletedJob_WorkspaceBuild_{
WorkspaceBuild: &proto.CompletedJob_WorkspaceBuild{ WorkspaceBuild: &proto.CompletedJob_WorkspaceBuild{
State: completedApply.GetState(), State: applyComplete.State,
Resources: completedApply.GetResources(), Resources: applyComplete.Resources,
}, },
}, },
}, nil }, nil

19
provisionersdk/errors.go Normal file
View File

@ -0,0 +1,19 @@
package provisionersdk
import (
"fmt"
"github.com/coder/coder/v2/provisionersdk/proto"
)
func ParseErrorf(format string, args ...any) *proto.ParseComplete {
return &proto.ParseComplete{Error: fmt.Sprintf(format, args...)}
}
func PlanErrorf(format string, args ...any) *proto.PlanComplete {
return &proto.PlanComplete{Error: fmt.Sprintf(format, args...)}
}
func ApplyErrorf(format string, args ...any) *proto.ApplyComplete {
return &proto.ApplyComplete{Error: fmt.Sprintf(format, args...)}
}

File diff suppressed because it is too large Load Diff

View File

@ -82,8 +82,8 @@ message InstanceIdentityAuth {
} }
message GitAuthProvider { message GitAuthProvider {
string id = 1; string id = 1;
string access_token = 2; string access_token = 2;
} }
// Agent represents a running agent on the workspace. // Agent represents a running agent on the workspace.
@ -110,15 +110,15 @@ message Agent {
string token = 9; string token = 9;
string instance_id = 10; string instance_id = 10;
} }
int32 connection_timeout_seconds = 11; int32 connection_timeout_seconds = 11;
string troubleshooting_url = 12; string troubleshooting_url = 12;
string motd_file = 13; string motd_file = 13;
// Field 14 was bool login_before_ready = 14, now removed. // Field 14 was bool login_before_ready = 14, now removed.
int32 startup_script_timeout_seconds = 15; int32 startup_script_timeout_seconds = 15;
string shutdown_script = 16; string shutdown_script = 16;
int32 shutdown_script_timeout_seconds = 17; int32 shutdown_script_timeout_seconds = 17;
repeated Metadata metadata = 18; repeated Metadata metadata = 18;
string startup_script_behavior = 19; string startup_script_behavior = 19;
} }
enum AppSharingLevel { enum AppSharingLevel {
@ -168,96 +168,111 @@ message Resource {
int32 daily_cost = 8; int32 daily_cost = 8;
} }
// Parse consumes source-code from a directory to produce inputs. // WorkspaceTransition is the desired outcome of a build
message Parse {
message Request {
string directory = 1;
}
message Complete {
reserved 2;
repeated TemplateVariable template_variables = 1;
}
message Response {
oneof type {
Log log = 1;
Complete complete = 2;
}
}
}
enum WorkspaceTransition { enum WorkspaceTransition {
START = 0; START = 0;
STOP = 1; STOP = 1;
DESTROY = 2; DESTROY = 2;
} }
// Provision consumes source-code from a directory to produce resources. // Metadata is information about a workspace used in the execution of a build
// Exactly one of Plan or Apply must be provided in a single session. message Metadata {
message Provision { string coder_url = 1;
message Metadata { WorkspaceTransition workspace_transition = 2;
string coder_url = 1; string workspace_name = 3;
WorkspaceTransition workspace_transition = 2; string workspace_owner = 4;
string workspace_name = 3; string workspace_id = 5;
string workspace_owner = 4; string workspace_owner_id = 6;
string workspace_id = 5; string workspace_owner_email = 7;
string workspace_owner_id = 6; string template_name = 8;
string workspace_owner_email = 7; string template_version = 9;
string template_name = 8; string workspace_owner_oidc_access_token = 10;
string template_version = 9; string workspace_owner_session_token = 11;
string workspace_owner_oidc_access_token = 10; }
string workspace_owner_session_token = 11;
}
// Config represents execution configuration shared by both Plan and // Config represents execution configuration shared by all subsequent requests in the Session
// Apply commands. message Config {
message Config { // template_source_archive is a tar of the template source files
string directory = 1; bytes template_source_archive = 1;
bytes state = 2; // state is the provisioner state (if any)
Metadata metadata = 3; bytes state = 2;
string provisioner_log_level = 3;
}
string provisioner_log_level = 4; // ParseRequest consumes source-code to produce inputs.
} message ParseRequest {
}
message Plan { // ParseComplete indicates a request to parse completed.
reserved 2; message ParseComplete {
string error = 1;
repeated TemplateVariable template_variables = 2;
bytes readme = 3;
}
// PlanRequest asks the provisioner to plan what resources & parameters it will create
message PlanRequest {
Metadata metadata = 1;
repeated RichParameterValue rich_parameter_values = 2;
repeated VariableValue variable_values = 3;
repeated GitAuthProvider git_auth_providers = 4;
}
// PlanComplete indicates a request to plan completed.
message PlanComplete {
string error = 1;
repeated Resource resources = 2;
repeated RichParameter parameters = 3;
repeated string git_auth_providers = 4;
}
// ApplyRequest asks the provisioner to apply the changes. Apply MUST be preceded by a successful plan request/response
// in the same Session. The plan data is not transmitted over the wire and is cached by the provisioner in the Session.
message ApplyRequest {
Metadata metadata = 1;
}
// ApplyComplete indicates a request to apply completed.
message ApplyComplete {
bytes state = 1;
string error = 2;
repeated Resource resources = 3;
repeated RichParameter parameters = 4;
repeated string git_auth_providers = 5;
}
// CancelRequest requests that the previous request be canceled gracefully.
message CancelRequest {}
message Request {
oneof type {
Config config = 1; Config config = 1;
repeated RichParameterValue rich_parameter_values = 3; ParseRequest parse = 2;
repeated VariableValue variable_values = 4; PlanRequest plan = 3;
repeated GitAuthProvider git_auth_providers = 5; ApplyRequest apply = 4;
CancelRequest cancel = 5;
} }
}
message Apply { message Response {
Config config = 1; oneof type {
bytes plan = 2; Log log = 1;
} ParseComplete parse = 2;
PlanComplete plan = 3;
message Cancel {} ApplyComplete apply = 4;
message Request {
oneof type {
Plan plan = 1;
Apply apply = 2;
Cancel cancel = 3;
}
}
message Complete {
bytes state = 1;
string error = 2;
repeated Resource resources = 3;
repeated RichParameter parameters = 4;
repeated string git_auth_providers = 5;
bytes plan = 6;
}
message Response {
oneof type {
Log log = 1;
Complete complete = 2;
}
} }
} }
service Provisioner { service Provisioner {
rpc Parse(Parse.Request) returns (stream Parse.Response); // Session represents provisioning a single template import or workspace. The daemon always sends Config followed
rpc Provision(stream Provision.Request) returns (stream Provision.Response); // by one of the requests (ParseRequest, PlanRequest, ApplyRequest). The provisioner should respond with a stream
// of zero or more Logs, followed by the corresponding complete message (ParseComplete, PlanComplete,
// ApplyComplete). The daemon may then send a new request. A request to apply MUST be preceded by a request plan,
// and the provisioner should store the plan data on the Session after a successful plan, so that the daemon may
// request an apply. If the daemon closes the Session without an apply, the plan data may be safely discarded.
//
// The daemon may send a CancelRequest, asynchronously to ask the provisioner to cancel the previous ParseRequest,
// PlanRequest, or ApplyRequest. The provisioner MUST reply with a complete message corresponding to the request
// that was canceled. If the provisioner has already completed the request, it may ignore the CancelRequest.
rpc Session(stream Request) returns (stream Response);
} }

View File

@ -38,8 +38,7 @@ func (drpcEncoding_File_provisionersdk_proto_provisioner_proto) JSONUnmarshal(bu
type DRPCProvisionerClient interface { type DRPCProvisionerClient interface {
DRPCConn() drpc.Conn DRPCConn() drpc.Conn
Parse(ctx context.Context, in *Parse_Request) (DRPCProvisioner_ParseClient, error) Session(ctx context.Context) (DRPCProvisioner_SessionClient, error)
Provision(ctx context.Context) (DRPCProvisioner_ProvisionClient, error)
} }
type drpcProvisionerClient struct { type drpcProvisionerClient struct {
@ -52,123 +51,69 @@ func NewDRPCProvisionerClient(cc drpc.Conn) DRPCProvisionerClient {
func (c *drpcProvisionerClient) DRPCConn() drpc.Conn { return c.cc } func (c *drpcProvisionerClient) DRPCConn() drpc.Conn { return c.cc }
func (c *drpcProvisionerClient) Parse(ctx context.Context, in *Parse_Request) (DRPCProvisioner_ParseClient, error) { func (c *drpcProvisionerClient) Session(ctx context.Context) (DRPCProvisioner_SessionClient, error) {
stream, err := c.cc.NewStream(ctx, "/provisioner.Provisioner/Parse", drpcEncoding_File_provisionersdk_proto_provisioner_proto{}) stream, err := c.cc.NewStream(ctx, "/provisioner.Provisioner/Session", drpcEncoding_File_provisionersdk_proto_provisioner_proto{})
if err != nil { if err != nil {
return nil, err return nil, err
} }
x := &drpcProvisioner_ParseClient{stream} x := &drpcProvisioner_SessionClient{stream}
if err := x.MsgSend(in, drpcEncoding_File_provisionersdk_proto_provisioner_proto{}); err != nil {
return nil, err
}
if err := x.CloseSend(); err != nil {
return nil, err
}
return x, nil return x, nil
} }
type DRPCProvisioner_ParseClient interface { type DRPCProvisioner_SessionClient interface {
drpc.Stream drpc.Stream
Recv() (*Parse_Response, error) Send(*Request) error
Recv() (*Response, error)
} }
type drpcProvisioner_ParseClient struct { type drpcProvisioner_SessionClient struct {
drpc.Stream drpc.Stream
} }
func (x *drpcProvisioner_ParseClient) GetStream() drpc.Stream { func (x *drpcProvisioner_SessionClient) GetStream() drpc.Stream {
return x.Stream return x.Stream
} }
func (x *drpcProvisioner_ParseClient) Recv() (*Parse_Response, error) { func (x *drpcProvisioner_SessionClient) Send(m *Request) error {
m := new(Parse_Response)
if err := x.MsgRecv(m, drpcEncoding_File_provisionersdk_proto_provisioner_proto{}); err != nil {
return nil, err
}
return m, nil
}
func (x *drpcProvisioner_ParseClient) RecvMsg(m *Parse_Response) error {
return x.MsgRecv(m, drpcEncoding_File_provisionersdk_proto_provisioner_proto{})
}
func (c *drpcProvisionerClient) Provision(ctx context.Context) (DRPCProvisioner_ProvisionClient, error) {
stream, err := c.cc.NewStream(ctx, "/provisioner.Provisioner/Provision", drpcEncoding_File_provisionersdk_proto_provisioner_proto{})
if err != nil {
return nil, err
}
x := &drpcProvisioner_ProvisionClient{stream}
return x, nil
}
type DRPCProvisioner_ProvisionClient interface {
drpc.Stream
Send(*Provision_Request) error
Recv() (*Provision_Response, error)
}
type drpcProvisioner_ProvisionClient struct {
drpc.Stream
}
func (x *drpcProvisioner_ProvisionClient) GetStream() drpc.Stream {
return x.Stream
}
func (x *drpcProvisioner_ProvisionClient) Send(m *Provision_Request) error {
return x.MsgSend(m, drpcEncoding_File_provisionersdk_proto_provisioner_proto{}) return x.MsgSend(m, drpcEncoding_File_provisionersdk_proto_provisioner_proto{})
} }
func (x *drpcProvisioner_ProvisionClient) Recv() (*Provision_Response, error) { func (x *drpcProvisioner_SessionClient) Recv() (*Response, error) {
m := new(Provision_Response) m := new(Response)
if err := x.MsgRecv(m, drpcEncoding_File_provisionersdk_proto_provisioner_proto{}); err != nil { if err := x.MsgRecv(m, drpcEncoding_File_provisionersdk_proto_provisioner_proto{}); err != nil {
return nil, err return nil, err
} }
return m, nil return m, nil
} }
func (x *drpcProvisioner_ProvisionClient) RecvMsg(m *Provision_Response) error { func (x *drpcProvisioner_SessionClient) RecvMsg(m *Response) error {
return x.MsgRecv(m, drpcEncoding_File_provisionersdk_proto_provisioner_proto{}) return x.MsgRecv(m, drpcEncoding_File_provisionersdk_proto_provisioner_proto{})
} }
type DRPCProvisionerServer interface { type DRPCProvisionerServer interface {
Parse(*Parse_Request, DRPCProvisioner_ParseStream) error Session(DRPCProvisioner_SessionStream) error
Provision(DRPCProvisioner_ProvisionStream) error
} }
type DRPCProvisionerUnimplementedServer struct{} type DRPCProvisionerUnimplementedServer struct{}
func (s *DRPCProvisionerUnimplementedServer) Parse(*Parse_Request, DRPCProvisioner_ParseStream) error { func (s *DRPCProvisionerUnimplementedServer) Session(DRPCProvisioner_SessionStream) error {
return drpcerr.WithCode(errors.New("Unimplemented"), drpcerr.Unimplemented)
}
func (s *DRPCProvisionerUnimplementedServer) Provision(DRPCProvisioner_ProvisionStream) error {
return drpcerr.WithCode(errors.New("Unimplemented"), drpcerr.Unimplemented) return drpcerr.WithCode(errors.New("Unimplemented"), drpcerr.Unimplemented)
} }
type DRPCProvisionerDescription struct{} type DRPCProvisionerDescription struct{}
func (DRPCProvisionerDescription) NumMethods() int { return 2 } func (DRPCProvisionerDescription) NumMethods() int { return 1 }
func (DRPCProvisionerDescription) Method(n int) (string, drpc.Encoding, drpc.Receiver, interface{}, bool) { func (DRPCProvisionerDescription) Method(n int) (string, drpc.Encoding, drpc.Receiver, interface{}, bool) {
switch n { switch n {
case 0: case 0:
return "/provisioner.Provisioner/Parse", drpcEncoding_File_provisionersdk_proto_provisioner_proto{}, return "/provisioner.Provisioner/Session", drpcEncoding_File_provisionersdk_proto_provisioner_proto{},
func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) { func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) {
return nil, srv.(DRPCProvisionerServer). return nil, srv.(DRPCProvisionerServer).
Parse( Session(
in1.(*Parse_Request), &drpcProvisioner_SessionStream{in1.(drpc.Stream)},
&drpcProvisioner_ParseStream{in2.(drpc.Stream)},
) )
}, DRPCProvisionerServer.Parse, true }, DRPCProvisionerServer.Session, true
case 1:
return "/provisioner.Provisioner/Provision", drpcEncoding_File_provisionersdk_proto_provisioner_proto{},
func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) {
return nil, srv.(DRPCProvisionerServer).
Provision(
&drpcProvisioner_ProvisionStream{in1.(drpc.Stream)},
)
}, DRPCProvisionerServer.Provision, true
default: default:
return "", nil, nil, nil, false return "", nil, nil, nil, false
} }
@ -178,41 +123,28 @@ func DRPCRegisterProvisioner(mux drpc.Mux, impl DRPCProvisionerServer) error {
return mux.Register(impl, DRPCProvisionerDescription{}) return mux.Register(impl, DRPCProvisionerDescription{})
} }
type DRPCProvisioner_ParseStream interface { type DRPCProvisioner_SessionStream interface {
drpc.Stream drpc.Stream
Send(*Parse_Response) error Send(*Response) error
Recv() (*Request, error)
} }
type drpcProvisioner_ParseStream struct { type drpcProvisioner_SessionStream struct {
drpc.Stream drpc.Stream
} }
func (x *drpcProvisioner_ParseStream) Send(m *Parse_Response) error { func (x *drpcProvisioner_SessionStream) Send(m *Response) error {
return x.MsgSend(m, drpcEncoding_File_provisionersdk_proto_provisioner_proto{}) return x.MsgSend(m, drpcEncoding_File_provisionersdk_proto_provisioner_proto{})
} }
type DRPCProvisioner_ProvisionStream interface { func (x *drpcProvisioner_SessionStream) Recv() (*Request, error) {
drpc.Stream m := new(Request)
Send(*Provision_Response) error
Recv() (*Provision_Request, error)
}
type drpcProvisioner_ProvisionStream struct {
drpc.Stream
}
func (x *drpcProvisioner_ProvisionStream) Send(m *Provision_Response) error {
return x.MsgSend(m, drpcEncoding_File_provisionersdk_proto_provisioner_proto{})
}
func (x *drpcProvisioner_ProvisionStream) Recv() (*Provision_Request, error) {
m := new(Provision_Request)
if err := x.MsgRecv(m, drpcEncoding_File_provisionersdk_proto_provisioner_proto{}); err != nil { if err := x.MsgRecv(m, drpcEncoding_File_provisionersdk_proto_provisioner_proto{}); err != nil {
return nil, err return nil, err
} }
return m, nil return m, nil
} }
func (x *drpcProvisioner_ProvisionStream) RecvMsg(m *Provision_Request) error { func (x *drpcProvisioner_SessionStream) RecvMsg(m *Request) error {
return x.MsgRecv(m, drpcEncoding_File_provisionersdk_proto_provisioner_proto{}) return x.MsgRecv(m, drpcEncoding_File_provisionersdk_proto_provisioner_proto{})
} }

View File

@ -13,6 +13,8 @@ import (
"storj.io/drpc/drpcmux" "storj.io/drpc/drpcmux"
"storj.io/drpc/drpcserver" "storj.io/drpc/drpcserver"
"cdr.dev/slog"
"github.com/coder/coder/v2/coderd/tracing" "github.com/coder/coder/v2/coderd/tracing"
"github.com/coder/coder/v2/provisionersdk/proto" "github.com/coder/coder/v2/provisionersdk/proto"
) )
@ -20,11 +22,19 @@ import (
// ServeOptions are configurations to serve a provisioner. // ServeOptions are configurations to serve a provisioner.
type ServeOptions struct { type ServeOptions struct {
// Conn specifies a custom transport to serve the dRPC connection. // Conn specifies a custom transport to serve the dRPC connection.
Listener net.Listener Listener net.Listener
Logger slog.Logger
WorkDirectory string
}
type Server interface {
Parse(s *Session, r *proto.ParseRequest, canceledOrComplete <-chan struct{}) *proto.ParseComplete
Plan(s *Session, r *proto.PlanRequest, canceledOrComplete <-chan struct{}) *proto.PlanComplete
Apply(s *Session, r *proto.ApplyRequest, canceledOrComplete <-chan struct{}) *proto.ApplyComplete
} }
// Serve starts a dRPC connection for the provisioner and transport provided. // Serve starts a dRPC connection for the provisioner and transport provided.
func Serve(ctx context.Context, server proto.DRPCProvisionerServer, options *ServeOptions) error { func Serve(ctx context.Context, server Server, options *ServeOptions) error {
if options == nil { if options == nil {
options = &ServeOptions{} options = &ServeOptions{}
} }
@ -45,11 +55,22 @@ func Serve(ctx context.Context, server proto.DRPCProvisionerServer, options *Ser
}() }()
options.Listener = stdio options.Listener = stdio
} }
if options.WorkDirectory == "" {
var err error
options.WorkDirectory, err = os.MkdirTemp("", "coderprovisioner")
if err != nil {
return xerrors.Errorf("failed to init temp work dir: %w", err)
}
}
// dRPC is a drop-in replacement for gRPC with less generated code, and faster transports. // dRPC is a drop-in replacement for gRPC with less generated code, and faster transports.
// See: https://www.storj.io/blog/introducing-drpc-our-replacement-for-grpc // See: https://www.storj.io/blog/introducing-drpc-our-replacement-for-grpc
mux := drpcmux.New() mux := drpcmux.New()
err := proto.DRPCRegisterProvisioner(mux, server) ps := &protoServer{
server: server,
opts: *options,
}
err := proto.DRPCRegisterProvisioner(mux, ps)
if err != nil { if err != nil {
return xerrors.Errorf("register provisioner: %w", err) return xerrors.Errorf("register provisioner: %w", err)
} }

View File

@ -7,7 +7,6 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"go.uber.org/goleak" "go.uber.org/goleak"
"storj.io/drpc/drpcerr"
"github.com/coder/coder/v2/provisionersdk" "github.com/coder/coder/v2/provisionersdk"
"github.com/coder/coder/v2/provisionersdk/proto" "github.com/coder/coder/v2/provisionersdk/proto"
@ -28,17 +27,37 @@ func TestProvisionerSDK(t *testing.T) {
ctx, cancelFunc := context.WithCancel(context.Background()) ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc() defer cancelFunc()
go func() { go func() {
err := provisionersdk.Serve(ctx, &proto.DRPCProvisionerUnimplementedServer{}, &provisionersdk.ServeOptions{ err := provisionersdk.Serve(ctx, unimplementedServer{}, &provisionersdk.ServeOptions{
Listener: server, Listener: server,
WorkDirectory: t.TempDir(),
}) })
assert.NoError(t, err) assert.NoError(t, err)
}() }()
api := proto.NewDRPCProvisionerClient(client) api := proto.NewDRPCProvisionerClient(client)
stream, err := api.Parse(context.Background(), &proto.Parse_Request{}) s, err := api.Session(ctx)
require.NoError(t, err) require.NoError(t, err)
_, err = stream.Recv() err = s.Send(&proto.Request{Type: &proto.Request_Config{Config: &proto.Config{}}})
require.Equal(t, drpcerr.Unimplemented, int(drpcerr.Code(err))) 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.Run("ServeClosedPipe", func(t *testing.T) {
@ -47,9 +66,24 @@ func TestProvisionerSDK(t *testing.T) {
_ = client.Close() _ = client.Close()
_ = server.Close() _ = server.Close()
err := provisionersdk.Serve(context.Background(), &proto.DRPCProvisionerUnimplementedServer{}, &provisionersdk.ServeOptions{ err := provisionersdk.Serve(context.Background(), unimplementedServer{}, &provisionersdk.ServeOptions{
Listener: server, Listener: server,
WorkDirectory: t.TempDir(),
}) })
require.NoError(t, err) require.NoError(t, err)
}) })
} }
type unimplementedServer struct{}
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"}
}

318
provisionersdk/session.go Normal file
View File

@ -0,0 +1,318 @@
package provisionersdk
import (
"archive/tar"
"bytes"
"context"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"time"
"github.com/google/uuid"
"golang.org/x/xerrors"
"cdr.dev/slog"
"github.com/coder/coder/v2/provisionersdk/proto"
)
// ReadmeFile is the location we look for to extract documentation from template
// versions.
const ReadmeFile = "README.md"
// protoServer is a wrapper that translates the dRPC protocol into a Session with method calls into the Server.
type protoServer struct {
server Server
opts ServeOptions
}
func (p *protoServer) Session(stream proto.DRPCProvisioner_SessionStream) error {
sessID := uuid.New().String()
s := &Session{
Logger: p.opts.Logger.With(slog.F("session_id", sessID)),
stream: stream,
server: p.server,
}
sessDir := fmt.Sprintf("Session%s", sessID)
s.WorkDirectory = filepath.Join(p.opts.WorkDirectory, sessDir)
err := os.MkdirAll(s.WorkDirectory, 0o700)
if err != nil {
return xerrors.Errorf("create work directory %q: %w", s.WorkDirectory, err)
}
defer func() {
var err error
// Cleanup the work directory after execution.
for attempt := 0; attempt < 5; attempt++ {
err = os.RemoveAll(s.WorkDirectory)
if err != nil {
// On Windows, open files cannot be removed.
// When the provisioner daemon is shutting down,
// it may take a few milliseconds for processes to exit.
// See: https://github.com/golang/go/issues/50510
s.Logger.Debug(s.Context(), "failed to clean work directory; trying again", slog.Error(err))
time.Sleep(250 * time.Millisecond)
continue
}
s.Logger.Debug(s.Context(), "cleaned up work directory")
return
}
s.Logger.Error(s.Context(), "failed to clean up work directory after multiple attempts",
slog.F("path", s.WorkDirectory), slog.Error(err))
}()
req, err := stream.Recv()
if err != nil {
return xerrors.Errorf("receive config: %w", err)
}
config := req.GetConfig()
if config == nil {
return xerrors.New("first request must be Config")
}
s.Config = config
if s.Config.ProvisionerLogLevel != "" {
s.logLevel = proto.LogLevel_value[strings.ToUpper(s.Config.ProvisionerLogLevel)]
}
err = s.extractArchive()
if err != nil {
return xerrors.Errorf("extract archive: %w", err)
}
return s.handleRequests()
}
func (s *Session) requestReader(done <-chan struct{}) <-chan *proto.Request {
ch := make(chan *proto.Request)
go func() {
defer close(ch)
for {
req, err := s.stream.Recv()
if err != nil {
s.Logger.Info(s.Context(), "recv done on Session", slog.Error(err))
return
}
select {
case ch <- req:
continue
case <-done:
return
}
}
}()
return ch
}
func (s *Session) handleRequests() error {
done := make(chan struct{})
defer close(done)
requests := s.requestReader(done)
planned := false
for req := range requests {
if req.GetCancel() != nil {
s.Logger.Warn(s.Context(), "ignoring cancel before request or after complete")
continue
}
resp := &proto.Response{}
if parse := req.GetParse(); parse != nil {
r := &request[*proto.ParseRequest, *proto.ParseComplete]{
req: parse,
session: s,
serverFn: s.server.Parse,
cancels: requests,
}
complete, err := r.do()
if err != nil {
return err
}
// Handle README centrally, so that individual provisioners don't need to mess with it.
readme, err := os.ReadFile(filepath.Join(s.WorkDirectory, ReadmeFile))
if err == nil {
complete.Readme = readme
} else {
s.Logger.Debug(s.Context(), "failed to parse readme (missing ok)", slog.Error(err))
}
resp.Type = &proto.Response_Parse{Parse: complete}
}
if plan := req.GetPlan(); plan != nil {
r := &request[*proto.PlanRequest, *proto.PlanComplete]{
req: plan,
session: s,
serverFn: s.server.Plan,
cancels: requests,
}
complete, err := r.do()
if err != nil {
return err
}
resp.Type = &proto.Response_Plan{Plan: complete}
if complete.Error == "" {
planned = true
}
}
if apply := req.GetApply(); apply != nil {
if !planned {
return xerrors.New("cannot apply before successful plan")
}
r := &request[*proto.ApplyRequest, *proto.ApplyComplete]{
req: apply,
session: s,
serverFn: s.server.Apply,
cancels: requests,
}
complete, err := r.do()
if err != nil {
return err
}
resp.Type = &proto.Response_Apply{Apply: complete}
}
err := s.stream.Send(resp)
if err != nil {
return xerrors.Errorf("send response: %w", err)
}
}
return nil
}
type Session struct {
Logger slog.Logger
WorkDirectory string
Config *proto.Config
server Server
stream proto.DRPCProvisioner_SessionStream
logLevel int32
}
func (s *Session) Context() context.Context {
return s.stream.Context()
}
func (s *Session) extractArchive() error {
ctx := s.Context()
s.Logger.Info(ctx, "unpacking template source archive",
slog.F("size_bytes", len(s.Config.TemplateSourceArchive)),
)
reader := tar.NewReader(bytes.NewBuffer(s.Config.TemplateSourceArchive))
// for safety, nil out the reference on Config, since the reader now owns it.
s.Config.TemplateSourceArchive = nil
for {
header, err := reader.Next()
if err != nil {
if xerrors.Is(err, io.EOF) {
break
}
return xerrors.Errorf("read template source archive: %w", err)
}
// Security: don't untar absolute or relative paths, as this can allow a malicious tar to overwrite
// files outside the workdir.
if !filepath.IsLocal(header.Name) {
return xerrors.Errorf("refusing to extract to non-local path")
}
// nolint: gosec
headerPath := filepath.Join(s.WorkDirectory, header.Name)
if !strings.HasPrefix(headerPath, filepath.Clean(s.WorkDirectory)) {
return xerrors.New("tar attempts to target relative upper directory")
}
mode := header.FileInfo().Mode()
if mode == 0 {
mode = 0o600
}
switch header.Typeflag {
case tar.TypeDir:
err = os.MkdirAll(headerPath, mode)
if err != nil {
return xerrors.Errorf("mkdir %q: %w", headerPath, err)
}
s.Logger.Debug(context.Background(), "extracted directory",
slog.F("path", headerPath),
slog.F("mode", fmt.Sprintf("%O", mode)))
case tar.TypeReg:
file, err := os.OpenFile(headerPath, os.O_CREATE|os.O_RDWR, mode)
if err != nil {
return xerrors.Errorf("create file %q (mode %s): %w", headerPath, mode, err)
}
// Max file size of 10MiB.
size, err := io.CopyN(file, reader, 10<<20)
if xerrors.Is(err, io.EOF) {
err = nil
}
if err != nil {
_ = file.Close()
return xerrors.Errorf("copy file %q: %w", headerPath, err)
}
err = file.Close()
if err != nil {
return xerrors.Errorf("close file %q: %s", headerPath, err)
}
s.Logger.Debug(context.Background(), "extracted file",
slog.F("size_bytes", size),
slog.F("path", headerPath),
slog.F("mode", mode),
)
}
}
return nil
}
func (s *Session) ProvisionLog(level proto.LogLevel, output string) {
if int32(level) < s.logLevel {
return
}
err := s.stream.Send(&proto.Response{Type: &proto.Response_Log{Log: &proto.Log{
Level: level,
Output: output,
}}})
if err != nil {
s.Logger.Error(s.Context(), "failed to transmit log",
slog.F("level", level), slog.F("output", output))
}
}
type pRequest interface {
*proto.ParseRequest | *proto.PlanRequest | *proto.ApplyRequest
}
type pComplete interface {
*proto.ParseComplete | *proto.PlanComplete | *proto.ApplyComplete
}
// request processes a single request call to the Server and returns its complete result, while also processing cancel
// requests from the daemon. Provisioner implementations read from canceledOrComplete to be asynchronously informed
// of cancel.
type request[R pRequest, C pComplete] struct {
req R
session *Session
cancels <-chan *proto.Request
serverFn func(*Session, R, <-chan struct{}) C
}
func (r *request[R, C]) do() (C, error) {
canceledOrComplete := make(chan struct{})
result := make(chan C)
go func() {
c := r.serverFn(r.session, r.req, canceledOrComplete)
result <- c
}()
select {
case req := <-r.cancels:
close(canceledOrComplete)
// wait for server to complete the request, even though we have canceled,
// so that we can't start a new request, and so that if the job was close
// to completion and the cancel was ignored, we return to complete.
c := <-result
// verify we got a cancel instead of another request or closed channel --- which is an error!
if req.GetCancel() != nil {
return c, nil
}
if req == nil {
return c, xerrors.New("got nil while old request still processing")
}
return c, xerrors.Errorf("got new request %T while old request still processing", req.Type)
case c := <-result:
close(canceledOrComplete)
return c, nil
}
}

View File

@ -19,7 +19,7 @@ const (
MaxMessageSize = 4 << 20 MaxMessageSize = 4 << 20
) )
// MultiplexedConn returns a multiplexed dRPC connection from a yamux session. // MultiplexedConn returns a multiplexed dRPC connection from a yamux Session.
func MultiplexedConn(session *yamux.Session) drpc.Conn { func MultiplexedConn(session *yamux.Session) drpc.Conn {
return &multiplexedDRPC{session} return &multiplexedDRPC{session}
} }

View File

@ -231,10 +231,10 @@ func setupRunnerTest(t *testing.T) (client *codersdk.Client, agentID uuid.UUID)
authToken := uuid.NewString() authToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: []*proto.Response{{
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{ Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{{ Resources: []*proto.Resource{{
Name: "example", Name: "example",
Type: "aws_instance", Type: "aws_instance",

View File

@ -48,10 +48,10 @@ func Test_Runner(t *testing.T) {
authToken := uuid.NewString() authToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: []*proto.Provision_Response{ ProvisionApply: []*proto.Response{
{ {
Type: &proto.Provision_Response_Log{ Type: &proto.Response_Log{
Log: &proto.Log{ Log: &proto.Log{
Level: proto.LogLevel_INFO, Level: proto.LogLevel_INFO,
Output: "hello from logs", Output: "hello from logs",
@ -59,8 +59,8 @@ func Test_Runner(t *testing.T) {
}, },
}, },
{ {
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{ Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{ Resources: []*proto.Resource{
{ {
Name: "example", Name: "example",
@ -170,10 +170,10 @@ func Test_Runner(t *testing.T) {
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: []*proto.Provision_Response{ ProvisionApply: []*proto.Response{
{ {
Type: &proto.Provision_Response_Log{Log: &proto.Log{}}, Type: &proto.Response_Log{Log: &proto.Log{}},
}, },
}, },
}) })
@ -282,10 +282,10 @@ func Test_Runner(t *testing.T) {
authToken := uuid.NewString() authToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: []*proto.Provision_Response{ ProvisionApply: []*proto.Response{
{ {
Type: &proto.Provision_Response_Log{ Type: &proto.Response_Log{
Log: &proto.Log{ Log: &proto.Log{
Level: proto.LogLevel_INFO, Level: proto.LogLevel_INFO,
Output: "hello from logs", Output: "hello from logs",
@ -293,8 +293,8 @@ func Test_Runner(t *testing.T) {
}, },
}, },
{ {
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{ Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{ Resources: []*proto.Resource{
{ {
Name: "example", Name: "example",
@ -407,11 +407,11 @@ func Test_Runner(t *testing.T) {
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: []*proto.Provision_Response{ ProvisionApply: []*proto.Response{
{ {
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{ Apply: &proto.ApplyComplete{
Error: "test error", Error: "test error",
}, },
}, },

View File

@ -252,10 +252,10 @@ func setupRunnerTest(t *testing.T) (client *codersdk.Client, agentID uuid.UUID)
authToken := uuid.NewString() authToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: []*proto.Response{{
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{ Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{{ Resources: []*proto.Resource{{
Name: "example", Name: "example",
Type: "aws_instance", Type: "aws_instance",

View File

@ -45,10 +45,10 @@ func Test_Runner(t *testing.T) {
authToken3 := uuid.NewString() authToken3 := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: []*proto.Provision_Response{ ProvisionApply: []*proto.Response{
{ {
Type: &proto.Provision_Response_Log{ Type: &proto.Response_Log{
Log: &proto.Log{ Log: &proto.Log{
Level: proto.LogLevel_INFO, Level: proto.LogLevel_INFO,
Output: "hello from logs", Output: "hello from logs",
@ -56,8 +56,8 @@ func Test_Runner(t *testing.T) {
}, },
}, },
{ {
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{ Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{ Resources: []*proto.Resource{
{ {
Name: "example1", Name: "example1",
@ -199,11 +199,11 @@ func Test_Runner(t *testing.T) {
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: []*proto.Provision_Response{ ProvisionApply: []*proto.Response{
{ {
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{ Apply: &proto.ApplyComplete{
Error: "test error", Error: "test error",
}, },
}, },

View File

@ -41,10 +41,10 @@ func TestRun(t *testing.T) {
agentName = "agent" agentName = "agent"
version = coderdtest.CreateTemplateVersion(t, client, firstUser.OrganizationID, &echo.Responses{ version = coderdtest.CreateTemplateVersion(t, client, firstUser.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: []*proto.Response{{
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{ Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{{ Resources: []*proto.Resource{{
Name: "example", Name: "example",
Type: "aws_instance", Type: "aws_instance",
@ -154,10 +154,10 @@ func TestRun(t *testing.T) {
agentName = "agent" agentName = "agent"
version = coderdtest.CreateTemplateVersion(t, client, firstUser.OrganizationID, &echo.Responses{ version = coderdtest.CreateTemplateVersion(t, client, firstUser.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete, Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete, ProvisionPlan: echo.PlanComplete,
ProvisionApply: []*proto.Provision_Response{{ ProvisionApply: []*proto.Response{{
Type: &proto.Provision_Response_Complete{ Type: &proto.Response_Apply{
Complete: &proto.Provision_Complete{ Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{{ Resources: []*proto.Resource{{
Name: "example", Name: "example",
Type: "aws_instance", Type: "aws_instance",

View File

@ -8,10 +8,10 @@ import {
Agent, Agent,
App, App,
AppSharingLevel, AppSharingLevel,
Parse_Complete, Response,
Parse_Response, ParseComplete,
Provision_Complete, PlanComplete,
Provision_Response, ApplyComplete,
Resource, Resource,
RichParameter, RichParameter,
} from "./provisionerGenerated" } from "./provisionerGenerated"
@ -337,11 +337,11 @@ type RecursivePartial<T> = {
interface EchoProvisionerResponses { interface EchoProvisionerResponses {
// parse is for observing any Terraform variables // parse is for observing any Terraform variables
parse?: RecursivePartial<Parse_Response>[] parse?: RecursivePartial<Response>[]
// plan occurs when the template is imported // plan occurs when the template is imported
plan?: RecursivePartial<Provision_Response>[] plan?: RecursivePartial<Response>[]
// apply occurs when the workspace is built // apply occurs when the workspace is built
apply?: RecursivePartial<Provision_Response>[] apply?: RecursivePartial<Response>[]
} }
// createTemplateVersionTar consumes a series of echo provisioner protobufs and // createTemplateVersionTar consumes a series of echo provisioner protobufs and
@ -353,109 +353,133 @@ const createTemplateVersionTar = async (
responses = {} responses = {}
} }
if (!responses.parse) { if (!responses.parse) {
responses.parse = [{}] responses.parse = [
{
parse: {},
},
]
} }
if (!responses.apply) { if (!responses.apply) {
responses.apply = [{}] responses.apply = [
{
apply: {},
},
]
} }
if (!responses.plan) { if (!responses.plan) {
responses.plan = responses.apply responses.plan = responses.apply.map((response) => {
if (response.log) {
return response
}
return {
plan: {
error: response.apply?.error ?? "",
resources: response.apply?.resources ?? [],
parameters: response.apply?.parameters ?? [],
gitAuthProviders: response.apply?.gitAuthProviders ?? [],
},
}
})
} }
const tar = new TarWriter() const tar = new TarWriter()
responses.parse.forEach((response, index) => { responses.parse.forEach((response, index) => {
response.complete = { response.parse = {
templateVariables: [], templateVariables: [],
...response.complete, error: "",
} as Parse_Complete readme: new Uint8Array(),
...response.parse,
} as ParseComplete
tar.addFile( tar.addFile(
`${index}.parse.protobuf`, `${index}.parse.protobuf`,
Parse_Response.encode(response as Parse_Response).finish(), Response.encode(response as Response).finish(),
) )
}) })
const fillProvisionResponse = ( const fillResource = (resource: RecursivePartial<Resource>) => {
response: RecursivePartial<Provision_Response>, if (resource.agents) {
) => { resource.agents = resource.agents?.map(
response.complete = { (agent: RecursivePartial<Agent>) => {
if (agent.apps) {
agent.apps = agent.apps?.map((app: RecursivePartial<App>) => {
return {
command: "",
displayName: "example",
external: false,
icon: "",
sharingLevel: AppSharingLevel.PUBLIC,
slug: "example",
subdomain: false,
url: "",
...app,
} as App
})
}
return {
apps: [],
architecture: "amd64",
connectionTimeoutSeconds: 300,
directory: "",
env: {},
id: randomUUID(),
metadata: [],
motdFile: "",
name: "dev",
operatingSystem: "linux",
shutdownScript: "",
shutdownScriptTimeoutSeconds: 0,
startupScript: "",
startupScriptBehavior: "",
startupScriptTimeoutSeconds: 300,
troubleshootingUrl: "",
token: randomUUID(),
...agent,
} as Agent
},
)
}
return {
agents: [],
dailyCost: 0,
hide: false,
icon: "",
instanceType: "",
metadata: [],
name: "dev",
type: "echo",
...resource,
} as Resource
}
responses.apply.forEach((response, index) => {
response.apply = {
error: "", error: "",
state: new Uint8Array(), state: new Uint8Array(),
resources: [], resources: [],
parameters: [], parameters: [],
gitAuthProviders: [], gitAuthProviders: [],
plan: new Uint8Array(), ...response.apply,
...response.complete, } as ApplyComplete
} as Provision_Complete response.apply.resources = response.apply.resources?.map(fillResource)
response.complete.resources = response.complete.resources?.map(
(resource) => {
if (resource.agents) {
resource.agents = resource.agents?.map((agent) => {
if (agent.apps) {
agent.apps = agent.apps?.map((app) => {
return {
command: "",
displayName: "example",
external: false,
icon: "",
sharingLevel: AppSharingLevel.PUBLIC,
slug: "example",
subdomain: false,
url: "",
...app,
} as App
})
}
return {
apps: [],
architecture: "amd64",
connectionTimeoutSeconds: 300,
directory: "",
env: {},
id: randomUUID(),
metadata: [],
motdFile: "",
name: "dev",
operatingSystem: "linux",
shutdownScript: "",
shutdownScriptTimeoutSeconds: 0,
startupScript: "",
startupScriptBehavior: "",
startupScriptTimeoutSeconds: 300,
troubleshootingUrl: "",
token: randomUUID(),
...agent,
} as Agent
})
}
return {
agents: [],
dailyCost: 0,
hide: false,
icon: "",
instanceType: "",
metadata: [],
name: "dev",
type: "echo",
...resource,
} as Resource
},
)
}
responses.apply.forEach((response, index) => {
fillProvisionResponse(response)
tar.addFile( tar.addFile(
`${index}.provision.apply.protobuf`, `${index}.apply.protobuf`,
Provision_Response.encode(response as Provision_Response).finish(), Response.encode(response as Response).finish(),
) )
}) })
responses.plan.forEach((response, index) => { responses.plan.forEach((response, index) => {
fillProvisionResponse(response) response.plan = {
error: "",
resources: [],
parameters: [],
gitAuthProviders: [],
...response.plan,
} as PlanComplete
response.plan.resources = response.plan.resources?.map(fillResource)
tar.addFile( tar.addFile(
`${index}.provision.plan.protobuf`, `${index}.plan.protobuf`,
Provision_Response.encode(response as Provision_Response).finish(), Response.encode(response as Response).finish(),
) )
}) })
const tarFile = await tar.write() const tarFile = await tar.write()
@ -512,16 +536,21 @@ export const echoResponsesWithParameters = (
richParameters: RichParameter[], richParameters: RichParameter[],
): EchoProvisionerResponses => { ): EchoProvisionerResponses => {
return { return {
parse: [
{
parse: {},
},
],
plan: [ plan: [
{ {
complete: { plan: {
parameters: richParameters, parameters: richParameters,
}, },
}, },
], ],
apply: [ apply: [
{ {
complete: { apply: {
resources: [ resources: [
{ {
name: "example", name: "example",

View File

@ -21,6 +21,7 @@ export enum AppSharingLevel {
UNRECOGNIZED = -1, UNRECOGNIZED = -1,
} }
/** WorkspaceTransition is the desired outcome of a build */
export enum WorkspaceTransition { export enum WorkspaceTransition {
START = 0, START = 0,
STOP = 1, STOP = 1,
@ -177,29 +178,8 @@ export interface Resource_Metadata {
isNull: boolean isNull: boolean
} }
/** Parse consumes source-code from a directory to produce inputs. */ /** Metadata is information about a workspace used in the execution of a build */
export interface Parse {} export interface Metadata {
export interface Parse_Request {
directory: string
}
export interface Parse_Complete {
templateVariables: TemplateVariable[]
}
export interface Parse_Response {
log?: Log | undefined
complete?: Parse_Complete | undefined
}
/**
* Provision consumes source-code from a directory to produce resources.
* Exactly one of Plan or Apply must be provided in a single session.
*/
export interface Provision {}
export interface Provision_Metadata {
coderUrl: string coderUrl: string
workspaceTransition: WorkspaceTransition workspaceTransition: WorkspaceTransition
workspaceName: string workspaceName: string
@ -213,49 +193,74 @@ export interface Provision_Metadata {
workspaceOwnerSessionToken: string workspaceOwnerSessionToken: string
} }
/** /** Config represents execution configuration shared by all subsequent requests in the Session */
* Config represents execution configuration shared by both Plan and export interface Config {
* Apply commands. /** template_source_archive is a tar of the template source files */
*/ templateSourceArchive: Uint8Array
export interface Provision_Config { /** state is the provisioner state (if any) */
directory: string
state: Uint8Array state: Uint8Array
metadata: Provision_Metadata | undefined
provisionerLogLevel: string provisionerLogLevel: string
} }
export interface Provision_Plan { /** ParseRequest consumes source-code to produce inputs. */
config: Provision_Config | undefined export interface ParseRequest {}
/** ParseComplete indicates a request to parse completed. */
export interface ParseComplete {
error: string
templateVariables: TemplateVariable[]
readme: Uint8Array
}
/** PlanRequest asks the provisioner to plan what resources & parameters it will create */
export interface PlanRequest {
metadata: Metadata | undefined
richParameterValues: RichParameterValue[] richParameterValues: RichParameterValue[]
variableValues: VariableValue[] variableValues: VariableValue[]
gitAuthProviders: GitAuthProvider[] gitAuthProviders: GitAuthProvider[]
} }
export interface Provision_Apply { /** PlanComplete indicates a request to plan completed. */
config: Provision_Config | undefined export interface PlanComplete {
plan: Uint8Array error: string
resources: Resource[]
parameters: RichParameter[]
gitAuthProviders: string[]
} }
export interface Provision_Cancel {} /**
* ApplyRequest asks the provisioner to apply the changes. Apply MUST be preceded by a successful plan request/response
export interface Provision_Request { * in the same Session. The plan data is not transmitted over the wire and is cached by the provisioner in the Session.
plan?: Provision_Plan | undefined */
apply?: Provision_Apply | undefined export interface ApplyRequest {
cancel?: Provision_Cancel | undefined metadata: Metadata | undefined
} }
export interface Provision_Complete { /** ApplyComplete indicates a request to apply completed. */
export interface ApplyComplete {
state: Uint8Array state: Uint8Array
error: string error: string
resources: Resource[] resources: Resource[]
parameters: RichParameter[] parameters: RichParameter[]
gitAuthProviders: string[] gitAuthProviders: string[]
plan: Uint8Array
} }
export interface Provision_Response { /** CancelRequest requests that the previous request be canceled gracefully. */
export interface CancelRequest {}
export interface Request {
config?: Config | undefined
parse?: ParseRequest | undefined
plan?: PlanRequest | undefined
apply?: ApplyRequest | undefined
cancel?: CancelRequest | undefined
}
export interface Response {
log?: Log | undefined log?: Log | undefined
complete?: Provision_Complete | undefined parse?: ParseComplete | undefined
plan?: PlanComplete | undefined
apply?: ApplyComplete | undefined
} }
export const Empty = { export const Empty = {
@ -648,60 +653,9 @@ export const Resource_Metadata = {
}, },
} }
export const Parse = { export const Metadata = {
encode(_: Parse, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer {
return writer
},
}
export const Parse_Request = {
encode( encode(
message: Parse_Request, message: Metadata,
writer: _m0.Writer = _m0.Writer.create(),
): _m0.Writer {
if (message.directory !== "") {
writer.uint32(10).string(message.directory)
}
return writer
},
}
export const Parse_Complete = {
encode(
message: Parse_Complete,
writer: _m0.Writer = _m0.Writer.create(),
): _m0.Writer {
for (const v of message.templateVariables) {
TemplateVariable.encode(v!, writer.uint32(10).fork()).ldelim()
}
return writer
},
}
export const Parse_Response = {
encode(
message: Parse_Response,
writer: _m0.Writer = _m0.Writer.create(),
): _m0.Writer {
if (message.log !== undefined) {
Log.encode(message.log, writer.uint32(10).fork()).ldelim()
}
if (message.complete !== undefined) {
Parse_Complete.encode(message.complete, writer.uint32(18).fork()).ldelim()
}
return writer
},
}
export const Provision = {
encode(_: Provision, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer {
return writer
},
}
export const Provision_Metadata = {
encode(
message: Provision_Metadata,
writer: _m0.Writer = _m0.Writer.create(), writer: _m0.Writer = _m0.Writer.create(),
): _m0.Writer { ): _m0.Writer {
if (message.coderUrl !== "") { if (message.coderUrl !== "") {
@ -741,96 +695,108 @@ export const Provision_Metadata = {
}, },
} }
export const Provision_Config = { export const Config = {
encode( encode(
message: Provision_Config, message: Config,
writer: _m0.Writer = _m0.Writer.create(), writer: _m0.Writer = _m0.Writer.create(),
): _m0.Writer { ): _m0.Writer {
if (message.directory !== "") { if (message.templateSourceArchive.length !== 0) {
writer.uint32(10).string(message.directory) writer.uint32(10).bytes(message.templateSourceArchive)
} }
if (message.state.length !== 0) { if (message.state.length !== 0) {
writer.uint32(18).bytes(message.state) writer.uint32(18).bytes(message.state)
} }
if (message.metadata !== undefined) {
Provision_Metadata.encode(
message.metadata,
writer.uint32(26).fork(),
).ldelim()
}
if (message.provisionerLogLevel !== "") { if (message.provisionerLogLevel !== "") {
writer.uint32(34).string(message.provisionerLogLevel) writer.uint32(26).string(message.provisionerLogLevel)
} }
return writer return writer
}, },
} }
export const Provision_Plan = { export const ParseRequest = {
encode( encode(
message: Provision_Plan, _: ParseRequest,
writer: _m0.Writer = _m0.Writer.create(), writer: _m0.Writer = _m0.Writer.create(),
): _m0.Writer { ): _m0.Writer {
if (message.config !== undefined) { return writer
Provision_Config.encode(message.config, writer.uint32(10).fork()).ldelim() },
}
export const ParseComplete = {
encode(
message: ParseComplete,
writer: _m0.Writer = _m0.Writer.create(),
): _m0.Writer {
if (message.error !== "") {
writer.uint32(10).string(message.error)
}
for (const v of message.templateVariables) {
TemplateVariable.encode(v!, writer.uint32(18).fork()).ldelim()
}
if (message.readme.length !== 0) {
writer.uint32(26).bytes(message.readme)
}
return writer
},
}
export const PlanRequest = {
encode(
message: PlanRequest,
writer: _m0.Writer = _m0.Writer.create(),
): _m0.Writer {
if (message.metadata !== undefined) {
Metadata.encode(message.metadata, writer.uint32(10).fork()).ldelim()
} }
for (const v of message.richParameterValues) { for (const v of message.richParameterValues) {
RichParameterValue.encode(v!, writer.uint32(26).fork()).ldelim() RichParameterValue.encode(v!, writer.uint32(18).fork()).ldelim()
} }
for (const v of message.variableValues) { for (const v of message.variableValues) {
VariableValue.encode(v!, writer.uint32(34).fork()).ldelim() VariableValue.encode(v!, writer.uint32(26).fork()).ldelim()
} }
for (const v of message.gitAuthProviders) { for (const v of message.gitAuthProviders) {
GitAuthProvider.encode(v!, writer.uint32(42).fork()).ldelim() GitAuthProvider.encode(v!, writer.uint32(34).fork()).ldelim()
} }
return writer return writer
}, },
} }
export const Provision_Apply = { export const PlanComplete = {
encode( encode(
message: Provision_Apply, message: PlanComplete,
writer: _m0.Writer = _m0.Writer.create(), writer: _m0.Writer = _m0.Writer.create(),
): _m0.Writer { ): _m0.Writer {
if (message.config !== undefined) { if (message.error !== "") {
Provision_Config.encode(message.config, writer.uint32(10).fork()).ldelim() writer.uint32(10).string(message.error)
} }
if (message.plan.length !== 0) { for (const v of message.resources) {
writer.uint32(18).bytes(message.plan) Resource.encode(v!, writer.uint32(18).fork()).ldelim()
}
for (const v of message.parameters) {
RichParameter.encode(v!, writer.uint32(26).fork()).ldelim()
}
for (const v of message.gitAuthProviders) {
writer.uint32(34).string(v!)
} }
return writer return writer
}, },
} }
export const Provision_Cancel = { export const ApplyRequest = {
encode( encode(
_: Provision_Cancel, message: ApplyRequest,
writer: _m0.Writer = _m0.Writer.create(), writer: _m0.Writer = _m0.Writer.create(),
): _m0.Writer { ): _m0.Writer {
return writer if (message.metadata !== undefined) {
}, Metadata.encode(message.metadata, writer.uint32(10).fork()).ldelim()
}
export const Provision_Request = {
encode(
message: Provision_Request,
writer: _m0.Writer = _m0.Writer.create(),
): _m0.Writer {
if (message.plan !== undefined) {
Provision_Plan.encode(message.plan, writer.uint32(10).fork()).ldelim()
}
if (message.apply !== undefined) {
Provision_Apply.encode(message.apply, writer.uint32(18).fork()).ldelim()
}
if (message.cancel !== undefined) {
Provision_Cancel.encode(message.cancel, writer.uint32(26).fork()).ldelim()
} }
return writer return writer
}, },
} }
export const Provision_Complete = { export const ApplyComplete = {
encode( encode(
message: Provision_Complete, message: ApplyComplete,
writer: _m0.Writer = _m0.Writer.create(), writer: _m0.Writer = _m0.Writer.create(),
): _m0.Writer { ): _m0.Writer {
if (message.state.length !== 0) { if (message.state.length !== 0) {
@ -848,34 +814,76 @@ export const Provision_Complete = {
for (const v of message.gitAuthProviders) { for (const v of message.gitAuthProviders) {
writer.uint32(42).string(v!) writer.uint32(42).string(v!)
} }
if (message.plan.length !== 0) { return writer
writer.uint32(50).bytes(message.plan) },
}
export const CancelRequest = {
encode(
_: CancelRequest,
writer: _m0.Writer = _m0.Writer.create(),
): _m0.Writer {
return writer
},
}
export const Request = {
encode(
message: Request,
writer: _m0.Writer = _m0.Writer.create(),
): _m0.Writer {
if (message.config !== undefined) {
Config.encode(message.config, writer.uint32(10).fork()).ldelim()
}
if (message.parse !== undefined) {
ParseRequest.encode(message.parse, writer.uint32(18).fork()).ldelim()
}
if (message.plan !== undefined) {
PlanRequest.encode(message.plan, writer.uint32(26).fork()).ldelim()
}
if (message.apply !== undefined) {
ApplyRequest.encode(message.apply, writer.uint32(34).fork()).ldelim()
}
if (message.cancel !== undefined) {
CancelRequest.encode(message.cancel, writer.uint32(42).fork()).ldelim()
} }
return writer return writer
}, },
} }
export const Provision_Response = { export const Response = {
encode( encode(
message: Provision_Response, message: Response,
writer: _m0.Writer = _m0.Writer.create(), writer: _m0.Writer = _m0.Writer.create(),
): _m0.Writer { ): _m0.Writer {
if (message.log !== undefined) { if (message.log !== undefined) {
Log.encode(message.log, writer.uint32(10).fork()).ldelim() Log.encode(message.log, writer.uint32(10).fork()).ldelim()
} }
if (message.complete !== undefined) { if (message.parse !== undefined) {
Provision_Complete.encode( ParseComplete.encode(message.parse, writer.uint32(18).fork()).ldelim()
message.complete, }
writer.uint32(18).fork(), if (message.plan !== undefined) {
).ldelim() PlanComplete.encode(message.plan, writer.uint32(26).fork()).ldelim()
}
if (message.apply !== undefined) {
ApplyComplete.encode(message.apply, writer.uint32(34).fork()).ldelim()
} }
return writer return writer
}, },
} }
export interface Provisioner { export interface Provisioner {
Parse(request: Parse_Request): Observable<Parse_Response> /**
Provision( * Session represents provisioning a single template import or workspace. The daemon always sends Config followed
request: Observable<Provision_Request>, * by one of the requests (ParseRequest, PlanRequest, ApplyRequest). The provisioner should respond with a stream
): Observable<Provision_Response> * of zero or more Logs, followed by the corresponding complete message (ParseComplete, PlanComplete,
* ApplyComplete). The daemon may then send a new request. A request to apply MUST be preceded by a request plan,
* and the provisioner should store the plan data on the Session after a successful plan, so that the daemon may
* request an apply. If the daemon closes the Session without an apply, the plan data may be safely discarded.
*
* The daemon may send a CancelRequest, asynchronously to ask the provisioner to cancel the previous ParseRequest,
* PlanRequest, or ApplyRequest. The provisioner MUST reply with a complete message corresponding to the request
* that was canceled. If the provisioner has already completed the request, it may ignore the CancelRequest.
*/
Session(request: Observable<Request>): Observable<Response>
} }

View File

@ -20,7 +20,7 @@ test("app", async ({ context, page }) => {
const template = await createTemplate(page, { const template = await createTemplate(page, {
apply: [ apply: [
{ {
complete: { apply: {
resources: [ resources: [
{ {
agents: [ agents: [

View File

@ -21,7 +21,7 @@ test("create workspace", async ({ page }) => {
const template = await createTemplate(page, { const template = await createTemplate(page, {
apply: [ apply: [
{ {
complete: { apply: {
resources: [ resources: [
{ {
name: "example", name: "example",

View File

@ -15,7 +15,7 @@ test("ssh with agent " + agentVersion, async ({ page }) => {
const template = await createTemplate(page, { const template = await createTemplate(page, {
apply: [ apply: [
{ {
complete: { apply: {
resources: [ resources: [
{ {
agents: [ agents: [

View File

@ -15,7 +15,7 @@ test("ssh with client " + clientVersion, async ({ page }) => {
const template = await createTemplate(page, { const template = await createTemplate(page, {
apply: [ apply: [
{ {
complete: { apply: {
resources: [ resources: [
{ {
agents: [ agents: [

View File

@ -7,7 +7,7 @@ test("web terminal", async ({ context, page }) => {
const template = await createTemplate(page, { const template = await createTemplate(page, {
apply: [ apply: [
{ {
complete: { apply: {
resources: [ resources: [
{ {
agents: [ agents: [