mirror of https://github.com/coder/coder.git
205 lines
6.5 KiB
Go
205 lines
6.5 KiB
Go
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
|
|
}
|