diff --git a/coderd/coderd.go b/coderd/coderd.go index 98ae7a8ede413..a12a5624b931c 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -1774,6 +1774,7 @@ func (api *API) CreateInMemoryTaggedProvisionerDaemon(dialCtx context.Context, n logger := api.Logger.Named(fmt.Sprintf("inmem-provisionerd-%s", name)) srv, err := provisionerdserver.NewServer( api.ctx, // use the same ctx as the API + daemon.APIVersion, api.AccessURL, daemon.ID, defaultOrg.ID, diff --git a/coderd/database/dbgen/dbgen.go b/coderd/database/dbgen/dbgen.go index fa1f297b908ba..8a345fa0fd6e7 100644 --- a/coderd/database/dbgen/dbgen.go +++ b/coderd/database/dbgen/dbgen.go @@ -29,6 +29,7 @@ import ( "github.com/coder/coder/v2/coderd/rbac" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/cryptorand" + "github.com/coder/coder/v2/provisionerd/proto" "github.com/coder/coder/v2/testutil" ) @@ -1000,10 +1001,11 @@ func TemplateVersionTerraformValues(t testing.TB, db database.Store, orig databa t.Helper() params := database.InsertTemplateVersionTerraformValuesByJobIDParams{ - JobID: takeFirst(orig.JobID, uuid.New()), - CachedPlan: takeFirstSlice(orig.CachedPlan, []byte("{}")), - CachedModuleFiles: orig.CachedModuleFiles, - UpdatedAt: takeFirst(orig.UpdatedAt, dbtime.Now()), + JobID: takeFirst(orig.JobID, uuid.New()), + CachedPlan: takeFirstSlice(orig.CachedPlan, []byte("{}")), + CachedModuleFiles: orig.CachedModuleFiles, + UpdatedAt: takeFirst(orig.UpdatedAt, dbtime.Now()), + ProvisionerdVersion: takeFirst(orig.ProvisionerdVersion, proto.CurrentVersion.String()), } err := db.InsertTemplateVersionTerraformValuesByJobID(genCtx, params) diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index dfa28097ab60c..63693604ae262 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -9343,10 +9343,11 @@ func (q *FakeQuerier) InsertTemplateVersionTerraformValuesByJobID(_ context.Cont // Insert the new row row := database.TemplateVersionTerraformValue{ - TemplateVersionID: templateVersion.ID, - CachedPlan: arg.CachedPlan, - CachedModuleFiles: arg.CachedModuleFiles, - UpdatedAt: arg.UpdatedAt, + TemplateVersionID: templateVersion.ID, + UpdatedAt: arg.UpdatedAt, + CachedPlan: arg.CachedPlan, + CachedModuleFiles: arg.CachedModuleFiles, + ProvisionerdVersion: arg.ProvisionerdVersion, } q.templateVersionTerraformValues = append(q.templateVersionTerraformValues, row) return nil diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index a03ea910f937c..f56b417dbe4d4 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -1441,9 +1441,12 @@ CREATE TABLE template_version_terraform_values ( template_version_id uuid NOT NULL, updated_at timestamp with time zone DEFAULT now() NOT NULL, cached_plan jsonb NOT NULL, - cached_module_files uuid + cached_module_files uuid, + provisionerd_version text DEFAULT ''::text NOT NULL ); +COMMENT ON COLUMN template_version_terraform_values.provisionerd_version IS 'What version of the provisioning engine was used to generate the cached plan and module files.'; + CREATE TABLE template_version_variables ( template_version_id uuid NOT NULL, name text NOT NULL, diff --git a/coderd/database/migrations/000325_dynamic_parameters_metadata.down.sql b/coderd/database/migrations/000325_dynamic_parameters_metadata.down.sql new file mode 100644 index 0000000000000..991871b5700ab --- /dev/null +++ b/coderd/database/migrations/000325_dynamic_parameters_metadata.down.sql @@ -0,0 +1 @@ +ALTER TABLE template_version_terraform_values DROP COLUMN provisionerd_version; diff --git a/coderd/database/migrations/000325_dynamic_parameters_metadata.up.sql b/coderd/database/migrations/000325_dynamic_parameters_metadata.up.sql new file mode 100644 index 0000000000000..211693b7f3e79 --- /dev/null +++ b/coderd/database/migrations/000325_dynamic_parameters_metadata.up.sql @@ -0,0 +1,4 @@ +ALTER TABLE template_version_terraform_values ADD COLUMN IF NOT EXISTS provisionerd_version TEXT NOT NULL DEFAULT ''; + +COMMENT ON COLUMN template_version_terraform_values.provisionerd_version IS + 'What version of the provisioning engine was used to generate the cached plan and module files.'; diff --git a/coderd/database/models.go b/coderd/database/models.go index 3944d56268eaf..1b6ea7591d652 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -3225,6 +3225,8 @@ type TemplateVersionTerraformValue struct { UpdatedAt time.Time `db:"updated_at" json:"updated_at"` CachedPlan json.RawMessage `db:"cached_plan" json:"cached_plan"` CachedModuleFiles uuid.NullUUID `db:"cached_module_files" json:"cached_module_files"` + // What version of the provisioning engine was used to generate the cached plan and module files. + ProvisionerdVersion string `db:"provisionerd_version" json:"provisionerd_version"` } type TemplateVersionVariable struct { diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index bd1d5cddd43ed..0fd886cf39f2b 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -11702,7 +11702,7 @@ func (q *sqlQuerier) UpdateTemplateVersionExternalAuthProvidersByJobID(ctx conte const getTemplateVersionTerraformValues = `-- name: GetTemplateVersionTerraformValues :one SELECT - template_version_terraform_values.template_version_id, template_version_terraform_values.updated_at, template_version_terraform_values.cached_plan, template_version_terraform_values.cached_module_files + template_version_terraform_values.template_version_id, template_version_terraform_values.updated_at, template_version_terraform_values.cached_plan, template_version_terraform_values.cached_module_files, template_version_terraform_values.provisionerd_version FROM template_version_terraform_values WHERE @@ -11717,6 +11717,7 @@ func (q *sqlQuerier) GetTemplateVersionTerraformValues(ctx context.Context, temp &i.UpdatedAt, &i.CachedPlan, &i.CachedModuleFiles, + &i.ProvisionerdVersion, ) return i, err } @@ -11727,22 +11728,25 @@ INSERT INTO template_version_id, cached_plan, cached_module_files, - updated_at + updated_at, + provisionerd_version ) VALUES ( (select id from template_versions where job_id = $1), $2, $3, - $4 + $4, + $5 ) ` type InsertTemplateVersionTerraformValuesByJobIDParams struct { - JobID uuid.UUID `db:"job_id" json:"job_id"` - CachedPlan json.RawMessage `db:"cached_plan" json:"cached_plan"` - CachedModuleFiles uuid.NullUUID `db:"cached_module_files" json:"cached_module_files"` - UpdatedAt time.Time `db:"updated_at" json:"updated_at"` + JobID uuid.UUID `db:"job_id" json:"job_id"` + CachedPlan json.RawMessage `db:"cached_plan" json:"cached_plan"` + CachedModuleFiles uuid.NullUUID `db:"cached_module_files" json:"cached_module_files"` + UpdatedAt time.Time `db:"updated_at" json:"updated_at"` + ProvisionerdVersion string `db:"provisionerd_version" json:"provisionerd_version"` } func (q *sqlQuerier) InsertTemplateVersionTerraformValuesByJobID(ctx context.Context, arg InsertTemplateVersionTerraformValuesByJobIDParams) error { @@ -11751,6 +11755,7 @@ func (q *sqlQuerier) InsertTemplateVersionTerraformValuesByJobID(ctx context.Con arg.CachedPlan, arg.CachedModuleFiles, arg.UpdatedAt, + arg.ProvisionerdVersion, ) return err } diff --git a/coderd/database/queries/templateversionterraformvalues.sql b/coderd/database/queries/templateversionterraformvalues.sql index b4c93081177f1..2ded4a2675375 100644 --- a/coderd/database/queries/templateversionterraformvalues.sql +++ b/coderd/database/queries/templateversionterraformvalues.sql @@ -12,12 +12,14 @@ INSERT INTO template_version_id, cached_plan, cached_module_files, - updated_at + updated_at, + provisionerd_version ) VALUES ( (select id from template_versions where job_id = @job_id), @cached_plan, @cached_module_files, - @updated_at + @updated_at, + @provisionerd_version ); diff --git a/coderd/parameters.go b/coderd/parameters.go index 6b6f4db531533..9d90bc5a0226f 100644 --- a/coderd/parameters.go +++ b/coderd/parameters.go @@ -8,9 +8,11 @@ import ( "time" "github.com/google/uuid" + "github.com/hashicorp/hcl/v2" "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" @@ -114,6 +116,9 @@ func (api *API) templateVersionDynamicParameters(rw http.ResponseWriter, r *http return } + // If the err is sql.ErrNoRows, an empty terraform values struct is correct. + staticDiagnostics := parameterProvisionerVersionDiagnostic(tf) + owner, err := api.getWorkspaceOwnerData(ctx, user, templateVersion.OrganizationID) if err != nil { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ @@ -148,7 +153,7 @@ func (api *API) templateVersionDynamicParameters(rw http.ResponseWriter, r *http result, diagnostics := preview.Preview(ctx, input, templateFS) response := codersdk.DynamicParametersResponse{ ID: -1, - Diagnostics: previewtypes.Diagnostics(diagnostics), + Diagnostics: previewtypes.Diagnostics(diagnostics.Extend(staticDiagnostics)), } if result != nil { response.Parameters = result.Parameters @@ -176,7 +181,7 @@ func (api *API) templateVersionDynamicParameters(rw http.ResponseWriter, r *http result, diagnostics := preview.Preview(ctx, input, templateFS) response := codersdk.DynamicParametersResponse{ ID: update.ID, - Diagnostics: previewtypes.Diagnostics(diagnostics), + Diagnostics: previewtypes.Diagnostics(diagnostics.Extend(staticDiagnostics)), } if result != nil { response.Parameters = result.Parameters @@ -269,3 +274,31 @@ func (api *API) getWorkspaceOwnerData( Groups: groupNames, }, nil } + +// parameterProvisionerVersionDiagnostic checks the version of the provisioner +// used to create the template version. If the version is less than 1.5, it +// returns a warning diagnostic. Only versions 1.5+ return the module & plan data +// required. +func parameterProvisionerVersionDiagnostic(tf database.TemplateVersionTerraformValue) hcl.Diagnostics { + missingMetadata := hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "This template version is missing required metadata to support dynamic parameters. Go back to the classic creation flow.", + Detail: "To restore full functionality, please re-import the terraform as a new template version.", + } + + if tf.ProvisionerdVersion == "" { + return hcl.Diagnostics{&missingMetadata} + } + + major, minor, err := apiversion.Parse(tf.ProvisionerdVersion) + if err != nil || tf.ProvisionerdVersion == "" { + return hcl.Diagnostics{&missingMetadata} + } else if major < 1 || (major == 1 && minor < 5) { + missingMetadata.Detail = "This template version does not support dynamic parameters. " + + "Some options may be missing or incorrect. " + + "Please contact an administrator to update the provisioner and re-import the template version." + return hcl.Diagnostics{&missingMetadata} + } + + return nil +} diff --git a/coderd/parameters_internal_test.go b/coderd/parameters_internal_test.go new file mode 100644 index 0000000000000..a02baeae380b6 --- /dev/null +++ b/coderd/parameters_internal_test.go @@ -0,0 +1,77 @@ +package coderd + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/coder/coder/v2/coderd/database" +) + +func Test_parameterProvisionerVersionDiagnostic(t *testing.T) { + t.Parallel() + + testCases := []struct { + version string + warning bool + }{ + { + version: "", + warning: true, + }, + { + version: "invalid", + warning: true, + }, + { + version: "0.4", + warning: true, + }, + { + version: "0.5", + warning: true, + }, + { + version: "0.6", + warning: true, + }, + { + version: "1.4", + warning: true, + }, + { + version: "1.5", + warning: false, + }, + { + version: "1.6", + warning: false, + }, + { + version: "2.0", + warning: false, + }, + { + version: "2.5", + warning: false, + }, + { + version: "2.6", + warning: false, + }, + } + + for _, tc := range testCases { + t.Run("Version_"+tc.version, func(t *testing.T) { + t.Parallel() + diags := parameterProvisionerVersionDiagnostic(database.TemplateVersionTerraformValue{ + ProvisionerdVersion: tc.version, + }) + if tc.warning { + require.Len(t, diags, 1, "expected warning") + } else { + require.Len(t, diags, 0, "expected no warning") + } + }) + } +} diff --git a/coderd/provisionerdserver/provisionerdserver.go b/coderd/provisionerdserver/provisionerdserver.go index 075f927650284..cb7aefb717ab0 100644 --- a/coderd/provisionerdserver/provisionerdserver.go +++ b/coderd/provisionerdserver/provisionerdserver.go @@ -95,6 +95,7 @@ type Options struct { } type server struct { + apiVersion string // lifecycleCtx must be tied to the API server's lifecycle // as when the API server shuts down, we want to cancel any // long-running operations. @@ -153,7 +154,9 @@ func (t Tags) Valid() error { return nil } -func NewServer(lifecycleCtx context.Context, +func NewServer( + lifecycleCtx context.Context, + apiVersion string, accessURL *url.URL, id uuid.UUID, organizationID uuid.UUID, @@ -214,6 +217,7 @@ func NewServer(lifecycleCtx context.Context, s := &server{ lifecycleCtx: lifecycleCtx, + apiVersion: apiVersion, AccessURL: accessURL, ID: id, OrganizationID: organizationID, @@ -1536,10 +1540,11 @@ func (s *server) CompleteJob(ctx context.Context, completed *proto.CompletedJob) } err = s.Database.InsertTemplateVersionTerraformValuesByJobID(ctx, database.InsertTemplateVersionTerraformValuesByJobIDParams{ - JobID: jobID, - UpdatedAt: now, - CachedPlan: plan, - CachedModuleFiles: fileID, + JobID: jobID, + UpdatedAt: now, + CachedPlan: plan, + CachedModuleFiles: fileID, + ProvisionerdVersion: s.apiVersion, }) if err != nil { return nil, xerrors.Errorf("insert template version terraform data: %w", err) diff --git a/coderd/provisionerdserver/provisionerdserver_test.go b/coderd/provisionerdserver/provisionerdserver_test.go index b6c60781dac35..e125db348e701 100644 --- a/coderd/provisionerdserver/provisionerdserver_test.go +++ b/coderd/provisionerdserver/provisionerdserver_test.go @@ -2993,6 +2993,7 @@ func setup(t *testing.T, ignoreLogErrors bool, ov *overrides) (proto.DRPCProvisi srv, err := provisionerdserver.NewServer( ov.ctx, + proto.CurrentVersion.String(), &url.URL{}, daemon.ID, defOrg.ID, diff --git a/enterprise/coderd/provisionerdaemons.go b/enterprise/coderd/provisionerdaemons.go index 0315209e29543..9039d2e97dbc5 100644 --- a/enterprise/coderd/provisionerdaemons.go +++ b/enterprise/coderd/provisionerdaemons.go @@ -336,6 +336,7 @@ func (api *API) provisionerDaemonServe(rw http.ResponseWriter, r *http.Request) logger.Info(ctx, "starting external provisioner daemon") srv, err := provisionerdserver.NewServer( srvCtx, + daemon.APIVersion, api.AccessURL, daemon.ID, authRes.orgID,