feat!: drop support for legacy parameters (#7663)

This commit is contained in:
Marcin Tojek
2023-06-02 11:16:46 +02:00
committed by GitHub
parent 2b63492649
commit a7366a8b76
106 changed files with 1153 additions and 8553 deletions

View File

@ -1,204 +0,0 @@
package parameter
import (
"context"
"database/sql"
"errors"
"github.com/google/uuid"
"golang.org/x/exp/slices"
"golang.org/x/xerrors"
"github.com/coder/coder/coderd/database"
)
// ComputeScope targets identifiers to pull parameters from.
type ComputeScope struct {
TemplateImportJobID uuid.UUID
TemplateID uuid.NullUUID
WorkspaceID uuid.NullUUID
AdditionalParameterValues []database.ParameterValue
}
type ComputeOptions struct {
// HideRedisplayValues removes the value from parameters that
// come from schemas with RedisplayValue set to false.
HideRedisplayValues bool
}
// ComputedValue represents a computed parameter value.
type ComputedValue struct {
database.ParameterValue
SchemaID uuid.UUID `json:"schema_id"`
DefaultSourceValue bool `json:"default_source_value"`
index int32 // Track parameter schema index for sorting.
}
// Compute accepts a scope in which parameter values are sourced.
// These sources are iterated in a hierarchical fashion to determine
// the runtime parameter values for schemas provided.
func Compute(ctx context.Context, db database.Store, scope ComputeScope, options *ComputeOptions) ([]ComputedValue, error) {
if options == nil {
options = &ComputeOptions{}
}
compute := &compute{
options: options,
db: db,
computedParameterByName: map[string]ComputedValue{},
parameterSchemasByName: map[string]database.ParameterSchema{},
}
// All parameters for the import job ID!
parameterSchemas, err := db.GetParameterSchemasByJobID(ctx, scope.TemplateImportJobID)
if errors.Is(err, sql.ErrNoRows) {
err = nil
}
if err != nil {
return nil, xerrors.Errorf("get template parameters: %w", err)
}
for _, parameterSchema := range parameterSchemas {
compute.parameterSchemasByName[parameterSchema.Name] = parameterSchema
}
// Job parameters come second!
err = compute.injectScope(ctx, database.ParameterValuesParams{
Scopes: []database.ParameterScope{database.ParameterScopeImportJob},
ScopeIds: []uuid.UUID{scope.TemplateImportJobID},
})
if err != nil {
return nil, err
}
// Default template parameter values come second!
for _, parameterSchema := range parameterSchemas {
if parameterSchema.DefaultSourceScheme == database.ParameterSourceSchemeNone {
continue
}
if _, ok := compute.computedParameterByName[parameterSchema.Name]; ok {
// We already have a value! No need to use the default.
continue
}
switch parameterSchema.DefaultSourceScheme {
case database.ParameterSourceSchemeData:
// Inject a default value scoped to the import job ID.
// This doesn't need to be inserted into the database,
// because it's a dynamic value associated with the schema.
err = compute.injectSingle(database.ParameterValue{
ID: uuid.New(),
CreatedAt: database.Now(),
UpdatedAt: database.Now(),
SourceScheme: database.ParameterSourceSchemeData,
Name: parameterSchema.Name,
DestinationScheme: parameterSchema.DefaultDestinationScheme,
SourceValue: parameterSchema.DefaultSourceValue,
Scope: database.ParameterScopeImportJob,
ScopeID: scope.TemplateImportJobID,
}, true)
if err != nil {
return nil, xerrors.Errorf("insert default value: %w", err)
}
default:
return nil, xerrors.Errorf("unsupported source scheme for template version parameter %q: %q", parameterSchema.Name, string(parameterSchema.DefaultSourceScheme))
}
}
if scope.TemplateID.Valid {
// Template parameters come third!
err = compute.injectScope(ctx, database.ParameterValuesParams{
Scopes: []database.ParameterScope{database.ParameterScopeTemplate},
ScopeIds: []uuid.UUID{scope.TemplateID.UUID},
})
if err != nil {
return nil, err
}
}
if scope.WorkspaceID.Valid {
// Workspace parameters come last!
err = compute.injectScope(ctx, database.ParameterValuesParams{
Scopes: []database.ParameterScope{database.ParameterScopeWorkspace},
ScopeIds: []uuid.UUID{scope.WorkspaceID.UUID},
})
if err != nil {
return nil, err
}
}
// Finally, any additional parameter values declared in the input
for _, v := range scope.AdditionalParameterValues {
err = compute.injectSingle(v, false)
if err != nil {
return nil, xerrors.Errorf("inject single parameter value: %w", err)
}
}
values := make([]ComputedValue, 0, len(compute.computedParameterByName))
for _, value := range compute.computedParameterByName {
values = append(values, value)
}
slices.SortFunc(values, func(a, b ComputedValue) bool {
return a.index < b.index
})
return values, nil
}
type compute struct {
options *ComputeOptions
db database.Store
computedParameterByName map[string]ComputedValue
parameterSchemasByName map[string]database.ParameterSchema
}
// Validates and computes the value for parameters; setting the value on "parameterByName".
func (c *compute) injectScope(ctx context.Context, scopeParams database.ParameterValuesParams) error {
scopedParameters, err := c.db.ParameterValues(ctx, scopeParams)
if errors.Is(err, sql.ErrNoRows) {
err = nil
}
if err != nil {
return xerrors.Errorf("get %s parameters: %w", scopeParams.Scopes, err)
}
for _, scopedParameter := range scopedParameters {
err = c.injectSingle(scopedParameter, false)
if err != nil {
return xerrors.Errorf("inject single %q: %w", scopedParameter.Name, err)
}
}
return nil
}
func (c *compute) injectSingle(scopedParameter database.ParameterValue, defaultValue bool) error {
parameterSchema, hasParameterSchema := c.parameterSchemasByName[scopedParameter.Name]
if !hasParameterSchema {
// Don't inject parameters that aren't defined by the template.
return nil
}
_, hasParameterValue := c.computedParameterByName[scopedParameter.Name]
if hasParameterValue {
if !parameterSchema.AllowOverrideSource &&
// Workspaces cannot override anything on a template!
scopedParameter.Scope == database.ParameterScopeWorkspace {
return nil
}
}
switch scopedParameter.SourceScheme {
case database.ParameterSourceSchemeData:
value := ComputedValue{
ParameterValue: scopedParameter,
SchemaID: parameterSchema.ID,
DefaultSourceValue: defaultValue,
index: parameterSchema.Index,
}
if c.options.HideRedisplayValues && !parameterSchema.RedisplayValue {
value.SourceValue = ""
}
c.computedParameterByName[scopedParameter.Name] = value
default:
return xerrors.Errorf("unsupported source scheme: %q", string(parameterSchema.DefaultSourceScheme))
}
return nil
}

View File

@ -1,173 +0,0 @@
package parameter_test
import (
"context"
"testing"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
"github.com/coder/coder/coderd/database"
"github.com/coder/coder/coderd/database/dbfake"
"github.com/coder/coder/coderd/database/dbgen"
"github.com/coder/coder/coderd/parameter"
)
func TestCompute(t *testing.T) {
t.Parallel()
generateScope := func() parameter.ComputeScope {
return parameter.ComputeScope{
TemplateImportJobID: uuid.New(),
TemplateID: uuid.NullUUID{
UUID: uuid.New(),
Valid: true,
},
WorkspaceID: uuid.NullUUID{
UUID: uuid.New(),
Valid: true,
},
}
}
type parameterOptions struct {
AllowOverrideSource bool
AllowOverrideDestination bool
DefaultDestinationScheme database.ParameterDestinationScheme
TemplateImportJobID uuid.UUID
}
generateParameter := func(t *testing.T, db database.Store, opts parameterOptions) database.ParameterSchema {
if opts.DefaultDestinationScheme == "" {
opts.DefaultDestinationScheme = database.ParameterDestinationSchemeEnvironmentVariable
}
param := dbgen.ParameterSchema(t, db, database.ParameterSchema{
JobID: opts.TemplateImportJobID,
DefaultSourceScheme: database.ParameterSourceSchemeData,
AllowOverrideSource: opts.AllowOverrideSource,
AllowOverrideDestination: opts.AllowOverrideDestination,
DefaultDestinationScheme: opts.DefaultDestinationScheme,
ValidationTypeSystem: database.ParameterTypeSystemNone,
})
return param
}
t.Run("NoValue", func(t *testing.T) {
t.Parallel()
db := dbfake.New()
scope := generateScope()
_ = dbgen.ParameterSchema(t, db, database.ParameterSchema{
JobID: scope.TemplateImportJobID,
DefaultSourceScheme: database.ParameterSourceSchemeNone,
DefaultDestinationScheme: database.ParameterDestinationSchemeNone,
ValidationTypeSystem: database.ParameterTypeSystemNone,
})
computed, err := parameter.Compute(context.Background(), db, scope, nil)
require.NoError(t, err)
require.Len(t, computed, 0)
})
t.Run("UseDefaultTemplateValue", func(t *testing.T) {
t.Parallel()
db := dbfake.New()
scope := generateScope()
parameterSchema := generateParameter(t, db, parameterOptions{
TemplateImportJobID: scope.TemplateImportJobID,
DefaultDestinationScheme: database.ParameterDestinationSchemeProvisionerVariable,
})
computed, err := parameter.Compute(context.Background(), db, scope, nil)
require.NoError(t, err)
require.Len(t, computed, 1)
computedValue := computed[0]
require.True(t, computedValue.DefaultSourceValue)
require.Equal(t, database.ParameterScopeImportJob, computedValue.Scope)
require.Equal(t, scope.TemplateImportJobID, computedValue.ScopeID)
require.Equal(t, computedValue.SourceValue, parameterSchema.DefaultSourceValue)
})
t.Run("TemplateOverridesTemplateDefault", func(t *testing.T) {
t.Parallel()
db := dbfake.New()
scope := generateScope()
parameterSchema := generateParameter(t, db, parameterOptions{
TemplateImportJobID: scope.TemplateImportJobID,
})
value := dbgen.ParameterValue(t, db, database.ParameterValue{
Name: parameterSchema.Name,
Scope: database.ParameterScopeTemplate,
ScopeID: scope.TemplateID.UUID,
SourceScheme: database.ParameterSourceSchemeData,
SourceValue: "nop",
DestinationScheme: database.ParameterDestinationSchemeEnvironmentVariable,
})
computed, err := parameter.Compute(context.Background(), db, scope, nil)
require.NoError(t, err)
require.Len(t, computed, 1)
require.Equal(t, false, computed[0].DefaultSourceValue)
require.Equal(t, value.SourceValue, computed[0].SourceValue)
})
t.Run("WorkspaceCannotOverwriteTemplateDefault", func(t *testing.T) {
t.Parallel()
db := dbfake.New()
scope := generateScope()
parameterSchema := generateParameter(t, db, parameterOptions{
TemplateImportJobID: scope.TemplateImportJobID,
})
_ = dbgen.ParameterValue(t, db, database.ParameterValue{
Name: parameterSchema.Name,
Scope: database.ParameterScopeWorkspace,
ScopeID: scope.WorkspaceID.UUID,
SourceScheme: database.ParameterSourceSchemeData,
SourceValue: "nop",
DestinationScheme: database.ParameterDestinationSchemeEnvironmentVariable,
})
computed, err := parameter.Compute(context.Background(), db, scope, nil)
require.NoError(t, err)
require.Len(t, computed, 1)
require.Equal(t, true, computed[0].DefaultSourceValue)
})
t.Run("WorkspaceOverwriteTemplateDefault", func(t *testing.T) {
t.Parallel()
db := dbfake.New()
scope := generateScope()
parameterSchema := generateParameter(t, db, parameterOptions{
AllowOverrideSource: true,
TemplateImportJobID: scope.TemplateImportJobID,
})
_ = dbgen.ParameterValue(t, db, database.ParameterValue{
Name: parameterSchema.Name,
Scope: database.ParameterScopeWorkspace,
ScopeID: scope.WorkspaceID.UUID,
SourceScheme: database.ParameterSourceSchemeData,
SourceValue: "nop",
DestinationScheme: database.ParameterDestinationSchemeEnvironmentVariable,
})
computed, err := parameter.Compute(context.Background(), db, scope, nil)
require.NoError(t, err)
require.Len(t, computed, 1)
require.Equal(t, false, computed[0].DefaultSourceValue)
})
t.Run("HideRedisplay", func(t *testing.T) {
t.Parallel()
db := dbfake.New()
scope := generateScope()
_ = generateParameter(t, db, parameterOptions{
TemplateImportJobID: scope.TemplateImportJobID,
DefaultDestinationScheme: database.ParameterDestinationSchemeProvisionerVariable,
})
computed, err := parameter.Compute(context.Background(), db, scope, &parameter.ComputeOptions{
HideRedisplayValues: true,
})
require.NoError(t, err)
require.Len(t, computed, 1)
computedValue := computed[0]
require.True(t, computedValue.DefaultSourceValue)
require.Equal(t, computedValue.SourceValue, "")
})
}

View File

@ -1,43 +0,0 @@
package parameter
import (
"sort"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
"golang.org/x/xerrors"
)
// Contains parses possible values for a conditional.
func Contains(condition string) ([]string, bool, error) {
if condition == "" {
return nil, false, nil
}
expression, diags := hclsyntax.ParseExpression([]byte(condition), "", hcl.InitialPos)
if len(diags) > 0 {
return nil, false, xerrors.Errorf("parse condition: %s", diags.Error())
}
functionCallExpression, valid := expression.(*hclsyntax.FunctionCallExpr)
if !valid {
return nil, false, nil
}
if functionCallExpression.Name != "contains" {
return nil, false, nil
}
if len(functionCallExpression.Args) < 2 {
return nil, false, nil
}
value, diags := functionCallExpression.Args[0].Value(&hcl.EvalContext{})
if len(diags) > 0 {
return nil, false, xerrors.Errorf("parse value: %s", diags.Error())
}
possible := make([]string, 0)
for _, subValue := range value.AsValueSlice() {
if subValue.Type().FriendlyName() != "string" {
continue
}
possible = append(possible, subValue.AsString())
}
sort.Strings(possible)
return possible, true, nil
}

View File

@ -1,20 +0,0 @@
package parameter_test
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/coder/coder/coderd/parameter"
)
func TestValidate(t *testing.T) {
t.Parallel()
t.Run("Contains", func(t *testing.T) {
t.Parallel()
values, valid, err := parameter.Contains(`contains(["us-east1-a", "us-central1-a"], var.region)`)
require.NoError(t, err)
require.True(t, valid)
require.Len(t, values, 2)
})
}