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
gen: \
coderd/database/dump.sql \
$(DB_GEN_FILES) \
provisionersdk/proto/provisioner.pb.go \
provisionerd/proto/provisionerd.pb.go \
coderd/database/dump.sql \
$(DB_GEN_FILES) \
site/src/api/typesGenerated.ts \
coderd/rbac/object_gen.go \
docs/admin/prometheus.md \
@ -478,10 +478,10 @@ gen: \
# used during releases so we don't run generation scripts.
gen/mark-fresh:
files="\
coderd/database/dump.sql \
$(DB_GEN_FILES) \
provisionersdk/proto/provisioner.pb.go \
provisionerd/proto/provisionerd.pb.go \
coderd/database/dump.sql \
$(DB_GEN_FILES) \
site/src/api/typesGenerated.ts \
coderd/rbac/object_gen.go \
docs/admin/prometheus.md \

View File

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

View File

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

View File

@ -29,11 +29,7 @@ func TestCreate(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
ProvisionApply: provisionCompleteWithAgent,
ProvisionPlan: provisionCompleteWithAgent,
})
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, completeWithAgent())
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
args := []string{
@ -84,11 +80,7 @@ func TestCreate(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
owner := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
ProvisionApply: provisionCompleteWithAgent,
ProvisionPlan: provisionCompleteWithAgent,
})
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, completeWithAgent())
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
_, user := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
@ -141,11 +133,7 @@ func TestCreate(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
ProvisionApply: provisionCompleteWithAgent,
ProvisionPlan: provisionCompleteWithAgent,
})
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, completeWithAgent())
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
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) {
t.Parallel()
@ -258,27 +262,12 @@ func TestCreateWithRichParameters(t *testing.T) {
immutableParameterValue = "4"
)
echoResponses := &echo.Responses{
Parse: echo.ParseComplete,
ProvisionPlan: []*proto.Provision_Response{
{
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{},
},
}},
}
echoResponses := prepareEchoResponses([]*proto.RichParameter{
{Name: firstParameterName, Description: firstParameterDescription, Mutable: true},
{Name: secondParameterName, DisplayName: secondParameterDisplayName, Description: secondParameterDescription, Mutable: true},
{Name: immutableParameterName, Description: immutableParameterDescription, Mutable: false},
},
)
t.Run("InputParameters", func(t *testing.T) {
t.Parallel()
@ -427,28 +416,6 @@ func TestCreateValidateRichParameters(t *testing.T) {
{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.Parallel()
@ -626,20 +593,16 @@ func TestCreateWithGitAuth(t *testing.T) {
t.Parallel()
echoResponses := &echo.Responses{
Parse: echo.ParseComplete,
ProvisionPlan: []*proto.Provision_Response{
ProvisionPlan: []*proto.Response{
{
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{
Type: &proto.Response_Plan{
Plan: &proto.PlanComplete{
GitAuthProviders: []string{"github"},
},
},
},
},
ProvisionApply: []*proto.Provision_Response{{
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{},
},
}},
ProvisionApply: echo.ApplyComplete,
}
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()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete,
ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ProvisionApplyWithAgent(agentToken),
})
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()
version := coderdtest.CreateTemplateVersion(t, client, orgID, &echo.Responses{
Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete,
ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ProvisionApplyWithAgent(agentToken),
})

View File

@ -20,30 +20,14 @@ import (
func TestRestart(t *testing.T) {
t.Parallel()
echoResponses := &echo.Responses{
Parse: echo.ParseComplete,
ProvisionPlan: []*proto.Provision_Response{
{
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{
Parameters: []*proto.RichParameter{
{
Name: ephemeralParameterName,
Description: ephemeralParameterDescription,
Mutable: true,
Ephemeral: true,
},
},
},
},
},
echoResponses := prepareEchoResponses([]*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.Parallel()
@ -187,10 +171,10 @@ func TestRestartWithParameters(t *testing.T) {
echoResponses := &echo.Responses{
Parse: echo.ParseComplete,
ProvisionPlan: []*proto.Provision_Response{
ProvisionPlan: []*proto.Response{
{
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{
Type: &proto.Response_Plan{
Plan: &proto.PlanComplete{
Parameters: []*proto.RichParameter{
{
Name: immutableParameterName,
@ -202,11 +186,7 @@ func TestRestartWithParameters(t *testing.T) {
},
},
},
ProvisionApply: []*proto.Provision_Response{{
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{},
},
}},
ProvisionApply: echo.ApplyComplete,
}
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/collectors"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/spf13/afero"
"go.opentelemetry.io/otel/trace"
"golang.org/x/mod/semver"
"golang.org/x/oauth2"
@ -1304,7 +1303,11 @@ func newProvisionerDaemon(
defer wg.Done()
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 {
select {
case errCh <- err:
@ -1336,10 +1339,11 @@ func newProvisionerDaemon(
err := terraform.Serve(ctx, &terraform.ServeOptions{
ServeOptions: &provisionersdk.ServeOptions{
Listener: terraformServer,
Listener: terraformServer,
Logger: logger.Named("terraform"),
WorkDirectory: workDir,
},
CachePath: tfDir,
Logger: logger.Named("terraform"),
Tracer: tracer,
})
if err != nil && !xerrors.Is(err, context.Canceled) {
@ -1366,7 +1370,6 @@ func newProvisionerDaemon(
UpdateInterval: time.Second,
ForceCancelInterval: cfg.Provisioner.ForceCancelInterval.Value(),
Provisioners: provisioners,
WorkDirectory: workDir,
TracerProvider: coderAPI.TracerProvider,
Metrics: &metrics,
}), nil

View File

@ -7,7 +7,6 @@ import (
"github.com/coder/coder/v2/cli/clitest"
"github.com/coder/coder/v2/coderd/coderdtest"
"github.com/coder/coder/v2/provisioner/echo"
"github.com/coder/coder/v2/pty/ptytest"
)
@ -17,11 +16,7 @@ func TestShow(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
ProvisionApply: provisionCompleteWithAgent,
ProvisionPlan: provisionCompleteWithAgent,
})
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, completeWithAgent())
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.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()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete,
ProvisionApply: []*proto.Provision_Response{{
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{
ProvisionPlan: echo.PlanComplete,
ProvisionApply: []*proto.Response{{
Type: &proto.Response_Apply{
Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{{
Name: "dev",
Type: "google_compute_instance",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -57,8 +57,8 @@ func TestUpdate(t *testing.T) {
version2 := coderdtest.UpdateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
ProvisionApply: echo.ProvisionComplete,
ProvisionPlan: echo.ProvisionComplete,
ProvisionApply: echo.ApplyComplete,
ProvisionPlan: echo.PlanComplete,
}, template.ID)
_ = coderdtest.AwaitTemplateVersionJob(t, client, version2.ID)
@ -100,28 +100,13 @@ func TestUpdateWithRichParameters(t *testing.T) {
immutableParameterValue = "4"
)
echoResponses := &echo.Responses{
Parse: echo.ParseComplete,
ProvisionPlan: []*proto.Provision_Response{
{
Type: &proto.Provision_Response_Complete{
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{},
},
}},
}
echoResponses := prepareEchoResponses([]*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},
},
)
t.Run("ImmutableCannotBeCustomized", func(t *testing.T) {
t.Parallel()
@ -313,28 +298,6 @@ func TestUpdateValidateRichParameters(t *testing.T) {
{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.Parallel()

View File

@ -17,7 +17,6 @@ import (
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/codersdk/agentsdk"
"github.com/coder/coder/v2/provisioner/echo"
"github.com/coder/coder/v2/provisionersdk/proto"
"github.com/coder/coder/v2/testutil"
)
@ -60,25 +59,9 @@ func TestWorkspaceActivityBump(t *testing.T) {
ttlMillis := int64(ttl / time.Millisecond)
agentToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete,
ProvisionApply: []*proto.Provision_Response{{
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,
},
}},
}},
},
},
}},
Parse: echo.ParseComplete,
ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ProvisionApplyWithAgent(agentToken),
})
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)

View File

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

View File

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

View File

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

View File

@ -23,7 +23,6 @@ import (
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/codersdk/agentsdk"
"github.com/coder/coder/v2/provisioner/echo"
"github.com/coder/coder/v2/provisionersdk/proto"
"github.com/coder/coder/v2/testutil"
)
@ -227,7 +226,7 @@ func TestGitAuthCallback(t *testing.T) {
authToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete,
ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
})
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
@ -256,24 +255,9 @@ func TestGitAuthCallback(t *testing.T) {
user := coderdtest.CreateFirstUser(t, client)
authToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete,
ProvisionApply: []*proto.Provision_Response{{
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,
},
}},
}},
},
},
}},
Parse: echo.ParseComplete,
ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
})
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
@ -342,7 +326,7 @@ func TestGitAuthCallback(t *testing.T) {
authToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete,
ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
})
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
@ -400,7 +384,7 @@ func TestGitAuthCallback(t *testing.T) {
authToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete,
ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
})
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
@ -443,7 +427,7 @@ func TestGitAuthCallback(t *testing.T) {
authToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete,
ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
})
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)

View File

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

View File

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

View File

@ -268,10 +268,10 @@ func TestAgents(t *testing.T) {
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete,
ProvisionApply: []*proto.Provision_Response{{
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{
ProvisionPlan: echo.PlanComplete,
ProvisionApply: []*proto.Response{{
Type: &proto.Response_Apply{
Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{{
Name: "example",
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{
Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete,
ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
})
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),
VariableValues: asVariableValues(templateVariables),
GitAuthProviders: gitAuthProviders,
Metadata: &sdkproto.Provision_Metadata{
Metadata: &sdkproto.Metadata{
CoderUrl: server.AccessURL.String(),
WorkspaceTransition: transition,
WorkspaceName: workspace.Name,
@ -316,7 +316,7 @@ func (server *Server) AcquireJob(ctx context.Context, _ *proto.Empty) (*proto.Ac
TemplateDryRun: &proto.AcquiredJob_TemplateDryRun{
RichParameterValues: convertRichParameterValues(input.RichParameterValues),
VariableValues: asVariableValues(templateVariables),
Metadata: &sdkproto.Provision_Metadata{
Metadata: &sdkproto.Metadata{
CoderUrl: server.AccessURL.String(),
WorkspaceName: input.WorkspaceName,
},
@ -337,7 +337,7 @@ func (server *Server) AcquireJob(ctx context.Context, _ *proto.Empty) (*proto.Ac
protoJob.Type = &proto.AcquiredJob_TemplateImport_{
TemplateImport: &proto.AcquiredJob_TemplateImport{
UserVariableValues: convertVariableValues(userVariableValues),
Metadata: &sdkproto.Provision_Metadata{
Metadata: &sdkproto.Metadata{
CoderUrl: server.AccessURL.String(),
},
},

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -376,12 +376,12 @@ func TestPatchCancelWorkspaceBuild(t *testing.T) {
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
ProvisionApply: []*proto.Provision_Response{{
Type: &proto.Provision_Response_Log{
ProvisionApply: []*proto.Response{{
Type: &proto.Response_Log{
Log: &proto.Log{},
},
}},
ProvisionPlan: echo.ProvisionComplete,
ProvisionPlan: echo.PlanComplete,
})
coderdtest.AwaitTemplateVersionJob(t, client, 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 {
var err error
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) &&
// The job will never actually cancel successfully because it will never send a
// provision complete response.
assert.Empty(t, build.Job.Error) &&
build.Job.Status == codersdk.ProvisionerJobCanceling
build.Job.Error == "canceled" &&
build.Job.Status == codersdk.ProvisionerJobFailed
}, testutil.WaitShort, testutil.IntervalFast)
})
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)
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
ProvisionApply: []*proto.Provision_Response{{
Type: &proto.Provision_Response_Log{
ProvisionApply: []*proto.Response{{
Type: &proto.Response_Log{
Log: &proto.Log{},
},
}},
ProvisionPlan: echo.ProvisionComplete,
ProvisionPlan: echo.PlanComplete,
})
coderdtest.AwaitTemplateVersionJob(t, client, 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)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
ProvisionApply: []*proto.Provision_Response{{
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{
ProvisionApply: []*proto.Response{{
Type: &proto.Response_Apply{
Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{{
Name: "some",
Type: "example",
@ -494,16 +495,16 @@ func TestWorkspaceBuildLogs(t *testing.T) {
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
ProvisionApply: []*proto.Provision_Response{{
Type: &proto.Provision_Response_Log{
ProvisionApply: []*proto.Response{{
Type: &proto.Response_Log{
Log: &proto.Log{
Level: proto.LogLevel_INFO,
Output: "example",
},
},
}, {
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{
Type: &proto.Response_Apply{
Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{{
Name: "some",
Type: "example",
@ -548,10 +549,10 @@ func TestWorkspaceBuildState(t *testing.T) {
wantState := []byte("some kinda state")
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete,
ProvisionApply: []*proto.Provision_Response{{
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{
ProvisionPlan: echo.PlanComplete,
ProvisionApply: []*proto.Response{{
Type: &proto.Response_Apply{
Apply: &proto.ApplyComplete{
State: wantState,
},
},
@ -764,31 +765,31 @@ func TestWorkspaceBuildDebugMode(t *testing.T) {
// Interact as template admin
echoResponses := &echo.Responses{
Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete,
ProvisionApply: []*proto.Provision_Response{{
Type: &proto.Provision_Response_Log{
ProvisionPlan: echo.PlanComplete,
ProvisionApply: []*proto.Response{{
Type: &proto.Response_Log{
Log: &proto.Log{
Level: proto.LogLevel_DEBUG,
Output: "want-it",
},
},
}, {
Type: &proto.Provision_Response_Log{
Type: &proto.Response_Log{
Log: &proto.Log{
Level: proto.LogLevel_TRACE,
Output: "dont-want-it",
},
},
}, {
Type: &proto.Provision_Response_Log{
Type: &proto.Response_Log{
Log: &proto.Log{
Level: proto.LogLevel_DEBUG,
Output: "done",
},
},
}, {
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{},
Type: &proto.Response_Apply{
Apply: &proto.ApplyComplete{},
},
}},
}
@ -831,7 +832,10 @@ func TestWorkspaceBuildDebugMode(t *testing.T) {
if !ok {
break processingLogs
}
t.Logf("got log: %s -- %s | %s | %s", log.Level, log.Stage, log.Source, log.Output)
if log.Source != "provisioner" {
continue
}
logsProcessed++
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.Len(t, echoResponses.ProvisionApply, logsProcessed)
require.Equal(t, 2, logsProcessed)
})
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -89,9 +89,9 @@ func TestWorkspaceQuota(t *testing.T) {
authToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
ProvisionApply: []*proto.Provision_Response{{
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{
ProvisionApply: []*proto.Response{{
Type: &proto.Response_Apply{
Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{{
Name: "example",
Type: "aws_instance",
@ -183,39 +183,15 @@ func TestWorkspaceQuota(t *testing.T) {
require.NoError(t, err)
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{
Parse: echo.ParseComplete,
ProvisionPlanMap: map[proto.WorkspaceTransition][]*proto.Provision_Response{
proto.WorkspaceTransition_START: startResp,
proto.WorkspaceTransition_STOP: stopResp,
ProvisionPlanMap: map[proto.WorkspaceTransition][]*proto.Response{
proto.WorkspaceTransition_START: planWithCost(2),
proto.WorkspaceTransition_STOP: planWithCost(1),
},
ProvisionApplyMap: map[proto.WorkspaceTransition][]*proto.Provision_Response{
proto.WorkspaceTransition_START: startResp,
proto.WorkspaceTransition_STOP: stopResp,
ProvisionApplyMap: map[proto.WorkspaceTransition][]*proto.Response{
proto.WorkspaceTransition_START: applyWithCost(2),
proto.WorkspaceTransition_STOP: applyWithCost(1),
},
})
@ -258,3 +234,31 @@ func TestWorkspaceQuota(t *testing.T) {
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{
Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete,
ProvisionApply: echo.ProvisionFailed,
ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ApplyFailed,
})
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
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{
Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete,
ProvisionApply: echo.ProvisionFailed,
ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ApplyFailed,
})
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
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{
Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete,
ProvisionApply: echo.ProvisionComplete,
ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ApplyComplete,
})
// Create a template without setting a failure_ttl.
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{
Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete,
ProvisionApply: echo.ProvisionComplete,
ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ApplyComplete,
})
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
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{
Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete,
ProvisionApply: echo.ProvisionComplete,
ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ApplyComplete,
})
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
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{
Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete,
ProvisionApply: echo.ProvisionComplete,
ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ApplyComplete,
})
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
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{
Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete,
ProvisionApply: echo.ProvisionComplete,
ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ApplyComplete,
})
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
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{
Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete,
ProvisionApply: echo.ProvisionComplete,
ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ApplyComplete,
})
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
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{
Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete,
ProvisionApply: echo.ProvisionComplete,
ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ApplyComplete,
})
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
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{
Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete,
ProvisionApply: echo.ProvisionComplete,
ProvisionPlan: echo.PlanComplete,
ProvisionApply: echo.ApplyComplete,
})
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)

View File

@ -5,25 +5,24 @@ import (
"bytes"
"context"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/google/uuid"
"golang.org/x/xerrors"
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/proto"
)
// ProvisionApplyWithAgent returns provision responses that will mock a fake
// "aws_instance" resource with an agent that has the given auth token.
func ProvisionApplyWithAgent(authToken string) []*proto.Provision_Response {
return []*proto.Provision_Response{{
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{
func ProvisionApplyWithAgent(authToken string) []*proto.Response {
return []*proto.Response{{
Type: &proto.Response_Apply{
Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{{
Name: "example",
Type: "aws_instance",
@ -42,23 +41,36 @@ func ProvisionApplyWithAgent(authToken string) []*proto.Provision_Response {
var (
// ParseComplete is a helper to indicate an empty parse completion.
ParseComplete = []*proto.Parse_Response{{
Type: &proto.Parse_Response_Complete{
Complete: &proto.Parse_Complete{},
ParseComplete = []*proto.Response{{
Type: &proto.Response_Parse{
Parse: &proto.ParseComplete{},
},
}}
// ProvisionComplete is a helper to indicate an empty provision completion.
ProvisionComplete = []*proto.Provision_Response{{
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{},
// PlanComplete is a helper to indicate an empty provision completion.
PlanComplete = []*proto.Response{{
Type: &proto.Response_Plan{
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
// operation.
ProvisionFailed = []*proto.Provision_Response{{
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{
// PlanFailed is a helper to convey a failed plan operation
PlanFailed = []*proto.Response{{
Type: &proto.Response_Plan{
Plan: &proto.PlanComplete{
Error: "failed!",
},
},
}}
// ApplyFailed is a helper to convey a failed apply operation
ApplyFailed = []*proto.Response{{
Type: &proto.Response_Apply{
Apply: &proto.ApplyComplete{
Error: "failed!",
},
},
@ -66,120 +78,116 @@ var (
)
// Serve starts the echo provisioner.
func Serve(ctx context.Context, filesystem afero.Fs, options *provisionersdk.ServeOptions) error {
return provisionersdk.Serve(ctx, &echo{
filesystem: filesystem,
}, options)
func Serve(ctx context.Context, options *provisionersdk.ServeOptions) error {
return provisionersdk.Serve(ctx, &echo{}, options)
}
// The echo provisioner serves as a dummy provisioner primarily
// used for testing. It echos responses from JSON files in the
// format %d.protobuf. It's used for testing.
type echo struct {
filesystem afero.Fs
type echo struct{}
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.
func (e *echo) Parse(request *proto.Parse_Request, stream proto.DRPCProvisioner_ParseStream) error {
for index := 0; ; index++ {
path := filepath.Join(request.Directory, fmt.Sprintf("%d.parse.protobuf", index))
_, err := e.filesystem.Stat(path)
if err != nil {
if index == 0 {
// Error if nothing is around to enable failed states.
return xerrors.Errorf("no state: %w", err)
}
break
func (*echo) Parse(sess *provisionersdk.Session, _ *proto.ParseRequest, _ <-chan struct{}) *proto.ParseComplete {
responses, err := readResponses(sess, "unspecified", "parse.protobuf")
if err != nil {
return &proto.ParseComplete{Error: err.Error()}
}
for _, response := range responses {
if log := response.GetLog(); log != nil {
sess.ProvisionLog(log.Level, log.Output)
}
data, err := afero.ReadFile(e.filesystem, path)
if err != nil {
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
if complete := response.GetParse(); complete != nil {
return complete
}
}
<-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.
func (e *echo) Provision(stream proto.DRPCProvisioner_ProvisionStream) error {
msg, err := stream.Recv()
// Plan reads requests from the provided directory to stream responses.
func (*echo) Plan(sess *provisionersdk.Session, req *proto.PlanRequest, canceledOrComplete <-chan struct{}) *proto.PlanComplete {
responses, err := readResponses(
sess,
strings.ToLower(req.GetMetadata().GetWorkspaceTransition().String()),
"plan.protobuf")
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
switch {
case msg.GetPlan() != nil:
config = msg.GetPlan().GetConfig()
case msg.GetApply() != nil:
config = msg.GetApply().GetConfig()
default:
// Probably a cancel
return nil
// some tests use Echo without a complete response to test cancel
<-canceledOrComplete
return provisionersdk.PlanErrorf("canceled")
}
// Apply reads requests from the provided directory to stream responses.
func (*echo) Apply(sess *provisionersdk.Session, req *proto.ApplyRequest, canceledOrComplete <-chan struct{}) *proto.ApplyComplete {
responses, err := readResponses(
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:
for i := 0; ; i++ {
var extension string
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()
// some tests use Echo without a complete response to test cancel
<-canceledOrComplete
return provisionersdk.ApplyErrorf("canceled")
}
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.
type Responses struct {
Parse []*proto.Parse_Response
Parse []*proto.Response
// ProvisionApply and ProvisionPlan are used to mock ALL responses of
// Apply and Plan, regardless of transition.
ProvisionApply []*proto.Provision_Response
ProvisionPlan []*proto.Provision_Response
ProvisionApply []*proto.Response
ProvisionPlan []*proto.Response
// ProvisionApplyMap and ProvisionPlanMap are used to mock specific
// transition responses. They are prioritized over the generic responses.
ProvisionApplyMap map[proto.WorkspaceTransition][]*proto.Provision_Response
ProvisionPlanMap map[proto.WorkspaceTransition][]*proto.Provision_Response
ProvisionApplyMap map[proto.WorkspaceTransition][]*proto.Response
ProvisionPlanMap map[proto.WorkspaceTransition][]*proto.Response
}
// Tar returns a tar archive of responses to provisioner operations.
func Tar(responses *Responses) ([]byte, error) {
if responses == nil {
responses = &Responses{
ParseComplete, ProvisionComplete, ProvisionComplete,
ParseComplete, ApplyComplete, PlanComplete,
nil, 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
@ -245,20 +266,20 @@ func Tar(responses *Responses) ([]byte, error) {
}
}
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 {
return nil, err
}
}
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 {
return nil, err
}
}
for trans, m := range responses.ProvisionApplyMap {
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 {
return nil, err
}
@ -266,7 +287,7 @@ func Tar(responses *Responses) ([]byte, error) {
}
for trans, m := range responses.ProvisionPlanMap {
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 {
return nil, err
}
@ -279,22 +300,14 @@ func Tar(responses *Responses) ([]byte, error) {
return buffer.Bytes(), nil
}
func filterLogResponses(config *proto.Provision_Config, response *proto.Provision_Response) (*proto.Provision_Response, bool) {
responseLog, ok := response.Type.(*proto.Provision_Response_Log)
if !ok {
// Pass all non-log responses
return response, true
func WithResources(resources []*proto.Resource) *Responses {
return &Responses{
Parse: ParseComplete,
ProvisionApply: []*proto.Response{{Type: &proto.Response_Apply{Apply: &proto.ApplyComplete{
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
import (
"archive/tar"
"bytes"
"context"
"io"
"os"
"path/filepath"
"testing"
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/coder/coder/v2/provisioner/echo"
"github.com/coder/coder/v2/provisionersdk"
"github.com/coder/coder/v2/provisionersdk/proto"
"github.com/coder/coder/v2/testutil"
)
func TestEcho(t *testing.T) {
t.Parallel()
fs := afero.NewMemMapFs()
workdir := t.TempDir()
// Create an in-memory provisioner to communicate with.
client, server := provisionersdk.MemTransportPipe()
ctx, cancelFunc := context.WithCancel(context.Background())
@ -31,8 +27,9 @@ func TestEcho(t *testing.T) {
cancelFunc()
})
go func() {
err := echo.Serve(ctx, fs, &provisionersdk.ServeOptions{
Listener: server,
err := echo.Serve(ctx, &provisionersdk.ServeOptions{
Listener: server,
WorkDirectory: workdir,
})
assert.NoError(t, err)
}()
@ -40,25 +37,39 @@ func TestEcho(t *testing.T) {
t.Run("Parse", func(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(ctx, testutil.WaitShort)
defer cancel()
responses := []*proto.Parse_Response{{
Type: &proto.Parse_Response_Log{
Log: &proto.Log{
Output: "log-output",
responses := []*proto.Response{
{
Type: &proto.Response_Log{
Log: &proto.Log{
Output: "log-output",
},
},
},
}, {
Type: &proto.Parse_Response_Complete{
Complete: &proto.Parse_Complete{},
{
Type: &proto.Response_Parse{
Parse: &proto.ParseComplete{},
},
},
}}
}
data, err := echo.Tar(&echo.Responses{
Parse: responses,
})
require.NoError(t, err)
client, err := api.Parse(ctx, &proto.Parse_Request{
Directory: unpackTar(t, fs, data),
})
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_Parse{Parse: &proto.ParseRequest{}}})
require.NoError(t, err)
log, err := client.Recv()
require.NoError(t, err)
@ -70,95 +81,117 @@ func TestEcho(t *testing.T) {
t.Run("Provision", func(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(ctx, testutil.WaitShort)
defer cancel()
responses := []*proto.Provision_Response{{
Type: &proto.Provision_Response_Log{
Log: &proto.Log{
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),
planResponses := []*proto.Response{
{
Type: &proto.Response_Log{
Log: &proto.Log{
Level: proto.LogLevel_INFO,
Output: "log-output",
},
},
},
{
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)
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()
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()
require.NoError(t, err)
require.Equal(t, responses[1].GetComplete().Resources[0].Name,
complete.GetComplete().Resources[0].Name)
require.Equal(t, planResponses[1].GetPlan().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.Parallel()
// 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{
ProvisionApply: defaultResponses,
ProvisionPlan: defaultResponses,
ProvisionPlanMap: map[proto.WorkspaceTransition][]*proto.Provision_Response{
proto.WorkspaceTransition_STOP: stopResponses,
ProvisionApply: applyCompleteResource("DEFAULT"),
ProvisionPlan: planCompleteResource("DEFAULT"),
ProvisionPlanMap: map[proto.WorkspaceTransition][]*proto.Response{
proto.WorkspaceTransition_STOP: planCompleteResource("STOP"),
},
ProvisionApplyMap: map[proto.WorkspaceTransition][]*proto.Provision_Response{
proto.WorkspaceTransition_STOP: stopResponses,
ProvisionApplyMap: map[proto.WorkspaceTransition][]*proto.Response{
proto.WorkspaceTransition_STOP: applyCompleteResource("STOP"),
},
})
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)
// Do stop.
err = client.Send(&proto.Provision_Request{
Type: &proto.Provision_Request_Plan{
Plan: &proto.Provision_Plan{
Config: &proto.Provision_Config{
Directory: unpackTar(t, fs, data),
Metadata: &proto.Provision_Metadata{
WorkspaceTransition: proto.WorkspaceTransition_STOP,
},
err = client.Send(&proto.Request{
Type: &proto.Request_Plan{
Plan: &proto.PlanRequest{
Metadata: &proto.Metadata{
WorkspaceTransition: proto.WorkspaceTransition_STOP,
},
},
},
@ -168,22 +201,16 @@ func TestEcho(t *testing.T) {
complete, err := client.Recv()
require.NoError(t, err)
require.Equal(t,
stopResponses[0].GetComplete().Resources[0].Name,
complete.GetComplete().Resources[0].Name,
"STOP",
complete.GetPlan().Resources[0].Name,
)
// Do start.
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),
Metadata: &proto.Provision_Metadata{
WorkspaceTransition: proto.WorkspaceTransition_START,
},
err = client.Send(&proto.Request{
Type: &proto.Request_Plan{
Plan: &proto.PlanRequest{
Metadata: &proto.Metadata{
WorkspaceTransition: proto.WorkspaceTransition_START,
},
},
},
@ -193,31 +220,33 @@ func TestEcho(t *testing.T) {
complete, err = client.Recv()
require.NoError(t, err)
require.Equal(t,
defaultResponses[0].GetComplete().Resources[0].Name,
complete.GetComplete().Resources[0].Name,
"DEFAULT",
complete.GetPlan().Resources[0].Name,
)
})
t.Run("ProvisionWithLogLevel", func(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(ctx, testutil.WaitShort)
defer cancel()
responses := []*proto.Provision_Response{{
Type: &proto.Provision_Response_Log{
responses := []*proto.Response{{
Type: &proto.Response_Log{
Log: &proto.Log{
Level: proto.LogLevel_TRACE,
Output: "log-output-trace",
},
},
}, {
Type: &proto.Provision_Response_Log{
Type: &proto.Response_Log{
Log: &proto.Log{
Level: proto.LogLevel_INFO,
Output: "log-output-info",
},
},
}, {
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{
Type: &proto.Response_Apply{
Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{{
Name: "resource",
}},
@ -225,49 +254,62 @@ func TestEcho(t *testing.T) {
},
}}
data, err := echo.Tar(&echo.Responses{
ProvisionPlan: echo.PlanComplete,
ProvisionApply: responses,
})
require.NoError(t, err)
client, err := api.Provision(ctx)
client, err := api.Session(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),
ProvisionerLogLevel: "debug",
},
},
},
})
defer func() {
err := client.Close()
require.NoError(t, err)
}()
err = client.Send(&proto.Request{Type: &proto.Request_Config{Config: &proto.Config{
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)
log, err := client.Recv()
require.NoError(t, err)
// Skip responses[0] as it's trace level
require.Equal(t, responses[1].GetLog().Output, log.GetLog().Output)
complete, err := client.Recv()
complete, err = client.Recv()
require.NoError(t, err)
require.Equal(t, responses[2].GetComplete().Resources[0].Name,
complete.GetComplete().Resources[0].Name)
require.Equal(t, responses[2].GetApply().Resources[0].Name,
complete.GetApply().Resources[0].Name)
})
}
func unpackTar(t *testing.T, fs afero.Fs, data []byte) string {
directory := t.TempDir()
reader := tar.NewReader(bytes.NewReader(data))
for {
header, err := reader.Next()
if err != nil {
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)
require.ErrorIs(t, err, io.EOF)
err = file.Close()
require.NoError(t, err)
}
return directory
func planCompleteResource(name string) []*proto.Response {
return []*proto.Response{{
Type: &proto.Response_Plan{
Plan: &proto.PlanComplete{
Resources: []*proto.Resource{{
Name: name,
}},
},
},
}}
}
func applyCompleteResource(name string) []*proto.Response {
return []*proto.Response{{
Type: &proto.Response_Apply{
Apply: &proto.ApplyComplete{
Resources: []*proto.Resource{{
Name: name,
}},
},
},
}}
}

View File

@ -25,6 +25,7 @@ import (
)
type executor struct {
logger slog.Logger
server *server
mut *sync.Mutex
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]))
defer span.End()
span.SetAttributes(attribute.StringSlice("args", args))
e.logger.Debug(ctx, "starting command", slog.F("args", args))
defer func() {
e.logger.Debug(ctx, "closing writers", slog.Error(err))
closeErr := stdOutWriter.Close()
if err == nil && closeErr != nil {
err = closeErr
@ -62,6 +65,7 @@ func (e *executor) execWriteOutput(ctx, killCtx context.Context, args, env []str
}
}()
if ctx.Err() != nil {
e.logger.Debug(ctx, "context canceled before command started", slog.F("args", args))
return ctx.Err()
}
@ -90,11 +94,14 @@ func (e *executor) execWriteOutput(ctx, killCtx context.Context, args, env []str
)
err = cmd.Start()
if err != nil {
e.logger.Debug(ctx, "failed to start command", slog.F("args", args))
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.
@ -120,7 +127,7 @@ func (e *executor) execParseJSON(ctx, killCtx context.Context, args, env []strin
if err != nil {
return err
}
interruptCommandOnCancel(ctx, killCtx, cmd)
interruptCommandOnCancel(ctx, killCtx, e.logger, cmd)
err = cmd.Wait()
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)
}
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
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())
defer span.End()
e.mut.Lock()
defer e.mut.Unlock()
planfilePath := filepath.Join(e.workdir, "terraform.tfplan")
planfilePath := getPlanFilePath(e.workdir)
args := []string{
"plan",
"-no-color",
@ -248,19 +263,10 @@ func (e *executor) plan(ctx, killCtx context.Context, env, vars []string, logr l
if err != nil {
return nil, err
}
planFileByt, err := os.ReadFile(planfilePath)
if err != nil {
return nil, err
}
return &proto.Provision_Response{
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{
Parameters: state.Parameters,
Resources: state.Resources,
GitAuthProviders: state.GitAuthProviders,
Plan: planFileByt,
},
},
return &proto.PlanComplete{
Parameters: state.Parameters,
Resources: state.Resources,
GitAuthProviders: state.GitAuthProviders,
}, nil
}
@ -346,7 +352,7 @@ func (e *executor) graph(ctx, killCtx context.Context) (string, error) {
if err != nil {
return "", err
}
interruptCommandOnCancel(ctx, killCtx, cmd)
interruptCommandOnCancel(ctx, killCtx, e.logger, cmd)
err = cmd.Wait()
if err != nil {
@ -357,33 +363,22 @@ func (e *executor) graph(ctx, killCtx context.Context) (string, error) {
func (e *executor) apply(
ctx, killCtx context.Context,
plan []byte,
env []string,
logr logSink,
) (*proto.Provision_Response, error) {
) (*proto.ApplyComplete, error) {
ctx, span := e.server.startTrace(ctx, tracing.FuncName())
defer span.End()
e.mut.Lock()
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{
"apply",
"-no-color",
"-auto-approve",
"-input=false",
"-json",
planFile.Name(),
getPlanFilePath(e.workdir),
}
outWriter, doneOut := provisionLogWriter(logr)
@ -395,7 +390,7 @@ func (e *executor) apply(
<-doneErr
}()
err = e.execWriteOutput(ctx, killCtx, args, env, outWriter, errWriter)
err := e.execWriteOutput(ctx, killCtx, args, env, outWriter, errWriter)
if err != nil {
return nil, xerrors.Errorf("terraform apply: %w", err)
}
@ -408,15 +403,11 @@ func (e *executor) apply(
if err != nil {
return nil, xerrors.Errorf("read statefile %q: %w", statefilePath, err)
}
return &proto.Provision_Response{
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{
Parameters: state.Parameters,
Resources: state.Resources,
GitAuthProviders: state.GitAuthProviders,
State: stateContent,
},
},
return &proto.ApplyComplete{
Parameters: state.Parameters,
Resources: state.Resources,
GitAuthProviders: state.GitAuthProviders,
State: stateContent,
}, nil
}
@ -461,48 +452,28 @@ func (e *executor) state(ctx, killCtx context.Context) (*tfjson.State, error) {
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() {
select {
case <-ctx.Done():
var err error
switch runtime.GOOS {
case "windows":
// Interrupts aren't supported by Windows.
_ = cmd.Process.Kill()
err = cmd.Process.Kill()
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():
logger.Debug(ctx, "kill context ended", slog.F("args", cmd.Args))
}
}()
}
type logSink interface {
Log(*proto.Log)
}
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),
)
}
ProvisionLog(l proto.LogLevel, o string)
}
// 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
}
sink.Log(&proto.Log{Level: level, Output: scanner.Text()})
sink.ProvisionLog(level, scanner.Text())
continue
}
@ -543,7 +514,7 @@ func readAndLog(sink logSink, r io.Reader, done chan<- any, level proto.LogLevel
if logLevel == proto.LogLevel_INFO {
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)
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 log.Diagnostic == nil {
@ -596,7 +567,7 @@ func provisionReadAndLog(sink logSink, r io.Reader, done chan<- any) {
}
logLevel = convertTerraformLogLevel(string(log.Diagnostic.Severity), sink)
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":
return proto.LogLevel_ERROR
default:
sink.Log(&proto.Log{
Level: proto.LogLevel_WARN,
Output: fmt.Sprintf("unable to convert log level %s", logLevel),
})
sink.ProvisionLog(proto.LogLevel_WARN, fmt.Sprintf("unable to convert log level %s", logLevel))
return proto.LogLevel_INFO
}
}

View File

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

View File

@ -12,18 +12,20 @@ import (
"golang.org/x/xerrors"
"github.com/coder/coder/v2/coderd/tracing"
"github.com/coder/coder/v2/provisionersdk"
"github.com/coder/coder/v2/provisionersdk/proto"
)
// Parse extracts Terraform variables from source-code.
func (s *server) Parse(request *proto.Parse_Request, stream proto.DRPCProvisioner_ParseStream) error {
_, span := s.startTrace(stream.Context(), tracing.FuncName())
func (s *server) Parse(sess *provisionersdk.Session, _ *proto.ParseRequest, _ <-chan struct{}) *proto.ParseComplete {
ctx := sess.Context()
_, span := s.startTrace(ctx, tracing.FuncName())
defer span.End()
// Load the module and print any parse errors.
module, diags := tfconfig.LoadModule(request.Directory)
module, diags := tfconfig.LoadModule(sess.WorkDirectory)
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
@ -40,17 +42,13 @@ func (s *server) Parse(request *proto.Parse_Request, stream proto.DRPCProvisione
for _, v := range variables {
mv, err := convertTerraformVariable(v)
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)
}
return stream.Send(&proto.Parse_Response{
Type: &proto.Parse_Response_Complete{
Complete: &proto.Parse_Complete{
TemplateVariables: templateVariables,
},
},
})
return &proto.ParseComplete{
TemplateVariables: templateVariables,
}
}
// Converts a Terraform variable to a template-wide variable, processed by Coder.

View File

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

View File

@ -4,11 +4,10 @@ import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"time"
"golang.org/x/xerrors"
"cdr.dev/slog"
"github.com/coder/coder/v2/coderd/tracing"
"github.com/coder/coder/v2/provisionersdk"
@ -16,48 +15,23 @@ import (
"github.com/coder/terraform-provider-coder/provider"
)
// Provision executes `terraform apply` or `terraform plan` for dry runs.
func (s *server) Provision(stream proto.DRPCProvisioner_ProvisionStream) error {
ctx, span := s.startTrace(stream.Context(), tracing.FuncName())
defer span.End()
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
func (s *server) setupContexts(parent context.Context, canceledOrComplete <-chan struct{}) (
ctx context.Context, cancel func(), killCtx context.Context, kill func(),
) {
// Create a context for graceful cancellation bound to the session
// context. This ensures that we will perform graceful cancellation
// even on connection loss.
ctx, cancel := context.WithCancel(ctx)
defer cancel()
ctx, cancel = context.WithCancel(parent)
// Create a separate context for forceful cancellation not tied to
// the stream so that we can control when to terminate the process.
killCtx, kill := context.WithCancel(context.Background())
defer kill()
killCtx, kill = context.WithCancel(context.Background())
// Ensure processes are eventually cleaned up on graceful
// cancellation or disconnect.
go func() {
<-ctx.Done()
s.logger.Debug(ctx, "graceful context done")
// TODO(mafredri): We should track this provision request as
// 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()
select {
case <-t.C:
s.logger.Debug(ctx, "exit timeout hit")
kill()
case <-killCtx.Done():
s.logger.Debug(ctx, "kill context done")
}
}()
// Process cancel
go func() {
for {
request, err := stream.Recv()
if err != nil {
return
}
if request.GetCancel() == nil {
// We only process cancellation requests here.
continue
}
cancel()
return
}
<-canceledOrComplete
s.logger.Debug(ctx, "canceledOrComplete closed")
cancel()
}()
return ctx, cancel, killCtx, kill
}
sink := streamLogSink{
logger: s.logger.Named("execution_logs"),
stream: stream,
}
func (s *server) Plan(
sess *provisionersdk.Session, request *proto.PlanRequest, canceledOrComplete <-chan struct{},
) *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)
if err = e.checkMinVersion(ctx); err != nil {
return err
}
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)
}
e := s.executor(sess.WorkDirectory)
if err := e.checkMinVersion(ctx); err != nil {
return provisionersdk.PlanErrorf(err.Error())
}
logTerraformEnvVars(sess)
// 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
// 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 config.Metadata.WorkspaceTransition == proto.WorkspaceTransition_DESTROY && len(config.State) == 0 {
_ = stream.Send(&proto.Provision_Response{
Type: &proto.Provision_Response_Log{
Log: &proto.Log{
Level: proto.LogLevel_INFO,
Output: "The terraform state does not exist, there is nothing to do",
},
},
})
if request.Metadata.GetWorkspaceTransition() == proto.WorkspaceTransition_DESTROY && len(sess.Config.State) == 0 {
sess.ProvisionLog(proto.LogLevel_INFO, "The terraform state does not exist, there is nothing to do")
return &proto.PlanComplete{}
}
return stream.Send(&proto.Provision_Response{
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{},
},
})
statefilePath := getStateFilePath(sess.WorkDirectory)
if len(sess.Config.State) > 0 {
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")
err = e.init(ctx, killCtx, sink)
err := e.init(ctx, killCtx, sess)
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("initialize terraform: %w", err)
s.logger.Debug(ctx, "init failed", slog.Error(err))
return provisionersdk.PlanErrorf("initialize terraform: %s", err)
}
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 {
return err
return provisionersdk.PlanErrorf("setup env: %s", err)
}
var resp *proto.Provision_Response
if planRequest != nil {
vars, err := planVars(planRequest)
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)
vars, err := planVars(request)
if err != nil {
return provisionersdk.PlanErrorf("plan vars: %s", err)
}
// Must be apply
resp, err = e.apply(
ctx, killCtx, applyRequest.Plan, env, sink,
resp, err := e.plan(
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 {
errorMessage := err.Error()
// Terraform can fail and apply and still need to store it's state.
// In this case, we return Complete with an explicit error message.
stateData, _ := os.ReadFile(statefilePath)
return stream.Send(&proto.Provision_Response{
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{
State: stateData,
Error: errorMessage,
},
},
})
return &proto.ApplyComplete{
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{}
for _, variable := range plan.VariableValues {
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
}
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 = append(env,
"CODER_AGENT_URL="+config.Metadata.CoderUrl,
"CODER_WORKSPACE_TRANSITION="+strings.ToLower(config.Metadata.WorkspaceTransition.String()),
"CODER_WORKSPACE_NAME="+config.Metadata.WorkspaceName,
"CODER_WORKSPACE_OWNER="+config.Metadata.WorkspaceOwner,
"CODER_WORKSPACE_OWNER_EMAIL="+config.Metadata.WorkspaceOwnerEmail,
"CODER_WORKSPACE_OWNER_OIDC_ACCESS_TOKEN="+config.Metadata.WorkspaceOwnerOidcAccessToken,
"CODER_WORKSPACE_ID="+config.Metadata.WorkspaceId,
"CODER_WORKSPACE_OWNER_ID="+config.Metadata.WorkspaceOwnerId,
"CODER_WORKSPACE_OWNER_SESSION_TOKEN="+config.Metadata.WorkspaceOwnerSessionToken,
"CODER_AGENT_URL="+metadata.GetCoderUrl(),
"CODER_WORKSPACE_TRANSITION="+strings.ToLower(metadata.GetWorkspaceTransition().String()),
"CODER_WORKSPACE_NAME="+metadata.GetWorkspaceName(),
"CODER_WORKSPACE_OWNER="+metadata.GetWorkspaceOwner(),
"CODER_WORKSPACE_OWNER_EMAIL="+metadata.GetWorkspaceOwnerEmail(),
"CODER_WORKSPACE_OWNER_OIDC_ACCESS_TOKEN="+metadata.GetWorkspaceOwnerOidcAccessToken(),
"CODER_WORKSPACE_ID="+metadata.GetWorkspaceId(),
"CODER_WORKSPACE_OWNER_ID="+metadata.GetWorkspaceOwnerId(),
"CODER_WORKSPACE_OWNER_SESSION_TOKEN="+metadata.GetWorkspaceOwnerSessionToken(),
)
for key, value := range provisionersdk.AgentScriptEnv() {
env = append(env, key+"="+value)
@ -258,10 +232,10 @@ func logTerraformEnvVars(sink logSink) {
if !tfEnvSafeToPrint[parts[0]] {
parts[1] = "<value redacted>"
}
sink.Log(&proto.Log{
Level: proto.LogLevel_WARN,
Output: fmt.Sprintf("terraform environment variable: %s=%s", parts[0], parts[1]),
})
sink.ProvisionLog(
proto.LogLevel_WARN,
fmt.Sprintf("terraform environment variable: %s=%s", parts[0], parts[1]),
)
}
}
}

View File

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

View File

@ -24,7 +24,6 @@ type ServeOptions struct {
BinaryPath string
// CachePath must not be used by multiple processes at once.
CachePath string
Logger slog.Logger
Tracer trace.Tracer
// 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,
cachePath: s.cachePath,
workdir: workdir,
logger: s.logger.Named("executor"),
}
}

View File

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

View File

@ -23,19 +23,19 @@ init)
echo "init"
exit 0
;;
apply)
plan)
trap 'json_print interrupt' INT
json_print apply_start
json_print plan_start
sleep 10 2>/dev/null >/dev/null
json_print apply_end
json_print plan_end
exit 0
;;
plan)
echo "plan not supported"
apply)
echo "apply not supported"
exit 1
;;
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"`
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"`
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"`
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
}
func (x *AcquiredJob_WorkspaceBuild) GetMetadata() *proto.Provision_Metadata {
func (x *AcquiredJob_WorkspaceBuild) GetMetadata() *proto.Metadata {
if x != nil {
return x.Metadata
}
@ -917,8 +917,8 @@ type AcquiredJob_TemplateImport struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Metadata *proto.Provision_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"`
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"`
}
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}
}
func (x *AcquiredJob_TemplateImport) GetMetadata() *proto.Provision_Metadata {
func (x *AcquiredJob_TemplateImport) GetMetadata() *proto.Metadata {
if x != nil {
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"`
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() {
@ -1023,7 +1023,7 @@ func (x *AcquiredJob_TemplateDryRun) GetVariableValues() []*proto.VariableValue
return nil
}
func (x *AcquiredJob_TemplateDryRun) GetMetadata() *proto.Provision_Metadata {
func (x *AcquiredJob_TemplateDryRun) GetMetadata() *proto.Metadata {
if x != nil {
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,
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,
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,
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,
@ -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,
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,
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,
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,
@ -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,
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,
0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x12, 0x3b, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61,
0x74, 0x61, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69,
0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e,
0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64,
0x61, 0x74, 0x61, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x08, 0x20, 0x01,
0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x6f, 0x67,
0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x6f,
0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x1a, 0x9b, 0x01, 0x0a,
0x0e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x12,
0x3b, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e,
0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61,
0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 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, 0x02, 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, 0x1a, 0xed, 0x01, 0x0a, 0x0e, 0x54,
0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x44, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x12, 0x53, 0x0a,
0x15, 0x72, 0x69, 0x63, 0x68, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f,
0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 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, 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, 0x73, 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, 0x12, 0x3b, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64,
0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76,
0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f,
0x6e, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61,
0x64, 0x61, 0x74, 0x61, 0x4a, 0x04, 0x08, 0x01, 0x10, 0x02, 0x1a, 0x40, 0x0a, 0x12, 0x54, 0x72,
0x61, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79,
0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b,
0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x06, 0x0a, 0x04,
0x74, 0x79, 0x70, 0x65, 0x22, 0xa5, 0x03, 0x0a, 0x09, 0x46, 0x61, 0x69, 0x6c, 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, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72,
0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12,
0x51, 0x0a, 0x0f, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x62, 0x75, 0x69,
0x6c, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69,
0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x46, 0x61, 0x69, 0x6c, 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, 0x51, 0x0a, 0x0f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x69,
0x6d, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x70, 0x72,
0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x46, 0x61, 0x69, 0x6c, 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, 0x52, 0x0a, 0x10, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74,
0x65, 0x5f, 0x64, 0x72, 0x79, 0x5f, 0x72, 0x75, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x26, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x46,
0x61, 0x69, 0x6c, 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, 0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x72, 0x72,
0x6f, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x65,
0x72, 0x72, 0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x1a, 0x26, 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,
0x1a, 0x10, 0x0a, 0x0e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x6d, 0x70, 0x6f,
0x72, 0x74, 0x1a, 0x10, 0x0a, 0x0e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x44, 0x72,
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,
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, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69,
0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52,
0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61,
0x74, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12,
0x1b, 0x0a, 0x09, 0x6c, 0x6f, 0x67, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x09, 0x20, 0x01,
0x28, 0x09, 0x52, 0x08, 0x6c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x4a, 0x04, 0x08, 0x03,
0x10, 0x04, 0x1a, 0x91, 0x01, 0x0a, 0x0e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49,
0x6d, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x31, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,
0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73,
0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08,
0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 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, 0x02, 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, 0x1a, 0xe3, 0x01, 0x0a, 0x0e, 0x54, 0x65, 0x6d, 0x70, 0x6c,
0x61, 0x74, 0x65, 0x44, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x12, 0x53, 0x0a, 0x15, 0x72, 0x69, 0x63,
0x68, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75,
0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 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, 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, 0x73, 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, 0x12, 0x31, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18,
0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f,
0x6e, 0x65, 0x72, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65,
0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x4a, 0x04, 0x08, 0x01, 0x10, 0x02, 0x1a, 0x40, 0x0a, 0x12,
0x54, 0x72, 0x61, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74,
0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20,
0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x06,
0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0xa5, 0x03, 0x0a, 0x09, 0x46, 0x61, 0x69, 0x6c, 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, 0x14, 0x0a, 0x05, 0x65,
0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f,
0x72, 0x12, 0x51, 0x0a, 0x0f, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x62,
0x75, 0x69, 0x6c, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x70, 0x72, 0x6f,
0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x46, 0x61, 0x69, 0x6c, 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, 0x51, 0x0a, 0x0f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65,
0x5f, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e,
0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x46, 0x61, 0x69,
0x6c, 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, 0x52, 0x0a, 0x10, 0x74, 0x65, 0x6d, 0x70, 0x6c,
0x61, 0x74, 0x65, 0x5f, 0x64, 0x72, 0x79, 0x5f, 0x72, 0x75, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x26, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64,
0x2e, 0x46, 0x61, 0x69, 0x6c, 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, 0x12, 0x1d, 0x0a, 0x0a, 0x65,
0x72, 0x72, 0x6f, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52,
0x09, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x1a, 0x26, 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, 0x1a, 0x10, 0x0a, 0x0e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x6d,
0x70, 0x6f, 0x72, 0x74, 0x1a, 0x10, 0x0a, 0x0e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65,
0x44, 0x72, 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, 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, 0x71,
0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e,
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, 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, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x09, 0x55, 0x70, 0x64, 0x61, 0x74,
0x65, 0x4a, 0x6f, 0x62, 0x12, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e,
0x65, 0x72, 0x64, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e,
0x65, 0x72, 0x64, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x07, 0x46, 0x61, 0x69, 0x6c, 0x4a, 0x6f, 0x62,
0x12, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e,
0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x1a, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76,
0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3e,
0x0a, 0x0b, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x12, 0x1a, 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, 0x1a, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76,
0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x42, 0x2e,
0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 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,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 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, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x09, 0x55, 0x70, 0x64,
0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x12, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69,
0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69,
0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x07, 0x46, 0x61, 0x69, 0x6c, 0x4a,
0x6f, 0x62, 0x12, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72,
0x64, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x1a, 0x13, 0x2e, 0x70, 0x72,
0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79,
0x12, 0x3e, 0x0a, 0x0b, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x12,
0x1a, 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, 0x1a, 0x13, 0x2e, 0x70, 0x72,
0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79,
0x42, 0x2e, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63,
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 (
@ -1618,7 +1616,7 @@ var file_provisionerd_proto_provisionerd_proto_goTypes = []interface{}{
(*proto.VariableValue)(nil), // 22: provisioner.VariableValue
(*proto.RichParameterValue)(nil), // 23: provisioner.RichParameterValue
(*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.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
22, // 17: provisionerd.AcquiredJob.WorkspaceBuild.variable_values:type_name -> provisioner.VariableValue
24, // 18: provisionerd.AcquiredJob.WorkspaceBuild.git_auth_providers:type_name -> provisioner.GitAuthProvider
25, // 19: provisionerd.AcquiredJob.WorkspaceBuild.metadata:type_name -> provisioner.Provision.Metadata
25, // 20: provisionerd.AcquiredJob.TemplateImport.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.Metadata
22, // 21: provisionerd.AcquiredJob.TemplateImport.user_variable_values:type_name -> provisioner.VariableValue
23, // 22: provisionerd.AcquiredJob.TemplateDryRun.rich_parameter_values:type_name -> provisioner.RichParameterValue
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, // 26: provisionerd.CompletedJob.TemplateImport.start_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.VariableValue variable_values = 5;
repeated provisioner.GitAuthProvider git_auth_providers = 6;
provisioner.Provision.Metadata metadata = 7;
provisioner.Metadata metadata = 7;
bytes state = 8;
string log_level = 9;
}
message TemplateImport {
provisioner.Provision.Metadata metadata = 1;
provisioner.Metadata metadata = 1;
repeated provisioner.VariableValue user_variable_values = 2;
}
message TemplateDryRun {
@ -32,7 +32,7 @@ message AcquiredJob {
repeated provisioner.RichParameterValue rich_parameter_values = 2;
repeated provisioner.VariableValue variable_values = 3;
provisioner.Provision.Metadata metadata = 4;
provisioner.Metadata metadata = 4;
}
string job_id = 1;
@ -45,9 +45,9 @@ message AcquiredJob {
TemplateImport template_import = 7;
TemplateDryRun template_dry_run = 8;
}
// trace_metadata is currently used for tracing information only. It allows
// jobs to be tied to the request that created them.
map<string, string> trace_metadata = 9;
// trace_metadata is currently used for tracing information only. It allows
// jobs to be tied to the request that created them.
map<string, string> trace_metadata = 9;
}
message FailedJob {
@ -113,7 +113,7 @@ message UpdateJobRequest {
string job_id = 1;
repeated Log logs = 2;
repeated provisioner.TemplateVariable template_variables = 4;
repeated provisioner.VariableValue user_variable_values = 5;
repeated provisioner.VariableValue user_variable_values = 5;
bytes readme = 6;
}
@ -121,7 +121,7 @@ message UpdateJobResponse {
reserved 2;
bool canceled = 1;
repeated provisioner.VariableValue variable_values = 3;
repeated provisioner.VariableValue variable_values = 3;
}
message CommitQuotaRequest {

View File

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

View File

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

View File

@ -1,15 +1,9 @@
package runner
import (
"archive/tar"
"bytes"
"context"
"errors"
"fmt"
"io"
"os"
"path"
"path/filepath"
"reflect"
"strings"
"sync"
@ -18,7 +12,6 @@ import (
"github.com/google/uuid"
"github.com/prometheus/client_golang/prometheus"
"github.com/spf13/afero"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
semconv "go.opentelemetry.io/otel/semconv/v1.14.0"
@ -54,14 +47,14 @@ type Runner struct {
sender JobUpdater
quotaCommitter QuotaCommitter
logger slog.Logger
filesystem afero.Fs
workDirectory string
provisioner sdkproto.DRPCProvisionerClient
lastUpdate atomic.Pointer[time.Time]
updateInterval time.Duration
forceCancelInterval 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.
done chan struct{}
// active as long as we are not canceled
@ -108,8 +101,6 @@ type Options struct {
Updater JobUpdater
QuotaCommitter QuotaCommitter
Logger slog.Logger
Filesystem afero.Fs
WorkDirectory string
Provisioner sdkproto.DRPCProvisionerClient
UpdateInterval time.Duration
ForceCancelInterval time.Duration
@ -149,8 +140,6 @@ func New(
sender: opts.Updater,
quotaCommitter: opts.QuotaCommitter,
logger: logger,
filesystem: opts.Filesystem,
workDirectory: opts.WorkDirectory,
provisioner: opts.Provisioner,
updateInterval: opts.UpdateInterval,
forceCancelInterval: opts.ForceCancelInterval,
@ -386,6 +375,14 @@ func (r *Runner) doCleanFinish(ctx context.Context) {
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() {
ctx, span := r.startTrace(ctx, tracing.FuncName())
defer span.End()
@ -396,23 +393,6 @@ func (r *Runner) doCleanFinish(ctx context.Context) {
Stage: "Cleaning Up",
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)
}()
@ -424,85 +404,19 @@ func (r *Runner) do(ctx context.Context) (*proto.CompletedJob, *proto.FailedJob)
ctx, span := r.startTrace(ctx, tracing.FuncName())
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{
Source: proto.LogSource_PROVISIONER_DAEMON,
Level: sdkproto.LogLevel_INFO,
Stage: "Setting up",
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) {
case *proto.AcquiredJob_TemplateImport_:
r.logger.Debug(context.Background(), "acquired job is template import",
slog.F("user_variable_values", redactVariableValues(jobType.TemplateImport.UserVariableValues)),
)
failedJob := r.runReadmeParse(ctx)
if failedJob != nil {
return nil, failedJob
}
return r.runTemplateImport(ctx)
case *proto.AcquiredJob_TemplateDryRun_:
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
// from assuming the job is stalled, and allows the runner to learn if the job
// 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) {
ctx, span := r.startTrace(ctx, tracing.FuncName())
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
r.queueLog(ctx, &proto.Log{
Source: proto.LogSource_PROVISIONER_DAEMON,
@ -623,7 +517,7 @@ func (r *Runner) runTemplateImport(ctx context.Context) (*proto.CompletedJob, *p
Stage: "Parsing template parameters",
CreatedAt: time.Now().UnixMilli(),
})
templateVariables, err := r.runTemplateImportParse(ctx)
templateVariables, readme, err := r.runTemplateImportParse(ctx)
if err != nil {
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,
TemplateVariables: templateVariables,
UserVariableValues: r.job.GetTemplateImport().GetUserVariableValues(),
Readme: readme,
})
if err != nil {
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",
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,
WorkspaceTransition: sdkproto.WorkspaceTransition_START,
})
@ -661,7 +556,7 @@ func (r *Runner) runTemplateImport(ctx context.Context) (*proto.CompletedJob, *p
Stage: "Detecting ephemeral resources",
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,
WorkspaceTransition: sdkproto.WorkspaceTransition_STOP,
})
@ -682,25 +577,24 @@ func (r *Runner) runTemplateImport(ctx context.Context) (*proto.CompletedJob, *p
}, nil
}
// Parses template variables and parameter schemas from source.
func (r *Runner) runTemplateImportParse(ctx context.Context) ([]*sdkproto.TemplateVariable, error) {
// Parses template variables and README from source.
func (r *Runner) runTemplateImportParse(ctx context.Context) (
vars []*sdkproto.TemplateVariable, readme []byte, err error,
) {
ctx, span := r.startTrace(ctx, tracing.FuncName())
defer span.End()
stream, err := r.provisioner.Parse(ctx, &sdkproto.Parse_Request{
Directory: r.workDirectory,
})
err = r.session.Send(&sdkproto.Request{Type: &sdkproto.Request_Parse{Parse: &sdkproto.ParseRequest{}}})
if err != nil {
return nil, xerrors.Errorf("parse source: %w", err)
return nil, nil, xerrors.Errorf("parse source: %w", err)
}
defer stream.Close()
for {
msg, err := stream.Recv()
msg, err := r.session.Recv()
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) {
case *sdkproto.Parse_Response_Log:
case *sdkproto.Response_Log:
r.logger.Debug(context.Background(), "parse job logged",
slog.F("level", msgType.Log.Level),
slog.F("output", msgType.Log.Output),
@ -713,14 +607,20 @@ func (r *Runner) runTemplateImportParse(ctx context.Context) ([]*sdkproto.Templa
Output: msgType.Log.Output,
Stage: "Parse parameters",
})
case *sdkproto.Parse_Response_Complete:
case *sdkproto.Response_Parse:
pc := msgType.Parse
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:
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())
}
}
@ -735,13 +635,18 @@ type templateImportProvision struct {
// Performs a dry-run provision when importing a template.
// This is used to detect resources that would be provisioned for a workspace in various states.
// It doesn't define values for rich parameters as they're unknown during template import.
func (r *Runner) runTemplateImportProvision(ctx context.Context, variableValues []*sdkproto.VariableValue, metadata *sdkproto.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)
}
// 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.
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())
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
// 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 {
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() {
select {
case <-nevermind:
return
case <-r.notStopped.Done():
return
case <-r.notCanceled.Done():
_ = stream.Send(&sdkproto.Provision_Request{
Type: &sdkproto.Provision_Request_Cancel{
Cancel: &sdkproto.Provision_Cancel{},
_ = r.session.Send(&sdkproto.Request{
Type: &sdkproto.Request_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 {
msg, err := stream.Recv()
msg, err := r.session.Recv()
if err != nil {
return nil, xerrors.Errorf("recv import provision: %w", err)
}
switch msgType := msg.Type.(type) {
case *sdkproto.Provision_Response_Log:
case *sdkproto.Response_Log:
r.logger.Debug(context.Background(), "template import provision job logged",
slog.F("level", msgType.Log.Level),
slog.F("output", msgType.Log.Output),
@ -805,25 +702,25 @@ func (r *Runner) runTemplateImportProvisionWithRichParameters(ctx context.Contex
Output: msgType.Log.Output,
Stage: stage,
})
case *sdkproto.Provision_Response_Complete:
if msgType.Complete.Error != "" {
case *sdkproto.Response_Plan:
c := msgType.Plan
if c.Error != "" {
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",
slog.F("resource_count", len(msgType.Complete.Resources)),
slog.F("resources", msgType.Complete.Resources),
slog.F("state_length", len(msgType.Complete.State)),
slog.F("resource_count", len(c.Resources)),
slog.F("resources", c.Resources),
)
return &templateImportProvision{
Resources: msgType.Complete.Resources,
Parameters: msgType.Complete.Parameters,
GitAuthProviders: msgType.Complete.GitAuthProviders,
Resources: c.Resources,
Parameters: c.Parameters,
GitAuthProviders: c.GitAuthProviders,
}, nil
default:
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()
}
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.
provision, err := r.runTemplateImportProvisionWithRichParameters(ctx,
r.job.GetTemplateDryRun().GetVariableValues(),
@ -884,41 +788,39 @@ func (r *Runner) runTemplateDryRun(ctx context.Context) (*proto.CompletedJob, *p
}, nil
}
func (r *Runner) buildWorkspace(ctx context.Context, stage string, req *sdkproto.Provision_Request) (
*sdkproto.Provision_Complete, *proto.FailedJob,
func (r *Runner) buildWorkspace(ctx context.Context, stage string, req *sdkproto.Request) (
*sdkproto.Response, *proto.FailedJob,
) {
// 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
stream, err := r.provisioner.Provision(ctx)
err := r.session.Send(req)
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() {
select {
case <-nevermind:
return
case <-r.notStopped.Done():
return
case <-r.notCanceled.Done():
_ = stream.Send(&sdkproto.Provision_Request{
Type: &sdkproto.Provision_Request_Cancel{
Cancel: &sdkproto.Provision_Cancel{},
_ = r.session.Send(&sdkproto.Request{
Type: &sdkproto.Request_Cancel{
Cancel: &sdkproto.CancelRequest{},
},
})
}
}()
err = stream.Send(req)
if err != nil {
return nil, r.failedWorkspaceBuildf("start provision: %s", err)
}
for {
msg, err := stream.Recv()
msg, err := r.session.Recv()
if err != nil {
return nil, r.failedWorkspaceBuildf("recv workspace provision: %s", err)
}
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",
slog.F("level", msgType.Log.Level),
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,
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:
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"
}
config := &sdkproto.Provision_Config{
Directory: r.workDirectory,
Metadata: r.job.GetWorkspaceBuild().Metadata,
State: r.job.GetWorkspaceBuild().State,
ProvisionerLogLevel: r.job.GetWorkspaceBuild().LogLevel,
failedJob := r.configure(&sdkproto.Config{
TemplateSourceArchive: r.job.GetTemplateSourceArchive(),
State: r.job.GetWorkspaceBuild().State,
ProvisionerLogLevel: r.job.GetWorkspaceBuild().LogLevel,
})
if failedJob != nil {
return nil, failedJob
}
completedPlan, failed := r.buildWorkspace(ctx, "Planning infrastructure", &sdkproto.Provision_Request{
Type: &sdkproto.Provision_Request_Plan{
Plan: &sdkproto.Provision_Plan{
Config: config,
resp, failed := r.buildWorkspace(ctx, "Planning infrastructure", &sdkproto.Request{
Type: &sdkproto.Request_Plan{
Plan: &sdkproto.PlanRequest{
Metadata: r.job.GetWorkspaceBuild().Metadata,
RichParameterValues: r.job.GetWorkspaceBuild().RichParameterValues,
VariableValues: r.job.GetWorkspaceBuild().VariableValues,
GitAuthProviders: r.job.GetWorkspaceBuild().GitAuthProviders,
@ -1056,9 +935,31 @@ func (r *Runner) runWorkspaceBuild(ctx context.Context) (*proto.CompletedJob, *p
if failed != nil {
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)
if commitQuota {
failed = r.commitQuota(ctx, completedPlan.GetResources())
failed = r.commitQuota(ctx, planComplete.Resources)
r.flushQueuedLogs(ctx)
if failed != nil {
return nil, failed
@ -1072,25 +973,50 @@ func (r *Runner) runWorkspaceBuild(ctx context.Context) (*proto.CompletedJob, *p
CreatedAt: time.Now().UnixMilli(),
})
completedApply, failed := r.buildWorkspace(ctx, applyStage, &sdkproto.Provision_Request{
Type: &sdkproto.Provision_Request_Apply{
Apply: &sdkproto.Provision_Apply{
Config: config,
Plan: completedPlan.GetPlan(),
resp, failed = r.buildWorkspace(ctx, applyStage, &sdkproto.Request{
Type: &sdkproto.Request_Apply{
Apply: &sdkproto.ApplyRequest{
Metadata: r.job.GetWorkspaceBuild().Metadata,
},
},
})
if failed != nil {
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)
return &proto.CompletedJob{
JobId: r.job.JobId,
Type: &proto.CompletedJob_WorkspaceBuild_{
WorkspaceBuild: &proto.CompletedJob_WorkspaceBuild{
State: completedApply.GetState(),
Resources: completedApply.GetResources(),
State: applyComplete.State,
Resources: applyComplete.Resources,
},
},
}, 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 {
string id = 1;
string access_token = 2;
string id = 1;
string access_token = 2;
}
// Agent represents a running agent on the workspace.
@ -110,15 +110,15 @@ message Agent {
string token = 9;
string instance_id = 10;
}
int32 connection_timeout_seconds = 11;
string troubleshooting_url = 12;
string motd_file = 13;
// Field 14 was bool login_before_ready = 14, now removed.
int32 startup_script_timeout_seconds = 15;
string shutdown_script = 16;
int32 shutdown_script_timeout_seconds = 17;
int32 connection_timeout_seconds = 11;
string troubleshooting_url = 12;
string motd_file = 13;
// Field 14 was bool login_before_ready = 14, now removed.
int32 startup_script_timeout_seconds = 15;
string shutdown_script = 16;
int32 shutdown_script_timeout_seconds = 17;
repeated Metadata metadata = 18;
string startup_script_behavior = 19;
string startup_script_behavior = 19;
}
enum AppSharingLevel {
@ -168,96 +168,111 @@ message Resource {
int32 daily_cost = 8;
}
// Parse consumes source-code from a directory to produce inputs.
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;
}
}
}
// WorkspaceTransition is the desired outcome of a build
enum WorkspaceTransition {
START = 0;
STOP = 1;
DESTROY = 2;
}
// Provision consumes source-code from a directory to produce resources.
// Exactly one of Plan or Apply must be provided in a single session.
message Provision {
message Metadata {
string coder_url = 1;
WorkspaceTransition workspace_transition = 2;
string workspace_name = 3;
string workspace_owner = 4;
string workspace_id = 5;
string workspace_owner_id = 6;
string workspace_owner_email = 7;
string template_name = 8;
string template_version = 9;
string workspace_owner_oidc_access_token = 10;
string workspace_owner_session_token = 11;
}
// Metadata is information about a workspace used in the execution of a build
message Metadata {
string coder_url = 1;
WorkspaceTransition workspace_transition = 2;
string workspace_name = 3;
string workspace_owner = 4;
string workspace_id = 5;
string workspace_owner_id = 6;
string workspace_owner_email = 7;
string template_name = 8;
string template_version = 9;
string workspace_owner_oidc_access_token = 10;
string workspace_owner_session_token = 11;
}
// Config represents execution configuration shared by both Plan and
// Apply commands.
message Config {
string directory = 1;
bytes state = 2;
Metadata metadata = 3;
// Config represents execution configuration shared by all subsequent requests in the Session
message Config {
// template_source_archive is a tar of the template source files
bytes template_source_archive = 1;
// state is the provisioner state (if any)
bytes state = 2;
string provisioner_log_level = 3;
}
string provisioner_log_level = 4;
}
// ParseRequest consumes source-code to produce inputs.
message ParseRequest {
}
message Plan {
reserved 2;
// ParseComplete indicates a request to parse completed.
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;
repeated RichParameterValue rich_parameter_values = 3;
repeated VariableValue variable_values = 4;
repeated GitAuthProvider git_auth_providers = 5;
ParseRequest parse = 2;
PlanRequest plan = 3;
ApplyRequest apply = 4;
CancelRequest cancel = 5;
}
}
message Apply {
Config config = 1;
bytes plan = 2;
}
message Cancel {}
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;
}
message Response {
oneof type {
Log log = 1;
ParseComplete parse = 2;
PlanComplete plan = 3;
ApplyComplete apply = 4;
}
}
service Provisioner {
rpc Parse(Parse.Request) returns (stream Parse.Response);
rpc Provision(stream Provision.Request) returns (stream Provision.Response);
// Session represents provisioning a single template import or workspace. The daemon always sends Config followed
// 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 {
DRPCConn() drpc.Conn
Parse(ctx context.Context, in *Parse_Request) (DRPCProvisioner_ParseClient, error)
Provision(ctx context.Context) (DRPCProvisioner_ProvisionClient, error)
Session(ctx context.Context) (DRPCProvisioner_SessionClient, error)
}
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) Parse(ctx context.Context, in *Parse_Request) (DRPCProvisioner_ParseClient, error) {
stream, err := c.cc.NewStream(ctx, "/provisioner.Provisioner/Parse", drpcEncoding_File_provisionersdk_proto_provisioner_proto{})
func (c *drpcProvisionerClient) Session(ctx context.Context) (DRPCProvisioner_SessionClient, error) {
stream, err := c.cc.NewStream(ctx, "/provisioner.Provisioner/Session", drpcEncoding_File_provisionersdk_proto_provisioner_proto{})
if err != nil {
return nil, err
}
x := &drpcProvisioner_ParseClient{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
}
x := &drpcProvisioner_SessionClient{stream}
return x, nil
}
type DRPCProvisioner_ParseClient interface {
type DRPCProvisioner_SessionClient interface {
drpc.Stream
Recv() (*Parse_Response, error)
Send(*Request) error
Recv() (*Response, error)
}
type drpcProvisioner_ParseClient struct {
type drpcProvisioner_SessionClient struct {
drpc.Stream
}
func (x *drpcProvisioner_ParseClient) GetStream() drpc.Stream {
func (x *drpcProvisioner_SessionClient) GetStream() drpc.Stream {
return x.Stream
}
func (x *drpcProvisioner_ParseClient) Recv() (*Parse_Response, 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 {
func (x *drpcProvisioner_SessionClient) Send(m *Request) error {
return x.MsgSend(m, drpcEncoding_File_provisionersdk_proto_provisioner_proto{})
}
func (x *drpcProvisioner_ProvisionClient) Recv() (*Provision_Response, error) {
m := new(Provision_Response)
func (x *drpcProvisioner_SessionClient) Recv() (*Response, error) {
m := new(Response)
if err := x.MsgRecv(m, drpcEncoding_File_provisionersdk_proto_provisioner_proto{}); err != nil {
return nil, err
}
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{})
}
type DRPCProvisionerServer interface {
Parse(*Parse_Request, DRPCProvisioner_ParseStream) error
Provision(DRPCProvisioner_ProvisionStream) error
Session(DRPCProvisioner_SessionStream) error
}
type DRPCProvisionerUnimplementedServer struct{}
func (s *DRPCProvisionerUnimplementedServer) Parse(*Parse_Request, DRPCProvisioner_ParseStream) error {
return drpcerr.WithCode(errors.New("Unimplemented"), drpcerr.Unimplemented)
}
func (s *DRPCProvisionerUnimplementedServer) Provision(DRPCProvisioner_ProvisionStream) error {
func (s *DRPCProvisionerUnimplementedServer) Session(DRPCProvisioner_SessionStream) error {
return drpcerr.WithCode(errors.New("Unimplemented"), drpcerr.Unimplemented)
}
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) {
switch n {
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) {
return nil, srv.(DRPCProvisionerServer).
Parse(
in1.(*Parse_Request),
&drpcProvisioner_ParseStream{in2.(drpc.Stream)},
Session(
&drpcProvisioner_SessionStream{in1.(drpc.Stream)},
)
}, DRPCProvisionerServer.Parse, 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
}, DRPCProvisionerServer.Session, true
default:
return "", nil, nil, nil, false
}
@ -178,41 +123,28 @@ func DRPCRegisterProvisioner(mux drpc.Mux, impl DRPCProvisionerServer) error {
return mux.Register(impl, DRPCProvisionerDescription{})
}
type DRPCProvisioner_ParseStream interface {
type DRPCProvisioner_SessionStream interface {
drpc.Stream
Send(*Parse_Response) error
Send(*Response) error
Recv() (*Request, error)
}
type drpcProvisioner_ParseStream struct {
type drpcProvisioner_SessionStream struct {
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{})
}
type DRPCProvisioner_ProvisionStream interface {
drpc.Stream
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)
func (x *drpcProvisioner_SessionStream) Recv() (*Request, error) {
m := new(Request)
if err := x.MsgRecv(m, drpcEncoding_File_provisionersdk_proto_provisioner_proto{}); err != nil {
return nil, err
}
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{})
}

View File

@ -13,6 +13,8 @@ import (
"storj.io/drpc/drpcmux"
"storj.io/drpc/drpcserver"
"cdr.dev/slog"
"github.com/coder/coder/v2/coderd/tracing"
"github.com/coder/coder/v2/provisionersdk/proto"
)
@ -20,11 +22,19 @@ import (
// ServeOptions are configurations to serve a provisioner.
type ServeOptions struct {
// 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.
func Serve(ctx context.Context, server proto.DRPCProvisionerServer, options *ServeOptions) error {
func Serve(ctx context.Context, server Server, options *ServeOptions) error {
if options == nil {
options = &ServeOptions{}
}
@ -45,11 +55,22 @@ func Serve(ctx context.Context, server proto.DRPCProvisionerServer, options *Ser
}()
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.
// See: https://www.storj.io/blog/introducing-drpc-our-replacement-for-grpc
mux := drpcmux.New()
err := proto.DRPCRegisterProvisioner(mux, server)
ps := &protoServer{
server: server,
opts: *options,
}
err := proto.DRPCRegisterProvisioner(mux, ps)
if err != nil {
return xerrors.Errorf("register provisioner: %w", err)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -8,10 +8,10 @@ import {
Agent,
App,
AppSharingLevel,
Parse_Complete,
Parse_Response,
Provision_Complete,
Provision_Response,
Response,
ParseComplete,
PlanComplete,
ApplyComplete,
Resource,
RichParameter,
} from "./provisionerGenerated"
@ -337,11 +337,11 @@ type RecursivePartial<T> = {
interface EchoProvisionerResponses {
// parse is for observing any Terraform variables
parse?: RecursivePartial<Parse_Response>[]
parse?: RecursivePartial<Response>[]
// plan occurs when the template is imported
plan?: RecursivePartial<Provision_Response>[]
plan?: RecursivePartial<Response>[]
// apply occurs when the workspace is built
apply?: RecursivePartial<Provision_Response>[]
apply?: RecursivePartial<Response>[]
}
// createTemplateVersionTar consumes a series of echo provisioner protobufs and
@ -353,109 +353,133 @@ const createTemplateVersionTar = async (
responses = {}
}
if (!responses.parse) {
responses.parse = [{}]
responses.parse = [
{
parse: {},
},
]
}
if (!responses.apply) {
responses.apply = [{}]
responses.apply = [
{
apply: {},
},
]
}
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()
responses.parse.forEach((response, index) => {
response.complete = {
response.parse = {
templateVariables: [],
...response.complete,
} as Parse_Complete
error: "",
readme: new Uint8Array(),
...response.parse,
} as ParseComplete
tar.addFile(
`${index}.parse.protobuf`,
Parse_Response.encode(response as Parse_Response).finish(),
Response.encode(response as Response).finish(),
)
})
const fillProvisionResponse = (
response: RecursivePartial<Provision_Response>,
) => {
response.complete = {
const fillResource = (resource: RecursivePartial<Resource>) => {
if (resource.agents) {
resource.agents = resource.agents?.map(
(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: "",
state: new Uint8Array(),
resources: [],
parameters: [],
gitAuthProviders: [],
plan: new Uint8Array(),
...response.complete,
} as Provision_Complete
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)
...response.apply,
} as ApplyComplete
response.apply.resources = response.apply.resources?.map(fillResource)
tar.addFile(
`${index}.provision.apply.protobuf`,
Provision_Response.encode(response as Provision_Response).finish(),
`${index}.apply.protobuf`,
Response.encode(response as Response).finish(),
)
})
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(
`${index}.provision.plan.protobuf`,
Provision_Response.encode(response as Provision_Response).finish(),
`${index}.plan.protobuf`,
Response.encode(response as Response).finish(),
)
})
const tarFile = await tar.write()
@ -512,16 +536,21 @@ export const echoResponsesWithParameters = (
richParameters: RichParameter[],
): EchoProvisionerResponses => {
return {
parse: [
{
parse: {},
},
],
plan: [
{
complete: {
plan: {
parameters: richParameters,
},
},
],
apply: [
{
complete: {
apply: {
resources: [
{
name: "example",

View File

@ -21,6 +21,7 @@ export enum AppSharingLevel {
UNRECOGNIZED = -1,
}
/** WorkspaceTransition is the desired outcome of a build */
export enum WorkspaceTransition {
START = 0,
STOP = 1,
@ -177,29 +178,8 @@ export interface Resource_Metadata {
isNull: boolean
}
/** Parse consumes source-code from a directory to produce inputs. */
export interface Parse {}
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 {
/** Metadata is information about a workspace used in the execution of a build */
export interface Metadata {
coderUrl: string
workspaceTransition: WorkspaceTransition
workspaceName: string
@ -213,49 +193,74 @@ export interface Provision_Metadata {
workspaceOwnerSessionToken: string
}
/**
* Config represents execution configuration shared by both Plan and
* Apply commands.
*/
export interface Provision_Config {
directory: string
/** Config represents execution configuration shared by all subsequent requests in the Session */
export interface Config {
/** template_source_archive is a tar of the template source files */
templateSourceArchive: Uint8Array
/** state is the provisioner state (if any) */
state: Uint8Array
metadata: Provision_Metadata | undefined
provisionerLogLevel: string
}
export interface Provision_Plan {
config: Provision_Config | undefined
/** ParseRequest consumes source-code to produce inputs. */
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[]
variableValues: VariableValue[]
gitAuthProviders: GitAuthProvider[]
}
export interface Provision_Apply {
config: Provision_Config | undefined
plan: Uint8Array
/** PlanComplete indicates a request to plan completed. */
export interface PlanComplete {
error: string
resources: Resource[]
parameters: RichParameter[]
gitAuthProviders: string[]
}
export interface Provision_Cancel {}
export interface Provision_Request {
plan?: Provision_Plan | undefined
apply?: Provision_Apply | undefined
cancel?: Provision_Cancel | undefined
/**
* 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.
*/
export interface ApplyRequest {
metadata: Metadata | undefined
}
export interface Provision_Complete {
/** ApplyComplete indicates a request to apply completed. */
export interface ApplyComplete {
state: Uint8Array
error: string
resources: Resource[]
parameters: RichParameter[]
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
complete?: Provision_Complete | undefined
parse?: ParseComplete | undefined
plan?: PlanComplete | undefined
apply?: ApplyComplete | undefined
}
export const Empty = {
@ -648,60 +653,9 @@ export const Resource_Metadata = {
},
}
export const Parse = {
encode(_: Parse, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer {
return writer
},
}
export const Parse_Request = {
export const Metadata = {
encode(
message: Parse_Request,
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,
message: Metadata,
writer: _m0.Writer = _m0.Writer.create(),
): _m0.Writer {
if (message.coderUrl !== "") {
@ -741,96 +695,108 @@ export const Provision_Metadata = {
},
}
export const Provision_Config = {
export const Config = {
encode(
message: Provision_Config,
message: Config,
writer: _m0.Writer = _m0.Writer.create(),
): _m0.Writer {
if (message.directory !== "") {
writer.uint32(10).string(message.directory)
if (message.templateSourceArchive.length !== 0) {
writer.uint32(10).bytes(message.templateSourceArchive)
}
if (message.state.length !== 0) {
writer.uint32(18).bytes(message.state)
}
if (message.metadata !== undefined) {
Provision_Metadata.encode(
message.metadata,
writer.uint32(26).fork(),
).ldelim()
}
if (message.provisionerLogLevel !== "") {
writer.uint32(34).string(message.provisionerLogLevel)
writer.uint32(26).string(message.provisionerLogLevel)
}
return writer
},
}
export const Provision_Plan = {
export const ParseRequest = {
encode(
message: Provision_Plan,
_: ParseRequest,
writer: _m0.Writer = _m0.Writer.create(),
): _m0.Writer {
if (message.config !== undefined) {
Provision_Config.encode(message.config, writer.uint32(10).fork()).ldelim()
return writer
},
}
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) {
RichParameterValue.encode(v!, writer.uint32(26).fork()).ldelim()
RichParameterValue.encode(v!, writer.uint32(18).fork()).ldelim()
}
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) {
GitAuthProvider.encode(v!, writer.uint32(42).fork()).ldelim()
GitAuthProvider.encode(v!, writer.uint32(34).fork()).ldelim()
}
return writer
},
}
export const Provision_Apply = {
export const PlanComplete = {
encode(
message: Provision_Apply,
message: PlanComplete,
writer: _m0.Writer = _m0.Writer.create(),
): _m0.Writer {
if (message.config !== undefined) {
Provision_Config.encode(message.config, writer.uint32(10).fork()).ldelim()
if (message.error !== "") {
writer.uint32(10).string(message.error)
}
if (message.plan.length !== 0) {
writer.uint32(18).bytes(message.plan)
for (const v of message.resources) {
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
},
}
export const Provision_Cancel = {
export const ApplyRequest = {
encode(
_: Provision_Cancel,
message: ApplyRequest,
writer: _m0.Writer = _m0.Writer.create(),
): _m0.Writer {
return writer
},
}
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()
if (message.metadata !== undefined) {
Metadata.encode(message.metadata, writer.uint32(10).fork()).ldelim()
}
return writer
},
}
export const Provision_Complete = {
export const ApplyComplete = {
encode(
message: Provision_Complete,
message: ApplyComplete,
writer: _m0.Writer = _m0.Writer.create(),
): _m0.Writer {
if (message.state.length !== 0) {
@ -848,34 +814,76 @@ export const Provision_Complete = {
for (const v of message.gitAuthProviders) {
writer.uint32(42).string(v!)
}
if (message.plan.length !== 0) {
writer.uint32(50).bytes(message.plan)
return writer
},
}
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
},
}
export const Provision_Response = {
export const Response = {
encode(
message: Provision_Response,
message: 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) {
Provision_Complete.encode(
message.complete,
writer.uint32(18).fork(),
).ldelim()
if (message.parse !== undefined) {
ParseComplete.encode(message.parse, writer.uint32(18).fork()).ldelim()
}
if (message.plan !== undefined) {
PlanComplete.encode(message.plan, writer.uint32(26).fork()).ldelim()
}
if (message.apply !== undefined) {
ApplyComplete.encode(message.apply, writer.uint32(34).fork()).ldelim()
}
return writer
},
}
export interface Provisioner {
Parse(request: Parse_Request): Observable<Parse_Response>
Provision(
request: Observable<Provision_Request>,
): Observable<Provision_Response>
/**
* Session represents provisioning a single template import or workspace. The daemon always sends Config followed
* 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.
*/
Session(request: Observable<Request>): Observable<Response>
}

View File

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

View File

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

View File

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

View File

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

View File

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