mirror of
https://github.com/coder/coder.git
synced 2025-07-09 11:45:56 +00:00
chore: disable parameter validatation for dynamic params for all transitions (#17926)
Dynamic params skip parameter validation in coder/coder. This is because conditional parameters cannot be validated with the static parameters in the database.
This commit is contained in:
4
coderd/apidoc/docs.go
generated
4
coderd/apidoc/docs.go
generated
@ -11998,6 +11998,10 @@ const docTemplate = `{
|
||||
"dry_run": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"enable_dynamic_parameters": {
|
||||
"description": "EnableDynamicParameters skips some of the static parameter checking.\nIt will default to whatever the template has marked as the default experience.\nRequires the \"dynamic-experiment\" to be used.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"log_level": {
|
||||
"description": "Log level changes the default logging verbosity of a provider (\"info\" if empty).",
|
||||
"enum": [
|
||||
|
4
coderd/apidoc/swagger.json
generated
4
coderd/apidoc/swagger.json
generated
@ -10716,6 +10716,10 @@
|
||||
"dry_run": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"enable_dynamic_parameters": {
|
||||
"description": "EnableDynamicParameters skips some of the static parameter checking.\nIt will default to whatever the template has marked as the default experience.\nRequires the \"dynamic-experiment\" to be used.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"log_level": {
|
||||
"description": "Log level changes the default logging verbosity of a provider (\"info\" if empty).",
|
||||
"enum": ["debug"],
|
||||
|
@ -27,6 +27,7 @@ import (
|
||||
"github.com/coder/coder/v2/coderd/notifications"
|
||||
"github.com/coder/coder/v2/coderd/schedule"
|
||||
"github.com/coder/coder/v2/coderd/wsbuilder"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
)
|
||||
|
||||
// Executor automatically starts or stops workspaces.
|
||||
@ -43,6 +44,7 @@ type Executor struct {
|
||||
// NotificationsEnqueuer handles enqueueing notifications for delivery by SMTP, webhook, etc.
|
||||
notificationsEnqueuer notifications.Enqueuer
|
||||
reg prometheus.Registerer
|
||||
experiments codersdk.Experiments
|
||||
|
||||
metrics executorMetrics
|
||||
}
|
||||
@ -59,7 +61,7 @@ type Stats struct {
|
||||
}
|
||||
|
||||
// New returns a new wsactions executor.
|
||||
func NewExecutor(ctx context.Context, db database.Store, ps pubsub.Pubsub, reg prometheus.Registerer, tss *atomic.Pointer[schedule.TemplateScheduleStore], auditor *atomic.Pointer[audit.Auditor], acs *atomic.Pointer[dbauthz.AccessControlStore], log slog.Logger, tick <-chan time.Time, enqueuer notifications.Enqueuer) *Executor {
|
||||
func NewExecutor(ctx context.Context, db database.Store, ps pubsub.Pubsub, reg prometheus.Registerer, tss *atomic.Pointer[schedule.TemplateScheduleStore], auditor *atomic.Pointer[audit.Auditor], acs *atomic.Pointer[dbauthz.AccessControlStore], log slog.Logger, tick <-chan time.Time, enqueuer notifications.Enqueuer, exp codersdk.Experiments) *Executor {
|
||||
factory := promauto.With(reg)
|
||||
le := &Executor{
|
||||
//nolint:gocritic // Autostart has a limited set of permissions.
|
||||
@ -73,6 +75,7 @@ func NewExecutor(ctx context.Context, db database.Store, ps pubsub.Pubsub, reg p
|
||||
accessControlStore: acs,
|
||||
notificationsEnqueuer: enqueuer,
|
||||
reg: reg,
|
||||
experiments: exp,
|
||||
metrics: executorMetrics{
|
||||
autobuildExecutionDuration: factory.NewHistogram(prometheus.HistogramOpts{
|
||||
Namespace: "coderd",
|
||||
@ -258,6 +261,7 @@ func (e *Executor) runOnce(t time.Time) Stats {
|
||||
builder := wsbuilder.New(ws, nextTransition).
|
||||
SetLastWorkspaceBuildInTx(&latestBuild).
|
||||
SetLastWorkspaceBuildJobInTx(&latestJob).
|
||||
Experiments(e.experiments).
|
||||
Reason(reason)
|
||||
log.Debug(e.ctx, "auto building workspace", slog.F("transition", nextTransition))
|
||||
if nextTransition == database.WorkspaceTransitionStart &&
|
||||
|
@ -354,6 +354,7 @@ func NewOptions(t testing.TB, options *Options) (func(http.Handler), context.Can
|
||||
auditor.Store(&options.Auditor)
|
||||
|
||||
ctx, cancelFunc := context.WithCancel(context.Background())
|
||||
experiments := coderd.ReadExperiments(*options.Logger, options.DeploymentValues.Experiments)
|
||||
lifecycleExecutor := autobuild.NewExecutor(
|
||||
ctx,
|
||||
options.Database,
|
||||
@ -365,6 +366,7 @@ func NewOptions(t testing.TB, options *Options) (func(http.Handler), context.Can
|
||||
*options.Logger,
|
||||
options.AutobuildTicker,
|
||||
options.NotificationsEnqueuer,
|
||||
experiments,
|
||||
).WithStatsChannel(options.AutobuildStats)
|
||||
lifecycleExecutor.Run()
|
||||
|
||||
|
@ -12,13 +12,13 @@ import (
|
||||
"golang.org/x/sync/errgroup"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/v2/apiversion"
|
||||
"github.com/coder/coder/v2/coderd/database"
|
||||
"github.com/coder/coder/v2/coderd/database/dbauthz"
|
||||
"github.com/coder/coder/v2/coderd/files"
|
||||
"github.com/coder/coder/v2/coderd/httpapi"
|
||||
"github.com/coder/coder/v2/coderd/httpmw"
|
||||
"github.com/coder/coder/v2/coderd/util/ptr"
|
||||
"github.com/coder/coder/v2/coderd/wsbuilder"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
"github.com/coder/coder/v2/codersdk/wsjson"
|
||||
sdkproto "github.com/coder/coder/v2/provisionersdk/proto"
|
||||
@ -69,13 +69,10 @@ func (api *API) templateVersionDynamicParameters(rw http.ResponseWriter, r *http
|
||||
return
|
||||
}
|
||||
|
||||
major, minor, err := apiversion.Parse(tf.ProvisionerdVersion)
|
||||
// If the api version is not valid or less than 1.5, we need to use the static parameters
|
||||
useStaticParams := err != nil || major < 1 || (major == 1 && minor < 6)
|
||||
if useStaticParams {
|
||||
api.handleStaticParameters(rw, r, templateVersion.ID)
|
||||
} else {
|
||||
if wsbuilder.ProvisionerVersionSupportsDynamicParameters(tf.ProvisionerdVersion) {
|
||||
api.handleDynamicParameters(rw, r, tf, templateVersion)
|
||||
} else {
|
||||
api.handleStaticParameters(rw, r, templateVersion.ID)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -338,6 +338,7 @@ func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) {
|
||||
RichParameterValues(createBuild.RichParameterValues).
|
||||
LogLevel(string(createBuild.LogLevel)).
|
||||
DeploymentValues(api.Options.DeploymentValues).
|
||||
Experiments(api.Experiments).
|
||||
TemplateVersionPresetID(createBuild.TemplateVersionPresetID)
|
||||
|
||||
var (
|
||||
@ -383,6 +384,22 @@ func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) {
|
||||
builder = builder.State(createBuild.ProvisionerState)
|
||||
}
|
||||
|
||||
// Only defer to dynamic parameters if the experiment is enabled.
|
||||
if api.Experiments.Enabled(codersdk.ExperimentDynamicParameters) {
|
||||
if createBuild.EnableDynamicParameters != nil {
|
||||
// Explicit opt-in
|
||||
builder = builder.DynamicParameters(*createBuild.EnableDynamicParameters)
|
||||
}
|
||||
} else {
|
||||
if createBuild.EnableDynamicParameters != nil {
|
||||
api.Logger.Warn(ctx, "ignoring dynamic parameter field sent by request, the experiment is not enabled",
|
||||
slog.F("field", *createBuild.EnableDynamicParameters),
|
||||
slog.F("user", apiKey.UserID.String()),
|
||||
slog.F("transition", string(createBuild.Transition)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
workspaceBuild, provisionerJob, provisionerDaemons, err = builder.Build(
|
||||
ctx,
|
||||
tx,
|
||||
|
@ -704,6 +704,8 @@ func createWorkspace(
|
||||
Reason(database.BuildReasonInitiator).
|
||||
Initiator(initiatorID).
|
||||
ActiveVersion().
|
||||
Experiments(api.Experiments).
|
||||
DeploymentValues(api.DeploymentValues).
|
||||
RichParameterValues(req.RichParameterValues)
|
||||
if req.TemplateVersionID != uuid.Nil {
|
||||
builder = builder.VersionID(req.TemplateVersionID)
|
||||
@ -716,7 +718,7 @@ func createWorkspace(
|
||||
}
|
||||
|
||||
if req.EnableDynamicParameters && api.Experiments.Enabled(codersdk.ExperimentDynamicParameters) {
|
||||
builder = builder.UsingDynamicParameters()
|
||||
builder = builder.DynamicParameters(req.EnableDynamicParameters)
|
||||
}
|
||||
|
||||
workspaceBuild, provisionerJob, provisionerDaemons, err = builder.Build(
|
||||
|
@ -13,7 +13,9 @@ import (
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||
|
||||
"github.com/coder/coder/v2/apiversion"
|
||||
"github.com/coder/coder/v2/coderd/rbac/policy"
|
||||
"github.com/coder/coder/v2/coderd/util/ptr"
|
||||
"github.com/coder/coder/v2/provisioner/terraform/tfparse"
|
||||
"github.com/coder/coder/v2/provisionersdk"
|
||||
sdkproto "github.com/coder/coder/v2/provisionersdk/proto"
|
||||
@ -51,9 +53,11 @@ type Builder struct {
|
||||
state stateTarget
|
||||
logLevel string
|
||||
deploymentValues *codersdk.DeploymentValues
|
||||
experiments codersdk.Experiments
|
||||
|
||||
richParameterValues []codersdk.WorkspaceBuildParameter
|
||||
dynamicParametersEnabled bool
|
||||
richParameterValues []codersdk.WorkspaceBuildParameter
|
||||
// dynamicParametersEnabled is non-nil if set externally
|
||||
dynamicParametersEnabled *bool
|
||||
initiator uuid.UUID
|
||||
reason database.BuildReason
|
||||
templateVersionPresetID uuid.UUID
|
||||
@ -66,6 +70,7 @@ type Builder struct {
|
||||
template *database.Template
|
||||
templateVersion *database.TemplateVersion
|
||||
templateVersionJob *database.ProvisionerJob
|
||||
terraformValues *database.TemplateVersionTerraformValue
|
||||
templateVersionParameters *[]database.TemplateVersionParameter
|
||||
templateVersionVariables *[]database.TemplateVersionVariable
|
||||
templateVersionWorkspaceTags *[]database.TemplateVersionWorkspaceTag
|
||||
@ -155,6 +160,14 @@ func (b Builder) DeploymentValues(dv *codersdk.DeploymentValues) Builder {
|
||||
return b
|
||||
}
|
||||
|
||||
func (b Builder) Experiments(exp codersdk.Experiments) Builder {
|
||||
// nolint: revive
|
||||
cpy := make(codersdk.Experiments, len(exp))
|
||||
copy(cpy, exp)
|
||||
b.experiments = cpy
|
||||
return b
|
||||
}
|
||||
|
||||
func (b Builder) Initiator(u uuid.UUID) Builder {
|
||||
// nolint: revive
|
||||
b.initiator = u
|
||||
@ -187,8 +200,9 @@ func (b Builder) MarkPrebuiltWorkspaceClaim() Builder {
|
||||
return b
|
||||
}
|
||||
|
||||
func (b Builder) UsingDynamicParameters() Builder {
|
||||
b.dynamicParametersEnabled = true
|
||||
func (b Builder) DynamicParameters(using bool) Builder {
|
||||
// nolint: revive
|
||||
b.dynamicParametersEnabled = ptr.Ref(using)
|
||||
return b
|
||||
}
|
||||
|
||||
@ -516,6 +530,22 @@ func (b *Builder) getTemplateVersionID() (uuid.UUID, error) {
|
||||
return bld.TemplateVersionID, nil
|
||||
}
|
||||
|
||||
func (b *Builder) getTemplateTerraformValues() (*database.TemplateVersionTerraformValue, error) {
|
||||
if b.terraformValues != nil {
|
||||
return b.terraformValues, nil
|
||||
}
|
||||
v, err := b.getTemplateVersion()
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("get template version so we can get terraform values: %w", err)
|
||||
}
|
||||
vals, err := b.store.GetTemplateVersionTerraformValues(b.ctx, v.ID)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("get template version terraform values %s: %w", v.JobID, err)
|
||||
}
|
||||
b.terraformValues = &vals
|
||||
return b.terraformValues, err
|
||||
}
|
||||
|
||||
func (b *Builder) getLastBuild() (*database.WorkspaceBuild, error) {
|
||||
if b.lastBuild != nil {
|
||||
return b.lastBuild, nil
|
||||
@ -593,9 +623,10 @@ func (b *Builder) getParameters() (names, values []string, err error) {
|
||||
return nil, nil, BuildError{http.StatusBadRequest, "Unable to build workspace with unsupported parameters", err}
|
||||
}
|
||||
|
||||
if b.dynamicParametersEnabled {
|
||||
// Dynamic parameters skip all parameter validation.
|
||||
// Pass the user's input as is.
|
||||
// Dynamic parameters skip all parameter validation.
|
||||
// Deleting a workspace also should skip parameter validation.
|
||||
// Pass the user's input as is.
|
||||
if b.usingDynamicParameters() {
|
||||
// TODO: The previous behavior was only to pass param values
|
||||
// for parameters that exist. Since dynamic params can have
|
||||
// conditional parameter existence, the static frame of reference
|
||||
@ -989,3 +1020,36 @@ func (b *Builder) checkRunningBuild() error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Builder) usingDynamicParameters() bool {
|
||||
if !b.experiments.Enabled(codersdk.ExperimentDynamicParameters) {
|
||||
// Experiment required
|
||||
return false
|
||||
}
|
||||
|
||||
vals, err := b.getTemplateTerraformValues()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if !ProvisionerVersionSupportsDynamicParameters(vals.ProvisionerdVersion) {
|
||||
return false
|
||||
}
|
||||
|
||||
if b.dynamicParametersEnabled != nil {
|
||||
return *b.dynamicParametersEnabled
|
||||
}
|
||||
|
||||
tpl, err := b.getTemplate()
|
||||
if err != nil {
|
||||
return false // Let another part of the code get this error
|
||||
}
|
||||
return !tpl.UseClassicParameterFlow
|
||||
}
|
||||
|
||||
func ProvisionerVersionSupportsDynamicParameters(version string) bool {
|
||||
major, minor, err := apiversion.Parse(version)
|
||||
// If the api version is not valid or less than 1.6, we need to use the static parameters
|
||||
useStaticParams := err != nil || major < 1 || (major == 1 && minor < 6)
|
||||
return !useStaticParams
|
||||
}
|
||||
|
@ -839,6 +839,32 @@ func TestWorkspaceBuildWithPreset(t *testing.T) {
|
||||
req.NoError(err)
|
||||
}
|
||||
|
||||
func TestProvisionerVersionSupportsDynamicParameters(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for v, dyn := range map[string]bool{
|
||||
"": false,
|
||||
"na": false,
|
||||
"0.0": false,
|
||||
"0.10": false,
|
||||
"1.4": false,
|
||||
"1.5": false,
|
||||
"1.6": true,
|
||||
"1.7": true,
|
||||
"1.8": true,
|
||||
"2.0": true,
|
||||
"2.17": true,
|
||||
"4.0": true,
|
||||
} {
|
||||
t.Run(v, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
does := wsbuilder.ProvisionerVersionSupportsDynamicParameters(v)
|
||||
require.Equal(t, dyn, does)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type txExpect func(mTx *dbmock.MockStore)
|
||||
|
||||
func expectDB(t *testing.T, opts ...txExpect) *dbmock.MockStore {
|
||||
|
Reference in New Issue
Block a user