Files
coder/coderd/dynamicparameters/static.go
T
dylanhuff-at-coder fb84e72319 feat: add secret requirement contract to dynamic parameters (#24785)
Adds structured `secret_requirements` to dynamic parameter responses and
enforces missing required secrets during workspace start.

Stop, delete, and tag rendering paths skip secret requirement
enforcement so unmet secrets do not prevent cleanup. The SDK, generated
API docs/types, and backend render/resolver/wsbuilder tests are updated
for the new contract.
2026-04-29 16:38:26 -07:00

150 lines
4.6 KiB
Go

package dynamicparameters
import (
"context"
"encoding/json"
"github.com/google/uuid"
"github.com/hashicorp/hcl/v2"
"golang.org/x/xerrors"
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/util/ptr"
"github.com/coder/coder/v2/coderd/util/slice"
sdkproto "github.com/coder/coder/v2/provisionersdk/proto"
"github.com/coder/preview"
previewtypes "github.com/coder/preview/types"
"github.com/coder/terraform-provider-coder/v2/provider"
)
type staticRender struct {
staticParams []previewtypes.Parameter
}
func (r *loader) staticRender(ctx context.Context, db database.Store) (*staticRender, error) {
dbTemplateVersionParameters, err := db.GetTemplateVersionParameters(ctx, r.templateVersionID)
if err != nil {
return nil, xerrors.Errorf("template version parameters: %w", err)
}
params := slice.List(dbTemplateVersionParameters, TemplateVersionParameter)
for i, param := range params {
// Update the diagnostics to validate the 'default' value.
// We do not have a user supplied value yet, so we use the default.
params[i].Diagnostics = append(params[i].Diagnostics, previewtypes.Diagnostics(param.Valid(param.Value))...)
}
return &staticRender{
staticParams: params,
}, nil
}
func (r *staticRender) Render(_ context.Context, _ uuid.UUID, values map[string]string, _ ...RenderOption) (*RenderResult, hcl.Diagnostics) {
params := r.staticParams
for i := range params {
param := &params[i]
paramValue, ok := values[param.Name]
if ok {
param.Value = previewtypes.StringLiteral(paramValue)
} else {
param.Value = param.DefaultValue
}
param.Diagnostics = previewtypes.Diagnostics(param.Valid(param.Value))
}
return &RenderResult{
Output: &preview.Output{
Parameters: params,
},
}, hcl.Diagnostics{
{
// Only a warning because the form does still work.
Severity: hcl.DiagWarning,
Summary: "This template version is missing required metadata to support dynamic parameters.",
Detail: "To restore full functionality, please re-import the terraform as a new template version.",
},
}
}
func (*staticRender) Close() {}
func TemplateVersionParameter(it database.TemplateVersionParameter) previewtypes.Parameter {
param := previewtypes.Parameter{
ParameterData: previewtypes.ParameterData{
Name: it.Name,
DisplayName: it.DisplayName,
Description: it.Description,
Type: previewtypes.ParameterType(it.Type),
FormType: provider.ParameterFormType(it.FormType),
Styling: previewtypes.ParameterStyling{},
Mutable: it.Mutable,
DefaultValue: previewtypes.StringLiteral(it.DefaultValue),
Icon: it.Icon,
Options: make([]*previewtypes.ParameterOption, 0),
Validations: make([]*previewtypes.ParameterValidation, 0),
Required: it.Required,
Order: int64(it.DisplayOrder),
Ephemeral: it.Ephemeral,
Source: nil,
},
// Always use the default, since we used to assume the empty string
Value: previewtypes.StringLiteral(it.DefaultValue),
Diagnostics: make(previewtypes.Diagnostics, 0),
}
if it.ValidationError != "" || it.ValidationRegex != "" || it.ValidationMonotonic != "" {
var reg *string
if it.ValidationRegex != "" {
reg = ptr.Ref(it.ValidationRegex)
}
var vMin *int64
if it.ValidationMin.Valid {
vMin = ptr.Ref(int64(it.ValidationMin.Int32))
}
var vMax *int64
if it.ValidationMax.Valid {
vMax = ptr.Ref(int64(it.ValidationMax.Int32))
}
var monotonic *string
if it.ValidationMonotonic != "" {
monotonic = ptr.Ref(it.ValidationMonotonic)
}
param.Validations = append(param.Validations, &previewtypes.ParameterValidation{
Error: it.ValidationError,
Regex: reg,
Min: vMin,
Max: vMax,
Monotonic: monotonic,
})
}
var protoOptions []*sdkproto.RichParameterOption
err := json.Unmarshal(it.Options, &protoOptions)
if err != nil {
param.Diagnostics = append(param.Diagnostics, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Failed to parse json parameter options",
Detail: err.Error(),
})
}
for _, opt := range protoOptions {
param.Options = append(param.Options, &previewtypes.ParameterOption{
Name: opt.Name,
Description: opt.Description,
Value: previewtypes.StringLiteral(opt.Value),
Icon: opt.Icon,
})
}
// Take the form type from the ValidateFormType function. This is a bit
// unfortunate we have to do this, but it will return the default form_type
// for a given set of conditions.
_, param.FormType, _ = provider.ValidateFormType(provider.OptionType(param.Type), len(param.Options), param.FormType)
return param
}