diff --git a/cli/cliui/provisionerjob.go b/cli/cliui/provisionerjob.go index 4255fba43c539..88375c9134772 100644 --- a/cli/cliui/provisionerjob.go +++ b/cli/cliui/provisionerjob.go @@ -1,6 +1,7 @@ package cliui import ( + "bytes" "context" "fmt" "io" @@ -35,6 +36,9 @@ type ProvisionerJobOptions struct { FetchInterval time.Duration // Verbose determines whether debug and trace logs will be shown. Verbose bool + // Silent determines whether log output will be shown unless there is an + // error. + Silent bool } // ProvisionerJob renders a provisioner job with interactive cancellation. @@ -133,12 +137,30 @@ func ProvisionerJob(ctx context.Context, writer io.Writer, opts ProvisionerJobOp return xerrors.Errorf("logs: %w", err) } + var ( + // logOutput is where log output is written + logOutput = writer + // logBuffer is where logs are buffered if opts.Silent is true + logBuffer = &bytes.Buffer{} + ) + if opts.Silent { + logOutput = logBuffer + } + flushLogBuffer := func() { + if opts.Silent { + _, _ = io.Copy(writer, logBuffer) + } + } + ticker := time.NewTicker(opts.FetchInterval) + defer ticker.Stop() for { select { case err = <-errChan: + flushLogBuffer() return err case <-ctx.Done(): + flushLogBuffer() return ctx.Err() case <-ticker.C: updateJob() @@ -160,8 +182,10 @@ func ProvisionerJob(ctx context.Context, writer io.Writer, opts ProvisionerJobOp } err = xerrors.New(job.Error) jobMutex.Unlock() + flushLogBuffer() return err } + output := "" switch log.Level { case codersdk.LogLevelTrace, codersdk.LogLevelDebug: @@ -176,14 +200,17 @@ func ProvisionerJob(ctx context.Context, writer io.Writer, opts ProvisionerJobOp case codersdk.LogLevelInfo: output = log.Output } + jobMutex.Lock() if log.Stage != currentStage && log.Stage != "" { updateStage(log.Stage, log.CreatedAt) jobMutex.Unlock() continue } - _, _ = fmt.Fprintf(writer, "%s %s\n", Styles.Placeholder.Render(" "), output) - didLogBetweenStage = true + _, _ = fmt.Fprintf(logOutput, "%s %s\n", Styles.Placeholder.Render(" "), output) + if !opts.Silent { + didLogBetweenStage = true + } jobMutex.Unlock() } } diff --git a/cli/create.go b/cli/create.go index 7bd7a7532a282..fedfb071b47e6 100644 --- a/cli/create.go +++ b/cli/create.go @@ -170,10 +170,40 @@ func create() *cobra.Command { } _, _ = fmt.Fprintln(cmd.OutOrStdout()) - resources, err := client.TemplateVersionResources(cmd.Context(), templateVersion.ID) + // Run a dry-run with the given parameters to check correctness + after := time.Now() + dryRun, err := client.CreateTemplateVersionDryRun(cmd.Context(), templateVersion.ID, codersdk.CreateTemplateVersionDryRunRequest{ + WorkspaceName: workspaceName, + ParameterValues: parameters, + }) if err != nil { - return err + return xerrors.Errorf("begin workspace dry-run: %w", err) + } + _, _ = fmt.Fprintln(cmd.OutOrStdout(), "Planning workspace...") + err = cliui.ProvisionerJob(cmd.Context(), cmd.OutOrStdout(), cliui.ProvisionerJobOptions{ + Fetch: func() (codersdk.ProvisionerJob, error) { + return client.TemplateVersionDryRun(cmd.Context(), templateVersion.ID, dryRun.ID) + }, + Cancel: func() error { + return client.CancelTemplateVersionDryRun(cmd.Context(), templateVersion.ID, dryRun.ID) + }, + Logs: func() (<-chan codersdk.ProvisionerJobLog, error) { + return client.TemplateVersionDryRunLogsAfter(cmd.Context(), templateVersion.ID, dryRun.ID, after) + }, + // Don't show log output for the dry-run unless there's an error. + Silent: true, + }) + if err != nil { + // TODO (Dean): reprompt for parameter values if we deem it to + // be a validation error + return xerrors.Errorf("dry-run workspace: %w", err) } + + resources, err := client.TemplateVersionDryRunResources(cmd.Context(), templateVersion.ID, dryRun.ID) + if err != nil { + return xerrors.Errorf("get workspace dry-run resources: %w", err) + } + err = cliui.WorkspaceResources(cmd.OutOrStdout(), resources, cliui.WorkspaceResourcesOptions{ WorkspaceName: workspaceName, // Since agent's haven't connected yet, hiding this makes more sense. @@ -192,7 +222,6 @@ func create() *cobra.Command { return err } - before := time.Now() workspace, err := client.CreateWorkspace(cmd.Context(), organization.ID, codersdk.CreateWorkspaceRequest{ TemplateID: template.ID, Name: workspaceName, @@ -204,7 +233,7 @@ func create() *cobra.Command { return err } - err = cliui.WorkspaceBuild(cmd.Context(), cmd.OutOrStdout(), client, workspace.LatestBuild.ID, before) + err = cliui.WorkspaceBuild(cmd.Context(), cmd.OutOrStdout(), client, workspace.LatestBuild.ID, after) if err != nil { return err } diff --git a/cli/create_test.go b/cli/create_test.go index 2a6ff752d201f..9675a68fc3139 100644 --- a/cli/create_test.go +++ b/cli/create_test.go @@ -2,6 +2,7 @@ package cli_test import ( "context" + "database/sql" "fmt" "os" "testing" @@ -12,6 +13,8 @@ import ( "github.com/coder/coder/cli/clitest" "github.com/coder/coder/coderd/coderdtest" + "github.com/coder/coder/coderd/database" + "github.com/coder/coder/codersdk" "github.com/coder/coder/provisioner/echo" "github.com/coder/coder/provisionersdk/proto" "github.com/coder/coder/pty/ptytest" @@ -249,6 +252,7 @@ func TestCreate(t *testing.T) { <-doneChan removeTmpDirUntilSuccess(t, tempDir) }) + t.Run("WithParameterFileNotContainingTheValue", func(t *testing.T) { t.Parallel() client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true}) @@ -279,6 +283,50 @@ func TestCreate(t *testing.T) { <-doneChan removeTmpDirUntilSuccess(t, tempDir) }) + + t.Run("FailedDryRun", func(t *testing.T) { + t.Parallel() + client, api := coderdtest.NewWithAPI(t, &coderdtest.Options{IncludeProvisionerD: true}) + user := coderdtest.CreateFirstUser(t, client) + version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ + Parse: echo.ParseComplete, + ProvisionDryRun: []*proto.Provision_Response{ + { + Type: &proto.Provision_Response_Complete{ + Complete: &proto.Provision_Complete{ + Error: "test error", + }, + }, + }, + }, + }) + + // The template import job should end up failed, but we need it to be + // succeeded so the dry-run can begin. + version = coderdtest.AwaitTemplateVersionJob(t, client, version.ID) + require.Equal(t, codersdk.ProvisionerJobFailed, version.Job.Status, "job is not failed") + err := api.Database.UpdateProvisionerJobWithCompleteByID(context.Background(), database.UpdateProvisionerJobWithCompleteByIDParams{ + ID: version.Job.ID, + CompletedAt: sql.NullTime{ + Time: time.Now(), + Valid: true, + }, + UpdatedAt: time.Now(), + Error: sql.NullString{}, + }) + require.NoError(t, err, "update provisioner job") + + _ = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) + cmd, root := clitest.New(t, "create", "test") + clitest.SetupConfig(t, client, root) + pty := ptytest.New(t) + cmd.SetIn(pty.Input()) + cmd.SetOut(pty.Output()) + + err = cmd.Execute() + require.Error(t, err) + require.ErrorContains(t, err, "dry-run workspace") + }) } func createTestParseResponseWithDefault(defaultValue string) []*proto.Parse_Response { diff --git a/coderd/coderd.go b/coderd/coderd.go index ed8cba833ccf1..999ec86a146bf 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -207,6 +207,13 @@ func New(options *Options) *API { r.Get("/parameters", api.templateVersionParameters) r.Get("/resources", api.templateVersionResources) r.Get("/logs", api.templateVersionLogs) + r.Route("/dry-run", func(r chi.Router) { + r.Post("/", api.postTemplateVersionDryRun) + r.Get("/{jobID}", api.templateVersionDryRun) + r.Get("/{jobID}/resources", api.templateVersionDryRunResources) + r.Get("/{jobID}/logs", api.templateVersionDryRunLogs) + r.Patch("/{jobID}/cancel", api.patchTemplateVersionDryRunCancel) + }) }) r.Route("/users", func(r chi.Router) { r.Get("/first", api.firstUser) diff --git a/coderd/coderd_test.go b/coderd/coderd_test.go index 9b1e83a252ee6..c9a5924d1a2a2 100644 --- a/coderd/coderd_test.go +++ b/coderd/coderd_test.go @@ -96,6 +96,10 @@ func TestAuthorizeAllEndpoints(t *testing.T) { require.NoError(t, err, "upload file") workspaceResources, err := client.WorkspaceResourcesByBuild(ctx, workspace.LatestBuild.ID) require.NoError(t, err, "workspace resources") + templateVersionDryRun, err := client.CreateTemplateVersionDryRun(ctx, version.ID, codersdk.CreateTemplateVersionDryRunRequest{ + ParameterValues: []codersdk.CreateParameterRequest{}, + }) + require.NoError(t, err, "template version dry-run") // Always fail auth from this point forward authorizer.AlwaysReturn = rbac.ForbiddenWithInternal(xerrors.New("fake implementation"), nil, nil) @@ -264,6 +268,27 @@ func TestAuthorizeAllEndpoints(t *testing.T) { AssertAction: rbac.ActionRead, AssertObject: rbac.ResourceTemplate.InOrg(template.OrganizationID).WithID(template.ID.String()), }, + "POST:/api/v2/templateversions/{templateversion}/dry-run": { + // The first check is to read the template + AssertAction: rbac.ActionRead, + AssertObject: rbac.ResourceTemplate.InOrg(version.OrganizationID).WithID(template.ID.String()), + }, + "GET:/api/v2/templateversions/{templateversion}/dry-run/{templateversiondryrun}": { + AssertAction: rbac.ActionRead, + AssertObject: rbac.ResourceTemplate.InOrg(version.OrganizationID).WithID(template.ID.String()), + }, + "GET:/api/v2/templateversions/{templateversion}/dry-run/{templateversiondryrun}/resources": { + AssertAction: rbac.ActionRead, + AssertObject: rbac.ResourceTemplate.InOrg(version.OrganizationID).WithID(template.ID.String()), + }, + "GET:/api/v2/templateversions/{templateversion}/dry-run/{templateversiondryrun}/logs": { + AssertAction: rbac.ActionRead, + AssertObject: rbac.ResourceTemplate.InOrg(version.OrganizationID).WithID(template.ID.String()), + }, + "PATCH:/api/v2/templateversions/{templateversion}/dry-run/{templateversiondryrun}/cancel": { + AssertAction: rbac.ActionRead, + AssertObject: rbac.ResourceTemplate.InOrg(version.OrganizationID).WithID(template.ID.String()), + }, "GET:/api/v2/provisionerdaemons": { StatusCode: http.StatusOK, AssertObject: rbac.ResourceProvisionerDaemon.WithID(provisionerds[0].ID.String()), @@ -326,6 +351,7 @@ func TestAuthorizeAllEndpoints(t *testing.T) { route = strings.ReplaceAll(route, "{hash}", file.Hash) route = strings.ReplaceAll(route, "{workspaceresource}", workspaceResources[0].ID.String()) route = strings.ReplaceAll(route, "{templateversion}", version.ID.String()) + route = strings.ReplaceAll(route, "{templateversiondryrun}", templateVersionDryRun.ID.String()) route = strings.ReplaceAll(route, "{templatename}", template.Name) // Only checking org scoped params here route = strings.ReplaceAll(route, "{scope}", string(organizationParam.Scope)) diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index a36f04d003474..4e82e2feed0b5 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -50,7 +50,8 @@ CREATE TYPE parameter_type_system AS ENUM ( CREATE TYPE provisioner_job_type AS ENUM ( 'template_version_import', - 'workspace_build' + 'workspace_build', + 'template_version_dry_run' ); CREATE TYPE provisioner_storage_method AS ENUM ( diff --git a/coderd/database/migrations/000099_provisioner_job_type_dry_run.down.sql b/coderd/database/migrations/000099_provisioner_job_type_dry_run.down.sql new file mode 100644 index 0000000000000..75b99db54da22 --- /dev/null +++ b/coderd/database/migrations/000099_provisioner_job_type_dry_run.down.sql @@ -0,0 +1,9 @@ +-- It's not possible to drop enum values from enum types, so the UP has "IF NOT +-- EXISTS". + +-- Delete all jobs that use the new enum value. +DELETE FROM + provisioner_jobs +WHERE + type = 'template_version_dry_run' +; diff --git a/coderd/database/migrations/000099_provisioner_job_type_dry_run.up.sql b/coderd/database/migrations/000099_provisioner_job_type_dry_run.up.sql new file mode 100644 index 0000000000000..b843aeb3621cb --- /dev/null +++ b/coderd/database/migrations/000099_provisioner_job_type_dry_run.up.sql @@ -0,0 +1,2 @@ +ALTER TYPE provisioner_job_type +ADD VALUE IF NOT EXISTS 'template_version_dry_run'; diff --git a/coderd/database/models.go b/coderd/database/models.go index dae316781354e..2e7b027355b73 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -179,6 +179,7 @@ type ProvisionerJobType string const ( ProvisionerJobTypeTemplateVersionImport ProvisionerJobType = "template_version_import" ProvisionerJobTypeWorkspaceBuild ProvisionerJobType = "workspace_build" + ProvisionerJobTypeTemplateVersionDryRun ProvisionerJobType = "template_version_dry_run" ) func (e *ProvisionerJobType) Scan(src interface{}) error { diff --git a/coderd/parameter/compute.go b/coderd/parameter/compute.go index 4551d3fd56772..407bd21717a36 100644 --- a/coderd/parameter/compute.go +++ b/coderd/parameter/compute.go @@ -13,11 +13,12 @@ import ( // ComputeScope targets identifiers to pull parameters from. type ComputeScope struct { - TemplateImportJobID uuid.UUID - OrganizationID uuid.UUID - UserID uuid.UUID - TemplateID uuid.NullUUID - WorkspaceID uuid.NullUUID + TemplateImportJobID uuid.UUID + OrganizationID uuid.UUID + UserID uuid.UUID + TemplateID uuid.NullUUID + WorkspaceID uuid.NullUUID + AdditionalParameterValues []database.ParameterValue } type ComputeOptions struct { @@ -142,6 +143,14 @@ func Compute(ctx context.Context, db database.Store, scope ComputeScope, options } } + // Finally, any additional parameter values declared in the input + for _, v := range scope.AdditionalParameterValues { + err = compute.injectSingle(v, false) + if err != nil { + return nil, xerrors.Errorf("inject single parameter value: %w", err) + } + } + values := make([]ComputedValue, 0, len(compute.computedParameterByName)) for _, value := range compute.computedParameterByName { values = append(values, value) diff --git a/coderd/provisionerdaemons.go b/coderd/provisionerdaemons.go index 023c238335a2b..a8f5b4c4c704a 100644 --- a/coderd/provisionerdaemons.go +++ b/coderd/provisionerdaemons.go @@ -110,6 +110,13 @@ type workspaceProvisionJob struct { DryRun bool `json:"dry_run"` } +// The input for a "template_version_dry_run" job. +type templateVersionDryRunJob struct { + TemplateVersionID uuid.UUID `json:"template_version_id"` + WorkspaceName string `json:"workspace_name"` + ParameterValues []database.ParameterValue `json:"parameter_values"` +} + // Implementation of the provisioner daemon protobuf server. type provisionerdServer struct { AccessURL *url.URL @@ -219,18 +226,15 @@ func (server *provisionerdServer) AcquireJob(ctx context.Context, _ *proto.Empty if err != nil { return nil, failJob(fmt.Sprintf("compute parameters: %s", err)) } - // Convert parameters to the protobuf type. - protoParameters := make([]*sdkproto.ParameterValue, 0, len(parameters)) - for _, computedParameter := range parameters { - converted, err := convertComputedParameterValue(computedParameter) - if err != nil { - return nil, failJob(fmt.Sprintf("convert parameter: %s", err)) - } - protoParameters = append(protoParameters, converted) + + // Convert types to their corresponding protobuf types. + protoParameters, err := convertComputedParameterValues(parameters) + if err != nil { + return nil, failJob(fmt.Sprintf("convert computed parameters to protobuf: %s", err)) } transition, err := convertWorkspaceTransition(workspaceBuild.Transition) if err != nil { - return nil, failJob(fmt.Sprint("convert workspace transition: %w", err)) + return nil, failJob(fmt.Sprintf("convert workspace transition: %s", err)) } protoJob.Type = &proto.AcquiredJob_WorkspaceBuild_{ @@ -249,6 +253,46 @@ func (server *provisionerdServer) AcquireJob(ctx context.Context, _ *proto.Empty }, }, } + case database.ProvisionerJobTypeTemplateVersionDryRun: + var input templateVersionDryRunJob + err = json.Unmarshal(job.Input, &input) + if err != nil { + return nil, failJob(fmt.Sprintf("unmarshal job input %q: %s", job.Input, err)) + } + + templateVersion, err := server.Database.GetTemplateVersionByID(ctx, input.TemplateVersionID) + if err != nil { + return nil, failJob(fmt.Sprintf("get template version: %s", err)) + } + + // Compute parameters for the dry-run to consume. + parameters, err := parameter.Compute(ctx, server.Database, parameter.ComputeScope{ + TemplateImportJobID: templateVersion.JobID, + OrganizationID: job.OrganizationID, + TemplateID: templateVersion.TemplateID, + UserID: user.ID, + WorkspaceID: uuid.NullUUID{}, + AdditionalParameterValues: input.ParameterValues, + }, nil) + if err != nil { + return nil, failJob(fmt.Sprintf("compute parameters: %s", err)) + } + + // Convert types to their corresponding protobuf types. + protoParameters, err := convertComputedParameterValues(parameters) + if err != nil { + return nil, failJob(fmt.Sprintf("convert computed parameters to protobuf: %s", err)) + } + + protoJob.Type = &proto.AcquiredJob_TemplateDryRun_{ + TemplateDryRun: &proto.AcquiredJob_TemplateDryRun{ + ParameterValues: protoParameters, + Metadata: &sdkproto.Provision_Metadata{ + CoderUrl: server.AccessURL.String(), + WorkspaceName: input.WorkspaceName, + }, + }, + } case database.ProvisionerJobTypeTemplateVersionImport: protoJob.Type = &proto.AcquiredJob_TemplateImport_{ TemplateImport: &proto.AcquiredJob_TemplateImport{ @@ -603,6 +647,35 @@ func (server *provisionerdServer) CompleteJob(ctx context.Context, completed *pr if err != nil { return nil, xerrors.Errorf("complete job: %w", err) } + case *proto.CompletedJob_TemplateDryRun_: + for _, resource := range jobType.TemplateDryRun.Resources { + server.Logger.Info(ctx, "inserting template dry-run job resource", + slog.F("job_id", job.ID.String()), + slog.F("resource_name", resource.Name), + slog.F("resource_type", resource.Type)) + + err = insertWorkspaceResource(ctx, server.Database, jobID, database.WorkspaceTransitionStart, resource) + if err != nil { + return nil, xerrors.Errorf("insert resource: %w", err) + } + } + + err = server.Database.UpdateProvisionerJobWithCompleteByID(ctx, database.UpdateProvisionerJobWithCompleteByIDParams{ + ID: jobID, + UpdatedAt: database.Now(), + CompletedAt: sql.NullTime{ + Time: database.Now(), + Valid: true, + }, + }) + if err != nil { + return nil, xerrors.Errorf("update provisioner job: %w", err) + } + server.Logger.Debug(ctx, "marked template dry-run job as completed", slog.F("job_id", jobID)) + if err != nil { + return nil, xerrors.Errorf("complete job: %w", err) + } + default: return nil, xerrors.Errorf("unknown job type %q; ensure coderd and provisionerd versions match", reflect.TypeOf(completed.Type).String()) @@ -733,6 +806,19 @@ func convertLogSource(logSource proto.LogSource) (database.LogSource, error) { } } +func convertComputedParameterValues(parameters []parameter.ComputedValue) ([]*sdkproto.ParameterValue, error) { + protoParameters := make([]*sdkproto.ParameterValue, len(parameters)) + for i, computedParameter := range parameters { + converted, err := convertComputedParameterValue(computedParameter) + if err != nil { + return nil, xerrors.Errorf("convert parameter: %w", err) + } + protoParameters[i] = converted + } + + return protoParameters, nil +} + func convertComputedParameterValue(param parameter.ComputedValue) (*sdkproto.ParameterValue, error) { var scheme sdkproto.ParameterDestination_Scheme switch param.DestinationScheme { diff --git a/coderd/templateversions.go b/coderd/templateversions.go index 0c1c1d3f7de83..43306bc7ffd6d 100644 --- a/coderd/templateversions.go +++ b/coderd/templateversions.go @@ -2,6 +2,7 @@ package coderd import ( "database/sql" + "encoding/json" "errors" "fmt" "net/http" @@ -163,6 +164,216 @@ func (api *API) templateVersionParameters(rw http.ResponseWriter, r *http.Reques httpapi.Write(rw, http.StatusOK, values) } +func (api *API) postTemplateVersionDryRun(rw http.ResponseWriter, r *http.Request) { + apiKey := httpmw.APIKey(r) + templateVersion := httpmw.TemplateVersionParam(r) + if !api.Authorize(rw, r, rbac.ActionRead, templateVersion) { + return + } + // We use the workspace RBAC check since we don't want to allow dry runs if + // the user can't create workspaces. + if !api.Authorize(rw, r, rbac.ActionCreate, + rbac.ResourceWorkspace.InOrg(templateVersion.OrganizationID).WithOwner(apiKey.UserID.String())) { + return + } + + var req codersdk.CreateTemplateVersionDryRunRequest + if !httpapi.Read(rw, r, &req) { + return + } + + job, err := api.Database.GetProvisionerJobByID(r.Context(), templateVersion.JobID) + if err != nil { + httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ + Message: fmt.Sprintf("get provisioner job: %s", err), + }) + return + } + if !job.CompletedAt.Valid { + httpapi.Write(rw, http.StatusPreconditionFailed, httpapi.Response{ + Message: "Template version import job hasn't completed!", + }) + return + } + + // Convert parameters from request to parameters for the job + parameterValues := make([]database.ParameterValue, len(req.ParameterValues)) + for i, v := range req.ParameterValues { + parameterValues[i] = database.ParameterValue{ + ID: uuid.Nil, + Scope: database.ParameterScopeWorkspace, + ScopeID: uuid.Nil, + Name: v.Name, + SourceScheme: database.ParameterSourceSchemeData, + SourceValue: v.SourceValue, + DestinationScheme: database.ParameterDestinationSchemeProvisionerVariable, + } + } + + // Marshal template version dry-run job with the parameters from the + // request. + input, err := json.Marshal(templateVersionDryRunJob{ + TemplateVersionID: templateVersion.ID, + WorkspaceName: req.WorkspaceName, + ParameterValues: parameterValues, + }) + if err != nil { + httpapi.Write(rw, http.StatusPreconditionFailed, httpapi.Response{ + Message: fmt.Sprintf("marshal new provisioner job: %s", err), + }) + return + } + + // Create a dry-run job + jobID := uuid.New() + provisionerJob, err := api.Database.InsertProvisionerJob(r.Context(), database.InsertProvisionerJobParams{ + ID: jobID, + CreatedAt: database.Now(), + UpdatedAt: database.Now(), + OrganizationID: templateVersion.OrganizationID, + InitiatorID: apiKey.UserID, + Provisioner: job.Provisioner, + StorageMethod: job.StorageMethod, + StorageSource: job.StorageSource, + Type: database.ProvisionerJobTypeTemplateVersionDryRun, + Input: input, + }) + if err != nil { + httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ + Message: fmt.Sprintf("insert provisioner job: %s", err), + }) + return + } + + httpapi.Write(rw, http.StatusCreated, convertProvisionerJob(provisionerJob)) +} + +func (api *API) templateVersionDryRun(rw http.ResponseWriter, r *http.Request) { + job, ok := api.fetchTemplateVersionDryRunJob(rw, r) + if !ok { + return + } + + httpapi.Write(rw, http.StatusOK, convertProvisionerJob(job)) +} + +func (api *API) templateVersionDryRunResources(rw http.ResponseWriter, r *http.Request) { + job, ok := api.fetchTemplateVersionDryRunJob(rw, r) + if !ok { + return + } + + api.provisionerJobResources(rw, r, job) +} + +func (api *API) templateVersionDryRunLogs(rw http.ResponseWriter, r *http.Request) { + job, ok := api.fetchTemplateVersionDryRunJob(rw, r) + if !ok { + return + } + + api.provisionerJobLogs(rw, r, job) +} + +func (api *API) patchTemplateVersionDryRunCancel(rw http.ResponseWriter, r *http.Request) { + templateVersion := httpmw.TemplateVersionParam(r) + + job, ok := api.fetchTemplateVersionDryRunJob(rw, r) + if !ok { + return + } + if !api.Authorize(rw, r, rbac.ActionUpdate, + rbac.ResourceWorkspace.InOrg(templateVersion.OrganizationID).WithOwner(job.InitiatorID.String())) { + return + } + + if job.CompletedAt.Valid { + httpapi.Write(rw, http.StatusPreconditionFailed, httpapi.Response{ + Message: "Job has already completed", + }) + return + } + if job.CanceledAt.Valid { + httpapi.Write(rw, http.StatusPreconditionFailed, httpapi.Response{ + Message: "Job has already been marked as canceled", + }) + return + } + + err := api.Database.UpdateProvisionerJobWithCancelByID(r.Context(), database.UpdateProvisionerJobWithCancelByIDParams{ + ID: job.ID, + CanceledAt: sql.NullTime{ + Time: database.Now(), + Valid: true, + }, + }) + if err != nil { + httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ + Message: fmt.Sprintf("update provisioner job: %s", err), + }) + return + } + + httpapi.Write(rw, http.StatusOK, httpapi.Response{ + Message: "Job has been marked as canceled", + }) +} + +func (api *API) fetchTemplateVersionDryRunJob(rw http.ResponseWriter, r *http.Request) (database.ProvisionerJob, bool) { + var ( + templateVersion = httpmw.TemplateVersionParam(r) + jobID = chi.URLParam(r, "jobID") + ) + if !api.Authorize(rw, r, rbac.ActionRead, templateVersion) { + return database.ProvisionerJob{}, false + } + + jobUUID, err := uuid.Parse(jobID) + if err != nil { + httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{ + Message: "Job ID must be a valid UUID", + }) + return database.ProvisionerJob{}, false + } + + job, err := api.Database.GetProvisionerJobByID(r.Context(), jobUUID) + if xerrors.Is(err, sql.ErrNoRows) { + httpapi.Forbidden(rw) + return database.ProvisionerJob{}, false + } + if err != nil { + httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ + Message: fmt.Sprintf("get provisioner job by ID %q: %s", jobUUID.String(), err), + }) + return database.ProvisionerJob{}, false + } + if job.Type != database.ProvisionerJobTypeTemplateVersionDryRun { + httpapi.Forbidden(rw) + return database.ProvisionerJob{}, false + } + // Do a workspace resource check since it's basically a workspace dry-run . + if !api.Authorize(rw, r, rbac.ActionRead, + rbac.ResourceWorkspace.InOrg(templateVersion.OrganizationID).WithOwner(job.InitiatorID.String())) { + return database.ProvisionerJob{}, false + } + + // Verify that the template version is the one used in the request. + var input templateVersionDryRunJob + err = json.Unmarshal(job.Input, &input) + if err != nil { + httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ + Message: fmt.Sprintf("unmarshal job metadata: %s", err), + }) + return database.ProvisionerJob{}, false + } + if input.TemplateVersionID != templateVersion.ID { + httpapi.Forbidden(rw) + return database.ProvisionerJob{}, false + } + + return job, true +} + func (api *API) templateVersionsByTemplate(rw http.ResponseWriter, r *http.Request) { template := httpmw.TemplateParam(r) if !api.Authorize(rw, r, rbac.ActionRead, template) { @@ -453,12 +664,13 @@ func (api *API) templateVersionLogs(rw http.ResponseWriter, r *http.Request) { func convertTemplateVersion(version database.TemplateVersion, job codersdk.ProvisionerJob) codersdk.TemplateVersion { return codersdk.TemplateVersion{ - ID: version.ID, - TemplateID: &version.TemplateID.UUID, - CreatedAt: version.CreatedAt, - UpdatedAt: version.UpdatedAt, - Name: version.Name, - Job: job, - Readme: version.Readme, + ID: version.ID, + TemplateID: &version.TemplateID.UUID, + OrganizationID: version.OrganizationID, + CreatedAt: version.CreatedAt, + UpdatedAt: version.UpdatedAt, + Name: version.Name, + Job: job, + Readme: version.Readme, } } diff --git a/coderd/templateversions_test.go b/coderd/templateversions_test.go index 92c5036090c15..359acf391b223 100644 --- a/coderd/templateversions_test.go +++ b/coderd/templateversions_test.go @@ -2,6 +2,7 @@ package coderd_test import ( "context" + "database/sql" "net/http" "testing" "time" @@ -11,6 +12,7 @@ import ( "github.com/stretchr/testify/require" "github.com/coder/coder/coderd/coderdtest" + "github.com/coder/coder/coderd/database" "github.com/coder/coder/codersdk" "github.com/coder/coder/provisioner/echo" "github.com/coder/coder/provisionersdk/proto" @@ -448,6 +450,207 @@ func TestPatchActiveTemplateVersion(t *testing.T) { }) } +func TestTemplateVersionDryRun(t *testing.T) { + t.Parallel() + + t.Run("OK", func(t *testing.T) { + t.Parallel() + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + resource := &proto.Resource{ + Name: "cool-resource", + Type: "cool_resource_type", + } + + client := coderdtest.New(t, &coderdtest.Options{APIRateLimit: -1, IncludeProvisionerD: true}) + user := coderdtest.CreateFirstUser(t, client) + version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ + Parse: echo.ParseComplete, + Provision: []*proto.Provision_Response{ + { + Type: &proto.Provision_Response_Log{ + Log: &proto.Log{}, + }, + }, + { + Type: &proto.Provision_Response_Complete{ + Complete: &proto.Provision_Complete{ + Resources: []*proto.Resource{resource}, + }, + }, + }, + }, + }) + _ = coderdtest.AwaitTemplateVersionJob(t, client, version.ID) + + // Create template version dry-run + after := time.Now() + job, err := client.CreateTemplateVersionDryRun(ctx, version.ID, codersdk.CreateTemplateVersionDryRunRequest{ + ParameterValues: []codersdk.CreateParameterRequest{}, + }) + require.NoError(t, err) + + // Fetch template version dry-run + newJob, err := client.TemplateVersionDryRun(ctx, version.ID, job.ID) + require.NoError(t, err) + require.Equal(t, job.ID, newJob.ID) + + // Stream logs + logs, err := client.TemplateVersionDryRunLogsAfter(ctx, version.ID, job.ID, after) + require.NoError(t, err) + + logsDone := make(chan struct{}) + go func() { + defer close(logsDone) + + logCount := 0 + for range logs { + logCount++ + } + assert.GreaterOrEqual(t, logCount, 1, "unexpected log count") + }() + + // Wait for the job to complete + require.Eventually(t, func() bool { + job, err := client.TemplateVersionDryRun(ctx, version.ID, job.ID) + assert.NoError(t, err) + + return job.Status == codersdk.ProvisionerJobSucceeded + }, 5*time.Second, 25*time.Millisecond) + + <-logsDone + + resources, err := client.TemplateVersionDryRunResources(ctx, version.ID, job.ID) + require.NoError(t, err) + require.Len(t, resources, 1) + require.Equal(t, resource.Name, resources[0].Name) + require.Equal(t, resource.Type, resources[0].Type) + }) + + t.Run("ImportNotFinished", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true}) + user := coderdtest.CreateFirstUser(t, client) + // This import job will never finish + version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ + Parse: echo.ParseComplete, + Provision: []*proto.Provision_Response{{ + Type: &proto.Provision_Response_Log{ + Log: &proto.Log{}, + }, + }}, + }) + + _, err := client.CreateTemplateVersionDryRun(context.Background(), version.ID, codersdk.CreateTemplateVersionDryRunRequest{ + ParameterValues: []codersdk.CreateParameterRequest{}, + }) + var apiErr *codersdk.Error + require.ErrorAs(t, err, &apiErr) + require.Equal(t, http.StatusPreconditionFailed, apiErr.StatusCode()) + }) + + t.Run("Cancel", func(t *testing.T) { + t.Parallel() + + t.Run("OK", func(t *testing.T) { + t.Parallel() + client, api := coderdtest.NewWithAPI(t, &coderdtest.Options{IncludeProvisionerD: true}) + user := coderdtest.CreateFirstUser(t, client) + version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ + Parse: echo.ParseComplete, + Provision: []*proto.Provision_Response{{ + Type: &proto.Provision_Response_Log{ + Log: &proto.Log{}, + }, + }}, + }) + forceCompleteTemplateVersionJob(t, api.Database, client, version) + + // Create the dry-run + job, err := client.CreateTemplateVersionDryRun(context.Background(), version.ID, codersdk.CreateTemplateVersionDryRunRequest{ + ParameterValues: []codersdk.CreateParameterRequest{}, + }) + require.NoError(t, err) + + require.Eventually(t, func() bool { + job, err := client.TemplateVersionDryRun(context.Background(), version.ID, job.ID) + assert.NoError(t, err) + + t.Logf("Status: %s", job.Status) + return job.Status == codersdk.ProvisionerJobRunning + }, 5*time.Second, 25*time.Millisecond) + + err = client.CancelTemplateVersionDryRun(context.Background(), version.ID, job.ID) + require.NoError(t, err) + + require.Eventually(t, func() bool { + job, err := client.TemplateVersionDryRun(context.Background(), version.ID, job.ID) + assert.NoError(t, err) + + t.Logf("Status: %s", job.Status) + return job.Status == codersdk.ProvisionerJobCanceled + }, 5*time.Second, 25*time.Millisecond) + }) + + t.Run("AlreadyCompleted", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true}) + user := coderdtest.CreateFirstUser(t, client) + version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil) + coderdtest.AwaitTemplateVersionJob(t, client, version.ID) + + // Create the dry-run + job, err := client.CreateTemplateVersionDryRun(context.Background(), version.ID, codersdk.CreateTemplateVersionDryRunRequest{ + ParameterValues: []codersdk.CreateParameterRequest{}, + }) + require.NoError(t, err) + + require.Eventually(t, func() bool { + job, err := client.TemplateVersionDryRun(context.Background(), version.ID, job.ID) + assert.NoError(t, err) + + t.Logf("Status: %s", job.Status) + return job.Status == codersdk.ProvisionerJobSucceeded + }, 5*time.Second, 25*time.Millisecond) + + err = client.CancelTemplateVersionDryRun(context.Background(), version.ID, job.ID) + var apiErr *codersdk.Error + require.ErrorAs(t, err, &apiErr) + require.Equal(t, http.StatusPreconditionFailed, apiErr.StatusCode()) + }) + + t.Run("AlreadyCanceled", func(t *testing.T) { + t.Parallel() + client, api := coderdtest.NewWithAPI(t, &coderdtest.Options{IncludeProvisionerD: true}) + user := coderdtest.CreateFirstUser(t, client) + version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ + Parse: echo.ParseComplete, + Provision: []*proto.Provision_Response{{ + Type: &proto.Provision_Response_Log{ + Log: &proto.Log{}, + }, + }}, + }) + forceCompleteTemplateVersionJob(t, api.Database, client, version) + + // Create the dry-run + job, err := client.CreateTemplateVersionDryRun(context.Background(), version.ID, codersdk.CreateTemplateVersionDryRunRequest{ + ParameterValues: []codersdk.CreateParameterRequest{}, + }) + require.NoError(t, err) + + err = client.CancelTemplateVersionDryRun(context.Background(), version.ID, job.ID) + require.NoError(t, err) + + err = client.CancelTemplateVersionDryRun(context.Background(), version.ID, job.ID) + var apiErr *codersdk.Error + require.ErrorAs(t, err, &apiErr) + require.Equal(t, http.StatusPreconditionFailed, apiErr.StatusCode()) + }) + }) +} + // TestPaginatedTemplateVersions creates a list of template versions and paginate. func TestPaginatedTemplateVersions(t *testing.T) { t.Parallel() @@ -539,3 +742,23 @@ func TestPaginatedTemplateVersions(t *testing.T) { }) } } + +func forceCompleteTemplateVersionJob(t *testing.T, db database.Store, client *codersdk.Client, version codersdk.TemplateVersion) { + t.Helper() + + // HACK: we need the template version job to be finished so the dry-run job + // can be created. We do this by canceling the job and then marking it as + // successful. + err := client.CancelTemplateVersion(context.Background(), version.ID) + require.NoError(t, err) + err = db.UpdateProvisionerJobWithCompleteByID(context.Background(), database.UpdateProvisionerJobWithCompleteByIDParams{ + ID: version.Job.ID, + UpdatedAt: time.Now(), + CompletedAt: sql.NullTime{ + Time: time.Now(), + Valid: true, + }, + Error: sql.NullString{}, + }) + require.NoError(t, err) +} diff --git a/coderd/workspaces_test.go b/coderd/workspaces_test.go index aa57b528e77b2..a77b66ea7221d 100644 --- a/coderd/workspaces_test.go +++ b/coderd/workspaces_test.go @@ -495,7 +495,7 @@ func TestWorkspaceUpdateAutostart(t *testing.T) { }) if testCase.expectedError != "" { - require.EqualError(t, err, testCase.expectedError, "unexpected error when setting workspace autostart schedule") + require.ErrorContains(t, err, testCase.expectedError, "unexpected error when setting workspace autostart schedule") return } diff --git a/codersdk/client.go b/codersdk/client.go index 2735c1900a2e9..0167bc43db96f 100644 --- a/codersdk/client.go +++ b/codersdk/client.go @@ -126,6 +126,14 @@ func (c *Client) dialWebsocket(ctx context.Context, path string) (*websocket.Con func readBodyAsError(res *http.Response) error { contentType := res.Header.Get("Content-Type") + var method, u string + if res.Request != nil { + method = res.Request.Method + if res.Request.URL != nil { + u = res.Request.URL.String() + } + } + var helper string if res.StatusCode == http.StatusUnauthorized { // 401 means the user is not logged in @@ -163,6 +171,8 @@ func readBodyAsError(res *http.Response) error { return &Error{ Response: m, statusCode: res.StatusCode, + method: method, + url: u, Helper: helper, } } @@ -173,6 +183,8 @@ type Error struct { httpapi.Response statusCode int + method string + url string Helper string } @@ -183,7 +195,10 @@ func (e *Error) StatusCode() int { func (e *Error) Error() string { var builder strings.Builder - _, _ = fmt.Fprintf(&builder, "status code %d: %s", e.statusCode, e.Message) + if e.method != "" && e.url != "" { + _, _ = fmt.Fprintf(&builder, "%v %v: ", e.method, e.url) + } + _, _ = fmt.Fprintf(&builder, "unexpected status code %d: %s", e.statusCode, e.Message) if e.Helper != "" { _, _ = fmt.Fprintf(&builder, ": %s", e.Helper) } diff --git a/codersdk/templateversions.go b/codersdk/templateversions.go index 0c72b6d26385e..afe8c32552eef 100644 --- a/codersdk/templateversions.go +++ b/codersdk/templateversions.go @@ -12,13 +12,14 @@ import ( // TemplateVersion represents a single version of a template. type TemplateVersion struct { - ID uuid.UUID `json:"id"` - TemplateID *uuid.UUID `json:"template_id,omitempty"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - Name string `json:"name"` - Job ProvisionerJob `json:"job"` - Readme string `json:"readme"` + ID uuid.UUID `json:"id"` + TemplateID *uuid.UUID `json:"template_id,omitempty"` + OrganizationID uuid.UUID `json:"organization_id,omitempty"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + Name string `json:"name"` + Job ProvisionerJob `json:"job"` + Readme string `json:"readme"` } // TemplateVersionParameter represents a computed parameter value. @@ -114,3 +115,83 @@ func (c *Client) TemplateVersionLogsBefore(ctx context.Context, version uuid.UUI func (c *Client) TemplateVersionLogsAfter(ctx context.Context, version uuid.UUID, after time.Time) (<-chan ProvisionerJobLog, error) { return c.provisionerJobLogsAfter(ctx, fmt.Sprintf("/api/v2/templateversions/%s/logs", version), after) } + +// CreateTemplateVersionDryRunRequest defines the request parameters for +// CreateTemplateVersionDryRun. +type CreateTemplateVersionDryRunRequest struct { + WorkspaceName string + ParameterValues []CreateParameterRequest +} + +// CreateTemplateVersionDryRun begins a dry-run provisioner job against the +// given template version with the given parameter values. +func (c *Client) CreateTemplateVersionDryRun(ctx context.Context, version uuid.UUID, req CreateTemplateVersionDryRunRequest) (ProvisionerJob, error) { + res, err := c.Request(ctx, http.MethodPost, fmt.Sprintf("/api/v2/templateversions/%s/dry-run", version), req) + if err != nil { + return ProvisionerJob{}, err + } + defer res.Body.Close() + if res.StatusCode != http.StatusCreated { + return ProvisionerJob{}, readBodyAsError(res) + } + + var job ProvisionerJob + return job, json.NewDecoder(res.Body).Decode(&job) +} + +// TemplateVersionDryRun returns the current state of a template version dry-run +// job. +func (c *Client) TemplateVersionDryRun(ctx context.Context, version, job uuid.UUID) (ProvisionerJob, error) { + res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/templateversions/%s/dry-run/%s", version, job), nil) + if err != nil { + return ProvisionerJob{}, err + } + defer res.Body.Close() + if res.StatusCode != http.StatusOK { + return ProvisionerJob{}, readBodyAsError(res) + } + + var j ProvisionerJob + return j, json.NewDecoder(res.Body).Decode(&j) +} + +// TemplateVersionDryRunResources returns the resources of a finished template +// version dry-run job. +func (c *Client) TemplateVersionDryRunResources(ctx context.Context, version, job uuid.UUID) ([]WorkspaceResource, error) { + res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/templateversions/%s/dry-run/%s/resources", version, job), nil) + if err != nil { + return nil, err + } + defer res.Body.Close() + if res.StatusCode != http.StatusOK { + return nil, readBodyAsError(res) + } + + var resources []WorkspaceResource + return resources, json.NewDecoder(res.Body).Decode(&resources) +} + +// TemplateVersionDryRunLogsBefore returns logs for a template version dry-run +// that occurred before a specific time. +func (c *Client) TemplateVersionDryRunLogsBefore(ctx context.Context, version, job uuid.UUID, before time.Time) ([]ProvisionerJobLog, error) { + return c.provisionerJobLogsBefore(ctx, fmt.Sprintf("/api/v2/templateversions/%s/dry-run/%s/logs", version, job), before) +} + +// TemplateVersionDryRunLogsAfter streams logs for a template version dry-run +// that occurred after a specific time. +func (c *Client) TemplateVersionDryRunLogsAfter(ctx context.Context, version, job uuid.UUID, after time.Time) (<-chan ProvisionerJobLog, error) { + return c.provisionerJobLogsAfter(ctx, fmt.Sprintf("/api/v2/templateversions/%s/dry-run/%s/logs", version, job), after) +} + +// CancelTemplateVersionDryRun marks a template version dry-run job as canceled. +func (c *Client) CancelTemplateVersionDryRun(ctx context.Context, version, job uuid.UUID) error { + res, err := c.Request(ctx, http.MethodPatch, fmt.Sprintf("/api/v2/templateversions/%s/dry-run/%s/cancel", version, job), nil) + if err != nil { + return err + } + defer res.Body.Close() + if res.StatusCode != http.StatusOK { + return readBodyAsError(res) + } + return nil +} diff --git a/provisioner/echo/serve.go b/provisioner/echo/serve.go index 6a9ecab23dee3..1aa79a93ef23e 100644 --- a/provisioner/echo/serve.go +++ b/provisioner/echo/serve.go @@ -38,8 +38,7 @@ func Serve(ctx context.Context, options *provisionersdk.ServeOptions) error { // 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 { -} +type echo struct{} // Parse reads requests from the provided directory to stream responses. func (*echo) Parse(request *proto.Parse_Request, stream proto.DRPCProvisioner_ParseStream) error { diff --git a/provisionerd/proto/provisionerd.pb.go b/provisionerd/proto/provisionerd.pb.go index 639e0d4bd1135..ccb028111f9ba 100644 --- a/provisionerd/proto/provisionerd.pb.go +++ b/provisionerd/proto/provisionerd.pb.go @@ -121,6 +121,7 @@ type AcquiredJob struct { // Types that are assignable to Type: // *AcquiredJob_WorkspaceBuild_ // *AcquiredJob_TemplateImport_ + // *AcquiredJob_TemplateDryRun_ Type isAcquiredJob_Type `protobuf_oneof:"type"` } @@ -212,6 +213,13 @@ func (x *AcquiredJob) GetTemplateImport() *AcquiredJob_TemplateImport { return nil } +func (x *AcquiredJob) GetTemplateDryRun() *AcquiredJob_TemplateDryRun { + if x, ok := x.GetType().(*AcquiredJob_TemplateDryRun_); ok { + return x.TemplateDryRun + } + return nil +} + type isAcquiredJob_Type interface { isAcquiredJob_Type() } @@ -224,10 +232,16 @@ type AcquiredJob_TemplateImport_ struct { TemplateImport *AcquiredJob_TemplateImport `protobuf:"bytes,7,opt,name=template_import,json=templateImport,proto3,oneof"` } +type AcquiredJob_TemplateDryRun_ struct { + TemplateDryRun *AcquiredJob_TemplateDryRun `protobuf:"bytes,8,opt,name=template_dry_run,json=templateDryRun,proto3,oneof"` +} + func (*AcquiredJob_WorkspaceBuild_) isAcquiredJob_Type() {} func (*AcquiredJob_TemplateImport_) isAcquiredJob_Type() {} +func (*AcquiredJob_TemplateDryRun_) isAcquiredJob_Type() {} + type FailedJob struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -238,6 +252,7 @@ type FailedJob struct { // Types that are assignable to Type: // *FailedJob_WorkspaceBuild_ // *FailedJob_TemplateImport_ + // *FailedJob_TemplateDryRun_ Type isFailedJob_Type `protobuf_oneof:"type"` } @@ -308,6 +323,13 @@ func (x *FailedJob) GetTemplateImport() *FailedJob_TemplateImport { return nil } +func (x *FailedJob) GetTemplateDryRun() *FailedJob_TemplateDryRun { + if x, ok := x.GetType().(*FailedJob_TemplateDryRun_); ok { + return x.TemplateDryRun + } + return nil +} + type isFailedJob_Type interface { isFailedJob_Type() } @@ -320,10 +342,16 @@ type FailedJob_TemplateImport_ struct { TemplateImport *FailedJob_TemplateImport `protobuf:"bytes,4,opt,name=template_import,json=templateImport,proto3,oneof"` } +type FailedJob_TemplateDryRun_ struct { + TemplateDryRun *FailedJob_TemplateDryRun `protobuf:"bytes,5,opt,name=template_dry_run,json=templateDryRun,proto3,oneof"` +} + func (*FailedJob_WorkspaceBuild_) isFailedJob_Type() {} func (*FailedJob_TemplateImport_) isFailedJob_Type() {} +func (*FailedJob_TemplateDryRun_) isFailedJob_Type() {} + // CompletedJob is sent when the provisioner daemon completes a job. type CompletedJob struct { state protoimpl.MessageState @@ -334,6 +362,7 @@ type CompletedJob struct { // Types that are assignable to Type: // *CompletedJob_WorkspaceBuild_ // *CompletedJob_TemplateImport_ + // *CompletedJob_TemplateDryRun_ Type isCompletedJob_Type `protobuf_oneof:"type"` } @@ -397,6 +426,13 @@ func (x *CompletedJob) GetTemplateImport() *CompletedJob_TemplateImport { return nil } +func (x *CompletedJob) GetTemplateDryRun() *CompletedJob_TemplateDryRun { + if x, ok := x.GetType().(*CompletedJob_TemplateDryRun_); ok { + return x.TemplateDryRun + } + return nil +} + type isCompletedJob_Type interface { isCompletedJob_Type() } @@ -409,10 +445,16 @@ type CompletedJob_TemplateImport_ struct { TemplateImport *CompletedJob_TemplateImport `protobuf:"bytes,3,opt,name=template_import,json=templateImport,proto3,oneof"` } +type CompletedJob_TemplateDryRun_ struct { + TemplateDryRun *CompletedJob_TemplateDryRun `protobuf:"bytes,4,opt,name=template_dry_run,json=templateDryRun,proto3,oneof"` +} + func (*CompletedJob_WorkspaceBuild_) isCompletedJob_Type() {} func (*CompletedJob_TemplateImport_) isCompletedJob_Type() {} +func (*CompletedJob_TemplateDryRun_) isCompletedJob_Type() {} + // Log represents output from a job. type Log struct { state protoimpl.MessageState @@ -748,6 +790,61 @@ func (x *AcquiredJob_TemplateImport) GetMetadata() *proto.Provision_Metadata { return nil } +type AcquiredJob_TemplateDryRun struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ParameterValues []*proto.ParameterValue `protobuf:"bytes,1,rep,name=parameter_values,json=parameterValues,proto3" json:"parameter_values,omitempty"` + Metadata *proto.Provision_Metadata `protobuf:"bytes,2,opt,name=metadata,proto3" json:"metadata,omitempty"` +} + +func (x *AcquiredJob_TemplateDryRun) Reset() { + *x = AcquiredJob_TemplateDryRun{} + if protoimpl.UnsafeEnabled { + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AcquiredJob_TemplateDryRun) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AcquiredJob_TemplateDryRun) ProtoMessage() {} + +func (x *AcquiredJob_TemplateDryRun) ProtoReflect() protoreflect.Message { + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AcquiredJob_TemplateDryRun.ProtoReflect.Descriptor instead. +func (*AcquiredJob_TemplateDryRun) Descriptor() ([]byte, []int) { + return file_provisionerd_proto_provisionerd_proto_rawDescGZIP(), []int{1, 2} +} + +func (x *AcquiredJob_TemplateDryRun) GetParameterValues() []*proto.ParameterValue { + if x != nil { + return x.ParameterValues + } + return nil +} + +func (x *AcquiredJob_TemplateDryRun) GetMetadata() *proto.Provision_Metadata { + if x != nil { + return x.Metadata + } + return nil +} + type FailedJob_WorkspaceBuild struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -759,7 +856,7 @@ type FailedJob_WorkspaceBuild struct { func (x *FailedJob_WorkspaceBuild) Reset() { *x = FailedJob_WorkspaceBuild{} if protoimpl.UnsafeEnabled { - mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[9] + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -772,7 +869,7 @@ func (x *FailedJob_WorkspaceBuild) String() string { func (*FailedJob_WorkspaceBuild) ProtoMessage() {} func (x *FailedJob_WorkspaceBuild) ProtoReflect() protoreflect.Message { - mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[9] + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -804,7 +901,7 @@ type FailedJob_TemplateImport struct { func (x *FailedJob_TemplateImport) Reset() { *x = FailedJob_TemplateImport{} if protoimpl.UnsafeEnabled { - mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[10] + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -817,7 +914,7 @@ func (x *FailedJob_TemplateImport) String() string { func (*FailedJob_TemplateImport) ProtoMessage() {} func (x *FailedJob_TemplateImport) ProtoReflect() protoreflect.Message { - mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[10] + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -833,6 +930,44 @@ func (*FailedJob_TemplateImport) Descriptor() ([]byte, []int) { return file_provisionerd_proto_provisionerd_proto_rawDescGZIP(), []int{2, 1} } +type FailedJob_TemplateDryRun struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *FailedJob_TemplateDryRun) Reset() { + *x = FailedJob_TemplateDryRun{} + if protoimpl.UnsafeEnabled { + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *FailedJob_TemplateDryRun) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FailedJob_TemplateDryRun) ProtoMessage() {} + +func (x *FailedJob_TemplateDryRun) ProtoReflect() protoreflect.Message { + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FailedJob_TemplateDryRun.ProtoReflect.Descriptor instead. +func (*FailedJob_TemplateDryRun) Descriptor() ([]byte, []int) { + return file_provisionerd_proto_provisionerd_proto_rawDescGZIP(), []int{2, 2} +} + type CompletedJob_WorkspaceBuild struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -845,7 +980,7 @@ type CompletedJob_WorkspaceBuild struct { func (x *CompletedJob_WorkspaceBuild) Reset() { *x = CompletedJob_WorkspaceBuild{} if protoimpl.UnsafeEnabled { - mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[11] + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -858,7 +993,7 @@ func (x *CompletedJob_WorkspaceBuild) String() string { func (*CompletedJob_WorkspaceBuild) ProtoMessage() {} func (x *CompletedJob_WorkspaceBuild) ProtoReflect() protoreflect.Message { - mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[11] + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[13] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -900,7 +1035,7 @@ type CompletedJob_TemplateImport struct { func (x *CompletedJob_TemplateImport) Reset() { *x = CompletedJob_TemplateImport{} if protoimpl.UnsafeEnabled { - mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[12] + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -913,7 +1048,7 @@ func (x *CompletedJob_TemplateImport) String() string { func (*CompletedJob_TemplateImport) ProtoMessage() {} func (x *CompletedJob_TemplateImport) ProtoReflect() protoreflect.Message { - mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[12] + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[14] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -943,6 +1078,53 @@ func (x *CompletedJob_TemplateImport) GetStopResources() []*proto.Resource { return nil } +type CompletedJob_TemplateDryRun struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Resources []*proto.Resource `protobuf:"bytes,1,rep,name=resources,proto3" json:"resources,omitempty"` +} + +func (x *CompletedJob_TemplateDryRun) Reset() { + *x = CompletedJob_TemplateDryRun{} + if protoimpl.UnsafeEnabled { + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CompletedJob_TemplateDryRun) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CompletedJob_TemplateDryRun) ProtoMessage() {} + +func (x *CompletedJob_TemplateDryRun) ProtoReflect() protoreflect.Message { + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[15] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CompletedJob_TemplateDryRun.ProtoReflect.Descriptor instead. +func (*CompletedJob_TemplateDryRun) Descriptor() ([]byte, []int) { + return file_provisionerd_proto_provisionerd_proto_rawDescGZIP(), []int{3, 2} +} + +func (x *CompletedJob_TemplateDryRun) GetResources() []*proto.Resource { + if x != nil { + return x.Resources + } + return nil +} + var File_provisionerd_proto_provisionerd_proto protoreflect.FileDescriptor var file_provisionerd_proto_provisionerd_proto_rawDesc = []byte{ @@ -952,7 +1134,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, 0xbe, 0x05, 0x0a, 0x0b, 0x41, 0x63, 0x71, 0x75, 0x69, + 0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0xac, 0x07, 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, @@ -974,130 +1156,161 @@ var file_provisionerd_proto_provisionerd_proto_rawDesc = []byte{ 0x0b, 0x32, 0x28, 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, 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, 0x1a, 0x80, 0x02, - 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, 0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x64, 0x12, 0x25, - 0x0a, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, - 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x46, 0x0a, 0x10, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, - 0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, - 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0f, 0x70, 0x61, - 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 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, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, - 0x61, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, - 0x1a, 0x4d, 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, 0x42, - 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0xa0, 0x02, 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, 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, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0xc7, 0x03, 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, 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, 0x8e, 0x01, 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, 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, 0xb3, 0x01, 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, 0x49, 0x0a, 0x11, 0x70, 0x61, - 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x73, 0x18, - 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, - 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x53, 0x63, 0x68, - 0x65, 0x6d, 0x61, 0x52, 0x10, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x53, 0x63, - 0x68, 0x65, 0x6d, 0x61, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x64, 0x6d, 0x65, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x72, 0x65, 0x61, 0x64, 0x6d, 0x65, 0x22, 0x77, 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, 0x46, - 0x0a, 0x10, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, - 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, - 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, - 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 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, 0x98, 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, 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, + 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x54, 0x0a, + 0x10, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x64, 0x72, 0x79, 0x5f, 0x72, 0x75, + 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 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, 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, 0x80, 0x02, 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, 0x63, 0x65, 0x42, 0x75, 0x69, + 0x6c, 0x64, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, + 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x77, 0x6f, + 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x46, 0x0a, 0x10, 0x70, + 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, + 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, + 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, + 0x75, 0x65, 0x52, 0x0f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 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, + 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x1a, 0x4d, 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, 0x1a, 0x95, 0x01, 0x0a, 0x0e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, + 0x74, 0x65, 0x44, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x12, 0x46, 0x0a, 0x10, 0x70, 0x61, 0x72, 0x61, + 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, + 0x2e, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, + 0x0f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, + 0x12, 0x3b, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 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, 0x42, 0x06, 0x0a, + 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x86, 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, 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, 0x2b, 0x5a, 0x29, 0x67, 0x69, 0x74, 0x68, 0x75, - 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x64, 0x65, - 0x72, 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, + 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, 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, 0xe5, + 0x04, 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, 0x8e, 0x01, 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, 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, 0xb3, 0x01, 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, 0x49, 0x0a, 0x11, + 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, + 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, + 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x53, + 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x10, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, + 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x64, 0x6d, + 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x72, 0x65, 0x61, 0x64, 0x6d, 0x65, 0x22, + 0x77, 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, 0x46, 0x0a, 0x10, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, + 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, + 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, + 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 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, 0x98, + 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, 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, 0x2b, 0x5a, 0x29, 0x67, 0x69, 0x74, + 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, + 0x64, 0x65, 0x72, 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 ( @@ -1113,7 +1326,7 @@ func file_provisionerd_proto_provisionerd_proto_rawDescGZIP() []byte { } var file_provisionerd_proto_provisionerd_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_provisionerd_proto_provisionerd_proto_msgTypes = make([]protoimpl.MessageInfo, 13) +var file_provisionerd_proto_provisionerd_proto_msgTypes = make([]protoimpl.MessageInfo, 16) var file_provisionerd_proto_provisionerd_proto_goTypes = []interface{}{ (LogSource)(0), // 0: provisionerd.LogSource (*Empty)(nil), // 1: provisionerd.Empty @@ -1125,47 +1338,56 @@ var file_provisionerd_proto_provisionerd_proto_goTypes = []interface{}{ (*UpdateJobResponse)(nil), // 7: provisionerd.UpdateJobResponse (*AcquiredJob_WorkspaceBuild)(nil), // 8: provisionerd.AcquiredJob.WorkspaceBuild (*AcquiredJob_TemplateImport)(nil), // 9: provisionerd.AcquiredJob.TemplateImport - (*FailedJob_WorkspaceBuild)(nil), // 10: provisionerd.FailedJob.WorkspaceBuild - (*FailedJob_TemplateImport)(nil), // 11: provisionerd.FailedJob.TemplateImport - (*CompletedJob_WorkspaceBuild)(nil), // 12: provisionerd.CompletedJob.WorkspaceBuild - (*CompletedJob_TemplateImport)(nil), // 13: provisionerd.CompletedJob.TemplateImport - (proto.LogLevel)(0), // 14: provisioner.LogLevel - (*proto.ParameterSchema)(nil), // 15: provisioner.ParameterSchema - (*proto.ParameterValue)(nil), // 16: provisioner.ParameterValue - (*proto.Provision_Metadata)(nil), // 17: provisioner.Provision.Metadata - (*proto.Resource)(nil), // 18: provisioner.Resource + (*AcquiredJob_TemplateDryRun)(nil), // 10: provisionerd.AcquiredJob.TemplateDryRun + (*FailedJob_WorkspaceBuild)(nil), // 11: provisionerd.FailedJob.WorkspaceBuild + (*FailedJob_TemplateImport)(nil), // 12: provisionerd.FailedJob.TemplateImport + (*FailedJob_TemplateDryRun)(nil), // 13: provisionerd.FailedJob.TemplateDryRun + (*CompletedJob_WorkspaceBuild)(nil), // 14: provisionerd.CompletedJob.WorkspaceBuild + (*CompletedJob_TemplateImport)(nil), // 15: provisionerd.CompletedJob.TemplateImport + (*CompletedJob_TemplateDryRun)(nil), // 16: provisionerd.CompletedJob.TemplateDryRun + (proto.LogLevel)(0), // 17: provisioner.LogLevel + (*proto.ParameterSchema)(nil), // 18: provisioner.ParameterSchema + (*proto.ParameterValue)(nil), // 19: provisioner.ParameterValue + (*proto.Provision_Metadata)(nil), // 20: provisioner.Provision.Metadata + (*proto.Resource)(nil), // 21: provisioner.Resource } var file_provisionerd_proto_provisionerd_proto_depIdxs = []int32{ 8, // 0: provisionerd.AcquiredJob.workspace_build:type_name -> provisionerd.AcquiredJob.WorkspaceBuild 9, // 1: provisionerd.AcquiredJob.template_import:type_name -> provisionerd.AcquiredJob.TemplateImport - 10, // 2: provisionerd.FailedJob.workspace_build:type_name -> provisionerd.FailedJob.WorkspaceBuild - 11, // 3: provisionerd.FailedJob.template_import:type_name -> provisionerd.FailedJob.TemplateImport - 12, // 4: provisionerd.CompletedJob.workspace_build:type_name -> provisionerd.CompletedJob.WorkspaceBuild - 13, // 5: provisionerd.CompletedJob.template_import:type_name -> provisionerd.CompletedJob.TemplateImport - 0, // 6: provisionerd.Log.source:type_name -> provisionerd.LogSource - 14, // 7: provisionerd.Log.level:type_name -> provisioner.LogLevel - 5, // 8: provisionerd.UpdateJobRequest.logs:type_name -> provisionerd.Log - 15, // 9: provisionerd.UpdateJobRequest.parameter_schemas:type_name -> provisioner.ParameterSchema - 16, // 10: provisionerd.UpdateJobResponse.parameter_values:type_name -> provisioner.ParameterValue - 16, // 11: provisionerd.AcquiredJob.WorkspaceBuild.parameter_values:type_name -> provisioner.ParameterValue - 17, // 12: provisionerd.AcquiredJob.WorkspaceBuild.metadata:type_name -> provisioner.Provision.Metadata - 17, // 13: provisionerd.AcquiredJob.TemplateImport.metadata:type_name -> provisioner.Provision.Metadata - 18, // 14: provisionerd.CompletedJob.WorkspaceBuild.resources:type_name -> provisioner.Resource - 18, // 15: provisionerd.CompletedJob.TemplateImport.start_resources:type_name -> provisioner.Resource - 18, // 16: provisionerd.CompletedJob.TemplateImport.stop_resources:type_name -> provisioner.Resource - 1, // 17: provisionerd.ProvisionerDaemon.AcquireJob:input_type -> provisionerd.Empty - 6, // 18: provisionerd.ProvisionerDaemon.UpdateJob:input_type -> provisionerd.UpdateJobRequest - 3, // 19: provisionerd.ProvisionerDaemon.FailJob:input_type -> provisionerd.FailedJob - 4, // 20: provisionerd.ProvisionerDaemon.CompleteJob:input_type -> provisionerd.CompletedJob - 2, // 21: provisionerd.ProvisionerDaemon.AcquireJob:output_type -> provisionerd.AcquiredJob - 7, // 22: provisionerd.ProvisionerDaemon.UpdateJob:output_type -> provisionerd.UpdateJobResponse - 1, // 23: provisionerd.ProvisionerDaemon.FailJob:output_type -> provisionerd.Empty - 1, // 24: provisionerd.ProvisionerDaemon.CompleteJob:output_type -> provisionerd.Empty - 21, // [21:25] is the sub-list for method output_type - 17, // [17:21] is the sub-list for method input_type - 17, // [17:17] is the sub-list for extension type_name - 17, // [17:17] is the sub-list for extension extendee - 0, // [0:17] is the sub-list for field type_name + 10, // 2: provisionerd.AcquiredJob.template_dry_run:type_name -> provisionerd.AcquiredJob.TemplateDryRun + 11, // 3: provisionerd.FailedJob.workspace_build:type_name -> provisionerd.FailedJob.WorkspaceBuild + 12, // 4: provisionerd.FailedJob.template_import:type_name -> provisionerd.FailedJob.TemplateImport + 13, // 5: provisionerd.FailedJob.template_dry_run:type_name -> provisionerd.FailedJob.TemplateDryRun + 14, // 6: provisionerd.CompletedJob.workspace_build:type_name -> provisionerd.CompletedJob.WorkspaceBuild + 15, // 7: provisionerd.CompletedJob.template_import:type_name -> provisionerd.CompletedJob.TemplateImport + 16, // 8: provisionerd.CompletedJob.template_dry_run:type_name -> provisionerd.CompletedJob.TemplateDryRun + 0, // 9: provisionerd.Log.source:type_name -> provisionerd.LogSource + 17, // 10: provisionerd.Log.level:type_name -> provisioner.LogLevel + 5, // 11: provisionerd.UpdateJobRequest.logs:type_name -> provisionerd.Log + 18, // 12: provisionerd.UpdateJobRequest.parameter_schemas:type_name -> provisioner.ParameterSchema + 19, // 13: provisionerd.UpdateJobResponse.parameter_values:type_name -> provisioner.ParameterValue + 19, // 14: provisionerd.AcquiredJob.WorkspaceBuild.parameter_values:type_name -> provisioner.ParameterValue + 20, // 15: provisionerd.AcquiredJob.WorkspaceBuild.metadata:type_name -> provisioner.Provision.Metadata + 20, // 16: provisionerd.AcquiredJob.TemplateImport.metadata:type_name -> provisioner.Provision.Metadata + 19, // 17: provisionerd.AcquiredJob.TemplateDryRun.parameter_values:type_name -> provisioner.ParameterValue + 20, // 18: provisionerd.AcquiredJob.TemplateDryRun.metadata:type_name -> provisioner.Provision.Metadata + 21, // 19: provisionerd.CompletedJob.WorkspaceBuild.resources:type_name -> provisioner.Resource + 21, // 20: provisionerd.CompletedJob.TemplateImport.start_resources:type_name -> provisioner.Resource + 21, // 21: provisionerd.CompletedJob.TemplateImport.stop_resources:type_name -> provisioner.Resource + 21, // 22: provisionerd.CompletedJob.TemplateDryRun.resources:type_name -> provisioner.Resource + 1, // 23: provisionerd.ProvisionerDaemon.AcquireJob:input_type -> provisionerd.Empty + 6, // 24: provisionerd.ProvisionerDaemon.UpdateJob:input_type -> provisionerd.UpdateJobRequest + 3, // 25: provisionerd.ProvisionerDaemon.FailJob:input_type -> provisionerd.FailedJob + 4, // 26: provisionerd.ProvisionerDaemon.CompleteJob:input_type -> provisionerd.CompletedJob + 2, // 27: provisionerd.ProvisionerDaemon.AcquireJob:output_type -> provisionerd.AcquiredJob + 7, // 28: provisionerd.ProvisionerDaemon.UpdateJob:output_type -> provisionerd.UpdateJobResponse + 1, // 29: provisionerd.ProvisionerDaemon.FailJob:output_type -> provisionerd.Empty + 1, // 30: provisionerd.ProvisionerDaemon.CompleteJob:output_type -> provisionerd.Empty + 27, // [27:31] is the sub-list for method output_type + 23, // [23:27] is the sub-list for method input_type + 23, // [23:23] is the sub-list for extension type_name + 23, // [23:23] is the sub-list for extension extendee + 0, // [0:23] is the sub-list for field type_name } func init() { file_provisionerd_proto_provisionerd_proto_init() } @@ -1283,7 +1505,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { } } file_provisionerd_proto_provisionerd_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*FailedJob_WorkspaceBuild); i { + switch v := v.(*AcquiredJob_TemplateDryRun); i { case 0: return &v.state case 1: @@ -1295,7 +1517,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { } } file_provisionerd_proto_provisionerd_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*FailedJob_TemplateImport); i { + switch v := v.(*FailedJob_WorkspaceBuild); i { case 0: return &v.state case 1: @@ -1307,7 +1529,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { } } file_provisionerd_proto_provisionerd_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CompletedJob_WorkspaceBuild); i { + switch v := v.(*FailedJob_TemplateImport); i { case 0: return &v.state case 1: @@ -1319,6 +1541,30 @@ func file_provisionerd_proto_provisionerd_proto_init() { } } file_provisionerd_proto_provisionerd_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FailedJob_TemplateDryRun); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_provisionerd_proto_provisionerd_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CompletedJob_WorkspaceBuild); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_provisionerd_proto_provisionerd_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*CompletedJob_TemplateImport); i { case 0: return &v.state @@ -1330,18 +1576,33 @@ func file_provisionerd_proto_provisionerd_proto_init() { return nil } } + file_provisionerd_proto_provisionerd_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CompletedJob_TemplateDryRun); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } file_provisionerd_proto_provisionerd_proto_msgTypes[1].OneofWrappers = []interface{}{ (*AcquiredJob_WorkspaceBuild_)(nil), (*AcquiredJob_TemplateImport_)(nil), + (*AcquiredJob_TemplateDryRun_)(nil), } file_provisionerd_proto_provisionerd_proto_msgTypes[2].OneofWrappers = []interface{}{ (*FailedJob_WorkspaceBuild_)(nil), (*FailedJob_TemplateImport_)(nil), + (*FailedJob_TemplateDryRun_)(nil), } file_provisionerd_proto_provisionerd_proto_msgTypes[3].OneofWrappers = []interface{}{ (*CompletedJob_WorkspaceBuild_)(nil), (*CompletedJob_TemplateImport_)(nil), + (*CompletedJob_TemplateDryRun_)(nil), } type x struct{} out := protoimpl.TypeBuilder{ @@ -1349,7 +1610,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_provisionerd_proto_provisionerd_proto_rawDesc, NumEnums: 1, - NumMessages: 13, + NumMessages: 16, NumExtensions: 0, NumServices: 1, }, diff --git a/provisionerd/proto/provisionerd.proto b/provisionerd/proto/provisionerd.proto index b1808c5443e55..57f768ad68ada 100644 --- a/provisionerd/proto/provisionerd.proto +++ b/provisionerd/proto/provisionerd.proto @@ -21,6 +21,11 @@ message AcquiredJob { message TemplateImport { provisioner.Provision.Metadata metadata = 1; } + message TemplateDryRun { + repeated provisioner.ParameterValue parameter_values = 1; + provisioner.Provision.Metadata metadata = 2; + } + string job_id = 1; int64 created_at = 2; string provisioner = 3; @@ -29,6 +34,7 @@ message AcquiredJob { oneof type { WorkspaceBuild workspace_build = 6; TemplateImport template_import = 7; + TemplateDryRun template_dry_run = 8; } } @@ -36,13 +42,15 @@ message FailedJob { message WorkspaceBuild { bytes state = 1; } - message TemplateImport{ - } + message TemplateImport {} + message TemplateDryRun {} + string job_id = 1; string error = 2; oneof type { WorkspaceBuild workspace_build = 3; TemplateImport template_import = 4; + TemplateDryRun template_dry_run = 5; } } @@ -56,10 +64,15 @@ message CompletedJob { repeated provisioner.Resource start_resources = 1; repeated provisioner.Resource stop_resources = 2; } + message TemplateDryRun { + repeated provisioner.Resource resources = 1; + } + string job_id = 1; oneof type { WorkspaceBuild workspace_build = 2; TemplateImport template_import = 3; + TemplateDryRun template_dry_run = 4; } } @@ -98,7 +111,7 @@ service ProvisionerDaemon { // hold a lock on the job until CompleteJob() is // called with the matching ID. rpc AcquireJob(Empty) returns (AcquiredJob); - + // UpdateJob streams periodic updates for a job. // Implementations should buffer logs so this stream // is non-blocking. diff --git a/provisionerd/provisionerd.go b/provisionerd/provisionerd.go index 56f2707a1bf18..4717dd7905c3a 100644 --- a/provisionerd/provisionerd.go +++ b/provisionerd/provisionerd.go @@ -15,6 +15,7 @@ import ( "sync" "time" + "github.com/google/uuid" "github.com/hashicorp/yamux" "go.uber.org/atomic" "golang.org/x/xerrors" @@ -414,6 +415,12 @@ func (p *Server) runJob(ctx context.Context, job *proto.AcquiredJob) { p.runReadmeParse(ctx, job) p.runTemplateImport(ctx, shutdown, provisioner, job) + case *proto.AcquiredJob_TemplateDryRun_: + p.opts.Logger.Debug(context.Background(), "acquired job is template dry-run", + slog.F("workspace_name", jobType.TemplateDryRun.Metadata.WorkspaceName), + slog.F("parameters", jobType.TemplateDryRun.ParameterValues), + ) + p.runTemplateDryRun(ctx, shutdown, provisioner, job) case *proto.AcquiredJob_WorkspaceBuild_: p.opts.Logger.Debug(context.Background(), "acquired job is workspace provision", slog.F("workspace_name", jobType.WorkspaceBuild.WorkspaceName), @@ -583,7 +590,7 @@ func (p *Server) runTemplateImport(ctx, shutdown context.Context, provisioner sd WorkspaceTransition: sdkproto.WorkspaceTransition_STOP, }) if err != nil { - p.failActiveJobf("template import provision for start: %s", err) + p.failActiveJobf("template import provision for stop: %s", err) return } @@ -719,6 +726,14 @@ func (p *Server) runTemplateImportProvision(ctx, shutdown context.Context, provi return nil, xerrors.Errorf("send job update: %w", err) } case *sdkproto.Provision_Response_Complete: + if msgType.Complete.Error != "" { + p.opts.Logger.Info(context.Background(), "dry-run provision failure", + slog.F("error", msgType.Complete.Error), + ) + + return nil, xerrors.New(msgType.Complete.Error) + } + p.opts.Logger.Info(context.Background(), "parse dry-run provision successful", slog.F("resource_count", len(msgType.Complete.Resources)), slog.F("resources", msgType.Complete.Resources), @@ -733,6 +748,60 @@ func (p *Server) runTemplateImportProvision(ctx, shutdown context.Context, provi } } +func (p *Server) runTemplateDryRun(ctx, shutdown context.Context, provisioner sdkproto.DRPCProvisionerClient, job *proto.AcquiredJob) { + // Ensure all metadata fields are set as they are all optional for dry-run. + metadata := job.GetTemplateDryRun().GetMetadata() + metadata.WorkspaceTransition = sdkproto.WorkspaceTransition_START + if metadata.CoderUrl == "" { + metadata.CoderUrl = "http://localhost:3000" + } + if metadata.WorkspaceName == "" { + metadata.WorkspaceName = "dryrun" + } + metadata.WorkspaceOwner = job.UserName + if metadata.WorkspaceOwner == "" { + metadata.WorkspaceOwner = "dryrunner" + } + if metadata.WorkspaceId == "" { + id, err := uuid.NewRandom() + if err != nil { + p.failActiveJobf("generate random ID: %s", err) + return + } + metadata.WorkspaceId = id.String() + } + if metadata.WorkspaceOwnerId == "" { + id, err := uuid.NewRandom() + if err != nil { + p.failActiveJobf("generate random ID: %s", err) + return + } + metadata.WorkspaceOwnerId = id.String() + } + + // Run the template import provision task since it's already a dry run. + resources, err := p.runTemplateImportProvision(ctx, + shutdown, + provisioner, + job, + job.GetTemplateDryRun().GetParameterValues(), + metadata, + ) + if err != nil { + p.failActiveJobf("run dry-run provision job: %s", err) + return + } + + p.completeJob(&proto.CompletedJob{ + JobId: job.JobId, + Type: &proto.CompletedJob_TemplateDryRun_{ + TemplateDryRun: &proto.CompletedJob_TemplateDryRun{ + Resources: resources, + }, + }, + }) +} + func (p *Server) runWorkspaceBuild(ctx, shutdown context.Context, provisioner sdkproto.DRPCProvisionerClient, job *proto.AcquiredJob) { var stage string switch job.GetWorkspaceBuild().Metadata.WorkspaceTransition { diff --git a/provisionerd/provisionerd_test.go b/provisionerd/provisionerd_test.go index 0b12720ce68d0..0031d662311f4 100644 --- a/provisionerd/provisionerd_test.go +++ b/provisionerd/provisionerd_test.go @@ -315,6 +315,85 @@ func TestProvisionerd(t *testing.T) { require.NoError(t, closer.Close()) }) + t.Run("TemplateDryRun", func(t *testing.T) { + t.Parallel() + var ( + didComplete atomic.Bool + didLog atomic.Bool + didAcquireJob atomic.Bool + completeChan = make(chan struct{}) + completeOnce sync.Once + + parameterValues = []*sdkproto.ParameterValue{ + { + DestinationScheme: sdkproto.ParameterDestination_PROVISIONER_VARIABLE, + Name: "test_var", + Value: "dean was here", + }, + { + DestinationScheme: sdkproto.ParameterDestination_PROVISIONER_VARIABLE, + Name: "test_var_2", + Value: "1234", + }, + } + metadata = &sdkproto.Provision_Metadata{} + ) + + closer := createProvisionerd(t, func(ctx context.Context) (proto.DRPCProvisionerDaemonClient, error) { + return createProvisionerDaemonClient(t, provisionerDaemonTestServer{ + acquireJob: func(ctx context.Context, _ *proto.Empty) (*proto.AcquiredJob, error) { + if !didAcquireJob.CAS(false, true) { + completeOnce.Do(func() { close(completeChan) }) + return &proto.AcquiredJob{}, nil + } + + return &proto.AcquiredJob{ + JobId: "test", + Provisioner: "someprovisioner", + TemplateSourceArchive: createTar(t, map[string]string{ + "test.txt": "content", + }), + Type: &proto.AcquiredJob_TemplateDryRun_{ + TemplateDryRun: &proto.AcquiredJob_TemplateDryRun{ + ParameterValues: parameterValues, + Metadata: metadata, + }, + }, + }, nil + }, + updateJob: func(ctx context.Context, update *proto.UpdateJobRequest) (*proto.UpdateJobResponse, error) { + if len(update.Logs) != 0 { + didLog.Store(true) + } + return &proto.UpdateJobResponse{}, nil + }, + completeJob: func(ctx context.Context, job *proto.CompletedJob) (*proto.Empty, error) { + didComplete.Store(true) + return &proto.Empty{}, nil + }, + }), nil + }, provisionerd.Provisioners{ + "someprovisioner": createProvisionerClient(t, 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 + }, + }), + }) + + <-completeChan + require.True(t, didLog.Load()) + require.True(t, didComplete.Load()) + require.NoError(t, closer.Close()) + }) + t.Run("WorkspaceBuild", func(t *testing.T) { t.Parallel() var ( diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 57bc83ebc6dc3..8ae256e447b72 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -65,6 +65,12 @@ export interface CreateTemplateRequest { readonly parameter_values?: CreateParameterRequest[] } +// From codersdk/templateversions.go:121:6 +export interface CreateTemplateVersionDryRunRequest { + readonly WorkspaceName: string + readonly ParameterValues: CreateParameterRequest[] +} + // From codersdk/organizations.go:36:6 export interface CreateTemplateVersionRequest { readonly template_id?: string @@ -244,6 +250,7 @@ export interface Template { export interface TemplateVersion { readonly id: string readonly template_id?: string + readonly organization_id?: string readonly created_at: string readonly updated_at: string readonly name: string @@ -251,7 +258,7 @@ export interface TemplateVersion { readonly readme: string } -// From codersdk/templateversions.go:25:6 +// From codersdk/templateversions.go:26:6 export interface TemplateVersionParameter { readonly id: string readonly created_at: string