mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
chore: remove coder_secret Terraform integration (#25512)
Removes the coder_secret Terraform integration: the data.coder_secret consumption path through provisionerdserver → provisioner.proto → provisioner/terraform, the dynamic-parameter secret-requirement validation, and the workspace-update / resolve-autostart surfaces that depended on it. This is being done due to a product/feature direction change (see PLAT-243). User-secret CRUD (DB, REST, CLI, UI, telemetry, audit) and the agent-manifest secret-injection path are untouched. The provisionerd API is bumped from v1.17 to v1.18 rather than rolled back: v1.17 shipped in v2.33.x, so user_secrets field numbers are reserved and the changelog documents both versions. Generated with assistance from Coder Agents.
This commit is contained in:
@@ -7,7 +7,7 @@
|
|||||||
"last_seen_at": "====[timestamp]=====",
|
"last_seen_at": "====[timestamp]=====",
|
||||||
"name": "test-daemon",
|
"name": "test-daemon",
|
||||||
"version": "v0.0.0-devel",
|
"version": "v0.0.0-devel",
|
||||||
"api_version": "1.17",
|
"api_version": "1.18",
|
||||||
"provisioners": [
|
"provisioners": [
|
||||||
"echo"
|
"echo"
|
||||||
],
|
],
|
||||||
|
|||||||
Generated
+1
-28
@@ -18895,7 +18895,7 @@ const docTemplate = `{
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"id": {
|
"id": {
|
||||||
"description": "ID identifies the request for response ordering. Websocket response\nIDs are monotonically increasing and may exceed the request ID when\nserver-side events trigger additional renders.",
|
"description": "ID identifies the request. The response contains the same\nID so that the client can match it to the request.",
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"inputs": {
|
"inputs": {
|
||||||
@@ -18928,12 +18928,6 @@ const docTemplate = `{
|
|||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/codersdk.PreviewParameter"
|
"$ref": "#/definitions/codersdk.PreviewParameter"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"secret_requirements": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/codersdk.SecretRequirementStatus"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -22554,10 +22548,6 @@ const docTemplate = `{
|
|||||||
"properties": {
|
"properties": {
|
||||||
"parameter_mismatch": {
|
"parameter_mismatch": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
|
||||||
"secret_mismatch": {
|
|
||||||
"description": "SecretMismatch is true when the active template version declares\n` + "`" + `coder_secret` + "`" + ` requirements that the workspace owner's secrets do not\nsatisfy.",
|
|
||||||
"type": "boolean"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -22778,23 +22768,6 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"codersdk.SecretRequirementStatus": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"env": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"file": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"help_message": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"satisfied": {
|
|
||||||
"type": "boolean"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"codersdk.ServerSentEvent": {
|
"codersdk.ServerSentEvent": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
Generated
+1
-28
@@ -17163,7 +17163,7 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"id": {
|
"id": {
|
||||||
"description": "ID identifies the request for response ordering. Websocket response\nIDs are monotonically increasing and may exceed the request ID when\nserver-side events trigger additional renders.",
|
"description": "ID identifies the request. The response contains the same\nID so that the client can match it to the request.",
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"inputs": {
|
"inputs": {
|
||||||
@@ -17196,12 +17196,6 @@
|
|||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/codersdk.PreviewParameter"
|
"$ref": "#/definitions/codersdk.PreviewParameter"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"secret_requirements": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/codersdk.SecretRequirementStatus"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -20687,10 +20681,6 @@
|
|||||||
"properties": {
|
"properties": {
|
||||||
"parameter_mismatch": {
|
"parameter_mismatch": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
|
||||||
"secret_mismatch": {
|
|
||||||
"description": "SecretMismatch is true when the active template version declares\n`coder_secret` requirements that the workspace owner's secrets do not\nsatisfy.",
|
|
||||||
"type": "boolean"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -20911,23 +20901,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"codersdk.SecretRequirementStatus": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"env": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"file": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"help_message": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"satisfied": {
|
|
||||||
"type": "boolean"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"codersdk.ServerSentEvent": {
|
"codersdk.ServerSentEvent": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
@@ -343,7 +343,6 @@ func (e *Executor) runOnce(t time.Time) Stats {
|
|||||||
SetLastWorkspaceBuildJobInTx(&latestJob).
|
SetLastWorkspaceBuildJobInTx(&latestJob).
|
||||||
Experiments(e.experiments).
|
Experiments(e.experiments).
|
||||||
Reason(reason).
|
Reason(reason).
|
||||||
Logger(log.Named("wsbuilder")).
|
|
||||||
BuildMetrics(e.workspaceBuilderMetrics)
|
BuildMetrics(e.workspaceBuilderMetrics)
|
||||||
log.Debug(e.ctx, "auto building workspace", slog.F("transition", nextTransition))
|
log.Debug(e.ctx, "auto building workspace", slog.F("transition", nextTransition))
|
||||||
if nextTransition == database.WorkspaceTransitionStart &&
|
if nextTransition == database.WorkspaceTransitionStart &&
|
||||||
|
|||||||
@@ -246,7 +246,6 @@ var (
|
|||||||
rbac.ResourceWorkspaceAgentDevcontainers.Type: {policy.ActionCreate},
|
rbac.ResourceWorkspaceAgentDevcontainers.Type: {policy.ActionCreate},
|
||||||
// Provisionerd creates usage events
|
// Provisionerd creates usage events
|
||||||
rbac.ResourceUsageEvent.Type: {policy.ActionCreate},
|
rbac.ResourceUsageEvent.Type: {policy.ActionCreate},
|
||||||
rbac.ResourceUserSecret.Type: {policy.ActionRead},
|
|
||||||
}),
|
}),
|
||||||
User: []rbac.Permission{},
|
User: []rbac.Permission{},
|
||||||
ByOrgID: map[string]rbac.OrgPermissions{},
|
ByOrgID: map[string]rbac.OrgPermissions{},
|
||||||
@@ -271,7 +270,6 @@ var (
|
|||||||
rbac.ResourceTask.Type: {policy.ActionRead, policy.ActionUpdate},
|
rbac.ResourceTask.Type: {policy.ActionRead, policy.ActionUpdate},
|
||||||
rbac.ResourceTemplate.Type: {policy.ActionRead, policy.ActionUpdate},
|
rbac.ResourceTemplate.Type: {policy.ActionRead, policy.ActionUpdate},
|
||||||
rbac.ResourceUser.Type: {policy.ActionRead},
|
rbac.ResourceUser.Type: {policy.ActionRead},
|
||||||
rbac.ResourceUserSecret.Type: {policy.ActionRead},
|
|
||||||
rbac.ResourceWorkspace.Type: {policy.ActionDelete, policy.ActionRead, policy.ActionUpdate, policy.ActionWorkspaceStart, policy.ActionWorkspaceStop},
|
rbac.ResourceWorkspace.Type: {policy.ActionDelete, policy.ActionRead, policy.ActionUpdate, policy.ActionWorkspaceStart, policy.ActionWorkspaceStop},
|
||||||
rbac.ResourceWorkspaceDormant.Type: {policy.ActionDelete, policy.ActionRead, policy.ActionUpdate, policy.ActionWorkspaceStop},
|
rbac.ResourceWorkspaceDormant.Type: {policy.ActionDelete, policy.ActionRead, policy.ActionUpdate, policy.ActionWorkspaceStop},
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -6841,19 +6841,6 @@ func TestAuthorizeProvisionerJob_SystemFastPath(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAsAutostart(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
ctx := dbauthz.AsAutostart(context.Background())
|
|
||||||
actor, ok := dbauthz.ActorFromContext(ctx)
|
|
||||||
require.True(t, ok, "actor must be present")
|
|
||||||
|
|
||||||
auth := rbac.NewStrictCachingAuthorizer(prometheus.NewRegistry())
|
|
||||||
|
|
||||||
err := auth.Authorize(ctx, actor, policy.ActionRead, rbac.ResourceUserSecret.WithOwner(uuid.NewString()))
|
|
||||||
require.NoError(t, err, "user secret metadata read should be allowed")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAsChatd(t *testing.T) {
|
func TestAsChatd(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
|
"log/slog"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -12,27 +13,14 @@ import (
|
|||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
"cdr.dev/slog/v3"
|
|
||||||
"github.com/coder/coder/v2/apiversion"
|
"github.com/coder/coder/v2/apiversion"
|
||||||
"github.com/coder/coder/v2/coderd/database"
|
"github.com/coder/coder/v2/coderd/database"
|
||||||
"github.com/coder/coder/v2/coderd/database/dbauthz"
|
"github.com/coder/coder/v2/coderd/database/dbauthz"
|
||||||
"github.com/coder/coder/v2/coderd/files"
|
"github.com/coder/coder/v2/coderd/files"
|
||||||
"github.com/coder/coder/v2/codersdk"
|
|
||||||
"github.com/coder/preview"
|
"github.com/coder/preview"
|
||||||
previewtypes "github.com/coder/preview/types"
|
previewtypes "github.com/coder/preview/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RenderResult is the structured output of Renderer.Render. The outer
|
|
||||||
// pointer is always non-nil; inner fields may be nil.
|
|
||||||
// SecretRequirements is nil when no coder_secret blocks are declared,
|
|
||||||
// when fetch was forbidden, or when fetch failed. Output may be nil
|
|
||||||
// when underlying rendering fails (matches preview.Preview's existing
|
|
||||||
// convention).
|
|
||||||
type RenderResult struct {
|
|
||||||
Output *preview.Output
|
|
||||||
SecretRequirements []codersdk.SecretRequirementStatus
|
|
||||||
}
|
|
||||||
|
|
||||||
// Renderer is able to execute and evaluate terraform with the given inputs.
|
// Renderer is able to execute and evaluate terraform with the given inputs.
|
||||||
// It may use the database to fetch additional state, such as a user's groups,
|
// It may use the database to fetch additional state, such as a user's groups,
|
||||||
// roles, etc. Therefore, it requires an authenticated `ctx`.
|
// roles, etc. Therefore, it requires an authenticated `ctx`.
|
||||||
@@ -40,40 +28,17 @@ type RenderResult struct {
|
|||||||
// 'Close()' **must** be called once the renderer is no longer needed.
|
// 'Close()' **must** be called once the renderer is no longer needed.
|
||||||
// Forgetting to do so will result in a memory leak.
|
// Forgetting to do so will result in a memory leak.
|
||||||
type Renderer interface {
|
type Renderer interface {
|
||||||
Render(ctx context.Context, ownerID uuid.UUID, values map[string]string, opts ...RenderOption) (*RenderResult, hcl.Diagnostics)
|
Render(ctx context.Context, ownerID uuid.UUID, values map[string]string) (*preview.Output, hcl.Diagnostics)
|
||||||
Close()
|
Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
var ErrTemplateVersionNotReady = xerrors.New("template version job not finished")
|
var ErrTemplateVersionNotReady = xerrors.New("template version job not finished")
|
||||||
|
|
||||||
// RenderOption configures optional behavior for Renderer.Render.
|
|
||||||
type RenderOption func(*renderOptions)
|
|
||||||
|
|
||||||
type renderOptions struct {
|
|
||||||
includeSecretRequirements bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// IncludeSecretRequirements returns structured secret-requirement statuses and
|
|
||||||
// diagnostics for the rendered template.
|
|
||||||
func IncludeSecretRequirements() RenderOption {
|
|
||||||
return func(o *renderOptions) {
|
|
||||||
o.includeSecretRequirements = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Diagnostic extra codes for secret-requirement validation.
|
|
||||||
const (
|
|
||||||
DiagCodeMissingSecret = "missing_secret"
|
|
||||||
DiagCodeOwnerSecretsFetchFailed = "owner_secrets_fetch_failed"
|
|
||||||
DiagCodeSecretValidationForbidden = "secret_validation_forbidden"
|
|
||||||
)
|
|
||||||
|
|
||||||
// loader is used to load the necessary coder objects for rendering a template
|
// loader is used to load the necessary coder objects for rendering a template
|
||||||
// version's parameters. The output is a Renderer, which is the object that uses
|
// version's parameters. The output is a Renderer, which is the object that uses
|
||||||
// the cached objects to render the template version's parameters.
|
// the cached objects to render the template version's parameters.
|
||||||
type loader struct {
|
type loader struct {
|
||||||
templateVersionID uuid.UUID
|
templateVersionID uuid.UUID
|
||||||
logger slog.Logger
|
|
||||||
|
|
||||||
// cache of objects
|
// cache of objects
|
||||||
templateVersion *database.TemplateVersion
|
templateVersion *database.TemplateVersion
|
||||||
@@ -125,13 +90,6 @@ func WithTerraformValues(values database.TemplateVersionTerraformValue) func(r *
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithLogger sets the logger used by the renderer.
|
|
||||||
func WithLogger(logger slog.Logger) func(r *loader) {
|
|
||||||
return func(r *loader) {
|
|
||||||
r.logger = logger
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *loader) loadData(ctx context.Context, db database.Store) error {
|
func (r *loader) loadData(ctx context.Context, db database.Store) error {
|
||||||
if r.templateVersion == nil {
|
if r.templateVersion == nil {
|
||||||
tv, err := db.GetTemplateVersionByID(ctx, r.templateVersionID)
|
tv, err := db.GetTemplateVersionByID(ctx, r.templateVersionID)
|
||||||
@@ -245,14 +203,12 @@ func (r *loader) dynamicRenderer(ctx context.Context, db database.Store, cache *
|
|||||||
|
|
||||||
closeFiles = false // Caller will have to call close
|
closeFiles = false // Caller will have to call close
|
||||||
return &dynamicRenderer{
|
return &dynamicRenderer{
|
||||||
data: r,
|
data: r,
|
||||||
templateFS: templateFS,
|
templateFS: templateFS,
|
||||||
db: db,
|
db: db,
|
||||||
logger: r.logger,
|
ownerErrors: make(map[uuid.UUID]error),
|
||||||
ownerErrors: make(map[uuid.UUID]error),
|
close: cache.Close,
|
||||||
ownerSecretErrors: make(map[uuid.UUID]error),
|
tfvarValues: tfVarValues,
|
||||||
close: cache.Close,
|
|
||||||
tfvarValues: tfVarValues,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,26 +216,16 @@ type dynamicRenderer struct {
|
|||||||
db database.Store
|
db database.Store
|
||||||
data *loader
|
data *loader
|
||||||
templateFS fs.FS
|
templateFS fs.FS
|
||||||
logger slog.Logger
|
|
||||||
|
|
||||||
ownerErrors map[uuid.UUID]error
|
ownerErrors map[uuid.UUID]error
|
||||||
currentOwner *previewtypes.WorkspaceOwner
|
currentOwner *previewtypes.WorkspaceOwner
|
||||||
|
tfvarValues map[string]cty.Value
|
||||||
// ownerSecretErrors caches NotAuthorized denials per owner.
|
|
||||||
ownerSecretErrors map[uuid.UUID]error
|
|
||||||
|
|
||||||
tfvarValues map[string]cty.Value
|
|
||||||
|
|
||||||
once sync.Once
|
once sync.Once
|
||||||
close func()
|
close func()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *dynamicRenderer) Render(ctx context.Context, ownerID uuid.UUID, values map[string]string, opts ...RenderOption) (*RenderResult, hcl.Diagnostics) {
|
func (r *dynamicRenderer) Render(ctx context.Context, ownerID uuid.UUID, values map[string]string) (*preview.Output, hcl.Diagnostics) {
|
||||||
options := renderOptions{}
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(&options)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Always start with the cached error, if we have one.
|
// Always start with the cached error, if we have one.
|
||||||
ownerErr := r.ownerErrors[ownerID]
|
ownerErr := r.ownerErrors[ownerID]
|
||||||
if ownerErr == nil {
|
if ownerErr == nil {
|
||||||
@@ -288,7 +234,7 @@ func (r *dynamicRenderer) Render(ctx context.Context, ownerID uuid.UUID, values
|
|||||||
|
|
||||||
if ownerErr != nil || r.currentOwner == nil {
|
if ownerErr != nil || r.currentOwner == nil {
|
||||||
r.ownerErrors[ownerID] = ownerErr
|
r.ownerErrors[ownerID] = ownerErr
|
||||||
return &RenderResult{}, hcl.Diagnostics{
|
return nil, hcl.Diagnostics{
|
||||||
{
|
{
|
||||||
Severity: hcl.DiagError,
|
Severity: hcl.DiagError,
|
||||||
Summary: "Failed to fetch workspace owner",
|
Summary: "Failed to fetch workspace owner",
|
||||||
@@ -305,122 +251,13 @@ func (r *dynamicRenderer) Render(ctx context.Context, ownerID uuid.UUID, values
|
|||||||
ParameterValues: values,
|
ParameterValues: values,
|
||||||
Owner: *r.currentOwner,
|
Owner: *r.currentOwner,
|
||||||
TFVars: r.tfvarValues,
|
TFVars: r.tfvarValues,
|
||||||
// Leave Logger nil so preview discards parser logs. Returning
|
// Do not emit parser logs to coderd output logs.
|
||||||
// those logs to callers would be useful, but they may be large.
|
// TODO: Returning this logs in the output would benefit the caller.
|
||||||
|
// Unsure how large the logs can be, so for now we just discard them.
|
||||||
|
Logger: slog.New(slog.DiscardHandler),
|
||||||
}
|
}
|
||||||
|
|
||||||
output, diags := preview.Preview(ctx, input, r.templateFS)
|
return preview.Preview(ctx, input, r.templateFS)
|
||||||
if output == nil {
|
|
||||||
return &RenderResult{}, diags
|
|
||||||
}
|
|
||||||
|
|
||||||
var secretRequirements []codersdk.SecretRequirementStatus
|
|
||||||
if options.includeSecretRequirements && len(output.SecretRequirements) > 0 {
|
|
||||||
var secretDiags hcl.Diagnostics
|
|
||||||
secretRequirements, secretDiags = r.checkSecretRequirements(ctx, ownerID, output.SecretRequirements)
|
|
||||||
diags = diags.Extend(secretDiags)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &RenderResult{
|
|
||||||
Output: output,
|
|
||||||
SecretRequirements: secretRequirements,
|
|
||||||
}, diags
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkSecretRequirements returns structured requirement statuses. Callers
|
|
||||||
// without user_secret:read on the owner get a single
|
|
||||||
// secret_validation_forbidden warning instead, to avoid leaking the target's
|
|
||||||
// secret names via structured status presence.
|
|
||||||
func (r *dynamicRenderer) checkSecretRequirements(ctx context.Context, ownerID uuid.UUID, reqs []previewtypes.SecretRequirement) ([]codersdk.SecretRequirementStatus, hcl.Diagnostics) {
|
|
||||||
secrets, err := r.getOwnerSecrets(ctx, ownerID)
|
|
||||||
if err != nil {
|
|
||||||
if dbauthz.IsNotAuthorizedError(err) {
|
|
||||||
// Warning keeps the Create Workspace button enabled.
|
|
||||||
return nil, hcl.Diagnostics{{
|
|
||||||
Severity: hcl.DiagWarning,
|
|
||||||
Summary: "Cannot validate secret requirements",
|
|
||||||
Detail: "You are not permitted to read secret metadata for this user. The workspace may fail to build if required secrets are not set.",
|
|
||||||
Extra: previewtypes.DiagnosticExtra{
|
|
||||||
Code: DiagCodeSecretValidationForbidden,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
r.logger.Warn(ctx, "failed to fetch owner secrets for secret-requirement validation",
|
|
||||||
slog.F("owner_id", ownerID),
|
|
||||||
slog.Error(err),
|
|
||||||
)
|
|
||||||
return nil, hcl.Diagnostics{{
|
|
||||||
Severity: hcl.DiagError,
|
|
||||||
Summary: "Failed to fetch owner secrets",
|
|
||||||
Detail: "Could not validate template secret requirements. Please try again.",
|
|
||||||
Extra: previewtypes.DiagnosticExtra{
|
|
||||||
Code: DiagCodeOwnerSecretsFetchFailed,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
envSet := make(map[string]struct{}, len(secrets))
|
|
||||||
fileSet := make(map[string]struct{}, len(secrets))
|
|
||||||
for _, s := range secrets {
|
|
||||||
if s.EnvName != "" {
|
|
||||||
envSet[s.EnvName] = struct{}{}
|
|
||||||
}
|
|
||||||
if s.FilePath != "" {
|
|
||||||
fileSet[s.FilePath] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
statuses := make([]codersdk.SecretRequirementStatus, 0, len(reqs))
|
|
||||||
type secretRequirementDedupKey struct {
|
|
||||||
env string
|
|
||||||
file string
|
|
||||||
}
|
|
||||||
seen := make(map[secretRequirementDedupKey]int, len(reqs))
|
|
||||||
for _, req := range reqs {
|
|
||||||
kind := secretRequirementKind(req.Env, req.File)
|
|
||||||
if kind == "" {
|
|
||||||
// Defensive: SecretFromBlock should reject invalid inputs upstream.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
var env string
|
|
||||||
var file string
|
|
||||||
satisfied := false
|
|
||||||
switch kind {
|
|
||||||
case secretRequirementKindEnv:
|
|
||||||
env = req.Env
|
|
||||||
_, satisfied = envSet[req.Env]
|
|
||||||
case secretRequirementKindFile:
|
|
||||||
file = req.File
|
|
||||||
_, satisfied = fileSet[req.File]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dedup by Env/File. On collision, keep the
|
|
||||||
// lexicographically smallest non-empty HelpMessage. This is
|
|
||||||
// deterministic across runs; preview's SortSecretRequirements
|
|
||||||
// sorts on (Env, File) and does not guarantee a stable order
|
|
||||||
// when multiple coder_secret blocks declare the same value, so
|
|
||||||
// we cannot rely on "first source wins."
|
|
||||||
key := secretRequirementDedupKey{
|
|
||||||
env: env,
|
|
||||||
file: file,
|
|
||||||
}
|
|
||||||
if i, ok := seen[key]; ok {
|
|
||||||
statuses[i].Satisfied = statuses[i].Satisfied || satisfied
|
|
||||||
if req.HelpMessage != "" && (statuses[i].HelpMessage == "" || req.HelpMessage < statuses[i].HelpMessage) {
|
|
||||||
statuses[i].HelpMessage = req.HelpMessage
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
seen[key] = len(statuses)
|
|
||||||
statuses = append(statuses, codersdk.SecretRequirementStatus{
|
|
||||||
Env: env,
|
|
||||||
File: file,
|
|
||||||
HelpMessage: req.HelpMessage,
|
|
||||||
Satisfied: satisfied,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return statuses, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *dynamicRenderer) getWorkspaceOwnerData(ctx context.Context, ownerID uuid.UUID) error {
|
func (r *dynamicRenderer) getWorkspaceOwnerData(ctx context.Context, ownerID uuid.UUID) error {
|
||||||
@@ -437,23 +274,6 @@ func (r *dynamicRenderer) getWorkspaceOwnerData(ctx context.Context, ownerID uui
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getOwnerSecrets fetches the owner's secrets under the caller's auth
|
|
||||||
// context. Only NotAuthorized denials are cached; successes re-fetch so
|
|
||||||
// newly-created secrets are picked up on the next render.
|
|
||||||
func (r *dynamicRenderer) getOwnerSecrets(ctx context.Context, ownerID uuid.UUID) ([]database.ListUserSecretsRow, error) {
|
|
||||||
if err, cached := r.ownerSecretErrors[ownerID]; cached {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rows, err := r.db.ListUserSecrets(ctx, ownerID)
|
|
||||||
if err != nil {
|
|
||||||
if dbauthz.IsNotAuthorizedError(err) {
|
|
||||||
r.ownerSecretErrors[ownerID] = err
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return rows, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *dynamicRenderer) Close() {
|
func (r *dynamicRenderer) Close() {
|
||||||
r.once.Do(r.close)
|
r.once.Do(r.close)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,394 +0,0 @@
|
|||||||
package dynamicparameters
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"sync"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/hashicorp/hcl/v2"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"golang.org/x/xerrors"
|
|
||||||
|
|
||||||
"github.com/coder/coder/v2/coderd/database"
|
|
||||||
"github.com/coder/coder/v2/coderd/database/dbauthz"
|
|
||||||
"github.com/coder/coder/v2/coderd/database/dbgen"
|
|
||||||
"github.com/coder/coder/v2/coderd/database/dbtestutil"
|
|
||||||
"github.com/coder/coder/v2/codersdk"
|
|
||||||
previewtypes "github.com/coder/preview/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// newTestRenderer builds a dynamicRenderer backed by the given testdata
|
|
||||||
// fixture. The caller must seed an org and member row.
|
|
||||||
func newTestRenderer(t *testing.T, db database.Store, orgID uuid.UUID, fixture string) *dynamicRenderer {
|
|
||||||
t.Helper()
|
|
||||||
return &dynamicRenderer{
|
|
||||||
db: db,
|
|
||||||
templateFS: os.DirFS(filepath.Join("testdata", fixture)),
|
|
||||||
ownerErrors: make(map[uuid.UUID]error),
|
|
||||||
ownerSecretErrors: make(map[uuid.UUID]error),
|
|
||||||
data: &loader{
|
|
||||||
templateVersion: &database.TemplateVersion{
|
|
||||||
OrganizationID: orgID,
|
|
||||||
},
|
|
||||||
terraformValues: &database.TemplateVersionTerraformValue{},
|
|
||||||
},
|
|
||||||
close: func() {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// seedOwner creates a user and org member so WorkspaceOwner resolves.
|
|
||||||
func seedOwner(t *testing.T, db database.Store, orgID uuid.UUID) database.User {
|
|
||||||
t.Helper()
|
|
||||||
u := dbgen.User(t, db, database.User{})
|
|
||||||
dbgen.OrganizationMember(t, db, database.OrganizationMember{
|
|
||||||
OrganizationID: orgID,
|
|
||||||
UserID: u.ID,
|
|
||||||
})
|
|
||||||
return u
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDynamicRender_MissingSecretRequirement(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
db, _ := dbtestutil.NewDB(t)
|
|
||||||
ctx := t.Context()
|
|
||||||
org := dbgen.Organization(t, db, database.Organization{})
|
|
||||||
owner := seedOwner(t, db, org.ID)
|
|
||||||
|
|
||||||
renderer := newTestRenderer(t, db, org.ID, "secret_required")
|
|
||||||
defer renderer.Close()
|
|
||||||
|
|
||||||
// Owner has no secrets; the GITHUB_TOKEN requirement is unmet.
|
|
||||||
out, diags := renderer.Render(ctx, owner.ID, nil, IncludeSecretRequirements())
|
|
||||||
require.NotNil(t, out)
|
|
||||||
require.NotNil(t, out.Output)
|
|
||||||
requireNoMissingSecret(t, diags)
|
|
||||||
require.Equal(t, []codersdk.SecretRequirementStatus{{
|
|
||||||
Env: "GITHUB_TOKEN",
|
|
||||||
HelpMessage: "Add a GitHub PAT with env=GITHUB_TOKEN",
|
|
||||||
Satisfied: false,
|
|
||||||
}}, out.SecretRequirements)
|
|
||||||
|
|
||||||
// The same renderer must pick up a newly-created secret on the
|
|
||||||
// next render, without a reload.
|
|
||||||
_ = dbgen.UserSecret(t, db, database.UserSecret{
|
|
||||||
UserID: owner.ID,
|
|
||||||
Name: "github_token",
|
|
||||||
EnvName: "GITHUB_TOKEN",
|
|
||||||
})
|
|
||||||
|
|
||||||
out, diags2 := renderer.Render(ctx, owner.ID, nil, IncludeSecretRequirements())
|
|
||||||
requireNoMissingSecret(t, diags2)
|
|
||||||
require.Equal(t, []codersdk.SecretRequirementStatus{{
|
|
||||||
Env: "GITHUB_TOKEN",
|
|
||||||
HelpMessage: "Add a GitHub PAT with env=GITHUB_TOKEN",
|
|
||||||
Satisfied: true,
|
|
||||||
}}, out.SecretRequirements)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDynamicRender_ConditionalSecretRequirement(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
db, _ := dbtestutil.NewDB(t)
|
|
||||||
ctx := t.Context()
|
|
||||||
org := dbgen.Organization(t, db, database.Organization{})
|
|
||||||
owner := seedOwner(t, db, org.ID)
|
|
||||||
|
|
||||||
renderer := newTestRenderer(t, db, org.ID, "secret_conditional")
|
|
||||||
defer renderer.Close()
|
|
||||||
|
|
||||||
// Block inactive: no validation.
|
|
||||||
out, diags := renderer.Render(ctx, owner.ID, map[string]string{"use_github": "false"}, IncludeSecretRequirements())
|
|
||||||
requireNoMissingSecret(t, diags)
|
|
||||||
require.Nil(t, out.SecretRequirements)
|
|
||||||
|
|
||||||
// Block active: requirement surfaces.
|
|
||||||
out, diags = renderer.Render(ctx, owner.ID, map[string]string{"use_github": "true"}, IncludeSecretRequirements())
|
|
||||||
requireNoMissingSecret(t, diags)
|
|
||||||
require.Equal(t, []codersdk.SecretRequirementStatus{{
|
|
||||||
Env: "GITHUB_TOKEN",
|
|
||||||
HelpMessage: "Add a GitHub PAT",
|
|
||||||
Satisfied: false,
|
|
||||||
}}, out.SecretRequirements)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDynamicRender_SingleSecretSatisfiesEnvAndFile(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
db, _ := dbtestutil.NewDB(t)
|
|
||||||
ctx := t.Context()
|
|
||||||
org := dbgen.Organization(t, db, database.Organization{})
|
|
||||||
owner := seedOwner(t, db, org.ID)
|
|
||||||
|
|
||||||
// One row must satisfy both an env and a file requirement: the
|
|
||||||
// check builds independent envSet and fileSet maps.
|
|
||||||
_ = dbgen.UserSecret(t, db, database.UserSecret{
|
|
||||||
UserID: owner.ID,
|
|
||||||
Name: "combined",
|
|
||||||
EnvName: "GITHUB_TOKEN",
|
|
||||||
FilePath: "~/.ssh/id_rsa",
|
|
||||||
})
|
|
||||||
|
|
||||||
renderer := newTestRenderer(t, db, org.ID, "secret_env_and_file")
|
|
||||||
defer renderer.Close()
|
|
||||||
|
|
||||||
out, diags := renderer.Render(ctx, owner.ID, nil, IncludeSecretRequirements())
|
|
||||||
requireNoMissingSecret(t, diags)
|
|
||||||
require.Equal(t, []codersdk.SecretRequirementStatus{
|
|
||||||
{
|
|
||||||
File: "~/.ssh/id_rsa",
|
|
||||||
HelpMessage: "needs file",
|
|
||||||
Satisfied: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Env: "GITHUB_TOKEN",
|
|
||||||
HelpMessage: "needs env",
|
|
||||||
Satisfied: true,
|
|
||||||
},
|
|
||||||
}, out.SecretRequirements)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDynamicRender_PartialEnvAndFileSatisfaction(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
db, _ := dbtestutil.NewDB(t)
|
|
||||||
ctx := t.Context()
|
|
||||||
org := dbgen.Organization(t, db, database.Organization{})
|
|
||||||
owner := seedOwner(t, db, org.ID)
|
|
||||||
|
|
||||||
// Env-only secret against an env+file requirement: only the file
|
|
||||||
// requirement should fail.
|
|
||||||
_ = dbgen.UserSecret(t, db, database.UserSecret{
|
|
||||||
UserID: owner.ID,
|
|
||||||
Name: "env_only",
|
|
||||||
EnvName: "GITHUB_TOKEN",
|
|
||||||
})
|
|
||||||
|
|
||||||
renderer := newTestRenderer(t, db, org.ID, "secret_env_and_file")
|
|
||||||
defer renderer.Close()
|
|
||||||
|
|
||||||
out, diags := renderer.Render(ctx, owner.ID, nil, IncludeSecretRequirements())
|
|
||||||
requireNoMissingSecret(t, diags)
|
|
||||||
require.Equal(t, []codersdk.SecretRequirementStatus{
|
|
||||||
{
|
|
||||||
File: "~/.ssh/id_rsa",
|
|
||||||
HelpMessage: "needs file",
|
|
||||||
Satisfied: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Env: "GITHUB_TOKEN",
|
|
||||||
HelpMessage: "needs env",
|
|
||||||
Satisfied: true,
|
|
||||||
},
|
|
||||||
}, out.SecretRequirements)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDynamicRender_OwnerSwitch(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
db, _ := dbtestutil.NewDB(t)
|
|
||||||
ctx := t.Context()
|
|
||||||
org := dbgen.Organization(t, db, database.Organization{})
|
|
||||||
|
|
||||||
// Owner A satisfies the requirement; owner B does not.
|
|
||||||
ownerA := seedOwner(t, db, org.ID)
|
|
||||||
ownerB := seedOwner(t, db, org.ID)
|
|
||||||
_ = dbgen.UserSecret(t, db, database.UserSecret{
|
|
||||||
UserID: ownerA.ID,
|
|
||||||
Name: "gh",
|
|
||||||
EnvName: "GITHUB_TOKEN",
|
|
||||||
})
|
|
||||||
|
|
||||||
renderer := newTestRenderer(t, db, org.ID, "secret_required")
|
|
||||||
defer renderer.Close()
|
|
||||||
|
|
||||||
out, diags := renderer.Render(ctx, ownerA.ID, nil, IncludeSecretRequirements())
|
|
||||||
requireNoMissingSecret(t, diags)
|
|
||||||
require.Equal(t, []codersdk.SecretRequirementStatus{{
|
|
||||||
Env: "GITHUB_TOKEN",
|
|
||||||
HelpMessage: "Add a GitHub PAT with env=GITHUB_TOKEN",
|
|
||||||
Satisfied: true,
|
|
||||||
}}, out.SecretRequirements)
|
|
||||||
|
|
||||||
// The cache must not serve owner A's rows to owner B.
|
|
||||||
out, diags = renderer.Render(ctx, ownerB.ID, nil, IncludeSecretRequirements())
|
|
||||||
requireNoMissingSecret(t, diags)
|
|
||||||
require.Equal(t, []codersdk.SecretRequirementStatus{{
|
|
||||||
Env: "GITHUB_TOKEN",
|
|
||||||
HelpMessage: "Add a GitHub PAT with env=GITHUB_TOKEN",
|
|
||||||
Satisfied: false,
|
|
||||||
}}, out.SecretRequirements)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDynamicRender_DeduplicatesSecretRequirements(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
db, _ := dbtestutil.NewDB(t)
|
|
||||||
ctx := t.Context()
|
|
||||||
org := dbgen.Organization(t, db, database.Organization{})
|
|
||||||
owner := seedOwner(t, db, org.ID)
|
|
||||||
|
|
||||||
renderer := newTestRenderer(t, db, org.ID, "secret_required")
|
|
||||||
defer renderer.Close()
|
|
||||||
|
|
||||||
reqs := []previewtypes.SecretRequirement{
|
|
||||||
{Env: "GITHUB_TOKEN", HelpMessage: "z help"},
|
|
||||||
{Env: "GITHUB_TOKEN", HelpMessage: "a help"},
|
|
||||||
}
|
|
||||||
statuses, diags := renderer.checkSecretRequirements(ctx, owner.ID, reqs)
|
|
||||||
require.Empty(t, diags)
|
|
||||||
require.Equal(t, []codersdk.SecretRequirementStatus{{
|
|
||||||
Env: "GITHUB_TOKEN",
|
|
||||||
HelpMessage: "a help",
|
|
||||||
Satisfied: false,
|
|
||||||
}}, statuses)
|
|
||||||
}
|
|
||||||
|
|
||||||
// countingStore counts ListUserSecrets calls per user.
|
|
||||||
type countingStore struct {
|
|
||||||
database.Store
|
|
||||||
mu sync.Mutex
|
|
||||||
calls map[uuid.UUID]int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *countingStore) ListUserSecrets(ctx context.Context, userID uuid.UUID) ([]database.ListUserSecretsRow, error) {
|
|
||||||
c.mu.Lock()
|
|
||||||
if c.calls == nil {
|
|
||||||
c.calls = map[uuid.UUID]int{}
|
|
||||||
}
|
|
||||||
c.calls[userID]++
|
|
||||||
c.mu.Unlock()
|
|
||||||
return c.Store.ListUserSecrets(ctx, userID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *countingStore) callsFor(id uuid.UUID) int {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
return c.calls[id]
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestDynamicRender_NotAuthorizedIsCached pins that NotAuthorized
|
|
||||||
// denials hit ListUserSecrets at most once per owner.
|
|
||||||
func TestDynamicRender_NotAuthorizedIsCached(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
inner, _ := dbtestutil.NewDB(t)
|
|
||||||
db := &countingStore{Store: secretAuthDenyingStore{Store: inner}}
|
|
||||||
ctx := t.Context()
|
|
||||||
org := dbgen.Organization(t, db, database.Organization{})
|
|
||||||
owner := seedOwner(t, db, org.ID)
|
|
||||||
|
|
||||||
renderer := newTestRenderer(t, db, org.ID, "secret_required")
|
|
||||||
defer renderer.Close()
|
|
||||||
|
|
||||||
for range 3 {
|
|
||||||
_, _ = renderer.Render(ctx, owner.ID, nil, IncludeSecretRequirements())
|
|
||||||
}
|
|
||||||
require.Equal(t, 1, db.callsFor(owner.ID),
|
|
||||||
"NotAuthorized must be cached across renders")
|
|
||||||
}
|
|
||||||
|
|
||||||
// secretAuthDenyingStore makes ListUserSecrets return NotAuthorized,
|
|
||||||
// simulating a non-owner caller.
|
|
||||||
type secretAuthDenyingStore struct {
|
|
||||||
database.Store
|
|
||||||
}
|
|
||||||
|
|
||||||
func (secretAuthDenyingStore) ListUserSecrets(_ context.Context, _ uuid.UUID) ([]database.ListUserSecretsRow, error) {
|
|
||||||
return nil, dbauthz.NotAuthorizedError{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type secretFetchFailingStore struct {
|
|
||||||
database.Store
|
|
||||||
}
|
|
||||||
|
|
||||||
func (secretFetchFailingStore) ListUserSecrets(_ context.Context, _ uuid.UUID) ([]database.ListUserSecretsRow, error) {
|
|
||||||
return nil, xerrors.New("fetch failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDynamicRender_SecretFetchFailedHasNilRequirements(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
inner, _ := dbtestutil.NewDB(t)
|
|
||||||
db := secretFetchFailingStore{Store: inner}
|
|
||||||
ctx := t.Context()
|
|
||||||
org := dbgen.Organization(t, db, database.Organization{})
|
|
||||||
owner := seedOwner(t, db, org.ID)
|
|
||||||
|
|
||||||
renderer := newTestRenderer(t, db, org.ID, "secret_required")
|
|
||||||
defer renderer.Close()
|
|
||||||
|
|
||||||
out, diags := renderer.Render(ctx, owner.ID, nil, IncludeSecretRequirements())
|
|
||||||
require.Nil(t, out.SecretRequirements)
|
|
||||||
requireNoMissingSecret(t, diags)
|
|
||||||
|
|
||||||
var sawErr bool
|
|
||||||
for _, d := range diags {
|
|
||||||
extra, ok := d.Extra.(previewtypes.DiagnosticExtra)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if extra.Code == DiagCodeOwnerSecretsFetchFailed {
|
|
||||||
require.Equal(t, hcl.DiagError, d.Severity)
|
|
||||||
sawErr = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
require.True(t, sawErr, "expected owner_secrets_fetch_failed error")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestDynamicRender_NonOwnerCannotLeakSecretRequirements guards against
|
|
||||||
// a non-owner enumerating secret names via missing_secret diagnostics.
|
|
||||||
func TestDynamicRender_NonOwnerCannotLeakSecretRequirements(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
inner, _ := dbtestutil.NewDB(t)
|
|
||||||
db := secretAuthDenyingStore{Store: inner}
|
|
||||||
ctx := t.Context()
|
|
||||||
org := dbgen.Organization(t, db, database.Organization{})
|
|
||||||
owner := seedOwner(t, db, org.ID)
|
|
||||||
|
|
||||||
// Secret matches the requirement; a non-owner must still never
|
|
||||||
// see it.
|
|
||||||
_ = dbgen.UserSecret(t, db, database.UserSecret{
|
|
||||||
UserID: owner.ID,
|
|
||||||
Name: "gh",
|
|
||||||
EnvName: "GITHUB_TOKEN",
|
|
||||||
})
|
|
||||||
|
|
||||||
renderer := newTestRenderer(t, db, org.ID, "secret_required")
|
|
||||||
defer renderer.Close()
|
|
||||||
|
|
||||||
out, diags := renderer.Render(ctx, owner.ID, nil, IncludeSecretRequirements())
|
|
||||||
require.Nil(t, out.SecretRequirements)
|
|
||||||
|
|
||||||
// No missing_secret diagnostic for a non-owner, regardless of
|
|
||||||
// whether the target satisfies the requirement.
|
|
||||||
requireNoMissingSecret(t, diags)
|
|
||||||
|
|
||||||
// Surface a warning so the admin knows validation didn't run.
|
|
||||||
var sawWarn bool
|
|
||||||
for _, d := range diags {
|
|
||||||
extra, ok := d.Extra.(previewtypes.DiagnosticExtra)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if extra.Code == DiagCodeSecretValidationForbidden {
|
|
||||||
require.Equal(t, hcl.DiagWarning, d.Severity,
|
|
||||||
"secret_validation_forbidden must be a warning")
|
|
||||||
sawWarn = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
require.True(t, sawWarn, "expected secret_validation_forbidden warning")
|
|
||||||
}
|
|
||||||
|
|
||||||
func requireNoMissingSecret(t *testing.T, diags hcl.Diagnostics) {
|
|
||||||
t.Helper()
|
|
||||||
for _, d := range diags {
|
|
||||||
if extra, ok := d.Extra.(previewtypes.DiagnosticExtra); ok && extra.Code == DiagCodeMissingSecret {
|
|
||||||
t.Fatalf("unexpected missing_secret diagnostic: %s", d.Detail)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
context "context"
|
context "context"
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
|
|
||||||
dynamicparameters "github.com/coder/coder/v2/coderd/dynamicparameters"
|
preview "github.com/coder/preview"
|
||||||
uuid "github.com/google/uuid"
|
uuid "github.com/google/uuid"
|
||||||
hcl "github.com/hashicorp/hcl/v2"
|
hcl "github.com/hashicorp/hcl/v2"
|
||||||
gomock "go.uber.org/mock/gomock"
|
gomock "go.uber.org/mock/gomock"
|
||||||
@@ -56,21 +56,16 @@ func (mr *MockRendererMockRecorder) Close() *gomock.Call {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Render mocks base method.
|
// Render mocks base method.
|
||||||
func (m *MockRenderer) Render(ctx context.Context, ownerID uuid.UUID, values map[string]string, opts ...dynamicparameters.RenderOption) (*dynamicparameters.RenderResult, hcl.Diagnostics) {
|
func (m *MockRenderer) Render(ctx context.Context, ownerID uuid.UUID, values map[string]string) (*preview.Output, hcl.Diagnostics) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
varargs := []any{ctx, ownerID, values}
|
ret := m.ctrl.Call(m, "Render", ctx, ownerID, values)
|
||||||
for _, a := range opts {
|
ret0, _ := ret[0].(*preview.Output)
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "Render", varargs...)
|
|
||||||
ret0, _ := ret[0].(*dynamicparameters.RenderResult)
|
|
||||||
ret1, _ := ret[1].(hcl.Diagnostics)
|
ret1, _ := ret[1].(hcl.Diagnostics)
|
||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render indicates an expected call of Render.
|
// Render indicates an expected call of Render.
|
||||||
func (mr *MockRendererMockRecorder) Render(ctx, ownerID, values any, opts ...any) *gomock.Call {
|
func (mr *MockRendererMockRecorder) Render(ctx, ownerID, values any) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
varargs := append([]any{ctx, ownerID, values}, opts...)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Render", reflect.TypeOf((*MockRenderer)(nil).Render), ctx, ownerID, values)
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Render", reflect.TypeOf((*MockRenderer)(nil).Render), varargs...)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package dynamicparameters
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/hashicorp/hcl/v2"
|
"github.com/hashicorp/hcl/v2"
|
||||||
@@ -11,7 +10,6 @@ import (
|
|||||||
"github.com/coder/coder/v2/coderd/database"
|
"github.com/coder/coder/v2/coderd/database"
|
||||||
"github.com/coder/coder/v2/coderd/util/slice"
|
"github.com/coder/coder/v2/coderd/util/slice"
|
||||||
"github.com/coder/coder/v2/codersdk"
|
"github.com/coder/coder/v2/codersdk"
|
||||||
previewtypes "github.com/coder/preview/types"
|
|
||||||
"github.com/coder/terraform-provider-coder/v2/provider"
|
"github.com/coder/terraform-provider-coder/v2/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -24,33 +22,11 @@ const (
|
|||||||
sourcePreset
|
sourcePreset
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
secretRequirementKindEnv = "env"
|
|
||||||
secretRequirementKindFile = "file"
|
|
||||||
)
|
|
||||||
|
|
||||||
type parameterValue struct {
|
type parameterValue struct {
|
||||||
Value string
|
Value string
|
||||||
Source parameterValueSource
|
Source parameterValueSource
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResolveOption configures optional behavior for ResolveParameters.
|
|
||||||
type ResolveOption func(*resolveOptions)
|
|
||||||
|
|
||||||
type resolveOptions struct {
|
|
||||||
skipSecretRequirements bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// SkipSecretRequirements skips structured secret-requirement validation and
|
|
||||||
// enforcement. Callers must pass this for non-start transitions so an
|
|
||||||
// unsatisfied coder_secret, or an admin who can't read the owner's secrets,
|
|
||||||
// doesn't block stop or delete.
|
|
||||||
func SkipSecretRequirements() ResolveOption {
|
|
||||||
return func(o *resolveOptions) {
|
|
||||||
o.skipSecretRequirements = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//nolint:revive // firstbuild is a control flag to turn on immutable validation
|
//nolint:revive // firstbuild is a control flag to turn on immutable validation
|
||||||
func ResolveParameters(
|
func ResolveParameters(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
@@ -60,12 +36,7 @@ func ResolveParameters(
|
|||||||
previousValues []database.WorkspaceBuildParameter,
|
previousValues []database.WorkspaceBuildParameter,
|
||||||
buildValues []codersdk.WorkspaceBuildParameter,
|
buildValues []codersdk.WorkspaceBuildParameter,
|
||||||
presetValues []database.TemplateVersionPresetParameter,
|
presetValues []database.TemplateVersionPresetParameter,
|
||||||
opts ...ResolveOption,
|
|
||||||
) (map[string]string, error) {
|
) (map[string]string, error) {
|
||||||
o := resolveOptions{}
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(&o)
|
|
||||||
}
|
|
||||||
previousValuesMap := slice.ToMapFunc(previousValues, func(p database.WorkspaceBuildParameter) (string, string) {
|
previousValuesMap := slice.ToMapFunc(previousValues, func(p database.WorkspaceBuildParameter) (string, string) {
|
||||||
return p.Name, p.Value
|
return p.Name, p.Value
|
||||||
})
|
})
|
||||||
@@ -99,7 +70,7 @@ func ResolveParameters(
|
|||||||
//
|
//
|
||||||
// This is how the form should look to the user on their workspace settings page.
|
// This is how the form should look to the user on their workspace settings page.
|
||||||
// This is the original form truth that our validations should initially be based on.
|
// This is the original form truth that our validations should initially be based on.
|
||||||
result, diags := renderer.Render(ctx, ownerID, previousValuesMap)
|
output, diags := renderer.Render(ctx, ownerID, previousValuesMap)
|
||||||
if diags.HasErrors() {
|
if diags.HasErrors() {
|
||||||
// Top level diagnostics should break the build. Previous values (and new) should
|
// Top level diagnostics should break the build. Previous values (and new) should
|
||||||
// always be valid. If there is a case where this is not true, then this has to
|
// always be valid. If there is a case where this is not true, then this has to
|
||||||
@@ -107,7 +78,6 @@ func ResolveParameters(
|
|||||||
|
|
||||||
return nil, parameterValidationError(diags)
|
return nil, parameterValidationError(diags)
|
||||||
}
|
}
|
||||||
output := result.Output
|
|
||||||
|
|
||||||
// The user's input now needs to be validated against the parameters.
|
// The user's input now needs to be validated against the parameters.
|
||||||
// Mutability & Ephemeral parameters depend on sequential workspace builds.
|
// Mutability & Ephemeral parameters depend on sequential workspace builds.
|
||||||
@@ -128,33 +98,10 @@ func ResolveParameters(
|
|||||||
|
|
||||||
// This is the final set of values that will be used. Any errors at this stage
|
// This is the final set of values that will be used. Any errors at this stage
|
||||||
// are fatal. Additional validation for immutability has to be done manually.
|
// are fatal. Additional validation for immutability has to be done manually.
|
||||||
var renderOpts []RenderOption
|
output, diags = renderer.Render(ctx, ownerID, values.ValuesMap())
|
||||||
if !o.skipSecretRequirements {
|
|
||||||
renderOpts = append(renderOpts, IncludeSecretRequirements())
|
|
||||||
}
|
|
||||||
result, diags = renderer.Render(ctx, ownerID, values.ValuesMap(), renderOpts...)
|
|
||||||
if !o.skipSecretRequirements && !diags.HasErrors() {
|
|
||||||
var missing []codersdk.SecretRequirementStatus
|
|
||||||
for _, req := range result.SecretRequirements {
|
|
||||||
if !req.Satisfied {
|
|
||||||
missing = append(missing, req)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(missing) > 0 {
|
|
||||||
diags = append(diags, &hcl.Diagnostic{
|
|
||||||
Severity: hcl.DiagError,
|
|
||||||
Summary: "Missing required secrets",
|
|
||||||
Detail: formatMissingSecrets(missing),
|
|
||||||
Extra: previewtypes.DiagnosticExtra{
|
|
||||||
Code: DiagCodeMissingSecret,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if diags.HasErrors() {
|
if diags.HasErrors() {
|
||||||
return nil, parameterValidationError(diags)
|
return nil, parameterValidationError(diags)
|
||||||
}
|
}
|
||||||
output = result.Output
|
|
||||||
|
|
||||||
// parameterNames is going to be used to remove any excess values left
|
// parameterNames is going to be used to remove any excess values left
|
||||||
// around without a parameter.
|
// around without a parameter.
|
||||||
@@ -281,37 +228,3 @@ func (p parameterValueMap) ValuesMap() map[string]string {
|
|||||||
}
|
}
|
||||||
return values
|
return values
|
||||||
}
|
}
|
||||||
|
|
||||||
func secretRequirementKind(env, file string) string {
|
|
||||||
switch {
|
|
||||||
case env != "" && file == "":
|
|
||||||
return secretRequirementKindEnv
|
|
||||||
case file != "" && env == "":
|
|
||||||
return secretRequirementKindFile
|
|
||||||
default:
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatMissingSecrets(reqs []codersdk.SecretRequirementStatus) string {
|
|
||||||
var b strings.Builder
|
|
||||||
for i, req := range reqs {
|
|
||||||
if i > 0 {
|
|
||||||
_, _ = b.WriteString("\n")
|
|
||||||
}
|
|
||||||
switch secretRequirementKind(req.Env, req.File) {
|
|
||||||
case secretRequirementKindEnv:
|
|
||||||
_, _ = fmt.Fprintf(&b, "%s %s", secretRequirementKindEnv, req.Env)
|
|
||||||
case secretRequirementKindFile:
|
|
||||||
_, _ = fmt.Fprintf(&b, "%s %s", secretRequirementKindFile, req.File)
|
|
||||||
default:
|
|
||||||
// checkSecretRequirements filters malformed requirements produced
|
|
||||||
// by preview before they reach the resolver.
|
|
||||||
_, _ = b.WriteString("malformed secret requirement")
|
|
||||||
}
|
|
||||||
if req.HelpMessage != "" {
|
|
||||||
_, _ = fmt.Fprintf(&b, ": %s", req.HelpMessage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return b.String()
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,68 +0,0 @@
|
|||||||
package dynamicparameters
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
"github.com/coder/coder/v2/codersdk"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestFormatMissingSecrets(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
reqs []codersdk.SecretRequirementStatus
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Env",
|
|
||||||
reqs: []codersdk.SecretRequirementStatus{{
|
|
||||||
Env: "GITHUB_TOKEN",
|
|
||||||
HelpMessage: "Add a GitHub PAT",
|
|
||||||
}},
|
|
||||||
want: "env GITHUB_TOKEN: Add a GitHub PAT",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "File",
|
|
||||||
reqs: []codersdk.SecretRequirementStatus{{
|
|
||||||
File: "~/.ssh/id_rsa",
|
|
||||||
}},
|
|
||||||
want: "file ~/.ssh/id_rsa",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Multiple",
|
|
||||||
reqs: []codersdk.SecretRequirementStatus{
|
|
||||||
{
|
|
||||||
Env: "GITHUB_TOKEN",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
File: "~/.ssh/id_rsa",
|
|
||||||
HelpMessage: "Add an SSH key",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
want: "env GITHUB_TOKEN\nfile ~/.ssh/id_rsa: Add an SSH key",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "MalformedEmpty",
|
|
||||||
reqs: []codersdk.SecretRequirementStatus{{}},
|
|
||||||
want: "malformed secret requirement",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "MalformedBothEnvAndFile",
|
|
||||||
reqs: []codersdk.SecretRequirementStatus{{
|
|
||||||
Env: "GITHUB_TOKEN",
|
|
||||||
File: "~/.ssh/id_rsa",
|
|
||||||
}},
|
|
||||||
want: "malformed secret requirement",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
require.Equal(t, tt.want, formatMissingSecrets(tt.reqs))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/hashicorp/hcl/v2"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"go.uber.org/mock/gomock"
|
"go.uber.org/mock/gomock"
|
||||||
|
|
||||||
@@ -33,37 +32,23 @@ func TestResolveParameters(t *testing.T) {
|
|||||||
render.EXPECT().
|
render.EXPECT().
|
||||||
Render(gomock.Any(), gomock.Any(), gomock.Any()).
|
Render(gomock.Any(), gomock.Any(), gomock.Any()).
|
||||||
AnyTimes().
|
AnyTimes().
|
||||||
Return(renderResult(
|
Return(&preview.Output{
|
||||||
previewtypes.Parameter{
|
Parameters: []previewtypes.Parameter{
|
||||||
ParameterData: previewtypes.ParameterData{
|
{
|
||||||
Name: "immutable",
|
ParameterData: previewtypes.ParameterData{
|
||||||
Type: previewtypes.ParameterTypeString,
|
Name: "immutable",
|
||||||
FormType: provider.ParameterFormTypeInput,
|
Type: previewtypes.ParameterTypeString,
|
||||||
Mutable: false,
|
FormType: provider.ParameterFormTypeInput,
|
||||||
DefaultValue: previewtypes.StringLiteral("foo"),
|
Mutable: false,
|
||||||
Required: true,
|
DefaultValue: previewtypes.StringLiteral("foo"),
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
Value: previewtypes.StringLiteral("foo"),
|
||||||
|
Diagnostics: nil,
|
||||||
},
|
},
|
||||||
Value: previewtypes.StringLiteral("foo"),
|
|
||||||
Diagnostics: nil,
|
|
||||||
},
|
},
|
||||||
), nil)
|
}, nil)
|
||||||
render.EXPECT().
|
|
||||||
Render(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
|
|
||||||
AnyTimes().
|
|
||||||
Return(renderResult(
|
|
||||||
previewtypes.Parameter{
|
|
||||||
ParameterData: previewtypes.ParameterData{
|
|
||||||
Name: "immutable",
|
|
||||||
Type: previewtypes.ParameterTypeString,
|
|
||||||
FormType: provider.ParameterFormTypeInput,
|
|
||||||
Mutable: false,
|
|
||||||
DefaultValue: previewtypes.StringLiteral("foo"),
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
Value: previewtypes.StringLiteral("foo"),
|
|
||||||
Diagnostics: nil,
|
|
||||||
},
|
|
||||||
), nil)
|
|
||||||
ctx := testutil.Context(t, testutil.WaitShort)
|
ctx := testutil.Context(t, testutil.WaitShort)
|
||||||
values, err := dynamicparameters.ResolveParameters(ctx, uuid.New(), render, false,
|
values, err := dynamicparameters.ResolveParameters(ctx, uuid.New(), render, false,
|
||||||
[]database.WorkspaceBuildParameter{}, // No previous values
|
[]database.WorkspaceBuildParameter{}, // No previous values
|
||||||
@@ -96,25 +81,29 @@ func TestResolveParameters(t *testing.T) {
|
|||||||
render.EXPECT().
|
render.EXPECT().
|
||||||
Render(gomock.Any(), gomock.Any(), gomock.Any()).
|
Render(gomock.Any(), gomock.Any(), gomock.Any()).
|
||||||
// Return the mutable param first
|
// Return the mutable param first
|
||||||
Return(renderResult(
|
Return(&preview.Output{
|
||||||
previewtypes.Parameter{
|
Parameters: []previewtypes.Parameter{
|
||||||
ParameterData: mutable,
|
{
|
||||||
Value: previewtypes.StringLiteral("foo"),
|
ParameterData: mutable,
|
||||||
Diagnostics: nil,
|
Value: previewtypes.StringLiteral("foo"),
|
||||||
|
Diagnostics: nil,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
), nil)
|
}, nil)
|
||||||
|
|
||||||
render.EXPECT().
|
render.EXPECT().
|
||||||
Render(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
|
Render(gomock.Any(), gomock.Any(), gomock.Any()).
|
||||||
// Then the immutable param
|
// Then the immutable param
|
||||||
Return(renderResult(
|
Return(&preview.Output{
|
||||||
previewtypes.Parameter{
|
Parameters: []previewtypes.Parameter{
|
||||||
ParameterData: immutable,
|
{
|
||||||
// The user set the value to bar
|
ParameterData: immutable,
|
||||||
Value: previewtypes.StringLiteral("bar"),
|
// The user set the value to bar
|
||||||
Diagnostics: nil,
|
Value: previewtypes.StringLiteral("bar"),
|
||||||
|
Diagnostics: nil,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
), nil)
|
}, nil)
|
||||||
|
|
||||||
ctx := testutil.Context(t, testutil.WaitShort)
|
ctx := testutil.Context(t, testutil.WaitShort)
|
||||||
_, err := dynamicparameters.ResolveParameters(ctx, uuid.New(), render, false,
|
_, err := dynamicparameters.ResolveParameters(ctx, uuid.New(), render, false,
|
||||||
@@ -170,39 +159,23 @@ func TestResolveParameters(t *testing.T) {
|
|||||||
render.EXPECT().
|
render.EXPECT().
|
||||||
Render(gomock.Any(), gomock.Any(), gomock.Any()).
|
Render(gomock.Any(), gomock.Any(), gomock.Any()).
|
||||||
AnyTimes().
|
AnyTimes().
|
||||||
Return(renderResult(
|
Return(&preview.Output{
|
||||||
previewtypes.Parameter{
|
Parameters: []previewtypes.Parameter{
|
||||||
ParameterData: previewtypes.ParameterData{
|
{
|
||||||
Name: "param",
|
ParameterData: previewtypes.ParameterData{
|
||||||
Type: previewtypes.ParameterTypeNumber,
|
Name: "param",
|
||||||
FormType: provider.ParameterFormTypeInput,
|
Type: previewtypes.ParameterTypeNumber,
|
||||||
Mutable: true,
|
FormType: provider.ParameterFormTypeInput,
|
||||||
Validations: []*previewtypes.ParameterValidation{
|
Mutable: true,
|
||||||
{Monotonic: ptr.Ref(tc.monotonic)},
|
Validations: []*previewtypes.ParameterValidation{
|
||||||
|
{Monotonic: ptr.Ref(tc.monotonic)},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
Value: previewtypes.StringLiteral(tc.cur),
|
||||||
|
Diagnostics: nil,
|
||||||
},
|
},
|
||||||
Value: previewtypes.StringLiteral(tc.cur),
|
|
||||||
Diagnostics: nil,
|
|
||||||
},
|
},
|
||||||
), nil)
|
}, nil)
|
||||||
render.EXPECT().
|
|
||||||
Render(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
|
|
||||||
AnyTimes().
|
|
||||||
Return(renderResult(
|
|
||||||
previewtypes.Parameter{
|
|
||||||
ParameterData: previewtypes.ParameterData{
|
|
||||||
Name: "param",
|
|
||||||
Type: previewtypes.ParameterTypeNumber,
|
|
||||||
FormType: provider.ParameterFormTypeInput,
|
|
||||||
Mutable: true,
|
|
||||||
Validations: []*previewtypes.ParameterValidation{
|
|
||||||
{Monotonic: ptr.Ref(tc.monotonic)},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Value: previewtypes.StringLiteral(tc.cur),
|
|
||||||
Diagnostics: nil,
|
|
||||||
},
|
|
||||||
), nil)
|
|
||||||
|
|
||||||
var previousValues []database.WorkspaceBuildParameter
|
var previousValues []database.WorkspaceBuildParameter
|
||||||
if tc.prev != "" {
|
if tc.prev != "" {
|
||||||
@@ -232,172 +205,4 @@ func TestResolveParameters(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("BaselineRenderDoesNotRequestSecretRequirementsWhenDeactivatingRequirement", func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
render := rendermock.NewMockRenderer(ctrl)
|
|
||||||
ownerID := uuid.New()
|
|
||||||
|
|
||||||
gomock.InOrder(
|
|
||||||
render.EXPECT().
|
|
||||||
Render(gomock.Any(), ownerID, map[string]string{"use_github": "true"}).
|
|
||||||
Return(renderResult(stringParameter("use_github", "true")), nil),
|
|
||||||
render.EXPECT().
|
|
||||||
Render(gomock.Any(), ownerID, map[string]string{"use_github": "false"}, gomock.Any()).
|
|
||||||
Return(renderResult(stringParameter("use_github", "false")), nil),
|
|
||||||
)
|
|
||||||
|
|
||||||
ctx := testutil.Context(t, testutil.WaitShort)
|
|
||||||
values, err := dynamicparameters.ResolveParameters(ctx, ownerID, render, false,
|
|
||||||
[]database.WorkspaceBuildParameter{{Name: "use_github", Value: "true"}},
|
|
||||||
[]codersdk.WorkspaceBuildParameter{{Name: "use_github", Value: "false"}},
|
|
||||||
[]database.TemplateVersionPresetParameter{},
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, map[string]string{"use_github": "false"}, values)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("SkipSecretRequirementsAllowsFinalMissingSecrets", func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
render := rendermock.NewMockRenderer(ctrl)
|
|
||||||
ownerID := uuid.New()
|
|
||||||
|
|
||||||
gomock.InOrder(
|
|
||||||
render.EXPECT().
|
|
||||||
Render(gomock.Any(), ownerID, map[string]string{"use_github": "true"}).
|
|
||||||
Return(renderResult(stringParameter("use_github", "true")), nil),
|
|
||||||
render.EXPECT().
|
|
||||||
Render(gomock.Any(), ownerID, map[string]string{"use_github": "true"}).
|
|
||||||
Return(renderResultWithSecretRequirements(
|
|
||||||
[]codersdk.SecretRequirementStatus{{
|
|
||||||
Env: "GITHUB_TOKEN",
|
|
||||||
HelpMessage: "Add a GitHub PAT",
|
|
||||||
Satisfied: false,
|
|
||||||
}},
|
|
||||||
stringParameter("use_github", "true"),
|
|
||||||
), nil),
|
|
||||||
)
|
|
||||||
|
|
||||||
ctx := testutil.Context(t, testutil.WaitShort)
|
|
||||||
values, err := dynamicparameters.ResolveParameters(ctx, ownerID, render, false,
|
|
||||||
[]database.WorkspaceBuildParameter{{Name: "use_github", Value: "true"}},
|
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
dynamicparameters.SkipSecretRequirements(),
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, map[string]string{"use_github": "true"}, values)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("FinalMissingSecretsBlockByDefault", func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
render := rendermock.NewMockRenderer(ctrl)
|
|
||||||
ownerID := uuid.New()
|
|
||||||
|
|
||||||
gomock.InOrder(
|
|
||||||
render.EXPECT().
|
|
||||||
Render(gomock.Any(), ownerID, map[string]string{"use_github": "true"}).
|
|
||||||
Return(renderResult(stringParameter("use_github", "true")), nil),
|
|
||||||
render.EXPECT().
|
|
||||||
Render(gomock.Any(), ownerID, map[string]string{"use_github": "true"}, gomock.Any()).
|
|
||||||
Return(renderResultWithSecretRequirements(
|
|
||||||
[]codersdk.SecretRequirementStatus{{
|
|
||||||
Env: "GITHUB_TOKEN",
|
|
||||||
HelpMessage: "Add a GitHub PAT",
|
|
||||||
Satisfied: false,
|
|
||||||
}},
|
|
||||||
stringParameter("use_github", "true"),
|
|
||||||
), nil),
|
|
||||||
)
|
|
||||||
|
|
||||||
ctx := testutil.Context(t, testutil.WaitShort)
|
|
||||||
_, err := dynamicparameters.ResolveParameters(ctx, ownerID, render, false,
|
|
||||||
[]database.WorkspaceBuildParameter{{Name: "use_github", Value: "true"}},
|
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
require.Error(t, err)
|
|
||||||
resp, ok := httperror.IsResponder(err)
|
|
||||||
require.True(t, ok)
|
|
||||||
_, respErr := resp.Response()
|
|
||||||
require.Contains(t, respErr.Detail, "Missing required secrets")
|
|
||||||
require.Contains(t, respErr.Detail, "env GITHUB_TOKEN: Add a GitHub PAT")
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("FinalRenderErrorSuppressesMissingSecretSynthesis", func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
render := rendermock.NewMockRenderer(ctrl)
|
|
||||||
ownerID := uuid.New()
|
|
||||||
|
|
||||||
gomock.InOrder(
|
|
||||||
render.EXPECT().
|
|
||||||
Render(gomock.Any(), ownerID, map[string]string{"use_github": "true"}).
|
|
||||||
Return(renderResult(stringParameter("use_github", "true")), nil),
|
|
||||||
render.EXPECT().
|
|
||||||
Render(gomock.Any(), ownerID, map[string]string{"use_github": "true"}, gomock.Any()).
|
|
||||||
Return(renderResultWithSecretRequirements(
|
|
||||||
[]codersdk.SecretRequirementStatus{{
|
|
||||||
Env: "GITHUB_TOKEN",
|
|
||||||
HelpMessage: "Add a GitHub PAT",
|
|
||||||
Satisfied: false,
|
|
||||||
}},
|
|
||||||
stringParameter("use_github", "true"),
|
|
||||||
), hcl.Diagnostics{{
|
|
||||||
Severity: hcl.DiagError,
|
|
||||||
Summary: "Render failed",
|
|
||||||
Detail: "Template parameter expression failed.",
|
|
||||||
}}),
|
|
||||||
)
|
|
||||||
|
|
||||||
ctx := testutil.Context(t, testutil.WaitShort)
|
|
||||||
_, err := dynamicparameters.ResolveParameters(ctx, ownerID, render, false,
|
|
||||||
[]database.WorkspaceBuildParameter{{Name: "use_github", Value: "true"}},
|
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
require.Error(t, err)
|
|
||||||
resp, ok := httperror.IsResponder(err)
|
|
||||||
require.True(t, ok)
|
|
||||||
_, respErr := resp.Response()
|
|
||||||
require.Contains(t, respErr.Detail, "Render failed")
|
|
||||||
require.NotContains(t, respErr.Detail, "Missing required secrets")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func stringParameter(name string, value string) previewtypes.Parameter {
|
|
||||||
return previewtypes.Parameter{
|
|
||||||
ParameterData: previewtypes.ParameterData{
|
|
||||||
Name: name,
|
|
||||||
Type: previewtypes.ParameterTypeString,
|
|
||||||
FormType: provider.ParameterFormTypeInput,
|
|
||||||
Mutable: true,
|
|
||||||
DefaultValue: previewtypes.StringLiteral(value),
|
|
||||||
},
|
|
||||||
Value: previewtypes.StringLiteral(value),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func renderResult(params ...previewtypes.Parameter) *dynamicparameters.RenderResult {
|
|
||||||
return &dynamicparameters.RenderResult{
|
|
||||||
Output: &preview.Output{
|
|
||||||
Parameters: params,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func renderResultWithSecretRequirements(reqs []codersdk.SecretRequirementStatus, params ...previewtypes.Parameter) *dynamicparameters.RenderResult {
|
|
||||||
return &dynamicparameters.RenderResult{
|
|
||||||
Output: &preview.Output{
|
|
||||||
Parameters: params,
|
|
||||||
},
|
|
||||||
SecretRequirements: reqs,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,100 +0,0 @@
|
|||||||
package dynamicparameters
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"slices"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/hashicorp/hcl/v2"
|
|
||||||
|
|
||||||
"cdr.dev/slog/v3"
|
|
||||||
"github.com/coder/coder/v2/coderd/database"
|
|
||||||
"github.com/coder/coder/v2/coderd/files"
|
|
||||||
"github.com/coder/coder/v2/coderd/util/slice"
|
|
||||||
"github.com/coder/coder/v2/codersdk"
|
|
||||||
previewtypes "github.com/coder/preview/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// EvaluateSecretMismatch reports whether the given template version
|
|
||||||
// declares coder_secret requirements that the workspace owner's secrets
|
|
||||||
// do not satisfy. Returns false (no mismatch) when the renderer cannot
|
|
||||||
// authoritatively evaluate the requirements; the reason is logged at the
|
|
||||||
// appropriate level so operators can distinguish a forbidden caller
|
|
||||||
// (expected for template admins) from a genuine renderer or DB failure.
|
|
||||||
// Returns ErrTemplateVersionNotReady when the version's provisioner job
|
|
||||||
// has not yet completed; callers should treat that as "unknown" and
|
|
||||||
// leave SecretMismatch false.
|
|
||||||
func EvaluateSecretMismatch(
|
|
||||||
ctx context.Context,
|
|
||||||
logger slog.Logger,
|
|
||||||
db database.Store,
|
|
||||||
cache files.FileAcquirer,
|
|
||||||
version database.TemplateVersion,
|
|
||||||
ownerID uuid.UUID,
|
|
||||||
buildParams []database.WorkspaceBuildParameter,
|
|
||||||
) (bool, error) {
|
|
||||||
paramValues := slice.ToMapFunc(buildParams, func(p database.WorkspaceBuildParameter) (string, string) {
|
|
||||||
return p.Name, p.Value
|
|
||||||
})
|
|
||||||
renderer, err := Prepare(ctx, db, cache, version.ID,
|
|
||||||
WithTemplateVersion(version),
|
|
||||||
WithLogger(logger))
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
defer renderer.Close()
|
|
||||||
|
|
||||||
result, diags := renderer.Render(ctx, ownerID, paramValues, IncludeSecretRequirements())
|
|
||||||
|
|
||||||
// Three distinct "unknown" cases. Returning false from any of them
|
|
||||||
// matches the resolve-autostart handler's semantics, but they have
|
|
||||||
// very different operator implications, so we log accordingly. The
|
|
||||||
// renderer already logs its own diagnostics through the same logger,
|
|
||||||
// so we omit them here to avoid duplication.
|
|
||||||
if result.Output == nil {
|
|
||||||
logger.Warn(ctx,
|
|
||||||
"secret requirement evaluation produced no preview output; treating as unknown",
|
|
||||||
slog.F("template_version_id", version.ID),
|
|
||||||
)
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
switch secretValidationBlockerCode(diags) {
|
|
||||||
case DiagCodeOwnerSecretsFetchFailed:
|
|
||||||
logger.Warn(ctx,
|
|
||||||
"failed to fetch owner secrets during requirement evaluation; treating as unknown",
|
|
||||||
slog.F("template_version_id", version.ID),
|
|
||||||
)
|
|
||||||
return false, nil
|
|
||||||
case DiagCodeSecretValidationForbidden:
|
|
||||||
// Expected when a caller without user_secret:read on the owner
|
|
||||||
// hits the renderer, e.g. a template admin viewing another user's
|
|
||||||
// workspace. Debug-level keeps production volume sane while
|
|
||||||
// preserving visibility under trace logging.
|
|
||||||
logger.Debug(ctx,
|
|
||||||
"secret requirement evaluation forbidden for caller; treating as unknown",
|
|
||||||
slog.F("template_version_id", version.ID),
|
|
||||||
)
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return slices.ContainsFunc(result.SecretRequirements,
|
|
||||||
func(s codersdk.SecretRequirementStatus) bool { return !s.Satisfied }), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// secretValidationBlockerCode returns the first diagnostic code among the
|
|
||||||
// codes that indicate secret-requirement evaluation could not be
|
|
||||||
// performed. Returns the empty string if no such diagnostic is present.
|
|
||||||
//
|
|
||||||
// ExtractDiagnosticExtra walks the wrapped-extra chain so we still
|
|
||||||
// detect our marker when another extra has been chained on top by
|
|
||||||
// preview's SetDiagnosticExtra.
|
|
||||||
func secretValidationBlockerCode(diags hcl.Diagnostics) string {
|
|
||||||
for _, d := range diags {
|
|
||||||
extra := previewtypes.ExtractDiagnosticExtra(d)
|
|
||||||
switch extra.Code {
|
|
||||||
case DiagCodeOwnerSecretsFetchFailed, DiagCodeSecretValidationForbidden:
|
|
||||||
return extra.Code
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
package dynamicparameters
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/hashicorp/hcl/v2"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
previewtypes "github.com/coder/preview/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSecretValidationBlockerCode(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
cases := []struct {
|
|
||||||
name string
|
|
||||||
in hcl.Diagnostics
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Empty",
|
|
||||||
in: hcl.Diagnostics{},
|
|
||||||
want: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "MissingSecretIsNotBlocking",
|
|
||||||
in: hcl.Diagnostics{{
|
|
||||||
Severity: hcl.DiagError,
|
|
||||||
Summary: "Missing required secrets",
|
|
||||||
Extra: previewtypes.DiagnosticExtra{
|
|
||||||
Code: DiagCodeMissingSecret,
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
want: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Forbidden",
|
|
||||||
in: hcl.Diagnostics{{
|
|
||||||
Severity: hcl.DiagWarning,
|
|
||||||
Summary: "Cannot validate secret requirements",
|
|
||||||
Extra: previewtypes.DiagnosticExtra{
|
|
||||||
Code: DiagCodeSecretValidationForbidden,
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
want: DiagCodeSecretValidationForbidden,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "FetchFailed",
|
|
||||||
in: hcl.Diagnostics{{
|
|
||||||
Severity: hcl.DiagError,
|
|
||||||
Summary: "Failed to fetch owner secrets",
|
|
||||||
Extra: previewtypes.DiagnosticExtra{
|
|
||||||
Code: DiagCodeOwnerSecretsFetchFailed,
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
want: DiagCodeOwnerSecretsFetchFailed,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "DiagnosticWithNoExtraIsIgnored",
|
|
||||||
in: hcl.Diagnostics{{
|
|
||||||
Severity: hcl.DiagError,
|
|
||||||
Summary: "Some other error",
|
|
||||||
}},
|
|
||||||
want: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "MixedKeepsLookingUntilMatch",
|
|
||||||
in: hcl.Diagnostics{
|
|
||||||
{
|
|
||||||
Severity: hcl.DiagError,
|
|
||||||
Summary: "Missing required secrets",
|
|
||||||
Extra: previewtypes.DiagnosticExtra{
|
|
||||||
Code: DiagCodeMissingSecret,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Severity: hcl.DiagError,
|
|
||||||
Summary: "Failed to fetch owner secrets",
|
|
||||||
Extra: previewtypes.DiagnosticExtra{
|
|
||||||
Code: DiagCodeOwnerSecretsFetchFailed,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
want: DiagCodeOwnerSecretsFetchFailed,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// SetDiagnosticExtra wraps any pre-existing extra into
|
|
||||||
// previewtypes.DiagnosticExtra.Wrapped. ExtractDiagnosticExtra
|
|
||||||
// walks that chain. A naive type assertion would miss it.
|
|
||||||
name: "WrappedExtraIsDetected",
|
|
||||||
in: func() hcl.Diagnostics {
|
|
||||||
d := &hcl.Diagnostic{
|
|
||||||
Severity: hcl.DiagWarning,
|
|
||||||
Summary: "Cannot validate secret requirements",
|
|
||||||
Extra: "some other extra",
|
|
||||||
}
|
|
||||||
previewtypes.SetDiagnosticExtra(d, previewtypes.DiagnosticExtra{
|
|
||||||
Code: DiagCodeSecretValidationForbidden,
|
|
||||||
})
|
|
||||||
return hcl.Diagnostics{d}
|
|
||||||
}(),
|
|
||||||
want: DiagCodeSecretValidationForbidden,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range cases {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
require.Equal(t, tc.want, secretValidationBlockerCode(tc.in))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -39,7 +39,7 @@ func (r *loader) staticRender(ctx context.Context, db database.Store) (*staticRe
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *staticRender) Render(_ context.Context, _ uuid.UUID, values map[string]string, _ ...RenderOption) (*RenderResult, hcl.Diagnostics) {
|
func (r *staticRender) Render(_ context.Context, _ uuid.UUID, values map[string]string) (*preview.Output, hcl.Diagnostics) {
|
||||||
params := r.staticParams
|
params := r.staticParams
|
||||||
for i := range params {
|
for i := range params {
|
||||||
param := ¶ms[i]
|
param := ¶ms[i]
|
||||||
@@ -52,10 +52,8 @@ func (r *staticRender) Render(_ context.Context, _ uuid.UUID, values map[string]
|
|||||||
param.Diagnostics = previewtypes.Diagnostics(param.Valid(param.Value))
|
param.Diagnostics = previewtypes.Diagnostics(param.Valid(param.Value))
|
||||||
}
|
}
|
||||||
|
|
||||||
return &RenderResult{
|
return &preview.Output{
|
||||||
Output: &preview.Output{
|
Parameters: params,
|
||||||
Parameters: params,
|
|
||||||
},
|
|
||||||
}, hcl.Diagnostics{
|
}, hcl.Diagnostics{
|
||||||
{
|
{
|
||||||
// Only a warning because the form does still work.
|
// Only a warning because the form does still work.
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
terraform {
|
|
||||||
required_providers {
|
|
||||||
coder = {
|
|
||||||
source = "coder/coder"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data "coder_parameter" "use_github" {
|
|
||||||
name = "use_github"
|
|
||||||
type = "bool"
|
|
||||||
default = "false"
|
|
||||||
mutable = true
|
|
||||||
}
|
|
||||||
|
|
||||||
data "coder_secret" "gh" {
|
|
||||||
count = data.coder_parameter.use_github.value == "true" ? 1 : 0
|
|
||||||
env = "GITHUB_TOKEN"
|
|
||||||
help_message = "Add a GitHub PAT"
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
terraform {
|
|
||||||
required_providers {
|
|
||||||
coder = {
|
|
||||||
source = "coder/coder"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data "coder_secret" "env_req" {
|
|
||||||
env = "GITHUB_TOKEN"
|
|
||||||
help_message = "needs env"
|
|
||||||
}
|
|
||||||
|
|
||||||
data "coder_secret" "file_req" {
|
|
||||||
file = "~/.ssh/id_rsa"
|
|
||||||
help_message = "needs file"
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
terraform {
|
|
||||||
required_providers {
|
|
||||||
coder = {
|
|
||||||
source = "coder/coder"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data "coder_secret" "gh" {
|
|
||||||
env = "GITHUB_TOKEN"
|
|
||||||
help_message = "Add a GitHub PAT with env=GITHUB_TOKEN"
|
|
||||||
}
|
|
||||||
+35
-158
@@ -8,23 +8,16 @@ import (
|
|||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
"cdr.dev/slog/v3"
|
|
||||||
"github.com/coder/coder/v2/coderd/database/db2sdk"
|
"github.com/coder/coder/v2/coderd/database/db2sdk"
|
||||||
"github.com/coder/coder/v2/coderd/database/dbauthz"
|
|
||||||
"github.com/coder/coder/v2/coderd/dynamicparameters"
|
"github.com/coder/coder/v2/coderd/dynamicparameters"
|
||||||
"github.com/coder/coder/v2/coderd/httpapi"
|
"github.com/coder/coder/v2/coderd/httpapi"
|
||||||
"github.com/coder/coder/v2/coderd/httpmw"
|
"github.com/coder/coder/v2/coderd/httpmw"
|
||||||
"github.com/coder/coder/v2/coderd/rbac"
|
|
||||||
"github.com/coder/coder/v2/coderd/rbac/policy"
|
|
||||||
"github.com/coder/coder/v2/coderd/usersecretspubsub"
|
|
||||||
"github.com/coder/coder/v2/coderd/util/slice"
|
"github.com/coder/coder/v2/coderd/util/slice"
|
||||||
"github.com/coder/coder/v2/codersdk"
|
"github.com/coder/coder/v2/codersdk"
|
||||||
"github.com/coder/coder/v2/codersdk/wsjson"
|
"github.com/coder/coder/v2/codersdk/wsjson"
|
||||||
"github.com/coder/websocket"
|
"github.com/coder/websocket"
|
||||||
)
|
)
|
||||||
|
|
||||||
const initialDynamicParametersResponseID = -1
|
|
||||||
|
|
||||||
// @Summary Evaluate dynamic parameters for template version
|
// @Summary Evaluate dynamic parameters for template version
|
||||||
// @ID evaluate-dynamic-parameters-for-template-version
|
// @ID evaluate-dynamic-parameters-for-template-version
|
||||||
// @Security CoderSessionToken
|
// @Security CoderSessionToken
|
||||||
@@ -70,7 +63,7 @@ func (api *API) templateVersionDynamicParametersWebsocket(rw http.ResponseWriter
|
|||||||
}
|
}
|
||||||
|
|
||||||
api.templateVersionDynamicParameters(true, codersdk.DynamicParametersRequest{
|
api.templateVersionDynamicParameters(true, codersdk.DynamicParametersRequest{
|
||||||
ID: initialDynamicParametersResponseID,
|
ID: -1,
|
||||||
Inputs: map[string]string{},
|
Inputs: map[string]string{},
|
||||||
OwnerID: userID,
|
OwnerID: userID,
|
||||||
})(rw, r)
|
})(rw, r)
|
||||||
@@ -89,7 +82,6 @@ func (api *API) templateVersionDynamicParameters(listen bool, initial codersdk.D
|
|||||||
|
|
||||||
renderer, err := dynamicparameters.Prepare(ctx, api.Database, api.FileCache, templateVersion.ID,
|
renderer, err := dynamicparameters.Prepare(ctx, api.Database, api.FileCache, templateVersion.ID,
|
||||||
dynamicparameters.WithTemplateVersion(templateVersion),
|
dynamicparameters.WithTemplateVersion(templateVersion),
|
||||||
dynamicparameters.WithLogger(api.Logger.Named("dynamicparameters")),
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if httpapi.Is404Error(err) {
|
if httpapi.Is404Error(err) {
|
||||||
@@ -124,7 +116,15 @@ func (*API) handleParameterEvaluate(rw http.ResponseWriter, r *http.Request, ini
|
|||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
// Send an initial form state, computed without any user input.
|
// Send an initial form state, computed without any user input.
|
||||||
response := renderDynamicParametersResponse(ctx, render, 0, initial.OwnerID, initial.Inputs)
|
result, diagnostics := render.Render(ctx, initial.OwnerID, initial.Inputs)
|
||||||
|
response := codersdk.DynamicParametersResponse{
|
||||||
|
ID: 0,
|
||||||
|
Diagnostics: db2sdk.HCLDiagnostics(diagnostics),
|
||||||
|
}
|
||||||
|
if result != nil {
|
||||||
|
response.Parameters = slice.List(result.Parameters, db2sdk.PreviewParameter)
|
||||||
|
}
|
||||||
|
|
||||||
httpapi.Write(ctx, rw, http.StatusOK, response)
|
httpapi.Write(ctx, rw, http.StatusOK, response)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,43 +149,30 @@ func (api *API) handleParameterWebsocket(rw http.ResponseWriter, r *http.Request
|
|||||||
api.Logger,
|
api.Logger,
|
||||||
)
|
)
|
||||||
|
|
||||||
secretEvents := make(chan uuid.UUID, 1)
|
|
||||||
secretSubscriber := ¶meterSecretEventSubscriber{
|
|
||||||
api: api,
|
|
||||||
events: secretEvents,
|
|
||||||
}
|
|
||||||
secretSubscriber.UpdateOwnerSubscription(ctx, initial.OwnerID)
|
|
||||||
defer secretSubscriber.Close()
|
|
||||||
|
|
||||||
sender := dynamicParametersResponseSender{
|
|
||||||
stream: stream,
|
|
||||||
render: render,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send an initial form state, computed without any user input.
|
// Send an initial form state, computed without any user input.
|
||||||
if !sender.Send(ctx, initialDynamicParametersResponseID, initial.OwnerID, initial.Inputs) {
|
result, diagnostics := render.Render(ctx, initial.OwnerID, initial.Inputs)
|
||||||
|
response := codersdk.DynamicParametersResponse{
|
||||||
|
ID: -1, // Always start with -1.
|
||||||
|
Diagnostics: db2sdk.HCLDiagnostics(diagnostics),
|
||||||
|
}
|
||||||
|
if result != nil {
|
||||||
|
response.Parameters = slice.List(result.Parameters, db2sdk.PreviewParameter)
|
||||||
|
}
|
||||||
|
err = stream.Send(response)
|
||||||
|
if err != nil {
|
||||||
|
stream.Drop()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// As the user types into the form or updates secrets in another client,
|
// As the user types into the form, reprocess the state using their input,
|
||||||
// reprocess the state using their input and respond with updates.
|
// and respond with updates.
|
||||||
updates := stream.Chan()
|
updates := stream.Chan()
|
||||||
ownerID := initial.OwnerID
|
ownerID := initial.OwnerID
|
||||||
inputs := initial.Inputs
|
|
||||||
lastResponseID := initialDynamicParametersResponseID
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
stream.Close(websocket.StatusGoingAway)
|
stream.Close(websocket.StatusGoingAway)
|
||||||
return
|
return
|
||||||
case eventOwnerID := <-secretEvents:
|
|
||||||
if eventOwnerID != ownerID {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
lastResponseID = nextDynamicParametersResponseID(lastResponseID, lastResponseID+1)
|
|
||||||
if !sender.Send(ctx, lastResponseID, ownerID, inputs) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case update, ok := <-updates:
|
case update, ok := <-updates:
|
||||||
if !ok {
|
if !ok {
|
||||||
// The connection has been closed, so there is no one to write to
|
// The connection has been closed, so there is no one to write to
|
||||||
@@ -199,130 +186,20 @@ func (api *API) handleParameterWebsocket(rw http.ResponseWriter, r *http.Request
|
|||||||
}
|
}
|
||||||
|
|
||||||
ownerID = update.OwnerID
|
ownerID = update.OwnerID
|
||||||
inputs = update.Inputs
|
|
||||||
secretSubscriber.UpdateOwnerSubscription(ctx, ownerID)
|
result, diagnostics := render.Render(ctx, update.OwnerID, update.Inputs)
|
||||||
responseID := nextDynamicParametersResponseID(lastResponseID, update.ID)
|
response := codersdk.DynamicParametersResponse{
|
||||||
lastResponseID = responseID
|
ID: update.ID,
|
||||||
if !sender.Send(ctx, responseID, ownerID, inputs) {
|
Diagnostics: db2sdk.HCLDiagnostics(diagnostics),
|
||||||
|
}
|
||||||
|
if result != nil {
|
||||||
|
response.Parameters = slice.List(result.Parameters, db2sdk.PreviewParameter)
|
||||||
|
}
|
||||||
|
err = stream.Send(response)
|
||||||
|
if err != nil {
|
||||||
|
stream.Drop()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderDynamicParametersResponse(
|
|
||||||
ctx context.Context,
|
|
||||||
render dynamicparameters.Renderer,
|
|
||||||
id int,
|
|
||||||
ownerID uuid.UUID,
|
|
||||||
inputs map[string]string,
|
|
||||||
) codersdk.DynamicParametersResponse {
|
|
||||||
result, diagnostics := render.Render(ctx, ownerID, inputs, dynamicparameters.IncludeSecretRequirements())
|
|
||||||
response := codersdk.DynamicParametersResponse{
|
|
||||||
ID: id,
|
|
||||||
Diagnostics: db2sdk.HCLDiagnostics(diagnostics),
|
|
||||||
}
|
|
||||||
if result.Output != nil {
|
|
||||||
response.Parameters = slice.List(result.Output.Parameters, db2sdk.PreviewParameter)
|
|
||||||
}
|
|
||||||
response.SecretRequirements = result.SecretRequirements
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
|
|
||||||
type dynamicParametersResponseSender struct {
|
|
||||||
stream *wsjson.Stream[codersdk.DynamicParametersRequest, codersdk.DynamicParametersResponse]
|
|
||||||
render dynamicparameters.Renderer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s dynamicParametersResponseSender) Send(
|
|
||||||
ctx context.Context,
|
|
||||||
id int,
|
|
||||||
ownerID uuid.UUID,
|
|
||||||
inputs map[string]string,
|
|
||||||
) bool {
|
|
||||||
response := renderDynamicParametersResponse(ctx, s.render, id, ownerID, inputs)
|
|
||||||
if err := s.stream.Send(response); err != nil {
|
|
||||||
s.stream.Drop()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
type parameterSecretEventSubscriber struct {
|
|
||||||
api *API
|
|
||||||
events chan uuid.UUID
|
|
||||||
|
|
||||||
cancel func()
|
|
||||||
ownerID uuid.UUID
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateOwnerSubscription switches the pubsub subscription to the owner's
|
|
||||||
// user secret channel. Dynamic parameters can render for a workspace owner
|
|
||||||
// other than the connected user, so owner changes must update the channel
|
|
||||||
// that drives secret requirement refreshes.
|
|
||||||
func (s *parameterSecretEventSubscriber) UpdateOwnerSubscription(ctx context.Context, ownerID uuid.UUID) {
|
|
||||||
if ownerID == s.ownerID {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if s.cancel != nil {
|
|
||||||
s.Close()
|
|
||||||
}
|
|
||||||
// Websocket authorization uses the actor snapshot from connection
|
|
||||||
// creation, matching the rest of the websocket handlers.
|
|
||||||
if !s.api.canSubscribeUserSecretEvents(ctx, ownerID) {
|
|
||||||
s.ownerID = ownerID
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.ownerID = ownerID
|
|
||||||
subscribedOwnerID := ownerID
|
|
||||||
cancel, err := s.api.Pubsub.Subscribe(usersecretspubsub.Channel(ownerID), func(context.Context, []byte) {
|
|
||||||
s.notify(subscribedOwnerID)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
// Leave the owner unset so transient pubsub failures can be
|
|
||||||
// retried on the next update for this owner.
|
|
||||||
s.ownerID = uuid.Nil
|
|
||||||
s.api.Logger.Warn(ctx, "failed to subscribe to user secret events",
|
|
||||||
slog.F("user_id", ownerID),
|
|
||||||
slog.Error(err),
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.cancel = cancel
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *parameterSecretEventSubscriber) Close() {
|
|
||||||
if s.cancel == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.cancel()
|
|
||||||
s.cancel = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *parameterSecretEventSubscriber) notify(ownerID uuid.UUID) {
|
|
||||||
select {
|
|
||||||
case s.events <- ownerID:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func nextDynamicParametersResponseID(lastResponseID int, requestID int) int {
|
|
||||||
if requestID <= lastResponseID {
|
|
||||||
return lastResponseID + 1
|
|
||||||
}
|
|
||||||
return requestID
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *API) canSubscribeUserSecretEvents(ctx context.Context, ownerID uuid.UUID) bool {
|
|
||||||
roles, ok := dbauthz.ActorFromContext(ctx)
|
|
||||||
if !ok {
|
|
||||||
api.Logger.Error(ctx, "no authorization actor for user secret event subscription")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return api.HTTPAuth.Authorizer.Authorize(
|
|
||||||
ctx,
|
|
||||||
roles,
|
|
||||||
policy.ActionRead,
|
|
||||||
rbac.ResourceUserSecret.WithOwner(ownerID.String()),
|
|
||||||
) == nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,148 +0,0 @@
|
|||||||
package coderd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"golang.org/x/xerrors"
|
|
||||||
|
|
||||||
"cdr.dev/slog/v3"
|
|
||||||
"cdr.dev/slog/v3/sloggers/slogtest"
|
|
||||||
"github.com/coder/coder/v2/coderd/database/dbauthz"
|
|
||||||
"github.com/coder/coder/v2/coderd/rbac"
|
|
||||||
"github.com/coder/coder/v2/coderd/rbac/policy"
|
|
||||||
"github.com/coder/coder/v2/testutil"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNextDynamicParametersResponseID(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
lastResponseID int
|
|
||||||
requestID int
|
|
||||||
want int
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "request ID advances response ID",
|
|
||||||
lastResponseID: 1,
|
|
||||||
requestID: 4,
|
|
||||||
want: 4,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "request ID collision advances response ID",
|
|
||||||
lastResponseID: 4,
|
|
||||||
requestID: 4,
|
|
||||||
want: 5,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "stale request ID advances response ID",
|
|
||||||
lastResponseID: 4,
|
|
||||||
requestID: 2,
|
|
||||||
want: 5,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
got := nextDynamicParametersResponseID(tt.lastResponseID, tt.requestID)
|
|
||||||
require.Equal(t, tt.want, got)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCanSubscribeUserSecretEventsRequiresSecretRead(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
ownerID := uuid.New()
|
|
||||||
actor := rbac.Subject{ID: uuid.NewString()}
|
|
||||||
|
|
||||||
t.Run("allowed", func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
auth := &recordingAuthorizer{}
|
|
||||||
api := &API{
|
|
||||||
Options: &Options{
|
|
||||||
Logger: testutil.Logger(t),
|
|
||||||
},
|
|
||||||
HTTPAuth: &HTTPAuthorizer{
|
|
||||||
Authorizer: auth,
|
|
||||||
Logger: testutil.Logger(t),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
ctx := dbauthz.As(t.Context(), actor) //nolint:gocritic // Testing authorization from the request context.
|
|
||||||
|
|
||||||
require.True(t, api.canSubscribeUserSecretEvents(ctx, ownerID))
|
|
||||||
require.Len(t, auth.calls, 1)
|
|
||||||
require.Equal(t, actor, auth.calls[0].Actor)
|
|
||||||
require.Equal(t, policy.ActionRead, auth.calls[0].Action)
|
|
||||||
require.Equal(t, rbac.ResourceUserSecret.Type, auth.calls[0].Object.Type)
|
|
||||||
require.Equal(t, ownerID.String(), auth.calls[0].Object.Owner)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("denied", func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
auth := &recordingAuthorizer{err: xerrors.New("denied")}
|
|
||||||
api := &API{
|
|
||||||
Options: &Options{
|
|
||||||
Logger: testutil.Logger(t),
|
|
||||||
},
|
|
||||||
HTTPAuth: &HTTPAuthorizer{
|
|
||||||
Authorizer: auth,
|
|
||||||
Logger: testutil.Logger(t),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
ctx := dbauthz.As(t.Context(), actor) //nolint:gocritic // Testing authorization from the request context.
|
|
||||||
|
|
||||||
require.False(t, api.canSubscribeUserSecretEvents(ctx, ownerID))
|
|
||||||
require.Len(t, auth.calls, 1)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("no actor", func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
auth := &recordingAuthorizer{}
|
|
||||||
logger := slogtest.Make(t, &slogtest.Options{
|
|
||||||
IgnoredErrorIs: []error{},
|
|
||||||
IgnoreErrorFn: func(entry slog.SinkEntry) bool {
|
|
||||||
return entry.Message == "no authorization actor for user secret event subscription"
|
|
||||||
},
|
|
||||||
})
|
|
||||||
api := &API{
|
|
||||||
Options: &Options{
|
|
||||||
Logger: logger,
|
|
||||||
},
|
|
||||||
HTTPAuth: &HTTPAuthorizer{
|
|
||||||
Authorizer: auth,
|
|
||||||
Logger: logger,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
require.False(t, api.canSubscribeUserSecretEvents(context.Background(), ownerID))
|
|
||||||
require.Empty(t, auth.calls)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
type recordingAuthorizer struct {
|
|
||||||
err error
|
|
||||||
calls []rbac.AuthCall
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *recordingAuthorizer) Authorize(_ context.Context, subject rbac.Subject, action policy.Action, object rbac.Object) error {
|
|
||||||
a.calls = append(a.calls, rbac.AuthCall{
|
|
||||||
Actor: subject,
|
|
||||||
Action: action,
|
|
||||||
Object: object,
|
|
||||||
})
|
|
||||||
return a.err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*recordingAuthorizer) Prepare(context.Context, rbac.Subject, policy.Action, string) (rbac.PreparedAuthorized, error) {
|
|
||||||
//nolint:nilnil // Prepare is unused by these tests.
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
+8
-465
@@ -16,7 +16,6 @@ import (
|
|||||||
"github.com/coder/coder/v2/coderd/database"
|
"github.com/coder/coder/v2/coderd/database"
|
||||||
"github.com/coder/coder/v2/coderd/database/dbtestutil"
|
"github.com/coder/coder/v2/coderd/database/dbtestutil"
|
||||||
"github.com/coder/coder/v2/coderd/database/pubsub"
|
"github.com/coder/coder/v2/coderd/database/pubsub"
|
||||||
"github.com/coder/coder/v2/coderd/dynamicparameters"
|
|
||||||
"github.com/coder/coder/v2/coderd/rbac"
|
"github.com/coder/coder/v2/coderd/rbac"
|
||||||
"github.com/coder/coder/v2/codersdk"
|
"github.com/coder/coder/v2/codersdk"
|
||||||
"github.com/coder/coder/v2/codersdk/wsjson"
|
"github.com/coder/coder/v2/codersdk/wsjson"
|
||||||
@@ -387,433 +386,11 @@ func TestDynamicParametersWithTerraformValues(t *testing.T) {
|
|||||||
coderdtest.AssertParameter(t, "variable_values", preview.Parameters).
|
coderdtest.AssertParameter(t, "variable_values", preview.Parameters).
|
||||||
Exists().Value("austin")
|
Exists().Value("austin")
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("MissingSecret", func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
dynamicParametersTerraformSource, err := os.ReadFile("testdata/parameters/secret_required/main.tf")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
setup := setupDynamicParamsTest(t, setupDynamicParamsTestParams{
|
|
||||||
provisionerDaemonVersion: provProto.CurrentVersion.String(),
|
|
||||||
mainTF: dynamicParametersTerraformSource,
|
|
||||||
})
|
|
||||||
|
|
||||||
ctx := testutil.Context(t, testutil.WaitShort)
|
|
||||||
previews := setup.stream.Chan()
|
|
||||||
|
|
||||||
preview := testutil.RequireReceive(ctx, t, previews)
|
|
||||||
require.Equal(t, -1, preview.ID)
|
|
||||||
for _, diag := range preview.Diagnostics {
|
|
||||||
require.NotEqual(t, dynamicparameters.DiagCodeMissingSecret, diag.Extra.Code)
|
|
||||||
}
|
|
||||||
require.Equal(t, []codersdk.SecretRequirementStatus{{
|
|
||||||
Env: "GITHUB_TOKEN",
|
|
||||||
HelpMessage: "Add a GitHub PAT with env=GITHUB_TOKEN",
|
|
||||||
Satisfied: false,
|
|
||||||
}}, preview.SecretRequirements)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("SecretRequirementPushesOnSecretChange", func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
dynamicParametersTerraformSource, err := os.ReadFile("testdata/parameters/secret_required/main.tf")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
setup := setupDynamicParamsTest(t, setupDynamicParamsTestParams{
|
|
||||||
provisionerDaemonVersion: provProto.CurrentVersion.String(),
|
|
||||||
mainTF: dynamicParametersTerraformSource,
|
|
||||||
})
|
|
||||||
|
|
||||||
ctx := testutil.Context(t, testutil.WaitMedium)
|
|
||||||
previews := setup.stream.Chan()
|
|
||||||
|
|
||||||
preview := testutil.RequireReceive(ctx, t, previews)
|
|
||||||
require.Equal(t, -1, preview.ID)
|
|
||||||
require.Len(t, preview.SecretRequirements, 1)
|
|
||||||
require.False(t, preview.SecretRequirements[0].Satisfied)
|
|
||||||
|
|
||||||
_, err = setup.dynamicParamsClient.CreateUserSecret(ctx, codersdk.Me, codersdk.CreateUserSecretRequest{
|
|
||||||
Name: "github-token",
|
|
||||||
Value: "ghp_test",
|
|
||||||
EnvName: "GITHUB_TOKEN",
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
preview = testutil.RequireReceive(ctx, t, previews)
|
|
||||||
require.Equal(t, 0, preview.ID)
|
|
||||||
require.Len(t, preview.SecretRequirements, 1)
|
|
||||||
require.True(t, preview.SecretRequirements[0].Satisfied)
|
|
||||||
|
|
||||||
err = setup.dynamicParamsClient.DeleteUserSecret(ctx, codersdk.Me, "github-token")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
preview = testutil.RequireReceive(ctx, t, previews)
|
|
||||||
require.Equal(t, 1, preview.ID)
|
|
||||||
require.Len(t, preview.SecretRequirements, 1)
|
|
||||||
require.False(t, preview.SecretRequirements[0].Satisfied)
|
|
||||||
|
|
||||||
_, err = setup.dynamicParamsClient.CreateUserSecret(ctx, codersdk.Me, codersdk.CreateUserSecretRequest{
|
|
||||||
Name: "github-token",
|
|
||||||
Value: "ghp_test",
|
|
||||||
EnvName: "GITHUB_TOKEN",
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
preview = testutil.RequireReceive(ctx, t, previews)
|
|
||||||
require.Equal(t, 2, preview.ID)
|
|
||||||
require.Len(t, preview.SecretRequirements, 1)
|
|
||||||
require.True(t, preview.SecretRequirements[0].Satisfied)
|
|
||||||
|
|
||||||
otherEnvName := "OTHER_GITHUB_TOKEN"
|
|
||||||
_, err = setup.dynamicParamsClient.UpdateUserSecret(ctx, codersdk.Me, "github-token", codersdk.UpdateUserSecretRequest{
|
|
||||||
EnvName: &otherEnvName,
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
preview = testutil.RequireReceive(ctx, t, previews)
|
|
||||||
require.Equal(t, 3, preview.ID)
|
|
||||||
require.Len(t, preview.SecretRequirements, 1)
|
|
||||||
require.False(t, preview.SecretRequirements[0].Satisfied)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("SecretRequirementPushesAfterOwnerSwitch", func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
dynamicParametersTerraformSource, err := os.ReadFile("testdata/parameters/secret_required/main.tf")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
setup := setupDynamicParamsTest(t, setupDynamicParamsTestParams{
|
|
||||||
// No production role grants cross-user user_secret:read today,
|
|
||||||
// so use an allow-all authorizer for lifecycle coverage.
|
|
||||||
authorizer: &coderdtest.FakeAuthorizer{},
|
|
||||||
provisionerDaemonVersion: provProto.CurrentVersion.String(),
|
|
||||||
mainTF: dynamicParametersTerraformSource,
|
|
||||||
})
|
|
||||||
|
|
||||||
ctx := testutil.Context(t, testutil.WaitMedium)
|
|
||||||
previews := setup.stream.Chan()
|
|
||||||
targetClient, target := coderdtest.CreateAnotherUser(t, setup.client, setup.template.OrganizationID)
|
|
||||||
|
|
||||||
preview := testutil.RequireReceive(ctx, t, previews)
|
|
||||||
require.Equal(t, -1, preview.ID)
|
|
||||||
require.Len(t, preview.SecretRequirements, 1)
|
|
||||||
require.False(t, preview.SecretRequirements[0].Satisfied)
|
|
||||||
|
|
||||||
err = setup.stream.Send(codersdk.DynamicParametersRequest{
|
|
||||||
ID: 0,
|
|
||||||
Inputs: map[string]string{},
|
|
||||||
OwnerID: target.ID,
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
preview = testutil.RequireReceive(ctx, t, previews)
|
|
||||||
require.Equal(t, 0, preview.ID)
|
|
||||||
require.Len(t, preview.SecretRequirements, 1)
|
|
||||||
require.False(t, preview.SecretRequirements[0].Satisfied)
|
|
||||||
|
|
||||||
_, err = targetClient.CreateUserSecret(ctx, codersdk.Me, codersdk.CreateUserSecretRequest{
|
|
||||||
Name: "github-token",
|
|
||||||
Value: "ghp_target",
|
|
||||||
EnvName: "GITHUB_TOKEN",
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
preview = testutil.RequireReceive(ctx, t, previews)
|
|
||||||
require.Equal(t, 1, preview.ID)
|
|
||||||
require.Len(t, preview.SecretRequirements, 1)
|
|
||||||
require.True(t, preview.SecretRequirements[0].Satisfied)
|
|
||||||
|
|
||||||
_, err = setup.dynamicParamsClient.CreateUserSecret(ctx, codersdk.Me, codersdk.CreateUserSecretRequest{
|
|
||||||
Name: "github-token",
|
|
||||||
Value: "ghp_initial",
|
|
||||||
EnvName: "GITHUB_TOKEN",
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Never(t, func() bool {
|
|
||||||
select {
|
|
||||||
case <-previews:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}, testutil.WaitShort/5, testutil.IntervalFast)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("SecretRequirementDoesNotSubscribeWhenOwnerUnauthorized", func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
dynamicParametersTerraformSource, err := os.ReadFile("testdata/parameters/secret_required/main.tf")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
setup := setupDynamicParamsTest(t, setupDynamicParamsTestParams{
|
|
||||||
provisionerDaemonVersion: provProto.CurrentVersion.String(),
|
|
||||||
mainTF: dynamicParametersTerraformSource,
|
|
||||||
})
|
|
||||||
|
|
||||||
ctx := testutil.Context(t, testutil.WaitMedium)
|
|
||||||
previews := setup.stream.Chan()
|
|
||||||
targetClient, target := coderdtest.CreateAnotherUser(t, setup.client, setup.template.OrganizationID)
|
|
||||||
|
|
||||||
preview := testutil.RequireReceive(ctx, t, previews)
|
|
||||||
require.Equal(t, -1, preview.ID)
|
|
||||||
require.Len(t, preview.SecretRequirements, 1)
|
|
||||||
require.False(t, preview.SecretRequirements[0].Satisfied)
|
|
||||||
|
|
||||||
err = setup.stream.Send(codersdk.DynamicParametersRequest{
|
|
||||||
ID: 0,
|
|
||||||
Inputs: map[string]string{},
|
|
||||||
OwnerID: target.ID,
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
preview = testutil.RequireReceive(ctx, t, previews)
|
|
||||||
require.Equal(t, 0, preview.ID)
|
|
||||||
require.Empty(t, preview.SecretRequirements)
|
|
||||||
require.Len(t, preview.Diagnostics, 1)
|
|
||||||
require.Equal(t, dynamicparameters.DiagCodeSecretValidationForbidden, preview.Diagnostics[0].Extra.Code)
|
|
||||||
|
|
||||||
_, err = targetClient.CreateUserSecret(ctx, codersdk.Me, codersdk.CreateUserSecretRequest{
|
|
||||||
Name: "github-token",
|
|
||||||
Value: "ghp_target",
|
|
||||||
EnvName: "GITHUB_TOKEN",
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Never(t, func() bool {
|
|
||||||
select {
|
|
||||||
case <-previews:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}, testutil.WaitShort/5, testutil.IntervalFast)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Regression test for PLAT-100: a workspace whose template has an
|
|
||||||
// unsatisfied coder_secret requirement must still be stoppable and
|
|
||||||
// deletable. Start remains blocked.
|
|
||||||
t.Run("SecretRequirementDoesNotBlockStopOrDelete", func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
dynamicParametersTerraformSource, err := os.ReadFile("testdata/parameters/secret_required/main.tf")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
setup := setupDynamicParamsTest(t, setupDynamicParamsTestParams{
|
|
||||||
provisionerDaemonVersion: provProto.CurrentVersion.String(),
|
|
||||||
mainTF: dynamicParametersTerraformSource,
|
|
||||||
})
|
|
||||||
_ = setup.stream.Close(websocket.StatusGoingAway)
|
|
||||||
|
|
||||||
ctx := testutil.Context(t, testutil.WaitLong)
|
|
||||||
|
|
||||||
// Owner must satisfy the coder_secret requirement to create
|
|
||||||
// the workspace; delete it later to provoke the bug scenario.
|
|
||||||
_, err = setup.client.CreateUserSecret(ctx, codersdk.Me, codersdk.CreateUserSecretRequest{
|
|
||||||
Name: "github-token",
|
|
||||||
Value: "ghp_test",
|
|
||||||
EnvName: "GITHUB_TOKEN",
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
wrk := coderdtest.CreateWorkspace(t, setup.client, setup.template.ID)
|
|
||||||
coderdtest.AwaitWorkspaceBuildJobCompleted(t, setup.client, wrk.LatestBuild.ID)
|
|
||||||
|
|
||||||
require.NoError(t, setup.client.DeleteUserSecret(ctx, codersdk.Me, "github-token"))
|
|
||||||
|
|
||||||
// Start on the now-unsatisfied requirement must still fail;
|
|
||||||
// otherwise we've over-filtered the diagnostic.
|
|
||||||
_, err = setup.client.CreateWorkspaceBuild(ctx, wrk.ID, codersdk.CreateWorkspaceBuildRequest{
|
|
||||||
Transition: codersdk.WorkspaceTransitionStart,
|
|
||||||
})
|
|
||||||
require.Error(t, err, "start must still reject unsatisfied secret requirement")
|
|
||||||
var sdkErr *codersdk.Error
|
|
||||||
require.ErrorAs(t, err, &sdkErr)
|
|
||||||
require.Contains(t, sdkErr.Detail, "Missing required secrets")
|
|
||||||
require.Contains(t, sdkErr.Detail, "env GITHUB_TOKEN")
|
|
||||||
|
|
||||||
// Stop must succeed despite the unsatisfied requirement.
|
|
||||||
stop, err := setup.client.CreateWorkspaceBuild(ctx, wrk.ID, codersdk.CreateWorkspaceBuildRequest{
|
|
||||||
Transition: codersdk.WorkspaceTransitionStop,
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
coderdtest.AwaitWorkspaceBuildJobCompleted(t, setup.client, stop.ID)
|
|
||||||
|
|
||||||
// Delete must succeed despite the unsatisfied requirement.
|
|
||||||
del, err := setup.client.CreateWorkspaceBuild(ctx, wrk.ID, codersdk.CreateWorkspaceBuildRequest{
|
|
||||||
Transition: codersdk.WorkspaceTransitionDelete,
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
coderdtest.AwaitWorkspaceBuildJobCompleted(t, setup.client, del.ID)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestResolveAutostartPreservesParameterMismatchOnSecretEvalError exercises
|
|
||||||
// the handler's default switch arm: when EvaluateSecretMismatch returns a
|
|
||||||
// non-ErrTemplateVersionNotReady error, the handler must log and treat
|
|
||||||
// SecretMismatch as "unknown" without dropping the already-computed
|
|
||||||
// ParameterMismatch signal.
|
|
||||||
func TestResolveAutostartPreservesParameterMismatchOnSecretEvalError(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
db, ps := dbtestutil.NewDB(t)
|
|
||||||
|
|
||||||
// Wrap the DB so we can fail the renderer's GetTemplateVersionTerraformValues
|
|
||||||
// call. Toggle is flipped on after setup so initial template version
|
|
||||||
// processing succeeds.
|
|
||||||
reject := &dbRejectTemplateVersionTerraformValues{Store: db}
|
|
||||||
|
|
||||||
noRequirementsTF := []byte(`terraform {
|
|
||||||
required_providers {
|
|
||||||
coder = {
|
|
||||||
source = "coder/coder"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
setup := setupDynamicParamsTest(t, setupDynamicParamsTestParams{
|
|
||||||
db: reject,
|
|
||||||
ps: ps,
|
|
||||||
provisionerDaemonVersion: provProto.CurrentVersion.String(),
|
|
||||||
mainTF: noRequirementsTF,
|
|
||||||
})
|
|
||||||
_ = setup.stream.Close(websocket.StatusGoingAway)
|
|
||||||
|
|
||||||
wrk := coderdtest.CreateWorkspace(t, setup.client, setup.template.ID,
|
|
||||||
func(req *codersdk.CreateWorkspaceRequest) {
|
|
||||||
req.AutomaticUpdates = codersdk.AutomaticUpdatesAlways
|
|
||||||
})
|
|
||||||
coderdtest.AwaitWorkspaceBuildJobCompleted(t, setup.client, wrk.LatestBuild.ID)
|
|
||||||
|
|
||||||
// Push a v2 that adds a required-no-default parameter so resolve-autostart
|
|
||||||
// computes ParameterMismatch=true. The new version becomes active via
|
|
||||||
// DynamicParameterTemplate's UpdateActiveTemplateVersion call.
|
|
||||||
paramRequiredTF := []byte(`terraform {
|
|
||||||
required_providers {
|
|
||||||
coder = {
|
|
||||||
source = "coder/coder"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data "coder_parameter" "required_param" {
|
|
||||||
name = "required_param"
|
|
||||||
type = "string"
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
// StaticParams populates the legacy template_version_parameters table via
|
|
||||||
// the GraphComplete response. resolve-autostart reads from this table to
|
|
||||||
// determine ParameterMismatch.
|
|
||||||
_, _ = coderdtest.DynamicParameterTemplate(t, setup.dynamicParamsClient,
|
|
||||||
wrk.OrganizationID,
|
|
||||||
coderdtest.DynamicParameterTemplateParams{
|
|
||||||
MainTF: string(paramRequiredTF),
|
|
||||||
TemplateID: setup.template.ID,
|
|
||||||
StaticParams: []*proto.RichParameter{{
|
|
||||||
Name: "required_param",
|
|
||||||
Type: "string",
|
|
||||||
Required: true,
|
|
||||||
}},
|
|
||||||
})
|
|
||||||
|
|
||||||
// Arm the rejection only for the resolve-autostart call. Setup has
|
|
||||||
// already completed, so all earlier calls passed through.
|
|
||||||
reject.SetReject(true)
|
|
||||||
|
|
||||||
ctx := testutil.Context(t, testutil.WaitLong)
|
|
||||||
resp, err := setup.client.ResolveAutostart(ctx, wrk.ID.String())
|
|
||||||
require.NoError(t, err, "resolve-autostart must not 500 when secret evaluation fails")
|
|
||||||
require.True(t, resp.ParameterMismatch, "ParameterMismatch should be preserved across secret evaluation failure")
|
|
||||||
require.False(t, resp.SecretMismatch, "SecretMismatch should be unknown (false) when evaluation fails")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestResolveAutostartSecretRequirements is the PLAT-81 backend coverage:
|
|
||||||
// resolve-autostart must surface coder_secret requirements declared by the
|
|
||||||
// active template version that the workspace owner's secrets do not
|
|
||||||
// satisfy. The dashboard banner uses this to tell the user autostart
|
|
||||||
// cannot run the auto-update build until they create the missing secrets.
|
|
||||||
func TestResolveAutostartSecretRequirements(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
noRequirementsTF := []byte(`terraform {
|
|
||||||
required_providers {
|
|
||||||
coder = {
|
|
||||||
source = "coder/coder"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
secretRequiredTF, err := os.ReadFile("testdata/parameters/secret_required/main.tf")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// v1 has no secret requirements; we need a workspace to exist so
|
|
||||||
// resolve-autostart enters its version-mismatch branch.
|
|
||||||
setup := setupDynamicParamsTest(t, setupDynamicParamsTestParams{
|
|
||||||
provisionerDaemonVersion: provProto.CurrentVersion.String(),
|
|
||||||
mainTF: noRequirementsTF,
|
|
||||||
})
|
|
||||||
_ = setup.stream.Close(websocket.StatusGoingAway)
|
|
||||||
|
|
||||||
wrk := coderdtest.CreateWorkspace(t, setup.client, setup.template.ID,
|
|
||||||
func(req *codersdk.CreateWorkspaceRequest) {
|
|
||||||
req.AutomaticUpdates = codersdk.AutomaticUpdatesAlways
|
|
||||||
})
|
|
||||||
coderdtest.AwaitWorkspaceBuildJobCompleted(t, setup.client, wrk.LatestBuild.ID)
|
|
||||||
|
|
||||||
// Push v2 with a coder_secret requirement and make it active.
|
|
||||||
_, _ = coderdtest.DynamicParameterTemplate(t, setup.dynamicParamsClient,
|
|
||||||
wrk.OrganizationID,
|
|
||||||
coderdtest.DynamicParameterTemplateParams{
|
|
||||||
MainTF: string(secretRequiredTF),
|
|
||||||
TemplateID: setup.template.ID,
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("OwnerSeesMismatchThenSatisfies", func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
ctx := testutil.Context(t, testutil.WaitLong)
|
|
||||||
|
|
||||||
// Owner has no GITHUB_TOKEN secret; resolve-autostart must surface
|
|
||||||
// the unsatisfied requirement.
|
|
||||||
resp, err := setup.client.ResolveAutostart(ctx, wrk.ID.String())
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.False(t, resp.ParameterMismatch)
|
|
||||||
require.True(t, resp.SecretMismatch)
|
|
||||||
|
|
||||||
// Creating the matching secret must clear the entry without further
|
|
||||||
// template changes.
|
|
||||||
_, err = setup.client.CreateUserSecret(ctx, codersdk.Me, codersdk.CreateUserSecretRequest{
|
|
||||||
Name: "github-token",
|
|
||||||
Value: "ghp_test",
|
|
||||||
EnvName: "GITHUB_TOKEN",
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
resp, err = setup.client.ResolveAutostart(ctx, wrk.ID.String())
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.False(t, resp.ParameterMismatch)
|
|
||||||
require.False(t, resp.SecretMismatch)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("ForbiddenCallerSeesNoMismatch", func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
ctx := testutil.Context(t, testutil.WaitLong)
|
|
||||||
|
|
||||||
// The template-admin client can read the workspace but lacks
|
|
||||||
// user_secret:read on the workspace owner. The renderer's secret
|
|
||||||
// fetch produces a forbidden diagnostic, and the handler must
|
|
||||||
// treat that as "unknown" and report SecretMismatch=false rather
|
|
||||||
// than leaking a 500 or a stale true value.
|
|
||||||
resp, err := setup.dynamicParamsClient.ResolveAutostart(ctx, wrk.ID.String())
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.False(t, resp.SecretMismatch)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type setupDynamicParamsTestParams struct {
|
type setupDynamicParamsTestParams struct {
|
||||||
db database.Store
|
db database.Store
|
||||||
ps pubsub.Pubsub
|
ps pubsub.Pubsub
|
||||||
authorizer rbac.Authorizer
|
|
||||||
provisionerDaemonVersion string
|
provisionerDaemonVersion string
|
||||||
mainTF []byte
|
mainTF []byte
|
||||||
modulesArchive []byte
|
modulesArchive []byte
|
||||||
@@ -825,18 +402,16 @@ type setupDynamicParamsTestParams struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type dynamicParamsTest struct {
|
type dynamicParamsTest struct {
|
||||||
client *codersdk.Client
|
client *codersdk.Client
|
||||||
dynamicParamsClient *codersdk.Client
|
api *coderd.API
|
||||||
api *coderd.API
|
stream *wsjson.Stream[codersdk.DynamicParametersResponse, codersdk.DynamicParametersRequest]
|
||||||
stream *wsjson.Stream[codersdk.DynamicParametersResponse, codersdk.DynamicParametersRequest]
|
template codersdk.Template
|
||||||
template codersdk.Template
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupDynamicParamsTest(t *testing.T, args setupDynamicParamsTestParams) dynamicParamsTest {
|
func setupDynamicParamsTest(t *testing.T, args setupDynamicParamsTestParams) dynamicParamsTest {
|
||||||
ownerClient, _, api := coderdtest.NewWithAPI(t, &coderdtest.Options{
|
ownerClient, _, api := coderdtest.NewWithAPI(t, &coderdtest.Options{
|
||||||
Database: args.db,
|
Database: args.db,
|
||||||
Pubsub: args.ps,
|
Pubsub: args.ps,
|
||||||
Authorizer: args.authorizer,
|
|
||||||
IncludeProvisionerDaemon: true,
|
IncludeProvisionerDaemon: true,
|
||||||
ProvisionerDaemonVersion: args.provisionerDaemonVersion,
|
ProvisionerDaemonVersion: args.provisionerDaemonVersion,
|
||||||
})
|
})
|
||||||
@@ -871,11 +446,10 @@ func setupDynamicParamsTest(t *testing.T, args setupDynamicParamsTestParams) dyn
|
|||||||
})
|
})
|
||||||
|
|
||||||
return dynamicParamsTest{
|
return dynamicParamsTest{
|
||||||
client: ownerClient,
|
client: ownerClient,
|
||||||
dynamicParamsClient: templateAdmin,
|
api: api,
|
||||||
api: api,
|
stream: stream,
|
||||||
stream: stream,
|
template: tpl,
|
||||||
template: tpl,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -910,34 +484,3 @@ func (d *dbRejectGitSSHKey) GetGitSSHKey(ctx context.Context, userID uuid.UUID)
|
|||||||
|
|
||||||
return d.Store.GetGitSSHKey(ctx, userID)
|
return d.Store.GetGitSSHKey(ctx, userID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// dbRejectTemplateVersionTerraformValues wraps a Store so the dynamic
|
|
||||||
// parameter renderer's GetTemplateVersionTerraformValues call can be made
|
|
||||||
// to fail on demand. This forces the resolve-autostart handler into the
|
|
||||||
// default switch arm where EvaluateSecretMismatch returns a non-
|
|
||||||
// ErrTemplateVersionNotReady error.
|
|
||||||
type dbRejectTemplateVersionTerraformValues struct {
|
|
||||||
database.Store
|
|
||||||
rejectMu sync.RWMutex
|
|
||||||
reject bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetReject toggles whether GetTemplateVersionTerraformValues should
|
|
||||||
// return an error or passthrough to the underlying store.
|
|
||||||
func (d *dbRejectTemplateVersionTerraformValues) SetReject(reject bool) {
|
|
||||||
d.rejectMu.Lock()
|
|
||||||
defer d.rejectMu.Unlock()
|
|
||||||
d.reject = reject
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *dbRejectTemplateVersionTerraformValues) GetTemplateVersionTerraformValues(ctx context.Context, templateVersionID uuid.UUID) (database.TemplateVersionTerraformValue, error) {
|
|
||||||
d.rejectMu.RLock()
|
|
||||||
reject := d.reject
|
|
||||||
d.rejectMu.RUnlock()
|
|
||||||
|
|
||||||
if reject {
|
|
||||||
return database.TemplateVersionTerraformValue{}, xerrors.New("forcing a fake error")
|
|
||||||
}
|
|
||||||
|
|
||||||
return d.Store.GetTemplateVersionTerraformValues(ctx, templateVersionID)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -591,26 +591,6 @@ func (s *server) acquireProtoJob(ctx context.Context, job database.ProvisionerJo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch user secrets for build-time injection, but only on start
|
|
||||||
// transitions where the workspace actually needs them.
|
|
||||||
var userSecrets []*sdkproto.UserSecretValue
|
|
||||||
if workspaceBuild.Transition == database.WorkspaceTransitionStart {
|
|
||||||
dbSecrets, err := s.Database.ListUserSecretsWithValues(ctx, owner.ID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, failJob(fmt.Sprintf("get user secrets: %s", err))
|
|
||||||
}
|
|
||||||
for _, secret := range dbSecrets {
|
|
||||||
if secret.EnvName == "" && secret.FilePath == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
userSecrets = append(userSecrets, &sdkproto.UserSecretValue{
|
|
||||||
EnvName: secret.EnvName,
|
|
||||||
FilePath: secret.FilePath,
|
|
||||||
Value: []byte(secret.Value),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
transition, err := convertWorkspaceTransition(workspaceBuild.Transition)
|
transition, err := convertWorkspaceTransition(workspaceBuild.Transition)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, failJob(fmt.Sprintf("convert workspace transition: %s", err))
|
return nil, failJob(fmt.Sprintf("convert workspace transition: %s", err))
|
||||||
@@ -793,8 +773,7 @@ func (s *server) acquireProtoJob(ctx context.Context, job database.ProvisionerJo
|
|||||||
TaskPrompt: task.Prompt,
|
TaskPrompt: task.Prompt,
|
||||||
TemplateVersionModulesFile: versionModulesFile,
|
TemplateVersionModulesFile: versionModulesFile,
|
||||||
},
|
},
|
||||||
LogLevel: input.LogLevel,
|
LogLevel: input.LogLevel,
|
||||||
UserSecrets: userSecrets,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
case database.ProvisionerJobTypeTemplateVersionDryRun:
|
case database.ProvisionerJobTypeTemplateVersionDryRun:
|
||||||
|
|||||||
@@ -856,368 +856,6 @@ func TestAcquireJob(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.JSONEq(t, string(want), string(got))
|
require.JSONEq(t, string(want), string(got))
|
||||||
})
|
})
|
||||||
t.Run(tc.name+"_UserSecrets", func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
srv, db, ps, pd := setup(t, false, nil)
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
user := dbgen.User(t, db, database.User{})
|
|
||||||
dbgen.OrganizationMember(t, db, database.OrganizationMember{
|
|
||||||
UserID: user.ID,
|
|
||||||
OrganizationID: pd.OrganizationID,
|
|
||||||
})
|
|
||||||
dbgen.GitSSHKey(t, db, database.GitSSHKey{UserID: user.ID})
|
|
||||||
|
|
||||||
// Create secrets: 4 valid + 1 that should be filtered out.
|
|
||||||
insert1 := database.UserSecret{ID: uuid.New(), UserID: user.ID, Name: "github-token", EnvName: "GITHUB_TOKEN", Value: "ghp_xxxx"}
|
|
||||||
secret1 := dbgen.UserSecret(t, db, insert1, func(p *database.CreateUserSecretParams) { p.FilePath = "" })
|
|
||||||
|
|
||||||
insert2 := database.UserSecret{ID: uuid.New(), UserID: user.ID, Name: "ssh-key", FilePath: "~/.ssh/id_rsa", Value: "private-key"}
|
|
||||||
secret2 := dbgen.UserSecret(t, db, insert2, func(p *database.CreateUserSecretParams) { p.EnvName = "" })
|
|
||||||
|
|
||||||
insert3 := database.UserSecret{ID: uuid.New(), UserID: user.ID, Name: "both", EnvName: "BOTH", FilePath: "/etc/both", Value: "both-val"}
|
|
||||||
secret3 := dbgen.UserSecret(t, db, insert3)
|
|
||||||
|
|
||||||
insert4 := database.UserSecret{ID: uuid.New(), UserID: user.ID, Name: "empty-value", Value: "", EnvName: "EMPTY_VALUE", FilePath: "/etc/empty-value"}
|
|
||||||
secret4 := dbgen.UserSecret(t, db, insert4, func(p *database.CreateUserSecretParams) { p.Value = "" })
|
|
||||||
|
|
||||||
insert5 := database.UserSecret{ID: uuid.New(), UserID: user.ID, Name: "no-injection", Value: "no-injection"}
|
|
||||||
_ = dbgen.UserSecret(t, db, insert5, func(p *database.CreateUserSecretParams) { p.EnvName = ""; p.FilePath = "" })
|
|
||||||
|
|
||||||
template := dbgen.Template(t, db, database.Template{
|
|
||||||
Name: "template",
|
|
||||||
Provisioner: database.ProvisionerTypeEcho,
|
|
||||||
OrganizationID: pd.OrganizationID,
|
|
||||||
CreatedBy: user.ID,
|
|
||||||
})
|
|
||||||
file := dbgen.File(t, db, database.File{CreatedBy: user.ID})
|
|
||||||
version := dbgen.TemplateVersion(t, db, database.TemplateVersion{
|
|
||||||
CreatedBy: user.ID,
|
|
||||||
OrganizationID: pd.OrganizationID,
|
|
||||||
TemplateID: uuid.NullUUID{UUID: template.ID, Valid: true},
|
|
||||||
JobID: uuid.New(),
|
|
||||||
})
|
|
||||||
// Import version job
|
|
||||||
_ = dbgen.ProvisionerJob(t, db, ps, database.ProvisionerJob{
|
|
||||||
OrganizationID: pd.OrganizationID,
|
|
||||||
ID: version.JobID,
|
|
||||||
InitiatorID: user.ID,
|
|
||||||
FileID: file.ID,
|
|
||||||
Provisioner: database.ProvisionerTypeEcho,
|
|
||||||
StorageMethod: database.ProvisionerStorageMethodFile,
|
|
||||||
Type: database.ProvisionerJobTypeTemplateVersionImport,
|
|
||||||
Input: must(json.Marshal(provisionerdserver.TemplateVersionImportJob{
|
|
||||||
TemplateVersionID: version.ID,
|
|
||||||
})),
|
|
||||||
})
|
|
||||||
workspace := dbgen.Workspace(t, db, database.WorkspaceTable{
|
|
||||||
TemplateID: template.ID,
|
|
||||||
OwnerID: user.ID,
|
|
||||||
OrganizationID: pd.OrganizationID,
|
|
||||||
})
|
|
||||||
buildID := uuid.New()
|
|
||||||
dbJob := dbgen.ProvisionerJob(t, db, ps, database.ProvisionerJob{
|
|
||||||
OrganizationID: pd.OrganizationID,
|
|
||||||
InitiatorID: user.ID,
|
|
||||||
Provisioner: database.ProvisionerTypeEcho,
|
|
||||||
StorageMethod: database.ProvisionerStorageMethodFile,
|
|
||||||
FileID: file.ID,
|
|
||||||
Type: database.ProvisionerJobTypeWorkspaceBuild,
|
|
||||||
Input: must(json.Marshal(provisionerdserver.WorkspaceProvisionJob{
|
|
||||||
WorkspaceBuildID: buildID,
|
|
||||||
})),
|
|
||||||
Tags: pd.Tags,
|
|
||||||
})
|
|
||||||
_ = dbgen.WorkspaceBuild(t, db, database.WorkspaceBuild{
|
|
||||||
ID: buildID,
|
|
||||||
WorkspaceID: workspace.ID,
|
|
||||||
BuildNumber: 1,
|
|
||||||
JobID: dbJob.ID,
|
|
||||||
TemplateVersionID: version.ID,
|
|
||||||
Transition: database.WorkspaceTransitionStart,
|
|
||||||
Reason: database.BuildReasonInitiator,
|
|
||||||
})
|
|
||||||
|
|
||||||
startPublished := make(chan struct{})
|
|
||||||
var closed bool
|
|
||||||
closeStartSubscribe, err := ps.SubscribeWithErr(wspubsub.WorkspaceEventChannel(workspace.OwnerID),
|
|
||||||
wspubsub.HandleWorkspaceEvent(
|
|
||||||
func(_ context.Context, e wspubsub.WorkspaceEvent, err error) {
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if e.Kind == wspubsub.WorkspaceEventKindStateChange && e.WorkspaceID == workspace.ID {
|
|
||||||
if !closed {
|
|
||||||
close(startPublished)
|
|
||||||
closed = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
require.NoError(t, err)
|
|
||||||
defer closeStartSubscribe()
|
|
||||||
|
|
||||||
// Grab jobs until we find the workspace build job.
|
|
||||||
var job *proto.AcquiredJob
|
|
||||||
testutil.Eventually(ctx, t, func(ctx context.Context) bool {
|
|
||||||
job, err = tc.acquire(ctx, srv)
|
|
||||||
require.NoError(t, err)
|
|
||||||
_, ok := job.Type.(*proto.AcquiredJob_WorkspaceBuild_)
|
|
||||||
return ok
|
|
||||||
}, testutil.IntervalMedium)
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-startPublished:
|
|
||||||
case <-time.After(testutil.WaitShort):
|
|
||||||
t.Fatalf("timed out waiting for workspace build job to start")
|
|
||||||
}
|
|
||||||
|
|
||||||
wb := job.Type.(*proto.AcquiredJob_WorkspaceBuild_).WorkspaceBuild
|
|
||||||
require.Len(t, wb.UserSecrets, 4, "expected 4 secrets (the one with empty env_name and file_path should be filtered)")
|
|
||||||
|
|
||||||
// Re-sort by (env_name+file_path) before asserting field values.
|
|
||||||
// The terraform-provider-coder contract does not require a
|
|
||||||
// specific secret order, so this test intentionally does not
|
|
||||||
// assert the order produced by ListUserSecretsWithValues.
|
|
||||||
slices.SortFunc(wb.UserSecrets, func(a, b *sdkproto.UserSecretValue) int {
|
|
||||||
return strings.Compare(a.EnvName+a.FilePath, b.EnvName+b.FilePath)
|
|
||||||
})
|
|
||||||
|
|
||||||
// After sorting: []{secret3, secret4, secret1, secret2}
|
|
||||||
require.Equal(t, secret3.EnvName, wb.UserSecrets[0].EnvName)
|
|
||||||
require.Equal(t, secret3.FilePath, wb.UserSecrets[0].FilePath)
|
|
||||||
require.Equal(t, []byte(secret3.Value), wb.UserSecrets[0].Value)
|
|
||||||
|
|
||||||
require.Equal(t, secret4.EnvName, wb.UserSecrets[1].EnvName)
|
|
||||||
require.Equal(t, secret4.FilePath, wb.UserSecrets[1].FilePath)
|
|
||||||
require.Equal(t, []byte(secret4.Value), wb.UserSecrets[1].Value)
|
|
||||||
|
|
||||||
require.Equal(t, secret1.EnvName, wb.UserSecrets[2].EnvName)
|
|
||||||
require.Equal(t, secret1.FilePath, wb.UserSecrets[2].FilePath)
|
|
||||||
require.Equal(t, []byte(secret1.Value), wb.UserSecrets[2].Value)
|
|
||||||
|
|
||||||
require.Equal(t, secret2.EnvName, wb.UserSecrets[3].EnvName)
|
|
||||||
require.Equal(t, secret2.FilePath, wb.UserSecrets[3].FilePath)
|
|
||||||
require.Equal(t, []byte(secret2.Value), wb.UserSecrets[3].Value)
|
|
||||||
})
|
|
||||||
|
|
||||||
for _, transitionCase := range []struct {
|
|
||||||
name string
|
|
||||||
transition database.WorkspaceTransition
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Stop",
|
|
||||||
transition: database.WorkspaceTransitionStop,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Delete",
|
|
||||||
transition: database.WorkspaceTransitionDelete,
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
t.Run(tc.name+"_UserSecrets"+transitionCase.name+"Transition", func(t *testing.T) {
|
|
||||||
// Secrets must never be populated on non-start transitions. The
|
|
||||||
// terraform-provider-coder data source intentionally returns empty
|
|
||||||
// values on stop/delete so that workspaces with revoked or deleted
|
|
||||||
// secrets can still be torn down.
|
|
||||||
t.Parallel()
|
|
||||||
srv, db, ps, pd := setup(t, false, nil)
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
user := dbgen.User(t, db, database.User{})
|
|
||||||
dbgen.OrganizationMember(t, db, database.OrganizationMember{
|
|
||||||
UserID: user.ID,
|
|
||||||
OrganizationID: pd.OrganizationID,
|
|
||||||
})
|
|
||||||
dbgen.GitSSHKey(t, db, database.GitSSHKey{UserID: user.ID})
|
|
||||||
|
|
||||||
// Give the owner a secret so we can prove it is not forwarded on a
|
|
||||||
// transition.
|
|
||||||
authCtx := dbauthz.AsSystemRestricted(ctx)
|
|
||||||
_, err := db.CreateUserSecret(authCtx, database.CreateUserSecretParams{
|
|
||||||
ID: uuid.New(),
|
|
||||||
UserID: user.ID,
|
|
||||||
Name: "github-token",
|
|
||||||
EnvName: "GITHUB_TOKEN",
|
|
||||||
Value: "must-not-leak",
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
template := dbgen.Template(t, db, database.Template{
|
|
||||||
Name: "template",
|
|
||||||
Provisioner: database.ProvisionerTypeEcho,
|
|
||||||
OrganizationID: pd.OrganizationID,
|
|
||||||
CreatedBy: user.ID,
|
|
||||||
})
|
|
||||||
file := dbgen.File(t, db, database.File{CreatedBy: user.ID})
|
|
||||||
version := dbgen.TemplateVersion(t, db, database.TemplateVersion{
|
|
||||||
CreatedBy: user.ID,
|
|
||||||
OrganizationID: pd.OrganizationID,
|
|
||||||
TemplateID: uuid.NullUUID{UUID: template.ID, Valid: true},
|
|
||||||
JobID: uuid.New(),
|
|
||||||
})
|
|
||||||
_ = dbgen.ProvisionerJob(t, db, ps, database.ProvisionerJob{
|
|
||||||
OrganizationID: pd.OrganizationID,
|
|
||||||
ID: version.JobID,
|
|
||||||
InitiatorID: user.ID,
|
|
||||||
FileID: file.ID,
|
|
||||||
Provisioner: database.ProvisionerTypeEcho,
|
|
||||||
StorageMethod: database.ProvisionerStorageMethodFile,
|
|
||||||
Type: database.ProvisionerJobTypeTemplateVersionImport,
|
|
||||||
Input: must(json.Marshal(provisionerdserver.TemplateVersionImportJob{
|
|
||||||
TemplateVersionID: version.ID,
|
|
||||||
})),
|
|
||||||
})
|
|
||||||
workspace := dbgen.Workspace(t, db, database.WorkspaceTable{
|
|
||||||
TemplateID: template.ID,
|
|
||||||
OwnerID: user.ID,
|
|
||||||
OrganizationID: pd.OrganizationID,
|
|
||||||
})
|
|
||||||
buildID := uuid.New()
|
|
||||||
dbJob := dbgen.ProvisionerJob(t, db, ps, database.ProvisionerJob{
|
|
||||||
OrganizationID: pd.OrganizationID,
|
|
||||||
InitiatorID: user.ID,
|
|
||||||
Provisioner: database.ProvisionerTypeEcho,
|
|
||||||
StorageMethod: database.ProvisionerStorageMethodFile,
|
|
||||||
FileID: file.ID,
|
|
||||||
Type: database.ProvisionerJobTypeWorkspaceBuild,
|
|
||||||
Input: must(json.Marshal(provisionerdserver.WorkspaceProvisionJob{
|
|
||||||
WorkspaceBuildID: buildID,
|
|
||||||
})),
|
|
||||||
Tags: pd.Tags,
|
|
||||||
})
|
|
||||||
_ = dbgen.WorkspaceBuild(t, db, database.WorkspaceBuild{
|
|
||||||
ID: buildID,
|
|
||||||
WorkspaceID: workspace.ID,
|
|
||||||
BuildNumber: 1,
|
|
||||||
JobID: dbJob.ID,
|
|
||||||
TemplateVersionID: version.ID,
|
|
||||||
Transition: transitionCase.transition,
|
|
||||||
Reason: database.BuildReasonInitiator,
|
|
||||||
})
|
|
||||||
|
|
||||||
var job *proto.AcquiredJob
|
|
||||||
for {
|
|
||||||
job, err = tc.acquire(ctx, srv)
|
|
||||||
require.NoError(t, err)
|
|
||||||
if _, ok := job.Type.(*proto.AcquiredJob_WorkspaceBuild_); ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
wb := job.Type.(*proto.AcquiredJob_WorkspaceBuild_).WorkspaceBuild
|
|
||||||
require.Empty(t, wb.UserSecrets)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run(tc.name+"_UserSecretsDBError", func(t *testing.T) {
|
|
||||||
// A DB failure fetching user secrets must surface as a provisioner
|
|
||||||
// job failure rather than being silently treated as "no secrets".
|
|
||||||
// Silent treatment would let a transient DB error cause a
|
|
||||||
// workspace to build without the secrets it needs, producing a
|
|
||||||
// confusing downstream terraform error about missing secrets that
|
|
||||||
// the user actually owns.
|
|
||||||
t.Parallel()
|
|
||||||
srv, db, ps, pd := setup(t, true, &overrides{
|
|
||||||
wrapDB: func(inner database.Store) database.Store {
|
|
||||||
return &errOnListUserSecretsWithValues{Store: inner}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
user := dbgen.User(t, db, database.User{})
|
|
||||||
dbgen.OrganizationMember(t, db, database.OrganizationMember{
|
|
||||||
UserID: user.ID,
|
|
||||||
OrganizationID: pd.OrganizationID,
|
|
||||||
})
|
|
||||||
dbgen.GitSSHKey(t, db, database.GitSSHKey{UserID: user.ID})
|
|
||||||
|
|
||||||
template := dbgen.Template(t, db, database.Template{
|
|
||||||
Name: "template",
|
|
||||||
Provisioner: database.ProvisionerTypeEcho,
|
|
||||||
OrganizationID: pd.OrganizationID,
|
|
||||||
CreatedBy: user.ID,
|
|
||||||
})
|
|
||||||
file := dbgen.File(t, db, database.File{CreatedBy: user.ID})
|
|
||||||
version := dbgen.TemplateVersion(t, db, database.TemplateVersion{
|
|
||||||
CreatedBy: user.ID,
|
|
||||||
OrganizationID: pd.OrganizationID,
|
|
||||||
TemplateID: uuid.NullUUID{UUID: template.ID, Valid: true},
|
|
||||||
JobID: uuid.New(),
|
|
||||||
})
|
|
||||||
_ = dbgen.ProvisionerJob(t, db, ps, database.ProvisionerJob{
|
|
||||||
OrganizationID: pd.OrganizationID,
|
|
||||||
ID: version.JobID,
|
|
||||||
InitiatorID: user.ID,
|
|
||||||
FileID: file.ID,
|
|
||||||
Provisioner: database.ProvisionerTypeEcho,
|
|
||||||
StorageMethod: database.ProvisionerStorageMethodFile,
|
|
||||||
Type: database.ProvisionerJobTypeTemplateVersionImport,
|
|
||||||
Input: must(json.Marshal(provisionerdserver.TemplateVersionImportJob{
|
|
||||||
TemplateVersionID: version.ID,
|
|
||||||
})),
|
|
||||||
})
|
|
||||||
workspace := dbgen.Workspace(t, db, database.WorkspaceTable{
|
|
||||||
TemplateID: template.ID,
|
|
||||||
OwnerID: user.ID,
|
|
||||||
OrganizationID: pd.OrganizationID,
|
|
||||||
})
|
|
||||||
buildID := uuid.New()
|
|
||||||
dbJob := dbgen.ProvisionerJob(t, db, ps, database.ProvisionerJob{
|
|
||||||
OrganizationID: pd.OrganizationID,
|
|
||||||
InitiatorID: user.ID,
|
|
||||||
Provisioner: database.ProvisionerTypeEcho,
|
|
||||||
StorageMethod: database.ProvisionerStorageMethodFile,
|
|
||||||
FileID: file.ID,
|
|
||||||
Type: database.ProvisionerJobTypeWorkspaceBuild,
|
|
||||||
Input: must(json.Marshal(provisionerdserver.WorkspaceProvisionJob{
|
|
||||||
WorkspaceBuildID: buildID,
|
|
||||||
})),
|
|
||||||
Tags: pd.Tags,
|
|
||||||
})
|
|
||||||
_ = dbgen.WorkspaceBuild(t, db, database.WorkspaceBuild{
|
|
||||||
ID: buildID,
|
|
||||||
WorkspaceID: workspace.ID,
|
|
||||||
BuildNumber: 1,
|
|
||||||
JobID: dbJob.ID,
|
|
||||||
TemplateVersionID: version.ID,
|
|
||||||
// Only start transitions fetch secrets.
|
|
||||||
Transition: database.WorkspaceTransitionStart,
|
|
||||||
Reason: database.BuildReasonInitiator,
|
|
||||||
})
|
|
||||||
|
|
||||||
var acquireErr error
|
|
||||||
for {
|
|
||||||
// Keep acquiring until we either get our build back (possible
|
|
||||||
// for the Deprecated path to return an empty AcquiredJob once
|
|
||||||
// its long-poll window elapses on unrelated jobs) or propagate
|
|
||||||
// an error.
|
|
||||||
job, err := tc.acquire(ctx, srv)
|
|
||||||
if err != nil {
|
|
||||||
acquireErr = err
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if job != nil && job.JobId != "" {
|
|
||||||
t.Fatalf("expected acquire to error, got job %s", job.JobId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
require.ErrorContains(t, acquireErr, "request job was invalidated",
|
|
||||||
"DB error should surface as a job invalidation")
|
|
||||||
require.ErrorContains(t, acquireErr, "get user secrets",
|
|
||||||
"error should identify the failing operation")
|
|
||||||
require.ErrorContains(t, acquireErr, "ListUserSecretsWithValues query failed",
|
|
||||||
"underlying DB error message should be preserved")
|
|
||||||
|
|
||||||
// Confirm the provisioner job itself was marked as failed so the
|
|
||||||
// workspace build does not remain stuck in-progress.
|
|
||||||
authCtx := dbauthz.AsSystemRestricted(ctx)
|
|
||||||
gotJob, err := db.GetProvisionerJobByID(authCtx, dbJob.ID)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.True(t, gotJob.Error.Valid, "job should be marked with an error")
|
|
||||||
require.Contains(t, gotJob.Error.String, "get user secrets")
|
|
||||||
require.True(t, gotJob.CompletedAt.Valid, "job should be marked complete")
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5157,9 +4795,6 @@ type overrides struct {
|
|||||||
auditor audit.Auditor
|
auditor audit.Auditor
|
||||||
notificationEnqueuer notifications.Enqueuer
|
notificationEnqueuer notifications.Enqueuer
|
||||||
prebuildsOrchestrator agplprebuilds.ReconciliationOrchestrator
|
prebuildsOrchestrator agplprebuilds.ReconciliationOrchestrator
|
||||||
// wrapDB wraps the raw DB before dbauthz.New. Use this to inject
|
|
||||||
// errors or observe calls on specific queries for a single test.
|
|
||||||
wrapDB func(database.Store) database.Store
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func setup(t *testing.T, ignoreLogErrors bool, ov *overrides) (proto.DRPCProvisionerDaemonServer, database.Store, pubsub.Pubsub, database.ProvisionerDaemon) {
|
func setup(t *testing.T, ignoreLogErrors bool, ov *overrides) (proto.DRPCProvisionerDaemonServer, database.Store, pubsub.Pubsub, database.ProvisionerDaemon) {
|
||||||
@@ -5260,9 +4895,6 @@ func setup(t *testing.T, ignoreLogErrors bool, ov *overrides) (proto.DRPCProvisi
|
|||||||
// Use an authz wrapped database for the server to ensure permission checks
|
// Use an authz wrapped database for the server to ensure permission checks
|
||||||
// work.
|
// work.
|
||||||
authorizer := rbac.NewStrictCachingAuthorizer(prometheus.NewRegistry())
|
authorizer := rbac.NewStrictCachingAuthorizer(prometheus.NewRegistry())
|
||||||
if ov.wrapDB != nil {
|
|
||||||
db = ov.wrapDB(db)
|
|
||||||
}
|
|
||||||
serverDB := dbauthz.New(db, authorizer, logger, coderdtest.AccessControlStorePointer())
|
serverDB := dbauthz.New(db, authorizer, logger, coderdtest.AccessControlStorePointer())
|
||||||
srv, err := provisionerdserver.NewServer(
|
srv, err := provisionerdserver.NewServer(
|
||||||
ov.ctx,
|
ov.ctx,
|
||||||
@@ -5407,32 +5039,3 @@ func newFakeUsageInserter() (*coderdtest.UsageInserter, *atomic.Pointer[usage.In
|
|||||||
poitr.Store(&inserter)
|
poitr.Store(&inserter)
|
||||||
return fake, poitr
|
return fake, poitr
|
||||||
}
|
}
|
||||||
|
|
||||||
// errListUserSecretsWithValues is the sentinel returned by the test wrapper
|
|
||||||
// below. Its message is matched by assertions that verify the underlying DB
|
|
||||||
// error propagated through failJob's formatting. The chain is not preserved
|
|
||||||
// via errors.Is because failJob uses fmt.Sprintf, not %w.
|
|
||||||
var errListUserSecretsWithValues = xerrors.New("ListUserSecretsWithValues query failed")
|
|
||||||
|
|
||||||
// errOnListUserSecretsWithValues is a database.Store wrapper that errors only
|
|
||||||
// on ListUserSecretsWithValues. All other methods pass through to the
|
|
||||||
// underlying store. Used to simulate a transient DB failure on the secret
|
|
||||||
// fetch without breaking the rest of the acquire flow (user lookup, job
|
|
||||||
// update, etc.).
|
|
||||||
type errOnListUserSecretsWithValues struct {
|
|
||||||
database.Store
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*errOnListUserSecretsWithValues) ListUserSecretsWithValues(context.Context, uuid.UUID) ([]database.UserSecret, error) {
|
|
||||||
return nil, errListUserSecretsWithValues
|
|
||||||
}
|
|
||||||
|
|
||||||
// InTx must be overridden to keep the wrapped store visible inside a
|
|
||||||
// transaction. Without this override, InTx would pass the raw inner store to
|
|
||||||
// its closure and tests would see the unwrapped behavior from anywhere that
|
|
||||||
// runs inside a transaction.
|
|
||||||
func (e *errOnListUserSecretsWithValues) InTx(fn func(database.Store) error, opts *database.TxOptions) error {
|
|
||||||
return e.Store.InTx(func(tx database.Store) error {
|
|
||||||
return fn(&errOnListUserSecretsWithValues{Store: tx})
|
|
||||||
}, opts)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
terraform {
|
|
||||||
required_providers {
|
|
||||||
coder = {
|
|
||||||
source = "coder/coder"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data "coder_secret" "gh" {
|
|
||||||
env = "GITHUB_TOKEN"
|
|
||||||
help_message = "Add a GitHub PAT with env=GITHUB_TOKEN"
|
|
||||||
}
|
|
||||||
@@ -10,13 +10,11 @@ import (
|
|||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
"cdr.dev/slog/v3"
|
|
||||||
"github.com/coder/coder/v2/coderd/audit"
|
"github.com/coder/coder/v2/coderd/audit"
|
||||||
"github.com/coder/coder/v2/coderd/database"
|
"github.com/coder/coder/v2/coderd/database"
|
||||||
"github.com/coder/coder/v2/coderd/database/db2sdk"
|
"github.com/coder/coder/v2/coderd/database/db2sdk"
|
||||||
"github.com/coder/coder/v2/coderd/httpapi"
|
"github.com/coder/coder/v2/coderd/httpapi"
|
||||||
"github.com/coder/coder/v2/coderd/httpmw"
|
"github.com/coder/coder/v2/coderd/httpmw"
|
||||||
"github.com/coder/coder/v2/coderd/usersecretspubsub"
|
|
||||||
"github.com/coder/coder/v2/codersdk"
|
"github.com/coder/coder/v2/codersdk"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -84,14 +82,6 @@ func (api *API) postUserSecret(rw http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
aReq.New = secret
|
aReq.New = secret
|
||||||
|
|
||||||
api.publishUserSecretEvent(ctx, usersecretspubsub.Event{
|
|
||||||
Kind: usersecretspubsub.EventKindCreated,
|
|
||||||
UserID: secret.UserID,
|
|
||||||
Name: secret.Name,
|
|
||||||
EnvName: secret.EnvName,
|
|
||||||
FilePath: secret.FilePath,
|
|
||||||
})
|
|
||||||
|
|
||||||
httpapi.Write(ctx, rw, http.StatusCreated, db2sdk.UserSecretFromFull(secret))
|
httpapi.Write(ctx, rw, http.StatusCreated, db2sdk.UserSecretFromFull(secret))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,14 +253,6 @@ func (api *API) patchUserSecret(rw http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
api.publishUserSecretEvent(ctx, usersecretspubsub.Event{
|
|
||||||
Kind: usersecretspubsub.EventKindUpdated,
|
|
||||||
UserID: secret.UserID,
|
|
||||||
Name: secret.Name,
|
|
||||||
EnvName: secret.EnvName,
|
|
||||||
FilePath: secret.FilePath,
|
|
||||||
})
|
|
||||||
|
|
||||||
httpapi.Write(ctx, rw, http.StatusOK, db2sdk.UserSecretFromFull(secret))
|
httpapi.Write(ctx, rw, http.StatusOK, db2sdk.UserSecretFromFull(secret))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,12 +296,6 @@ func (api *API) deleteUserSecret(rw http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
aReq.Old = deleted
|
aReq.Old = deleted
|
||||||
|
|
||||||
api.publishUserSecretEvent(ctx, usersecretspubsub.Event{
|
|
||||||
Kind: usersecretspubsub.EventKindDeleted,
|
|
||||||
UserID: user.ID,
|
|
||||||
Name: name,
|
|
||||||
})
|
|
||||||
|
|
||||||
rw.WriteHeader(http.StatusNoContent)
|
rw.WriteHeader(http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -391,14 +367,3 @@ func userSecretConflictValidationErrors(err error) []codersdk.ValidationError {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) publishUserSecretEvent(ctx context.Context, event usersecretspubsub.Event) {
|
|
||||||
if err := usersecretspubsub.Publish(api.Pubsub, event); err != nil {
|
|
||||||
api.Logger.Warn(ctx, "failed to publish user secret event",
|
|
||||||
slog.F("user_id", event.UserID),
|
|
||||||
slog.F("secret_name", event.Name),
|
|
||||||
slog.F("event_kind", event.Kind),
|
|
||||||
slog.Error(err),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
package usersecretspubsub
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"golang.org/x/xerrors"
|
|
||||||
|
|
||||||
"github.com/coder/coder/v2/coderd/database/pubsub"
|
|
||||||
)
|
|
||||||
|
|
||||||
type EventKind string
|
|
||||||
|
|
||||||
const (
|
|
||||||
EventKindCreated EventKind = "created"
|
|
||||||
EventKindUpdated EventKind = "updated"
|
|
||||||
EventKindDeleted EventKind = "deleted"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Event struct {
|
|
||||||
Kind EventKind `json:"kind"`
|
|
||||||
UserID uuid.UUID `json:"user_id" format:"uuid"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
EnvName string `json:"env_name,omitempty"`
|
|
||||||
FilePath string `json:"file_path,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func Channel(userID uuid.UUID) string {
|
|
||||||
return fmt.Sprintf("user_secrets:%s", userID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Publish(ps pubsub.Publisher, event Event) error {
|
|
||||||
msg, err := json.Marshal(event)
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Errorf("marshal user secret event: %w", err)
|
|
||||||
}
|
|
||||||
if err := ps.Publish(Channel(event.UserID), msg); err != nil {
|
|
||||||
return xerrors.Errorf("publish user secret event: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -383,7 +383,6 @@ func (api *API) postWorkspaceBuildsInternal(
|
|||||||
DeploymentValues(api.Options.DeploymentValues).
|
DeploymentValues(api.Options.DeploymentValues).
|
||||||
Experiments(api.Experiments).
|
Experiments(api.Experiments).
|
||||||
TemplateVersionPresetID(createBuild.TemplateVersionPresetID).
|
TemplateVersionPresetID(createBuild.TemplateVersionPresetID).
|
||||||
Logger(api.Logger.Named("wsbuilder")).
|
|
||||||
BuildMetrics(api.WorkspaceBuilderMetrics)
|
BuildMetrics(api.WorkspaceBuilderMetrics)
|
||||||
|
|
||||||
if (transition == database.WorkspaceTransitionStart || transition == database.WorkspaceTransitionStop) && createBuild.Reason != "" {
|
if (transition == database.WorkspaceTransitionStart || transition == database.WorkspaceTransitionStop) && createBuild.Reason != "" {
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import (
|
|||||||
"github.com/coder/coder/v2/coderd/database/dbauthz"
|
"github.com/coder/coder/v2/coderd/database/dbauthz"
|
||||||
"github.com/coder/coder/v2/coderd/database/dbtime"
|
"github.com/coder/coder/v2/coderd/database/dbtime"
|
||||||
"github.com/coder/coder/v2/coderd/database/provisionerjobs"
|
"github.com/coder/coder/v2/coderd/database/provisionerjobs"
|
||||||
"github.com/coder/coder/v2/coderd/dynamicparameters"
|
|
||||||
"github.com/coder/coder/v2/coderd/httpapi"
|
"github.com/coder/coder/v2/coderd/httpapi"
|
||||||
"github.com/coder/coder/v2/coderd/httpapi/httperror"
|
"github.com/coder/coder/v2/coderd/httpapi/httperror"
|
||||||
"github.com/coder/coder/v2/coderd/httpmw"
|
"github.com/coder/coder/v2/coderd/httpmw"
|
||||||
@@ -795,7 +794,6 @@ func createWorkspace(
|
|||||||
Experiments(api.Experiments).
|
Experiments(api.Experiments).
|
||||||
DeploymentValues(api.DeploymentValues).
|
DeploymentValues(api.DeploymentValues).
|
||||||
RichParameterValues(req.RichParameterValues).
|
RichParameterValues(req.RichParameterValues).
|
||||||
Logger(api.Logger.Named("wsbuilder")).
|
|
||||||
BuildMetrics(api.WorkspaceBuilderMetrics)
|
BuildMetrics(api.WorkspaceBuilderMetrics)
|
||||||
if req.TemplateVersionID != uuid.Nil {
|
if req.TemplateVersionID != uuid.Nil {
|
||||||
builder = builder.VersionID(req.TemplateVersionID)
|
builder = builder.VersionID(req.TemplateVersionID)
|
||||||
@@ -2010,36 +2008,6 @@ func (api *API) resolveAutostart(rw http.ResponseWriter, r *http.Request) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Surface whether the active template version declares coder_secret
|
|
||||||
// requirements that the workspace owner's secrets do not satisfy. The
|
|
||||||
// intention is for this information to inform the workspace update
|
|
||||||
// requirement so the user knows autostart will not run an auto-update
|
|
||||||
// build until the missing secrets are satisfied.
|
|
||||||
//
|
|
||||||
// Callers without user_secret:read on the workspace owner produce a
|
|
||||||
// forbidden warning diagnostic. This is treated as "unknown" and
|
|
||||||
// no mismatch is reported rather than returning a partial answer.
|
|
||||||
secretMismatch, err := dynamicparameters.EvaluateSecretMismatch(
|
|
||||||
ctx,
|
|
||||||
api.Logger.Named("dynamicparameters"),
|
|
||||||
api.Database, api.FileCache, version, workspace.OwnerID, dbBuildParams,
|
|
||||||
)
|
|
||||||
switch {
|
|
||||||
case err == nil:
|
|
||||||
response.SecretMismatch = secretMismatch
|
|
||||||
case xerrors.Is(err, dynamicparameters.ErrTemplateVersionNotReady):
|
|
||||||
// Active version's provisioner job hasn't completed yet. Leave
|
|
||||||
// SecretMismatch false.
|
|
||||||
default:
|
|
||||||
// Don't drop the already-computed ParameterMismatch signal on a
|
|
||||||
// renderer infrastructure error. Log and treat as "unknown."
|
|
||||||
api.Logger.Warn(ctx, "failed to evaluate secret requirements",
|
|
||||||
slog.F("workspace_id", workspace.ID),
|
|
||||||
slog.Error(err),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
httpapi.Write(ctx, rw, http.StatusOK, response)
|
httpapi.Write(ctx, rw, http.StatusOK, response)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import (
|
|||||||
"github.com/sqlc-dev/pqtype"
|
"github.com/sqlc-dev/pqtype"
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
"cdr.dev/slog/v3"
|
|
||||||
"github.com/coder/coder/v2/coderd/audit"
|
"github.com/coder/coder/v2/coderd/audit"
|
||||||
"github.com/coder/coder/v2/coderd/database"
|
"github.com/coder/coder/v2/coderd/database"
|
||||||
"github.com/coder/coder/v2/coderd/database/db2sdk"
|
"github.com/coder/coder/v2/coderd/database/db2sdk"
|
||||||
@@ -60,7 +59,6 @@ type Builder struct {
|
|||||||
deploymentValues *codersdk.DeploymentValues
|
deploymentValues *codersdk.DeploymentValues
|
||||||
experiments codersdk.Experiments
|
experiments codersdk.Experiments
|
||||||
usageChecker UsageChecker
|
usageChecker UsageChecker
|
||||||
logger slog.Logger
|
|
||||||
|
|
||||||
richParameterValues []codersdk.WorkspaceBuildParameter
|
richParameterValues []codersdk.WorkspaceBuildParameter
|
||||||
initiator uuid.UUID
|
initiator uuid.UUID
|
||||||
@@ -197,12 +195,6 @@ func (b Builder) Experiments(exp codersdk.Experiments) Builder {
|
|||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b Builder) Logger(log slog.Logger) Builder {
|
|
||||||
// nolint: revive
|
|
||||||
b.logger = log
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b Builder) Initiator(u uuid.UUID) Builder {
|
func (b Builder) Initiator(u uuid.UUID) Builder {
|
||||||
// nolint: revive
|
// nolint: revive
|
||||||
b.initiator = u
|
b.initiator = u
|
||||||
@@ -776,7 +768,6 @@ func (b *Builder) getDynamicParameterRenderer() (dynamicparameters.Renderer, err
|
|||||||
dynamicparameters.WithProvisionerJob(*job),
|
dynamicparameters.WithProvisionerJob(*job),
|
||||||
dynamicparameters.WithTerraformValues(*tfVals),
|
dynamicparameters.WithTerraformValues(*tfVals),
|
||||||
dynamicparameters.WithTemplateVariableValues(variableValues),
|
dynamicparameters.WithTemplateVariableValues(variableValues),
|
||||||
dynamicparameters.WithLogger(b.logger.Named("dynamicparameters")),
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, xerrors.Errorf("get template version renderer: %w", err)
|
return nil, xerrors.Errorf("get template version renderer: %w", err)
|
||||||
@@ -902,16 +893,10 @@ func (b *Builder) getDynamicParameters() (names, values []string, err error) {
|
|||||||
return nil, nil, BuildError{http.StatusInternalServerError, "failed to check if first build", err}
|
return nil, nil, BuildError{http.StatusInternalServerError, "failed to check if first build", err}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't let missing secrets block stop or delete.
|
|
||||||
var resolveOpts []dynamicparameters.ResolveOption
|
|
||||||
if b.trans != database.WorkspaceTransitionStart {
|
|
||||||
resolveOpts = append(resolveOpts, dynamicparameters.SkipSecretRequirements())
|
|
||||||
}
|
|
||||||
buildValues, err := dynamicparameters.ResolveParameters(b.ctx, b.workspace.OwnerID, render, firstBuild,
|
buildValues, err := dynamicparameters.ResolveParameters(b.ctx, b.workspace.OwnerID, render, firstBuild,
|
||||||
lastBuildParameters,
|
lastBuildParameters,
|
||||||
b.richParameterValues,
|
b.richParameterValues,
|
||||||
presetParameterValues,
|
presetParameterValues)
|
||||||
resolveOpts...)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, BuildError{http.StatusBadRequest, "resolve parameters", err}
|
return nil, nil, BuildError{http.StatusBadRequest, "resolve parameters", err}
|
||||||
}
|
}
|
||||||
@@ -1147,13 +1132,13 @@ func (b *Builder) getDynamicProvisionerTags() (map[string]string, error) {
|
|||||||
vals[name] = values[i]
|
vals[name] = values[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
result, diags := render.Render(b.ctx, b.workspace.OwnerID, vals)
|
output, diags := render.Render(b.ctx, b.workspace.OwnerID, vals)
|
||||||
tagErr := dynamicparameters.CheckTags(result.Output, diags)
|
tagErr := dynamicparameters.CheckTags(output, diags)
|
||||||
if tagErr != nil {
|
if tagErr != nil {
|
||||||
return nil, BuildError{http.StatusBadRequest, "workspace tags validation failed", tagErr}
|
return nil, BuildError{http.StatusBadRequest, "workspace tags validation failed", tagErr}
|
||||||
}
|
}
|
||||||
|
|
||||||
for k, v := range result.Output.WorkspaceTags.Tags() {
|
for k, v := range output.WorkspaceTags.Tags() {
|
||||||
tags[k] = v
|
tags[k] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,70 +0,0 @@
|
|||||||
package wsbuilder
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/hashicorp/hcl/v2"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
"github.com/coder/coder/v2/coderd/database"
|
|
||||||
"github.com/coder/coder/v2/coderd/dynamicparameters"
|
|
||||||
"github.com/coder/coder/v2/provisionersdk"
|
|
||||||
"github.com/coder/preview"
|
|
||||||
previewtypes "github.com/coder/preview/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestBuilderDynamicProvisionerTagsDoesNotRequestSecretRequirements(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
ownerID := uuid.New()
|
|
||||||
names := []string{"region"}
|
|
||||||
values := []string{"us-east"}
|
|
||||||
|
|
||||||
render := &tagsPathRenderer{
|
|
||||||
result: &dynamicparameters.RenderResult{
|
|
||||||
Output: &preview.Output{
|
|
||||||
WorkspaceTags: previewtypes.TagBlocks{{
|
|
||||||
Tags: previewtypes.Tags{{
|
|
||||||
Key: previewtypes.StringLiteral("region"),
|
|
||||||
Value: previewtypes.StringLiteral("us-east"),
|
|
||||||
}},
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
builder := New(database.Workspace{
|
|
||||||
ID: uuid.New(),
|
|
||||||
OwnerID: ownerID,
|
|
||||||
}, database.WorkspaceTransitionStart, NoopUsageChecker{})
|
|
||||||
builder.ctx = t.Context()
|
|
||||||
builder.parameterRender = render
|
|
||||||
builder.parameterNames = &names
|
|
||||||
builder.parameterValues = &values
|
|
||||||
builder.templateVersionJob = &database.ProvisionerJob{
|
|
||||||
Tags: database.StringMap{
|
|
||||||
provisionersdk.TagScope: provisionersdk.ScopeUser,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
tags, err := builder.getDynamicProvisionerTags()
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, "us-east", tags["region"])
|
|
||||||
require.Equal(t, ownerID.String(), tags[provisionersdk.TagOwner])
|
|
||||||
require.Empty(t, render.opts, "tags path should not request secret requirements")
|
|
||||||
}
|
|
||||||
|
|
||||||
type tagsPathRenderer struct {
|
|
||||||
result *dynamicparameters.RenderResult
|
|
||||||
diags hcl.Diagnostics
|
|
||||||
opts []dynamicparameters.RenderOption
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *tagsPathRenderer) Render(_ context.Context, _ uuid.UUID, _ map[string]string, opts ...dynamicparameters.RenderOption) (*dynamicparameters.RenderResult, hcl.Diagnostics) {
|
|
||||||
r.opts = opts
|
|
||||||
return r.result, r.diags
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*tagsPathRenderer) Close() {}
|
|
||||||
+5
-14
@@ -162,27 +162,18 @@ type PreviewParameterValidation struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type DynamicParametersRequest struct {
|
type DynamicParametersRequest struct {
|
||||||
// ID identifies the request for response ordering. Websocket response
|
// ID identifies the request. The response contains the same
|
||||||
// IDs are monotonically increasing and may exceed the request ID when
|
// ID so that the client can match it to the request.
|
||||||
// server-side events trigger additional renders.
|
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
Inputs map[string]string `json:"inputs"`
|
Inputs map[string]string `json:"inputs"`
|
||||||
// OwnerID if uuid.Nil, it defaults to `codersdk.Me`
|
// OwnerID if uuid.Nil, it defaults to `codersdk.Me`
|
||||||
OwnerID uuid.UUID `json:"owner_id,omitempty" format:"uuid"`
|
OwnerID uuid.UUID `json:"owner_id,omitempty" format:"uuid"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SecretRequirementStatus struct {
|
|
||||||
Env string `json:"env,omitempty"`
|
|
||||||
File string `json:"file,omitempty"`
|
|
||||||
HelpMessage string `json:"help_message"`
|
|
||||||
Satisfied bool `json:"satisfied"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DynamicParametersResponse struct {
|
type DynamicParametersResponse struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
Diagnostics []FriendlyDiagnostic `json:"diagnostics"`
|
Diagnostics []FriendlyDiagnostic `json:"diagnostics"`
|
||||||
Parameters []PreviewParameter `json:"parameters"`
|
Parameters []PreviewParameter `json:"parameters"`
|
||||||
SecretRequirements []SecretRequirementStatus `json:"secret_requirements,omitempty"`
|
|
||||||
// TODO: Workspace tags
|
// TODO: Workspace tags
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -680,10 +680,6 @@ func (c *Client) WorkspaceQuota(ctx context.Context, organizationID string, user
|
|||||||
|
|
||||||
type ResolveAutostartResponse struct {
|
type ResolveAutostartResponse struct {
|
||||||
ParameterMismatch bool `json:"parameter_mismatch"`
|
ParameterMismatch bool `json:"parameter_mismatch"`
|
||||||
// SecretMismatch is true when the active template version declares
|
|
||||||
// `coder_secret` requirements that the workspace owner's secrets do not
|
|
||||||
// satisfy.
|
|
||||||
SecretMismatch bool `json:"secret_mismatch"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) ResolveAutostart(ctx context.Context, workspaceID string) (ResolveAutostartResponse, error) {
|
func (c *Client) ResolveAutostart(ctx context.Context, workspaceID string) (ResolveAutostartResponse, error) {
|
||||||
|
|||||||
Generated
+15
-46
@@ -6897,12 +6897,12 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
|
|||||||
|
|
||||||
### Properties
|
### Properties
|
||||||
|
|
||||||
| Name | Type | Required | Restrictions | Description |
|
| Name | Type | Required | Restrictions | Description |
|
||||||
|--------------------|---------|----------|--------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|--------------------|---------|----------|--------------|--------------------------------------------------------------------------------------------------------------|
|
||||||
| `id` | integer | false | | ID identifies the request for response ordering. Websocket response IDs are monotonically increasing and may exceed the request ID when server-side events trigger additional renders. |
|
| `id` | integer | false | | ID identifies the request. The response contains the same ID so that the client can match it to the request. |
|
||||||
| `inputs` | object | false | | |
|
| `inputs` | object | false | | |
|
||||||
| » `[any property]` | string | false | | |
|
| » `[any property]` | string | false | | |
|
||||||
| `owner_id` | string | false | | Owner ID if uuid.Nil, it defaults to `codersdk.Me` |
|
| `owner_id` | string | false | | Owner ID if uuid.Nil, it defaults to `codersdk.Me` |
|
||||||
|
|
||||||
## codersdk.DynamicParametersResponse
|
## codersdk.DynamicParametersResponse
|
||||||
|
|
||||||
@@ -6976,26 +6976,17 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
|
|||||||
"value": "string"
|
"value": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
|
||||||
"secret_requirements": [
|
|
||||||
{
|
|
||||||
"env": "string",
|
|
||||||
"file": "string",
|
|
||||||
"help_message": "string",
|
|
||||||
"satisfied": true
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Properties
|
### Properties
|
||||||
|
|
||||||
| Name | Type | Required | Restrictions | Description |
|
| Name | Type | Required | Restrictions | Description |
|
||||||
|-----------------------|-------------------------------------------------------------------------------|----------|--------------|-------------|
|
|---------------|---------------------------------------------------------------------|----------|--------------|-------------|
|
||||||
| `diagnostics` | array of [codersdk.FriendlyDiagnostic](#codersdkfriendlydiagnostic) | false | | |
|
| `diagnostics` | array of [codersdk.FriendlyDiagnostic](#codersdkfriendlydiagnostic) | false | | |
|
||||||
| `id` | integer | false | | |
|
| `id` | integer | false | | |
|
||||||
| `parameters` | array of [codersdk.PreviewParameter](#codersdkpreviewparameter) | false | | |
|
| `parameters` | array of [codersdk.PreviewParameter](#codersdkpreviewparameter) | false | | |
|
||||||
| `secret_requirements` | array of [codersdk.SecretRequirementStatus](#codersdksecretrequirementstatus) | false | | |
|
|
||||||
|
|
||||||
## codersdk.DynamicTool
|
## codersdk.DynamicTool
|
||||||
|
|
||||||
@@ -11074,17 +11065,15 @@ Only certain features set these fields: - FeatureManagedAgentLimit|
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"parameter_mismatch": true,
|
"parameter_mismatch": true
|
||||||
"secret_mismatch": true
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Properties
|
### Properties
|
||||||
|
|
||||||
| Name | Type | Required | Restrictions | Description |
|
| Name | Type | Required | Restrictions | Description |
|
||||||
|----------------------|---------|----------|--------------|--------------------------------------------------------------------------------------------------------------------------------------------------|
|
|----------------------|---------|----------|--------------|-------------|
|
||||||
| `parameter_mismatch` | boolean | false | | |
|
| `parameter_mismatch` | boolean | false | | |
|
||||||
| `secret_mismatch` | boolean | false | | Secret mismatch is true when the active template version declares `coder_secret` requirements that the workspace owner's secrets do not satisfy. |
|
|
||||||
|
|
||||||
## codersdk.ResourceType
|
## codersdk.ResourceType
|
||||||
|
|
||||||
@@ -11479,26 +11468,6 @@ Only certain features set these fields: - FeatureManagedAgentLimit|
|
|||||||
| `ssh_config_options` | object | false | | |
|
| `ssh_config_options` | object | false | | |
|
||||||
| » `[any property]` | string | false | | |
|
| » `[any property]` | string | false | | |
|
||||||
|
|
||||||
## codersdk.SecretRequirementStatus
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"env": "string",
|
|
||||||
"file": "string",
|
|
||||||
"help_message": "string",
|
|
||||||
"satisfied": true
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Properties
|
|
||||||
|
|
||||||
| Name | Type | Required | Restrictions | Description |
|
|
||||||
|----------------|---------|----------|--------------|-------------|
|
|
||||||
| `env` | string | false | | |
|
|
||||||
| `file` | string | false | | |
|
|
||||||
| `help_message` | string | false | | |
|
|
||||||
| `satisfied` | boolean | false | | |
|
|
||||||
|
|
||||||
## codersdk.ServerSentEvent
|
## codersdk.ServerSentEvent
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
|||||||
Generated
-8
@@ -2802,14 +2802,6 @@ curl -X POST http://coder-server:8080/api/v2/templateversions/{templateversion}/
|
|||||||
"value": "string"
|
"value": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
|
||||||
"secret_requirements": [
|
|
||||||
{
|
|
||||||
"env": "string",
|
|
||||||
"file": "string",
|
|
||||||
"help_message": "string",
|
|
||||||
"satisfied": true
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
Generated
+1
-2
@@ -2386,8 +2386,7 @@ curl -X GET http://coder-server:8080/api/v2/workspaces/{workspace}/resolve-autos
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"parameter_mismatch": true,
|
"parameter_mismatch": true
|
||||||
"secret_mismatch": true
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -198,7 +198,7 @@ func (s *server) Plan(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
env, err := provisionEnv(sess.Config, request.Metadata, request.PreviousParameterValues, request.RichParameterValues, request.ExternalAuthProviders, request.UserSecrets)
|
env, err := provisionEnv(sess.Config, request.Metadata, request.PreviousParameterValues, request.RichParameterValues, request.ExternalAuthProviders)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return provisionersdk.PlanErrorf("setup env: %s", err)
|
return provisionersdk.PlanErrorf("setup env: %s", err)
|
||||||
}
|
}
|
||||||
@@ -311,7 +311,7 @@ func (s *server) Apply(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
env, err := provisionEnv(sess.Config, request.Metadata, nil, nil, nil, nil)
|
env, err := provisionEnv(sess.Config, request.Metadata, nil, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return provisionersdk.ApplyErrorf("provision env: %s", err)
|
return provisionersdk.ApplyErrorf("provision env: %s", err)
|
||||||
}
|
}
|
||||||
@@ -347,7 +347,6 @@ func planVars(plan *proto.PlanRequest) ([]string, error) {
|
|||||||
func provisionEnv(
|
func provisionEnv(
|
||||||
config *proto.Config, metadata *proto.Metadata,
|
config *proto.Config, metadata *proto.Metadata,
|
||||||
previousParams, richParams []*proto.RichParameterValue, externalAuth []*proto.ExternalAuthProvider,
|
previousParams, richParams []*proto.RichParameterValue, externalAuth []*proto.ExternalAuthProvider,
|
||||||
userSecrets []*proto.UserSecretValue,
|
|
||||||
) ([]string, error) {
|
) ([]string, error) {
|
||||||
env := safeEnviron()
|
env := safeEnviron()
|
||||||
ownerGroups, err := json.Marshal(metadata.GetWorkspaceOwnerGroups())
|
ownerGroups, err := json.Marshal(metadata.GetWorkspaceOwnerGroups())
|
||||||
@@ -416,19 +415,6 @@ func provisionEnv(
|
|||||||
env = append(env, provider.ExternalAuthAccessTokenEnvironmentVariable(extAuth.Id)+"="+extAuth.AccessToken)
|
env = append(env, provider.ExternalAuthAccessTokenEnvironmentVariable(extAuth.Id)+"="+extAuth.AccessToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, secret := range userSecrets {
|
|
||||||
if secret.EnvName != "" {
|
|
||||||
env = append(env, provider.SecretEnvEnvironmentVariable(secret.EnvName)+"="+string(secret.Value))
|
|
||||||
}
|
|
||||||
if secret.FilePath != "" {
|
|
||||||
// Environment variables are used to communicate the file path a
|
|
||||||
// secret should be written to. The hex encoding is done because
|
|
||||||
// file paths contain slashes, tildes, and dots that are illegal
|
|
||||||
// in environment variable names.
|
|
||||||
env = append(env, provider.SecretFileEnvironmentVariable(secret.FilePath)+"="+string(secret.Value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.ProvisionerLogLevel != "" {
|
if config.ProvisionerLogLevel != "" {
|
||||||
// TF_LOG=JSON enables all kind of logging: trace-debug-info-warn-error.
|
// TF_LOG=JSON enables all kind of logging: trace-debug-info-warn-error.
|
||||||
// The idea behind using TF_LOG=JSON instead of TF_LOG=debug is ensuring the proper log format.
|
// The idea behind using TF_LOG=JSON instead of TF_LOG=debug is ensuring the proper log format.
|
||||||
|
|||||||
@@ -1,119 +0,0 @@
|
|||||||
package terraform
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/hex"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
"github.com/coder/coder/v2/provisionersdk/proto"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestProvisionEnv_UserSecrets(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
t.Run("EnvSecret", func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
secrets := []*proto.UserSecretValue{
|
|
||||||
{EnvName: "MY_TOKEN", Value: []byte("secret-value")},
|
|
||||||
}
|
|
||||||
env, err := provisionEnv(&proto.Config{}, &proto.Metadata{}, nil, nil, nil, secrets)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
want := "CODER_SECRET_ENV_MY_TOKEN=secret-value"
|
|
||||||
assert.Contains(t, env, want)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("FileSecret", func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
filePath := "~/.ssh/id_rsa"
|
|
||||||
secrets := []*proto.UserSecretValue{
|
|
||||||
{FilePath: filePath, Value: []byte("key-data")},
|
|
||||||
}
|
|
||||||
env, err := provisionEnv(&proto.Config{}, &proto.Metadata{}, nil, nil, nil, secrets)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
hexPath := hex.EncodeToString([]byte(filePath))
|
|
||||||
want := "CODER_SECRET_FILE_" + hexPath + "=key-data"
|
|
||||||
assert.Contains(t, env, want)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("BothEnvAndFile", func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
filePath := "/tmp/secret.txt"
|
|
||||||
secrets := []*proto.UserSecretValue{
|
|
||||||
{EnvName: "DUAL", FilePath: filePath, Value: []byte("both-value")},
|
|
||||||
}
|
|
||||||
env, err := provisionEnv(&proto.Config{}, &proto.Metadata{}, nil, nil, nil, secrets)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
wantEnv := "CODER_SECRET_ENV_DUAL=both-value"
|
|
||||||
hexPath := hex.EncodeToString([]byte(filePath))
|
|
||||||
wantFile := "CODER_SECRET_FILE_" + hexPath + "=both-value"
|
|
||||||
assert.Contains(t, env, wantEnv)
|
|
||||||
assert.Contains(t, env, wantFile)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("NilSecrets", func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
env, err := provisionEnv(&proto.Config{}, &proto.Metadata{}, nil, nil, nil, nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
for _, e := range env {
|
|
||||||
assert.False(t, strings.HasPrefix(e, "CODER_SECRET_"),
|
|
||||||
"unexpected secret env var: %s", e)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("EmptyEnvAndFile", func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
secrets := []*proto.UserSecretValue{
|
|
||||||
{EnvName: "", FilePath: "", Value: []byte("ignored")},
|
|
||||||
}
|
|
||||||
env, err := provisionEnv(&proto.Config{}, &proto.Metadata{}, nil, nil, nil, secrets)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
for _, e := range env {
|
|
||||||
assert.False(t, strings.HasPrefix(e, "CODER_SECRET_"),
|
|
||||||
"unexpected secret env var: %s", e)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// nolint:paralleltest // t.Setenv is incompatible with t.Parallel.
|
|
||||||
func TestProvisionEnv_HostSecretsStripped(t *testing.T) {
|
|
||||||
// Host CODER_* env vars must be stripped by safeEnviron before provisionEnv
|
|
||||||
// appends its own entries. If the order of operations in provisionEnv ever
|
|
||||||
// changes (e.g. appending before stripping, or adding a post-filter that
|
|
||||||
// drops CODER_*), this test catches it. The host var below would otherwise
|
|
||||||
// leak into the terraform environment and could be interpreted as a real
|
|
||||||
// secret.
|
|
||||||
t.Setenv("CODER_SECRET_ENV_PREEXISTING", "host-value")
|
|
||||||
env, err := provisionEnv(&proto.Config{}, &proto.Metadata{}, nil, nil, nil, nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
for _, e := range env {
|
|
||||||
assert.False(t, strings.HasPrefix(e, "CODER_SECRET_"),
|
|
||||||
"host CODER_SECRET_* var leaked into provisioner env: %s", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// nolint:paralleltest // t.Setenv is incompatible with t.Parallel.
|
|
||||||
func TestProvisionEnv_InputSecretsSurviveHostCollision(t *testing.T) {
|
|
||||||
// When the host has a CODER_SECRET_ENV_X var set and the caller also passes
|
|
||||||
// X in the secrets slice, the caller's value must win. This proves secrets
|
|
||||||
// are appended after safeEnviron strips the host's CODER_* vars, not before.
|
|
||||||
t.Setenv("CODER_SECRET_ENV_COLLIDE", "host-value-should-not-win")
|
|
||||||
secrets := []*proto.UserSecretValue{
|
|
||||||
{EnvName: "COLLIDE", Value: []byte("caller-value")},
|
|
||||||
}
|
|
||||||
env, err := provisionEnv(&proto.Config{}, &proto.Metadata{}, nil, nil, nil, secrets)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Contains(t, env, "CODER_SECRET_ENV_COLLIDE=caller-value",
|
|
||||||
"caller-supplied secret must be present")
|
|
||||||
assert.NotContains(t, env, "CODER_SECRET_ENV_COLLIDE=host-value-should-not-win",
|
|
||||||
"host value must be stripped before secrets are appended")
|
|
||||||
}
|
|
||||||
Generated
+302
-319
@@ -927,10 +927,6 @@ type AcquiredJob_WorkspaceBuild struct {
|
|||||||
// workspace build. Omit these values if the workspace is being created
|
// workspace build. Omit these values if the workspace is being created
|
||||||
// for the first time.
|
// for the first time.
|
||||||
PreviousParameterValues []*proto.RichParameterValue `protobuf:"bytes,10,rep,name=previous_parameter_values,json=previousParameterValues,proto3" json:"previous_parameter_values,omitempty"`
|
PreviousParameterValues []*proto.RichParameterValue `protobuf:"bytes,10,rep,name=previous_parameter_values,json=previousParameterValues,proto3" json:"previous_parameter_values,omitempty"`
|
||||||
// User secrets belonging to the workspace owner, to be forwarded into the
|
|
||||||
// plan request sent to the provisioner. Only populated for start
|
|
||||||
// transitions.
|
|
||||||
UserSecrets []*proto.UserSecretValue `protobuf:"bytes,12,rep,name=user_secrets,json=userSecrets,proto3" json:"user_secrets,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *AcquiredJob_WorkspaceBuild) Reset() {
|
func (x *AcquiredJob_WorkspaceBuild) Reset() {
|
||||||
@@ -1028,13 +1024,6 @@ func (x *AcquiredJob_WorkspaceBuild) GetPreviousParameterValues() []*proto.RichP
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *AcquiredJob_WorkspaceBuild) GetUserSecrets() []*proto.UserSecretValue {
|
|
||||||
if x != nil {
|
|
||||||
return x.UserSecrets
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type AcquiredJob_TemplateImport struct {
|
type AcquiredJob_TemplateImport struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
@@ -1570,7 +1559,7 @@ var file_provisionerd_proto_provisionerd_proto_rawDesc = []byte{
|
|||||||
0x6f, 0x6e, 0x65, 0x72, 0x64, 0x1a, 0x26, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e,
|
0x6f, 0x6e, 0x65, 0x72, 0x64, 0x1a, 0x26, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e,
|
||||||
0x65, 0x72, 0x73, 0x64, 0x6b, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x76,
|
0x65, 0x72, 0x73, 0x64, 0x6b, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x76,
|
||||||
0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x07, 0x0a,
|
0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x07, 0x0a,
|
||||||
0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0xc0, 0x0c, 0x0a, 0x0b, 0x41, 0x63, 0x71, 0x75, 0x69,
|
0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x85, 0x0c, 0x0a, 0x0b, 0x41, 0x63, 0x71, 0x75, 0x69,
|
||||||
0x72, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64,
|
0x72, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64,
|
||||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x12, 0x1d, 0x0a,
|
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x12, 0x1d, 0x0a,
|
||||||
0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28,
|
0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28,
|
||||||
@@ -1603,7 +1592,7 @@ var file_provisionerd_proto_provisionerd_proto_rawDesc = []byte{
|
|||||||
0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x41, 0x63, 0x71, 0x75, 0x69,
|
0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x41, 0x63, 0x71, 0x75, 0x69,
|
||||||
0x72, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x61,
|
0x72, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x61,
|
||||||
0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x63, 0x65,
|
0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x63, 0x65,
|
||||||
0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x1a, 0xea, 0x04, 0x0a, 0x0e, 0x57, 0x6f, 0x72,
|
0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x1a, 0xaf, 0x04, 0x0a, 0x0e, 0x57, 0x6f, 0x72,
|
||||||
0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x2c, 0x0a, 0x12, 0x77,
|
0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x2c, 0x0a, 0x12, 0x77,
|
||||||
0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x69,
|
0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x69,
|
||||||
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61,
|
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61,
|
||||||
@@ -1637,266 +1626,262 @@ var file_provisionerd_proto_provisionerd_proto_rawDesc = []byte{
|
|||||||
0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63,
|
0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63,
|
||||||
0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52,
|
0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52,
|
||||||
0x17, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74,
|
0x17, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74,
|
||||||
0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x3f, 0x0a, 0x0c, 0x75, 0x73, 0x65, 0x72,
|
0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x4a, 0x04,
|
||||||
0x5f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c,
|
0x08, 0x0b, 0x10, 0x0c, 0x4a, 0x04, 0x08, 0x0c, 0x10, 0x0d, 0x1a, 0x91, 0x01, 0x0a, 0x0e, 0x54,
|
||||||
0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x55, 0x73, 0x65,
|
0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x31, 0x0a,
|
||||||
0x72, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0b, 0x75, 0x73,
|
0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
|
||||||
0x65, 0x72, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x4a,
|
0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4d, 0x65,
|
||||||
0x04, 0x08, 0x0b, 0x10, 0x0c, 0x1a, 0x91, 0x01, 0x0a, 0x0e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61,
|
0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
|
||||||
0x74, 0x65, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x31, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61,
|
|
||||||
0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f,
|
|
||||||
0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,
|
|
||||||
0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x4c, 0x0a, 0x14, 0x75,
|
|
||||||
0x73, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x61, 0x6c,
|
|
||||||
0x75, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76,
|
|
||||||
0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65,
|
|
||||||
0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x12, 0x75, 0x73, 0x65, 0x72, 0x56, 0x61, 0x72, 0x69, 0x61,
|
|
||||||
0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x1a, 0xe3, 0x01, 0x0a, 0x0e, 0x54, 0x65,
|
|
||||||
0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x44, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x12, 0x53, 0x0a, 0x15,
|
|
||||||
0x72, 0x69, 0x63, 0x68, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x76,
|
|
||||||
0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72,
|
|
||||||
0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61,
|
|
||||||
0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x13, 0x72, 0x69,
|
|
||||||
0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65,
|
|
||||||
0x73, 0x12, 0x43, 0x0a, 0x0f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x61,
|
|
||||||
0x6c, 0x75, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f,
|
|
||||||
0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c,
|
|
||||||
0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0e, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65,
|
|
||||||
0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x31, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61,
|
|
||||||
0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69,
|
|
||||||
0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52,
|
|
||||||
0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x4a, 0x04, 0x08, 0x01, 0x10, 0x02, 0x1a,
|
|
||||||
0x40, 0x0a, 0x12, 0x54, 0x72, 0x61, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
|
|
||||||
0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01,
|
|
||||||
0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
|
|
||||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38,
|
|
||||||
0x01, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0xd4, 0x03, 0x0a, 0x09, 0x46, 0x61,
|
|
||||||
0x69, 0x6c, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69,
|
|
||||||
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x12, 0x14,
|
|
||||||
0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65,
|
|
||||||
0x72, 0x72, 0x6f, 0x72, 0x12, 0x51, 0x0a, 0x0f, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63,
|
|
||||||
0x65, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e,
|
|
||||||
0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x46, 0x61, 0x69,
|
|
||||||
0x6c, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65,
|
|
||||||
0x42, 0x75, 0x69, 0x6c, 0x64, 0x48, 0x00, 0x52, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61,
|
|
||||||
0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x51, 0x0a, 0x0f, 0x74, 0x65, 0x6d, 0x70, 0x6c,
|
|
||||||
0x61, 0x74, 0x65, 0x5f, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b,
|
|
||||||
0x32, 0x26, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e,
|
|
||||||
0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61,
|
|
||||||
0x74, 0x65, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x48, 0x00, 0x52, 0x0e, 0x74, 0x65, 0x6d, 0x70,
|
|
||||||
0x6c, 0x61, 0x74, 0x65, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x52, 0x0a, 0x10, 0x74, 0x65,
|
|
||||||
0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x64, 0x72, 0x79, 0x5f, 0x72, 0x75, 0x6e, 0x18, 0x05,
|
|
||||||
0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e,
|
|
||||||
0x65, 0x72, 0x64, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x2e, 0x54, 0x65,
|
|
||||||
0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x44, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x48, 0x00, 0x52, 0x0e,
|
|
||||||
0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x44, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x12, 0x1d,
|
|
||||||
0x0a, 0x0a, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01,
|
|
||||||
0x28, 0x09, 0x52, 0x09, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x1a, 0x55, 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, 0x2d, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x73,
|
|
||||||
0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69,
|
|
||||||
0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x74, 0x69, 0x6d,
|
|
||||||
0x69, 0x6e, 0x67, 0x73, 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, 0x89, 0x0b, 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, 0xc0, 0x02, 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, 0x12, 0x2d, 0x0a, 0x07, 0x74, 0x69, 0x6d,
|
|
||||||
0x69, 0x6e, 0x67, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f,
|
|
||||||
0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x52,
|
|
||||||
0x07, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x2d, 0x0a, 0x07, 0x6d, 0x6f, 0x64, 0x75,
|
|
||||||
0x6c, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76,
|
|
||||||
0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x52, 0x07,
|
|
||||||
0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x55, 0x0a, 0x15, 0x72, 0x65, 0x73, 0x6f, 0x75,
|
|
||||||
0x72, 0x63, 0x65, 0x5f, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73,
|
|
||||||
0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69,
|
|
||||||
0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x70,
|
|
||||||
0x6c, 0x61, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x14, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72,
|
|
||||||
0x63, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x2e,
|
|
||||||
0x0a, 0x08, 0x61, 0x69, 0x5f, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b,
|
|
||||||
0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41,
|
|
||||||
0x49, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x07, 0x61, 0x69, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x1a, 0x9d,
|
|
||||||
0x05, 0x0a, 0x0e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x6d, 0x70, 0x6f, 0x72,
|
|
||||||
0x74, 0x12, 0x3e, 0x0a, 0x0f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75,
|
|
||||||
0x72, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f,
|
|
||||||
0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63,
|
|
||||||
0x65, 0x52, 0x0e, 0x73, 0x74, 0x61, 0x72, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,
|
|
||||||
0x73, 0x12, 0x3c, 0x0a, 0x0e, 0x73, 0x74, 0x6f, 0x70, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72,
|
|
||||||
0x63, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76,
|
|
||||||
0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,
|
|
||||||
0x52, 0x0d, 0x73, 0x74, 0x6f, 0x70, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12,
|
|
||||||
0x43, 0x0a, 0x0f, 0x72, 0x69, 0x63, 0x68, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65,
|
|
||||||
0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69,
|
|
||||||
0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d,
|
|
||||||
0x65, 0x74, 0x65, 0x72, 0x52, 0x0e, 0x72, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65,
|
|
||||||
0x74, 0x65, 0x72, 0x73, 0x12, 0x41, 0x0a, 0x1d, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c,
|
|
||||||
0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x5f,
|
|
||||||
0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x1a, 0x65, 0x78, 0x74,
|
|
||||||
0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65,
|
|
||||||
0x72, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x61, 0x0a, 0x17, 0x65, 0x78, 0x74, 0x65, 0x72,
|
|
||||||
0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65,
|
|
||||||
0x72, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69,
|
|
||||||
0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41,
|
|
||||||
0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75,
|
|
||||||
0x72, 0x63, 0x65, 0x52, 0x15, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74,
|
|
||||||
0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x12, 0x38, 0x0a, 0x0d, 0x73, 0x74,
|
|
||||||
0x61, 0x72, 0x74, 0x5f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28,
|
|
||||||
0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e,
|
|
||||||
0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x52, 0x0c, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4d, 0x6f, 0x64,
|
|
||||||
0x75, 0x6c, 0x65, 0x73, 0x12, 0x2d, 0x0a, 0x07, 0x70, 0x72, 0x65, 0x73, 0x65, 0x74, 0x73, 0x18,
|
|
||||||
0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f,
|
|
||||||
0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x65, 0x73, 0x65, 0x74, 0x52, 0x07, 0x70, 0x72, 0x65, 0x73,
|
|
||||||
0x65, 0x74, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28,
|
|
||||||
0x0c, 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x6f, 0x64, 0x75, 0x6c,
|
|
||||||
0x65, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x6d,
|
|
||||||
0x6f, 0x64, 0x75, 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x6d, 0x6f,
|
|
||||||
0x64, 0x75, 0x6c, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18,
|
|
||||||
0x0b, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x46, 0x69, 0x6c,
|
|
||||||
0x65, 0x73, 0x48, 0x61, 0x73, 0x68, 0x12, 0x20, 0x0a, 0x0c, 0x68, 0x61, 0x73, 0x5f, 0x61, 0x69,
|
|
||||||
0x5f, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x68, 0x61,
|
|
||||||
0x73, 0x41, 0x69, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x68, 0x61, 0x73, 0x5f,
|
|
||||||
0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x18,
|
|
||||||
0x0d, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x68, 0x61, 0x73, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e,
|
|
||||||
0x61, 0x6c, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x4a, 0x04, 0x08, 0x07, 0x10, 0x08, 0x1a, 0x74,
|
|
||||||
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, 0x12, 0x2d, 0x0a, 0x07, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73,
|
|
||||||
0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69,
|
|
||||||
0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x52, 0x07, 0x6d, 0x6f, 0x64,
|
|
||||||
0x75, 0x6c, 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,
|
|
||||||
0xa6, 0x03, 0x0a, 0x10, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71,
|
|
||||||
0x75, 0x65, 0x73, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x18, 0x01,
|
|
||||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x04, 0x6c,
|
|
||||||
0x6f, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x70, 0x72, 0x6f, 0x76,
|
|
||||||
0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x4c, 0x6f, 0x67, 0x52, 0x04, 0x6c, 0x6f,
|
|
||||||
0x67, 0x73, 0x12, 0x4c, 0x0a, 0x12, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x76,
|
|
||||||
0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d,
|
|
||||||
0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x54, 0x65, 0x6d,
|
|
||||||
0x70, 0x6c, 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x11, 0x74,
|
|
||||||
0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73,
|
|
||||||
0x12, 0x4c, 0x0a, 0x14, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c,
|
0x12, 0x4c, 0x0a, 0x14, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c,
|
||||||
0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a,
|
0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a,
|
||||||
0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x56, 0x61, 0x72,
|
0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x56, 0x61, 0x72,
|
||||||
0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x12, 0x75, 0x73, 0x65, 0x72,
|
0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x12, 0x75, 0x73, 0x65, 0x72,
|
||||||
0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x16,
|
0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x1a, 0xe3,
|
||||||
0x0a, 0x06, 0x72, 0x65, 0x61, 0x64, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06,
|
0x01, 0x0a, 0x0e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x44, 0x72, 0x79, 0x52, 0x75,
|
||||||
0x72, 0x65, 0x61, 0x64, 0x6d, 0x65, 0x12, 0x58, 0x0a, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70,
|
0x6e, 0x12, 0x53, 0x0a, 0x15, 0x72, 0x69, 0x63, 0x68, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65,
|
||||||
0x61, 0x63, 0x65, 0x5f, 0x74, 0x61, 0x67, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x31,
|
0x74, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b,
|
||||||
0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x55, 0x70,
|
0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52,
|
||||||
0x64, 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x57,
|
0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75,
|
||||||
0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x61, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72,
|
0x65, 0x52, 0x13, 0x72, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72,
|
||||||
0x79, 0x52, 0x0d, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x61, 0x67, 0x73,
|
0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x43, 0x0a, 0x0f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62,
|
||||||
0x1a, 0x40, 0x0a, 0x12, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x61, 0x67,
|
0x6c, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32,
|
||||||
0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20,
|
0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x56, 0x61,
|
||||||
0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75,
|
0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0e, 0x76, 0x61, 0x72,
|
||||||
0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02,
|
0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x31, 0x0a, 0x08, 0x6d,
|
||||||
0x38, 0x01, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x22, 0x7a, 0x0a, 0x11, 0x55, 0x70, 0x64, 0x61,
|
0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e,
|
||||||
0x74, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a,
|
0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4d, 0x65, 0x74, 0x61,
|
||||||
0x08, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52,
|
0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x4a, 0x04,
|
||||||
0x08, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x65, 0x64, 0x12, 0x43, 0x0a, 0x0f, 0x76, 0x61, 0x72,
|
0x08, 0x01, 0x10, 0x02, 0x1a, 0x40, 0x0a, 0x12, 0x54, 0x72, 0x61, 0x63, 0x65, 0x4d, 0x65, 0x74,
|
||||||
0x69, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03,
|
0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65,
|
||||||
0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72,
|
0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05,
|
||||||
0x2e, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0e,
|
0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c,
|
||||||
0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x4a, 0x04,
|
0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0xd4,
|
||||||
0x08, 0x02, 0x10, 0x03, 0x22, 0x4a, 0x0a, 0x12, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x51, 0x75,
|
0x03, 0x0a, 0x09, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x12, 0x15, 0x0a, 0x06,
|
||||||
0x6f, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f,
|
0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f,
|
||||||
0x62, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49,
|
0x62, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01,
|
||||||
0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x64, 0x61, 0x69, 0x6c, 0x79, 0x5f, 0x63, 0x6f, 0x73, 0x74, 0x18,
|
0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x51, 0x0a, 0x0f, 0x77, 0x6f, 0x72,
|
||||||
0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x64, 0x61, 0x69, 0x6c, 0x79, 0x43, 0x6f, 0x73, 0x74,
|
0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x18, 0x03, 0x20, 0x01,
|
||||||
0x22, 0x68, 0x0a, 0x13, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x61, 0x52,
|
0x28, 0x0b, 0x32, 0x26, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72,
|
||||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x6f, 0x6b, 0x18, 0x01, 0x20,
|
0x64, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x2e, 0x57, 0x6f, 0x72, 0x6b,
|
||||||
0x01, 0x28, 0x08, 0x52, 0x02, 0x6f, 0x6b, 0x12, 0x29, 0x0a, 0x10, 0x63, 0x72, 0x65, 0x64, 0x69,
|
0x73, 0x70, 0x61, 0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x48, 0x00, 0x52, 0x0e, 0x77, 0x6f,
|
||||||
0x74, 0x73, 0x5f, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28,
|
0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x51, 0x0a, 0x0f,
|
||||||
0x05, 0x52, 0x0f, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x73, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d,
|
0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x18,
|
||||||
0x65, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x62, 0x75, 0x64, 0x67, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01,
|
0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f,
|
||||||
0x28, 0x05, 0x52, 0x06, 0x62, 0x75, 0x64, 0x67, 0x65, 0x74, 0x22, 0x0f, 0x0a, 0x0d, 0x43, 0x61,
|
0x6e, 0x65, 0x72, 0x64, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x2e, 0x54,
|
||||||
0x6e, 0x63, 0x65, 0x6c, 0x41, 0x63, 0x71, 0x75, 0x69, 0x72, 0x65, 0x22, 0x64, 0x0a, 0x0b, 0x46,
|
0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x48, 0x00, 0x52,
|
||||||
0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x66, 0x69,
|
0x0e, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x12,
|
||||||
0x6c, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x69, 0x6c,
|
0x52, 0x0a, 0x10, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x64, 0x72, 0x79, 0x5f,
|
||||||
0x65, 0x49, 0x64, 0x12, 0x3c, 0x0a, 0x0b, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x74, 0x79,
|
0x72, 0x75, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x70, 0x72, 0x6f, 0x76,
|
||||||
0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69,
|
0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x4a,
|
||||||
0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x55, 0x70, 0x6c, 0x6f, 0x61,
|
0x6f, 0x62, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x44, 0x72, 0x79, 0x52, 0x75,
|
||||||
0x64, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x54, 0x79, 0x70,
|
0x6e, 0x48, 0x00, 0x52, 0x0e, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x44, 0x72, 0x79,
|
||||||
0x65, 0x2a, 0x34, 0x0a, 0x09, 0x4c, 0x6f, 0x67, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x16,
|
0x52, 0x75, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x63, 0x6f, 0x64,
|
||||||
0x0a, 0x12, 0x50, 0x52, 0x4f, 0x56, 0x49, 0x53, 0x49, 0x4f, 0x4e, 0x45, 0x52, 0x5f, 0x44, 0x41,
|
0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x43, 0x6f,
|
||||||
0x45, 0x4d, 0x4f, 0x4e, 0x10, 0x00, 0x12, 0x0f, 0x0a, 0x0b, 0x50, 0x52, 0x4f, 0x56, 0x49, 0x53,
|
0x64, 0x65, 0x1a, 0x55, 0x0a, 0x0e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x42,
|
||||||
0x49, 0x4f, 0x4e, 0x45, 0x52, 0x10, 0x01, 0x32, 0xc9, 0x04, 0x0a, 0x11, 0x50, 0x72, 0x6f, 0x76,
|
0x75, 0x69, 0x6c, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20,
|
||||||
0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x44, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x12, 0x41, 0x0a,
|
0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x2d, 0x0a, 0x07, 0x74, 0x69,
|
||||||
0x0a, 0x41, 0x63, 0x71, 0x75, 0x69, 0x72, 0x65, 0x4a, 0x6f, 0x62, 0x12, 0x13, 0x2e, 0x70, 0x72,
|
0x6d, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72,
|
||||||
0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79,
|
0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67,
|
||||||
0x1a, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e,
|
0x52, 0x07, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x73, 0x1a, 0x10, 0x0a, 0x0e, 0x54, 0x65, 0x6d,
|
||||||
0x41, 0x63, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x22, 0x03, 0x88, 0x02, 0x01,
|
0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x1a, 0x10, 0x0a, 0x0e, 0x54,
|
||||||
0x12, 0x52, 0x0a, 0x14, 0x41, 0x63, 0x71, 0x75, 0x69, 0x72, 0x65, 0x4a, 0x6f, 0x62, 0x57, 0x69,
|
0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x44, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x42, 0x06, 0x0a,
|
||||||
0x74, 0x68, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x12, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69,
|
0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x89, 0x0b, 0x0a, 0x0c, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65,
|
||||||
0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x41, 0x63,
|
0x74, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64,
|
||||||
0x71, 0x75, 0x69, 0x72, 0x65, 0x1a, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f,
|
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, 0xc0, 0x02, 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, 0x12, 0x2d,
|
||||||
|
0x0a, 0x07, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32,
|
||||||
|
0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x54, 0x69,
|
||||||
|
0x6d, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x2d, 0x0a,
|
||||||
|
0x07, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13,
|
||||||
|
0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4d, 0x6f, 0x64,
|
||||||
|
0x75, 0x6c, 0x65, 0x52, 0x07, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x55, 0x0a, 0x15,
|
||||||
|
0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65,
|
||||||
|
0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x72,
|
||||||
|
0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72,
|
||||||
|
0x63, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x14, 0x72,
|
||||||
|
0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x6d, 0x65,
|
||||||
|
0x6e, 0x74, 0x73, 0x12, 0x2e, 0x0a, 0x08, 0x61, 0x69, 0x5f, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x18,
|
||||||
|
0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f,
|
||||||
|
0x6e, 0x65, 0x72, 0x2e, 0x41, 0x49, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x07, 0x61, 0x69, 0x54, 0x61,
|
||||||
|
0x73, 0x6b, 0x73, 0x1a, 0x9d, 0x05, 0x0a, 0x0e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65,
|
||||||
|
0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x3e, 0x0a, 0x0f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f,
|
||||||
|
0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32,
|
||||||
|
0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65,
|
||||||
|
0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x0e, 0x73, 0x74, 0x61, 0x72, 0x74, 0x52, 0x65, 0x73,
|
||||||
|
0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x3c, 0x0a, 0x0e, 0x73, 0x74, 0x6f, 0x70, 0x5f, 0x72,
|
||||||
|
0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15,
|
||||||
|
0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73,
|
||||||
|
0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x0d, 0x73, 0x74, 0x6f, 0x70, 0x52, 0x65, 0x73, 0x6f, 0x75,
|
||||||
|
0x72, 0x63, 0x65, 0x73, 0x12, 0x43, 0x0a, 0x0f, 0x72, 0x69, 0x63, 0x68, 0x5f, 0x70, 0x61, 0x72,
|
||||||
|
0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
|
||||||
|
0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68,
|
||||||
|
0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x52, 0x0e, 0x72, 0x69, 0x63, 0x68, 0x50,
|
||||||
|
0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x41, 0x0a, 0x1d, 0x65, 0x78, 0x74,
|
||||||
|
0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69,
|
||||||
|
0x64, 0x65, 0x72, 0x73, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09,
|
||||||
|
0x52, 0x1a, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72,
|
||||||
|
0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x61, 0x0a, 0x17,
|
||||||
|
0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72,
|
||||||
|
0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e,
|
||||||
|
0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x45, 0x78, 0x74, 0x65,
|
||||||
|
0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72,
|
||||||
|
0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x15, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e,
|
||||||
|
0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x12,
|
||||||
|
0x38, 0x0a, 0x0d, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73,
|
||||||
|
0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69,
|
||||||
|
0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x52, 0x0c, 0x73, 0x74, 0x61,
|
||||||
|
0x72, 0x74, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x2d, 0x0a, 0x07, 0x70, 0x72, 0x65,
|
||||||
|
0x73, 0x65, 0x74, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f,
|
||||||
|
0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x65, 0x73, 0x65, 0x74, 0x52,
|
||||||
|
0x07, 0x70, 0x72, 0x65, 0x73, 0x65, 0x74, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e,
|
||||||
|
0x18, 0x09, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x12, 0x21, 0x0a, 0x0c,
|
||||||
|
0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x01,
|
||||||
|
0x28, 0x0c, 0x52, 0x0b, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x12,
|
||||||
|
0x2a, 0x0a, 0x11, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x5f,
|
||||||
|
0x68, 0x61, 0x73, 0x68, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x6d, 0x6f, 0x64, 0x75,
|
||||||
|
0x6c, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x48, 0x61, 0x73, 0x68, 0x12, 0x20, 0x0a, 0x0c, 0x68,
|
||||||
|
0x61, 0x73, 0x5f, 0x61, 0x69, 0x5f, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28,
|
||||||
|
0x08, 0x52, 0x0a, 0x68, 0x61, 0x73, 0x41, 0x69, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x12, 0x2e, 0x0a,
|
||||||
|
0x13, 0x68, 0x61, 0x73, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x67,
|
||||||
|
0x65, 0x6e, 0x74, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x68, 0x61, 0x73, 0x45,
|
||||||
|
0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x4a, 0x04, 0x08,
|
||||||
|
0x07, 0x10, 0x08, 0x1a, 0x74, 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, 0x12, 0x2d, 0x0a, 0x07, 0x6d, 0x6f,
|
||||||
|
0x64, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72,
|
||||||
|
0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65,
|
||||||
|
0x52, 0x07, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 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, 0xa6, 0x03, 0x0a, 0x10, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4a,
|
||||||
|
0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62,
|
||||||
|
0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64,
|
||||||
|
0x12, 0x25, 0x0a, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11,
|
||||||
|
0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x4c, 0x6f,
|
||||||
|
0x67, 0x52, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x12, 0x4c, 0x0a, 0x12, 0x74, 0x65, 0x6d, 0x70, 0x6c,
|
||||||
|
0x61, 0x74, 0x65, 0x5f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x18, 0x04, 0x20,
|
||||||
|
0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65,
|
||||||
|
0x72, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62,
|
||||||
|
0x6c, 0x65, 0x52, 0x11, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x69,
|
||||||
|
0x61, 0x62, 0x6c, 0x65, 0x73, 0x12, 0x4c, 0x0a, 0x14, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x76, 0x61,
|
||||||
|
0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x05, 0x20,
|
||||||
|
0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65,
|
||||||
|
0x72, 0x2e, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52,
|
||||||
|
0x12, 0x75, 0x73, 0x65, 0x72, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c,
|
||||||
|
0x75, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x64, 0x6d, 0x65, 0x18, 0x06, 0x20,
|
||||||
|
0x01, 0x28, 0x0c, 0x52, 0x06, 0x72, 0x65, 0x61, 0x64, 0x6d, 0x65, 0x12, 0x58, 0x0a, 0x0e, 0x77,
|
||||||
|
0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x74, 0x61, 0x67, 0x73, 0x18, 0x07, 0x20,
|
||||||
|
0x03, 0x28, 0x0b, 0x32, 0x31, 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, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x61, 0x67,
|
||||||
|
0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0d, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63,
|
||||||
|
0x65, 0x54, 0x61, 0x67, 0x73, 0x1a, 0x40, 0x0a, 0x12, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61,
|
||||||
|
0x63, 0x65, 0x54, 0x61, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b,
|
||||||
|
0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a,
|
||||||
|
0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61,
|
||||||
|
0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x22, 0x7a, 0x0a,
|
||||||
|
0x11, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||||
|
0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x65, 0x64, 0x18, 0x01,
|
||||||
|
0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x65, 0x64, 0x12, 0x43,
|
||||||
|
0x0a, 0x0f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65,
|
||||||
|
0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73,
|
||||||
|
0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61,
|
||||||
|
0x6c, 0x75, 0x65, 0x52, 0x0e, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c,
|
||||||
|
0x75, 0x65, 0x73, 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x22, 0x4a, 0x0a, 0x12, 0x43, 0x6f, 0x6d,
|
||||||
|
0x6d, 0x69, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
|
||||||
|
0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||||
|
0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x64, 0x61, 0x69, 0x6c, 0x79, 0x5f,
|
||||||
|
0x63, 0x6f, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x64, 0x61, 0x69, 0x6c,
|
||||||
|
0x79, 0x43, 0x6f, 0x73, 0x74, 0x22, 0x68, 0x0a, 0x13, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x51,
|
||||||
|
0x75, 0x6f, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02,
|
||||||
|
0x6f, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x02, 0x6f, 0x6b, 0x12, 0x29, 0x0a, 0x10,
|
||||||
|
0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x73, 0x5f, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x64,
|
||||||
|
0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0f, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x73, 0x43,
|
||||||
|
0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x62, 0x75, 0x64, 0x67, 0x65,
|
||||||
|
0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x62, 0x75, 0x64, 0x67, 0x65, 0x74, 0x22,
|
||||||
|
0x0f, 0x0a, 0x0d, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x41, 0x63, 0x71, 0x75, 0x69, 0x72, 0x65,
|
||||||
|
0x22, 0x64, 0x0a, 0x0b, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
|
||||||
|
0x17, 0x0a, 0x07, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
|
||||||
|
0x52, 0x06, 0x66, 0x69, 0x6c, 0x65, 0x49, 0x64, 0x12, 0x3c, 0x0a, 0x0b, 0x75, 0x70, 0x6c, 0x6f,
|
||||||
|
0x61, 0x64, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e,
|
||||||
|
0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x44, 0x61, 0x74, 0x61,
|
||||||
|
0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x75, 0x70, 0x6c, 0x6f,
|
||||||
|
0x61, 0x64, 0x54, 0x79, 0x70, 0x65, 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, 0xc9, 0x04, 0x0a,
|
||||||
|
0x11, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x44, 0x61, 0x65, 0x6d,
|
||||||
|
0x6f, 0x6e, 0x12, 0x41, 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,
|
0x6e, 0x65, 0x72, 0x64, 0x2e, 0x41, 0x63, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x4a, 0x6f, 0x62,
|
||||||
0x28, 0x01, 0x30, 0x01, 0x12, 0x52, 0x0a, 0x0b, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x51, 0x75,
|
0x22, 0x03, 0x88, 0x02, 0x01, 0x12, 0x52, 0x0a, 0x14, 0x41, 0x63, 0x71, 0x75, 0x69, 0x72, 0x65,
|
||||||
0x6f, 0x74, 0x61, 0x12, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65,
|
0x4a, 0x6f, 0x62, 0x57, 0x69, 0x74, 0x68, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x12, 0x1b, 0x2e,
|
||||||
0x72, 0x64, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x61, 0x52, 0x65,
|
0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x43, 0x61, 0x6e,
|
||||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f,
|
0x63, 0x65, 0x6c, 0x41, 0x63, 0x71, 0x75, 0x69, 0x72, 0x65, 0x1a, 0x19, 0x2e, 0x70, 0x72, 0x6f,
|
||||||
0x6e, 0x65, 0x72, 0x64, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x61,
|
0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x41, 0x63, 0x71, 0x75, 0x69, 0x72,
|
||||||
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x09, 0x55, 0x70, 0x64, 0x61,
|
0x65, 0x64, 0x4a, 0x6f, 0x62, 0x28, 0x01, 0x30, 0x01, 0x12, 0x52, 0x0a, 0x0b, 0x43, 0x6f, 0x6d,
|
||||||
0x74, 0x65, 0x4a, 0x6f, 0x62, 0x12, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f,
|
0x6d, 0x69, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x61, 0x12, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69,
|
||||||
0x6e, 0x65, 0x72, 0x64, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65,
|
0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x51, 0x75,
|
||||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f,
|
0x6f, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x70, 0x72, 0x6f,
|
||||||
0x6e, 0x65, 0x72, 0x64, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65,
|
0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74,
|
||||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x07, 0x46, 0x61, 0x69, 0x6c, 0x4a, 0x6f,
|
0x51, 0x75, 0x6f, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a,
|
||||||
0x62, 0x12, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64,
|
0x09, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x12, 0x1e, 0x2e, 0x70, 0x72, 0x6f,
|
||||||
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, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65,
|
||||||
0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12,
|
0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x70, 0x72, 0x6f,
|
||||||
0x3e, 0x0a, 0x0b, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x12, 0x1a,
|
0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65,
|
||||||
0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x43, 0x6f,
|
0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x07, 0x46,
|
||||||
0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x1a, 0x13, 0x2e, 0x70, 0x72, 0x6f,
|
0x61, 0x69, 0x6c, 0x4a, 0x6f, 0x62, 0x12, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69,
|
||||||
0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12,
|
0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x1a,
|
||||||
0x3c, 0x0a, 0x0a, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x17, 0x2e,
|
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, 0x12, 0x3c, 0x0a, 0x0a, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x46, 0x69,
|
||||||
|
0x6c, 0x65, 0x12, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72,
|
||||||
|
0x2e, 0x46, 0x69, 0x6c, 0x65, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x1a, 0x13, 0x2e, 0x70, 0x72,
|
||||||
|
0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79,
|
||||||
|
0x28, 0x01, 0x12, 0x44, 0x0a, 0x0c, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x46, 0x69,
|
||||||
|
0x6c, 0x65, 0x12, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72,
|
||||||
|
0x64, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e,
|
||||||
0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x46, 0x69, 0x6c, 0x65,
|
0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x46, 0x69, 0x6c, 0x65,
|
||||||
0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x1a, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69,
|
0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x30, 0x01, 0x42, 0x2e, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68,
|
||||||
0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x28, 0x01, 0x12, 0x44, 0x0a,
|
0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x64,
|
||||||
0x0c, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x19, 0x2e,
|
0x65, 0x72, 0x2f, 0x76, 0x32, 0x2f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65,
|
||||||
0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x46, 0x69, 0x6c,
|
0x72, 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69,
|
|
||||||
0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x55, 0x70, 0x6c, 0x6f, 0x61,
|
|
||||||
0x64, 0x30, 0x01, 0x42, 0x2e, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f,
|
|
||||||
0x6d, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x76, 0x32,
|
|
||||||
0x2f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2f, 0x70, 0x72,
|
|
||||||
0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -1944,16 +1929,15 @@ var file_provisionerd_proto_provisionerd_proto_goTypes = []interface{}{
|
|||||||
(*proto.RichParameterValue)(nil), // 27: provisioner.RichParameterValue
|
(*proto.RichParameterValue)(nil), // 27: provisioner.RichParameterValue
|
||||||
(*proto.ExternalAuthProvider)(nil), // 28: provisioner.ExternalAuthProvider
|
(*proto.ExternalAuthProvider)(nil), // 28: provisioner.ExternalAuthProvider
|
||||||
(*proto.Metadata)(nil), // 29: provisioner.Metadata
|
(*proto.Metadata)(nil), // 29: provisioner.Metadata
|
||||||
(*proto.UserSecretValue)(nil), // 30: provisioner.UserSecretValue
|
(*proto.Timing)(nil), // 30: provisioner.Timing
|
||||||
(*proto.Timing)(nil), // 31: provisioner.Timing
|
(*proto.Resource)(nil), // 31: provisioner.Resource
|
||||||
(*proto.Resource)(nil), // 32: provisioner.Resource
|
(*proto.Module)(nil), // 32: provisioner.Module
|
||||||
(*proto.Module)(nil), // 33: provisioner.Module
|
(*proto.ResourceReplacement)(nil), // 33: provisioner.ResourceReplacement
|
||||||
(*proto.ResourceReplacement)(nil), // 34: provisioner.ResourceReplacement
|
(*proto.AITask)(nil), // 34: provisioner.AITask
|
||||||
(*proto.AITask)(nil), // 35: provisioner.AITask
|
(*proto.RichParameter)(nil), // 35: provisioner.RichParameter
|
||||||
(*proto.RichParameter)(nil), // 36: provisioner.RichParameter
|
(*proto.ExternalAuthProviderResource)(nil), // 36: provisioner.ExternalAuthProviderResource
|
||||||
(*proto.ExternalAuthProviderResource)(nil), // 37: provisioner.ExternalAuthProviderResource
|
(*proto.Preset)(nil), // 37: provisioner.Preset
|
||||||
(*proto.Preset)(nil), // 38: provisioner.Preset
|
(*proto.FileUpload)(nil), // 38: provisioner.FileUpload
|
||||||
(*proto.FileUpload)(nil), // 39: provisioner.FileUpload
|
|
||||||
}
|
}
|
||||||
var file_provisionerd_proto_provisionerd_proto_depIdxs = []int32{
|
var file_provisionerd_proto_provisionerd_proto_depIdxs = []int32{
|
||||||
12, // 0: provisionerd.AcquiredJob.workspace_build:type_name -> provisionerd.AcquiredJob.WorkspaceBuild
|
12, // 0: provisionerd.AcquiredJob.workspace_build:type_name -> provisionerd.AcquiredJob.WorkspaceBuild
|
||||||
@@ -1979,47 +1963,46 @@ var file_provisionerd_proto_provisionerd_proto_depIdxs = []int32{
|
|||||||
28, // 20: provisionerd.AcquiredJob.WorkspaceBuild.external_auth_providers:type_name -> provisioner.ExternalAuthProvider
|
28, // 20: provisionerd.AcquiredJob.WorkspaceBuild.external_auth_providers:type_name -> provisioner.ExternalAuthProvider
|
||||||
29, // 21: provisionerd.AcquiredJob.WorkspaceBuild.metadata:type_name -> provisioner.Metadata
|
29, // 21: provisionerd.AcquiredJob.WorkspaceBuild.metadata:type_name -> provisioner.Metadata
|
||||||
27, // 22: provisionerd.AcquiredJob.WorkspaceBuild.previous_parameter_values:type_name -> provisioner.RichParameterValue
|
27, // 22: provisionerd.AcquiredJob.WorkspaceBuild.previous_parameter_values:type_name -> provisioner.RichParameterValue
|
||||||
30, // 23: provisionerd.AcquiredJob.WorkspaceBuild.user_secrets:type_name -> provisioner.UserSecretValue
|
29, // 23: provisionerd.AcquiredJob.TemplateImport.metadata:type_name -> provisioner.Metadata
|
||||||
29, // 24: provisionerd.AcquiredJob.TemplateImport.metadata:type_name -> provisioner.Metadata
|
25, // 24: provisionerd.AcquiredJob.TemplateImport.user_variable_values:type_name -> provisioner.VariableValue
|
||||||
25, // 25: provisionerd.AcquiredJob.TemplateImport.user_variable_values:type_name -> provisioner.VariableValue
|
27, // 25: provisionerd.AcquiredJob.TemplateDryRun.rich_parameter_values:type_name -> provisioner.RichParameterValue
|
||||||
27, // 26: provisionerd.AcquiredJob.TemplateDryRun.rich_parameter_values:type_name -> provisioner.RichParameterValue
|
25, // 26: provisionerd.AcquiredJob.TemplateDryRun.variable_values:type_name -> provisioner.VariableValue
|
||||||
25, // 27: provisionerd.AcquiredJob.TemplateDryRun.variable_values:type_name -> provisioner.VariableValue
|
29, // 27: provisionerd.AcquiredJob.TemplateDryRun.metadata:type_name -> provisioner.Metadata
|
||||||
29, // 28: provisionerd.AcquiredJob.TemplateDryRun.metadata:type_name -> provisioner.Metadata
|
30, // 28: provisionerd.FailedJob.WorkspaceBuild.timings:type_name -> provisioner.Timing
|
||||||
31, // 29: provisionerd.FailedJob.WorkspaceBuild.timings:type_name -> provisioner.Timing
|
31, // 29: provisionerd.CompletedJob.WorkspaceBuild.resources:type_name -> provisioner.Resource
|
||||||
32, // 30: provisionerd.CompletedJob.WorkspaceBuild.resources:type_name -> provisioner.Resource
|
30, // 30: provisionerd.CompletedJob.WorkspaceBuild.timings:type_name -> provisioner.Timing
|
||||||
31, // 31: provisionerd.CompletedJob.WorkspaceBuild.timings:type_name -> provisioner.Timing
|
32, // 31: provisionerd.CompletedJob.WorkspaceBuild.modules:type_name -> provisioner.Module
|
||||||
33, // 32: provisionerd.CompletedJob.WorkspaceBuild.modules:type_name -> provisioner.Module
|
33, // 32: provisionerd.CompletedJob.WorkspaceBuild.resource_replacements:type_name -> provisioner.ResourceReplacement
|
||||||
34, // 33: provisionerd.CompletedJob.WorkspaceBuild.resource_replacements:type_name -> provisioner.ResourceReplacement
|
34, // 33: provisionerd.CompletedJob.WorkspaceBuild.ai_tasks:type_name -> provisioner.AITask
|
||||||
35, // 34: provisionerd.CompletedJob.WorkspaceBuild.ai_tasks:type_name -> provisioner.AITask
|
31, // 34: provisionerd.CompletedJob.TemplateImport.start_resources:type_name -> provisioner.Resource
|
||||||
32, // 35: provisionerd.CompletedJob.TemplateImport.start_resources:type_name -> provisioner.Resource
|
31, // 35: provisionerd.CompletedJob.TemplateImport.stop_resources:type_name -> provisioner.Resource
|
||||||
32, // 36: provisionerd.CompletedJob.TemplateImport.stop_resources:type_name -> provisioner.Resource
|
35, // 36: provisionerd.CompletedJob.TemplateImport.rich_parameters:type_name -> provisioner.RichParameter
|
||||||
36, // 37: provisionerd.CompletedJob.TemplateImport.rich_parameters:type_name -> provisioner.RichParameter
|
36, // 37: provisionerd.CompletedJob.TemplateImport.external_auth_providers:type_name -> provisioner.ExternalAuthProviderResource
|
||||||
37, // 38: provisionerd.CompletedJob.TemplateImport.external_auth_providers:type_name -> provisioner.ExternalAuthProviderResource
|
32, // 38: provisionerd.CompletedJob.TemplateImport.start_modules:type_name -> provisioner.Module
|
||||||
33, // 39: provisionerd.CompletedJob.TemplateImport.start_modules:type_name -> provisioner.Module
|
37, // 39: provisionerd.CompletedJob.TemplateImport.presets:type_name -> provisioner.Preset
|
||||||
38, // 40: provisionerd.CompletedJob.TemplateImport.presets:type_name -> provisioner.Preset
|
31, // 40: provisionerd.CompletedJob.TemplateDryRun.resources:type_name -> provisioner.Resource
|
||||||
32, // 41: provisionerd.CompletedJob.TemplateDryRun.resources:type_name -> provisioner.Resource
|
32, // 41: provisionerd.CompletedJob.TemplateDryRun.modules:type_name -> provisioner.Module
|
||||||
33, // 42: provisionerd.CompletedJob.TemplateDryRun.modules:type_name -> provisioner.Module
|
1, // 42: provisionerd.ProvisionerDaemon.AcquireJob:input_type -> provisionerd.Empty
|
||||||
1, // 43: provisionerd.ProvisionerDaemon.AcquireJob:input_type -> provisionerd.Empty
|
10, // 43: provisionerd.ProvisionerDaemon.AcquireJobWithCancel:input_type -> provisionerd.CancelAcquire
|
||||||
10, // 44: provisionerd.ProvisionerDaemon.AcquireJobWithCancel:input_type -> provisionerd.CancelAcquire
|
8, // 44: provisionerd.ProvisionerDaemon.CommitQuota:input_type -> provisionerd.CommitQuotaRequest
|
||||||
8, // 45: provisionerd.ProvisionerDaemon.CommitQuota:input_type -> provisionerd.CommitQuotaRequest
|
6, // 45: provisionerd.ProvisionerDaemon.UpdateJob:input_type -> provisionerd.UpdateJobRequest
|
||||||
6, // 46: provisionerd.ProvisionerDaemon.UpdateJob:input_type -> provisionerd.UpdateJobRequest
|
3, // 46: provisionerd.ProvisionerDaemon.FailJob:input_type -> provisionerd.FailedJob
|
||||||
3, // 47: provisionerd.ProvisionerDaemon.FailJob:input_type -> provisionerd.FailedJob
|
4, // 47: provisionerd.ProvisionerDaemon.CompleteJob:input_type -> provisionerd.CompletedJob
|
||||||
4, // 48: provisionerd.ProvisionerDaemon.CompleteJob:input_type -> provisionerd.CompletedJob
|
38, // 48: provisionerd.ProvisionerDaemon.UploadFile:input_type -> provisioner.FileUpload
|
||||||
39, // 49: provisionerd.ProvisionerDaemon.UploadFile:input_type -> provisioner.FileUpload
|
11, // 49: provisionerd.ProvisionerDaemon.DownloadFile:input_type -> provisionerd.FileRequest
|
||||||
11, // 50: provisionerd.ProvisionerDaemon.DownloadFile:input_type -> provisionerd.FileRequest
|
2, // 50: provisionerd.ProvisionerDaemon.AcquireJob:output_type -> provisionerd.AcquiredJob
|
||||||
2, // 51: provisionerd.ProvisionerDaemon.AcquireJob:output_type -> provisionerd.AcquiredJob
|
2, // 51: provisionerd.ProvisionerDaemon.AcquireJobWithCancel:output_type -> provisionerd.AcquiredJob
|
||||||
2, // 52: provisionerd.ProvisionerDaemon.AcquireJobWithCancel:output_type -> provisionerd.AcquiredJob
|
9, // 52: provisionerd.ProvisionerDaemon.CommitQuota:output_type -> provisionerd.CommitQuotaResponse
|
||||||
9, // 53: provisionerd.ProvisionerDaemon.CommitQuota:output_type -> provisionerd.CommitQuotaResponse
|
7, // 53: provisionerd.ProvisionerDaemon.UpdateJob:output_type -> provisionerd.UpdateJobResponse
|
||||||
7, // 54: provisionerd.ProvisionerDaemon.UpdateJob:output_type -> provisionerd.UpdateJobResponse
|
1, // 54: provisionerd.ProvisionerDaemon.FailJob:output_type -> provisionerd.Empty
|
||||||
1, // 55: provisionerd.ProvisionerDaemon.FailJob:output_type -> provisionerd.Empty
|
1, // 55: provisionerd.ProvisionerDaemon.CompleteJob:output_type -> provisionerd.Empty
|
||||||
1, // 56: provisionerd.ProvisionerDaemon.CompleteJob:output_type -> provisionerd.Empty
|
1, // 56: provisionerd.ProvisionerDaemon.UploadFile:output_type -> provisionerd.Empty
|
||||||
1, // 57: provisionerd.ProvisionerDaemon.UploadFile:output_type -> provisionerd.Empty
|
38, // 57: provisionerd.ProvisionerDaemon.DownloadFile:output_type -> provisioner.FileUpload
|
||||||
39, // 58: provisionerd.ProvisionerDaemon.DownloadFile:output_type -> provisioner.FileUpload
|
50, // [50:58] is the sub-list for method output_type
|
||||||
51, // [51:59] is the sub-list for method output_type
|
42, // [42:50] is the sub-list for method input_type
|
||||||
43, // [43:51] is the sub-list for method input_type
|
42, // [42:42] is the sub-list for extension type_name
|
||||||
43, // [43:43] is the sub-list for extension type_name
|
42, // [42:42] is the sub-list for extension extendee
|
||||||
43, // [43:43] is the sub-list for extension extendee
|
0, // [0:42] is the sub-list for field type_name
|
||||||
0, // [0:43] is the sub-list for field type_name
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { file_provisionerd_proto_provisionerd_proto_init() }
|
func init() { file_provisionerd_proto_provisionerd_proto_init() }
|
||||||
|
|||||||
@@ -28,10 +28,9 @@ message AcquiredJob {
|
|||||||
repeated provisioner.RichParameterValue previous_parameter_values = 10;
|
repeated provisioner.RichParameterValue previous_parameter_values = 10;
|
||||||
// Reserved 11 for an experiment `exp_reuse_terraform_workspace` (bool) that was replaced.
|
// Reserved 11 for an experiment `exp_reuse_terraform_workspace` (bool) that was replaced.
|
||||||
reserved 11;
|
reserved 11;
|
||||||
// User secrets belonging to the workspace owner, to be forwarded into the
|
// Reserved 12 for `user_secrets` introduced in v1.17 (#24542) and removed
|
||||||
// plan request sent to the provisioner. Only populated for start
|
// in v1.18 along with the rest of the `coder_secret` Terraform integration.
|
||||||
// transitions.
|
reserved 12;
|
||||||
repeated provisioner.UserSecretValue user_secrets = 12;
|
|
||||||
}
|
}
|
||||||
message TemplateImport {
|
message TemplateImport {
|
||||||
provisioner.Metadata metadata = 1;
|
provisioner.Metadata metadata = 1;
|
||||||
|
|||||||
@@ -85,9 +85,16 @@ import "github.com/coder/coder/v2/apiversion"
|
|||||||
// - Added `UserSecretValue` message and `user_secrets` field to `PlanRequest`,
|
// - Added `UserSecretValue` message and `user_secrets` field to `PlanRequest`,
|
||||||
// carrying user secret values from provisioner daemons to provisioners
|
// carrying user secret values from provisioner daemons to provisioners
|
||||||
// during plan.
|
// during plan.
|
||||||
|
//
|
||||||
|
// API v1.18:
|
||||||
|
// - Removed `user_secrets` from `AcquiredJob.WorkspaceBuild` (field 12) and
|
||||||
|
// `PlanRequest` (field 7), along with the `UserSecretValue` message. The
|
||||||
|
// `coder_secret` Terraform integration is being removed; user secrets are
|
||||||
|
// still delivered to running workspaces via the agent manifest path, which
|
||||||
|
// is independent of this proto.
|
||||||
const (
|
const (
|
||||||
CurrentMajor = 1
|
CurrentMajor = 1
|
||||||
CurrentMinor = 17
|
CurrentMinor = 18
|
||||||
)
|
)
|
||||||
|
|
||||||
// CurrentVersion is the current provisionerd API version.
|
// CurrentVersion is the current provisionerd API version.
|
||||||
|
|||||||
@@ -964,7 +964,6 @@ func (r *Runner) runWorkspaceBuild(ctx context.Context) (*proto.CompletedJob, *p
|
|||||||
ExternalAuthProviders: r.job.GetWorkspaceBuild().ExternalAuthProviders,
|
ExternalAuthProviders: r.job.GetWorkspaceBuild().ExternalAuthProviders,
|
||||||
PreviousParameterValues: r.job.GetWorkspaceBuild().PreviousParameterValues,
|
PreviousParameterValues: r.job.GetWorkspaceBuild().PreviousParameterValues,
|
||||||
State: r.job.GetWorkspaceBuild().State,
|
State: r.job.GetWorkspaceBuild().State,
|
||||||
UserSecrets: r.job.GetWorkspaceBuild().UserSecrets,
|
|
||||||
})
|
})
|
||||||
if failed != nil {
|
if failed != nil {
|
||||||
return nil, failed
|
return nil, failed
|
||||||
|
|||||||
Generated
+369
-485
File diff suppressed because it is too large
Load Diff
@@ -422,29 +422,6 @@ message InitComplete {
|
|||||||
bytes module_files_hash = 5;
|
bytes module_files_hash = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserSecretValue carries a single user secret to a provisioner. env_name and
|
|
||||||
// file_path describe the bindings the user requested when creating the secret.
|
|
||||||
// The terraform provisioner exposes secrets via CODER_SECRET_ENV_* and
|
|
||||||
// CODER_SECRET_FILE_* environment variables consumed by terraform-provider-coder's
|
|
||||||
// coder_secret data source
|
|
||||||
message UserSecretValue {
|
|
||||||
// Environment variable name the user selected (e.g. "GITHUB_TOKEN"). Intended
|
|
||||||
// to be treated as an opaque lookup key, i.e. consumers must preserve it
|
|
||||||
// verbatim when matching against a data.coder_secret.env_name attribute.
|
|
||||||
// Consumers can assume names are POSIX-compliant. Optional: env_name and
|
|
||||||
// file_path are independent.
|
|
||||||
string env_name = 1;
|
|
||||||
// Filesystem path the user requested this secret be bound to (e.g. "~/creds"
|
|
||||||
// or "/etc/creds"). This path is not expanded. Expansion happens only where
|
|
||||||
// the secret is actually materialized on disk. Intended to be treated as an
|
|
||||||
// opaque lookup key, i.e. consumers must preserve it verbatim when matching
|
|
||||||
// against a data.coder_secret.file attribute. Optional; env_name and
|
|
||||||
// file_path are independent.
|
|
||||||
string file_path = 2;
|
|
||||||
// Secret value, which may be arbitrary binary data.
|
|
||||||
bytes value = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
// PlanRequest asks the provisioner to plan what resources & parameters it will create
|
// PlanRequest asks the provisioner to plan what resources & parameters it will create
|
||||||
message PlanRequest {
|
message PlanRequest {
|
||||||
Metadata metadata = 1;
|
Metadata metadata = 1;
|
||||||
@@ -456,11 +433,9 @@ message PlanRequest {
|
|||||||
// state is the provisioner state (if any)
|
// state is the provisioner state (if any)
|
||||||
bytes state = 6;
|
bytes state = 6;
|
||||||
|
|
||||||
// User secrets to make available during plan. Not carried on ApplyRequest
|
// Reserved 7 for `user_secrets` introduced in v1.17 (#24542) and removed
|
||||||
// because plan evaluates data.coder_secret references and bakes the
|
// in v1.18 along with the rest of the `coder_secret` Terraform integration.
|
||||||
// resolved values into plan state, so apply does not need the raw secrets.
|
reserved 7;
|
||||||
// Provisioner-specific handling is documented on the UserSecretValue message.
|
|
||||||
repeated UserSecretValue user_secrets = 7;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PlanComplete indicates a request to plan completed.
|
// PlanComplete indicates a request to plan completed.
|
||||||
|
|||||||
Generated
-54
@@ -477,35 +477,6 @@ export interface InitComplete {
|
|||||||
moduleFilesHash: Uint8Array;
|
moduleFilesHash: Uint8Array;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* UserSecretValue carries a single user secret to a provisioner. env_name and
|
|
||||||
* file_path describe the bindings the user requested when creating the secret.
|
|
||||||
* The terraform provisioner exposes secrets via CODER_SECRET_ENV_* and
|
|
||||||
* CODER_SECRET_FILE_* environment variables consumed by terraform-provider-coder's
|
|
||||||
* coder_secret data source
|
|
||||||
*/
|
|
||||||
export interface UserSecretValue {
|
|
||||||
/**
|
|
||||||
* Environment variable name the user selected (e.g. "GITHUB_TOKEN"). Intended
|
|
||||||
* to be treated as an opaque lookup key, i.e. consumers must preserve it
|
|
||||||
* verbatim when matching against a data.coder_secret.env_name attribute.
|
|
||||||
* Consumers can assume names are POSIX-compliant. Optional: env_name and
|
|
||||||
* file_path are independent.
|
|
||||||
*/
|
|
||||||
envName: string;
|
|
||||||
/**
|
|
||||||
* Filesystem path the user requested this secret be bound to (e.g. "~/creds"
|
|
||||||
* or "/etc/creds"). This path is not expanded. Expansion happens only where
|
|
||||||
* the secret is actually materialized on disk. Intended to be treated as an
|
|
||||||
* opaque lookup key, i.e. consumers must preserve it verbatim when matching
|
|
||||||
* against a data.coder_secret.file attribute. Optional; env_name and
|
|
||||||
* file_path are independent.
|
|
||||||
*/
|
|
||||||
filePath: string;
|
|
||||||
/** Secret value, which may be arbitrary binary data. */
|
|
||||||
value: Uint8Array;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** PlanRequest asks the provisioner to plan what resources & parameters it will create */
|
/** PlanRequest asks the provisioner to plan what resources & parameters it will create */
|
||||||
export interface PlanRequest {
|
export interface PlanRequest {
|
||||||
metadata: Metadata | undefined;
|
metadata: Metadata | undefined;
|
||||||
@@ -515,13 +486,6 @@ export interface PlanRequest {
|
|||||||
previousParameterValues: RichParameterValue[];
|
previousParameterValues: RichParameterValue[];
|
||||||
/** state is the provisioner state (if any) */
|
/** state is the provisioner state (if any) */
|
||||||
state: Uint8Array;
|
state: Uint8Array;
|
||||||
/**
|
|
||||||
* User secrets to make available during plan. Not carried on ApplyRequest
|
|
||||||
* because plan evaluates data.coder_secret references and bakes the
|
|
||||||
* resolved values into plan state, so apply does not need the raw secrets.
|
|
||||||
* Provisioner-specific handling is documented on the UserSecretValue message.
|
|
||||||
*/
|
|
||||||
userSecrets: UserSecretValue[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** PlanComplete indicates a request to plan completed. */
|
/** PlanComplete indicates a request to plan completed. */
|
||||||
@@ -1512,21 +1476,6 @@ export const InitComplete = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const UserSecretValue = {
|
|
||||||
encode(message: UserSecretValue, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer {
|
|
||||||
if (message.envName !== "") {
|
|
||||||
writer.uint32(10).string(message.envName);
|
|
||||||
}
|
|
||||||
if (message.filePath !== "") {
|
|
||||||
writer.uint32(18).string(message.filePath);
|
|
||||||
}
|
|
||||||
if (message.value.length !== 0) {
|
|
||||||
writer.uint32(26).bytes(message.value);
|
|
||||||
}
|
|
||||||
return writer;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const PlanRequest = {
|
export const PlanRequest = {
|
||||||
encode(message: PlanRequest, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer {
|
encode(message: PlanRequest, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer {
|
||||||
if (message.metadata !== undefined) {
|
if (message.metadata !== undefined) {
|
||||||
@@ -1547,9 +1496,6 @@ export const PlanRequest = {
|
|||||||
if (message.state.length !== 0) {
|
if (message.state.length !== 0) {
|
||||||
writer.uint32(50).bytes(message.state);
|
writer.uint32(50).bytes(message.state);
|
||||||
}
|
}
|
||||||
for (const v of message.userSecrets) {
|
|
||||||
UserSecretValue.encode(v!, writer.uint32(58).fork()).ldelim();
|
|
||||||
}
|
|
||||||
return writer;
|
return writer;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Generated
+2
-18
@@ -4171,9 +4171,8 @@ export const DisplayApps: DisplayApp[] = [
|
|||||||
// From codersdk/parameters.go
|
// From codersdk/parameters.go
|
||||||
export interface DynamicParametersRequest {
|
export interface DynamicParametersRequest {
|
||||||
/**
|
/**
|
||||||
* ID identifies the request for response ordering. Websocket response
|
* ID identifies the request. The response contains the same
|
||||||
* IDs are monotonically increasing and may exceed the request ID when
|
* ID so that the client can match it to the request.
|
||||||
* server-side events trigger additional renders.
|
|
||||||
*/
|
*/
|
||||||
readonly id: number;
|
readonly id: number;
|
||||||
readonly inputs: Record<string, string>;
|
readonly inputs: Record<string, string>;
|
||||||
@@ -4188,7 +4187,6 @@ export interface DynamicParametersResponse {
|
|||||||
readonly id: number;
|
readonly id: number;
|
||||||
readonly diagnostics: readonly FriendlyDiagnostic[];
|
readonly diagnostics: readonly FriendlyDiagnostic[];
|
||||||
readonly parameters: readonly PreviewParameter[];
|
readonly parameters: readonly PreviewParameter[];
|
||||||
readonly secret_requirements?: readonly SecretRequirementStatus[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// From codersdk/chats.go
|
// From codersdk/chats.go
|
||||||
@@ -6944,12 +6942,6 @@ export interface RequestOneTimePasscodeRequest {
|
|||||||
// From codersdk/workspaces.go
|
// From codersdk/workspaces.go
|
||||||
export interface ResolveAutostartResponse {
|
export interface ResolveAutostartResponse {
|
||||||
readonly parameter_mismatch: boolean;
|
readonly parameter_mismatch: boolean;
|
||||||
/**
|
|
||||||
* SecretMismatch is true when the active template version declares
|
|
||||||
* `coder_secret` requirements that the workspace owner's secrets do not
|
|
||||||
* satisfy.
|
|
||||||
*/
|
|
||||||
readonly secret_mismatch: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// From codersdk/audit.go
|
// From codersdk/audit.go
|
||||||
@@ -7241,14 +7233,6 @@ export interface STUNReport {
|
|||||||
readonly Error: string | null;
|
readonly Error: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// From codersdk/parameters.go
|
|
||||||
export interface SecretRequirementStatus {
|
|
||||||
readonly env?: string;
|
|
||||||
readonly file?: string;
|
|
||||||
readonly help_message: string;
|
|
||||||
readonly satisfied: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
// From serpent/serpent.go
|
// From serpent/serpent.go
|
||||||
/**
|
/**
|
||||||
* Annotations is an arbitrary key-mapping used to extend the Option and Command types.
|
* Annotations is an arbitrary key-mapping used to extend the Option and Command types.
|
||||||
|
|||||||
Reference in New Issue
Block a user