feat: Expose the values contained in an HCL validation string to the API (#1587)

* feat: Expose the values contained in an HCL validation string to the API

This allows the frontend to render inputs displaying these values!

* Update codersdk/parameters.go

Co-authored-by: Cian Johnston <cian@coder.com>

* Call a spade a space

* Fix linting errors with type conversion

Co-authored-by: Cian Johnston <cian@coder.com>
This commit is contained in:
Kyle Carberry
2022-05-19 08:29:36 -05:00
committed by GitHub
parent ad9bdb7bd1
commit 38ee519f42
9 changed files with 144 additions and 54 deletions

View File

@ -10,7 +10,7 @@ import (
"github.com/coder/coder/codersdk" "github.com/coder/coder/codersdk"
) )
func ParameterSchema(cmd *cobra.Command, parameterSchema codersdk.TemplateVersionParameterSchema) (string, error) { func ParameterSchema(cmd *cobra.Command, parameterSchema codersdk.ParameterSchema) (string, error) {
_, _ = fmt.Fprintln(cmd.OutOrStdout(), Styles.Bold.Render("var."+parameterSchema.Name)) _, _ = fmt.Fprintln(cmd.OutOrStdout(), Styles.Bold.Render("var."+parameterSchema.Name))
if parameterSchema.Description != "" { if parameterSchema.Description != "" {
_, _ = fmt.Fprintln(cmd.OutOrStdout(), " "+strings.TrimSpace(strings.Join(strings.Split(parameterSchema.Description, "\n"), "\n "))+"\n") _, _ = fmt.Fprintln(cmd.OutOrStdout(), " "+strings.TrimSpace(strings.Join(strings.Split(parameterSchema.Description, "\n"), "\n "))+"\n")

View File

@ -135,7 +135,7 @@ func create() *cobra.Command {
Name: parameterSchema.Name, Name: parameterSchema.Name,
SourceValue: value, SourceValue: value,
SourceScheme: database.ParameterSourceSchemeData, SourceScheme: database.ParameterSourceSchemeData,
DestinationScheme: parameterSchema.DefaultDestinationScheme, DestinationScheme: database.ParameterDestinationScheme(parameterSchema.DefaultDestinationScheme),
}) })
} }
_, _ = fmt.Fprintln(cmd.OutOrStdout()) _, _ = fmt.Fprintln(cmd.OutOrStdout())

View File

@ -175,7 +175,7 @@ func createValidTemplateVersion(cmd *cobra.Command, client *codersdk.Client, org
sort.Slice(parameterSchemas, func(i, j int) bool { sort.Slice(parameterSchemas, func(i, j int) bool {
return parameterSchemas[i].Name < parameterSchemas[j].Name return parameterSchemas[i].Name < parameterSchemas[j].Name
}) })
missingSchemas := make([]codersdk.TemplateVersionParameterSchema, 0) missingSchemas := make([]codersdk.ParameterSchema, 0)
for _, parameterSchema := range parameterSchemas { for _, parameterSchema := range parameterSchemas {
_, ok := valuesBySchemaID[parameterSchema.ID.String()] _, ok := valuesBySchemaID[parameterSchema.ID.String()]
if ok { if ok {
@ -193,7 +193,7 @@ func createValidTemplateVersion(cmd *cobra.Command, client *codersdk.Client, org
Name: parameterSchema.Name, Name: parameterSchema.Name,
SourceValue: value, SourceValue: value,
SourceScheme: database.ParameterSourceSchemeData, SourceScheme: database.ParameterSourceSchemeData,
DestinationScheme: parameterSchema.DefaultDestinationScheme, DestinationScheme: database.ParameterDestinationScheme(parameterSchema.DefaultDestinationScheme),
}) })
_, _ = fmt.Fprintln(cmd.OutOrStdout()) _, _ = fmt.Fprintln(cmd.OutOrStdout())
} }

View File

@ -8,9 +8,11 @@ import (
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"github.com/google/uuid" "github.com/google/uuid"
"golang.org/x/xerrors"
"github.com/coder/coder/coderd/database" "github.com/coder/coder/coderd/database"
"github.com/coder/coder/coderd/httpapi" "github.com/coder/coder/coderd/httpapi"
"github.com/coder/coder/coderd/parameter"
"github.com/coder/coder/codersdk" "github.com/coder/coder/codersdk"
) )
@ -122,6 +124,37 @@ func (api *api) deleteParameter(rw http.ResponseWriter, r *http.Request) {
}) })
} }
func convertParameterSchema(parameterSchema database.ParameterSchema) (codersdk.ParameterSchema, error) {
contains := []string{}
if parameterSchema.ValidationCondition != "" {
var err error
contains, _, err = parameter.Contains(parameterSchema.ValidationCondition)
if err != nil {
return codersdk.ParameterSchema{}, xerrors.Errorf("parse validation condition for %q: %w", parameterSchema.Name, err)
}
}
return codersdk.ParameterSchema{
ID: parameterSchema.ID,
CreatedAt: parameterSchema.CreatedAt,
JobID: parameterSchema.JobID,
Name: parameterSchema.Name,
Description: parameterSchema.Description,
DefaultSourceScheme: string(parameterSchema.DefaultSourceScheme),
DefaultSourceValue: parameterSchema.DefaultSourceValue,
AllowOverrideSource: parameterSchema.AllowOverrideSource,
DefaultDestinationScheme: string(parameterSchema.DefaultDestinationScheme),
AllowOverrideDestination: parameterSchema.AllowOverrideDestination,
DefaultRefresh: parameterSchema.DefaultRefresh,
RedisplayValue: parameterSchema.RedisplayValue,
ValidationError: parameterSchema.ValidationError,
ValidationCondition: parameterSchema.ValidationCondition,
ValidationTypeSystem: string(parameterSchema.ValidationTypeSystem),
ValidationValueType: parameterSchema.ValidationValueType,
ValidationContains: contains,
}, nil
}
func convertParameterValue(parameterValue database.ParameterValue) codersdk.Parameter { func convertParameterValue(parameterValue database.ParameterValue) codersdk.Parameter {
return codersdk.Parameter{ return codersdk.Parameter{
ID: parameterValue.ID, ID: parameterValue.ID,

View File

@ -95,11 +95,18 @@ func (api *api) templateVersionSchema(rw http.ResponseWriter, r *http.Request) {
}) })
return return
} }
if schemas == nil { apiSchemas := make([]codersdk.ParameterSchema, 0)
schemas = []database.ParameterSchema{} for _, schema := range schemas {
apiSchema, err := convertParameterSchema(schema)
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("convert: %s", err),
})
return
}
apiSchemas = append(apiSchemas, apiSchema)
} }
httpapi.Write(rw, http.StatusOK, apiSchemas)
httpapi.Write(rw, http.StatusOK, schemas)
} }
func (api *api) templateVersionParameters(rw http.ResponseWriter, r *http.Request) { func (api *api) templateVersionParameters(rw http.ResponseWriter, r *http.Request) {

View File

@ -198,6 +198,36 @@ func TestTemplateVersionSchema(t *testing.T) {
require.NotNil(t, schemas) require.NotNil(t, schemas)
require.Len(t, schemas, 1) require.Len(t, schemas, 1)
}) })
t.Run("ListContains", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
coderdtest.NewProvisionerDaemon(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: []*proto.Parse_Response{{
Type: &proto.Parse_Response_Complete{
Complete: &proto.Parse_Complete{
ParameterSchemas: []*proto.ParameterSchema{{
Name: "example",
ValidationTypeSystem: proto.ParameterSchema_HCL,
ValidationValueType: "string",
ValidationCondition: `contains(["first", "second"], var.example)`,
DefaultDestination: &proto.ParameterDestination{
Scheme: proto.ParameterDestination_PROVISIONER_VARIABLE,
},
}},
},
},
}},
Provision: echo.ProvisionComplete,
})
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
schemas, err := client.TemplateVersionSchema(context.Background(), version.ID)
require.NoError(t, err)
require.NotNil(t, schemas)
require.Len(t, schemas, 1)
require.Equal(t, []string{"first", "second"}, schemas[0].ValidationContains)
})
} }
func TestTemplateVersionParameters(t *testing.T) { func TestTemplateVersionParameters(t *testing.T) {

View File

@ -24,14 +24,37 @@ const (
// Parameter represents a set value for the scope. // Parameter represents a set value for the scope.
type Parameter struct { type Parameter struct {
ID uuid.UUID `db:"id" json:"id"` ID uuid.UUID `json:"id"`
CreatedAt time.Time `db:"created_at" json:"created_at"` CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"` UpdatedAt time.Time `json:"updated_at"`
Scope ParameterScope `db:"scope" json:"scope"` Scope ParameterScope `json:"scope"`
ScopeID uuid.UUID `db:"scope_id" json:"scope_id"` ScopeID uuid.UUID `json:"scope_id"`
Name string `db:"name" json:"name"` Name string `json:"name"`
SourceScheme database.ParameterSourceScheme `db:"source_scheme" json:"source_scheme"` SourceScheme database.ParameterSourceScheme `json:"source_scheme"`
DestinationScheme database.ParameterDestinationScheme `db:"destination_scheme" json:"destination_scheme"` DestinationScheme database.ParameterDestinationScheme `json:"destination_scheme"`
}
type ParameterSchema struct {
ID uuid.UUID `json:"id"`
CreatedAt time.Time `json:"created_at"`
JobID uuid.UUID `json:"job_id"`
Name string `json:"name"`
Description string `json:"description"`
DefaultSourceScheme string `json:"default_source_scheme"`
DefaultSourceValue string `json:"default_source_value"`
AllowOverrideSource bool `json:"allow_override_source"`
DefaultDestinationScheme string `json:"default_destination_scheme"`
AllowOverrideDestination bool `json:"allow_override_destination"`
DefaultRefresh string `json:"default_refresh"`
RedisplayValue bool `json:"redisplay_value"`
ValidationError string `json:"validation_error"`
ValidationCondition string `json:"validation_condition"`
ValidationTypeSystem string `json:"validation_type_system"`
ValidationValueType string `json:"validation_value_type"`
// This is a special array of items provided if the validation condition
// explicitly states the value must be one of a set.
ValidationContains []string `json:"validation_contains"`
} }
// CreateParameterRequest is used to create a new parameter value for a scope. // CreateParameterRequest is used to create a new parameter value for a scope.

View File

@ -9,7 +9,6 @@ import (
"github.com/google/uuid" "github.com/google/uuid"
"github.com/coder/coder/coderd/database"
"github.com/coder/coder/coderd/parameter" "github.com/coder/coder/coderd/parameter"
) )
@ -24,9 +23,6 @@ type TemplateVersion struct {
Readme string `json:"readme"` Readme string `json:"readme"`
} }
// TemplateVersionParameterSchema represents a parameter parsed from template version source.
type TemplateVersionParameterSchema database.ParameterSchema
// TemplateVersionParameter represents a computed parameter value. // TemplateVersionParameter represents a computed parameter value.
type TemplateVersionParameter parameter.ComputedValue type TemplateVersionParameter parameter.ComputedValue
@ -58,7 +54,7 @@ func (c *Client) CancelTemplateVersion(ctx context.Context, version uuid.UUID) e
} }
// TemplateVersionSchema returns schemas for a template version by ID. // TemplateVersionSchema returns schemas for a template version by ID.
func (c *Client) TemplateVersionSchema(ctx context.Context, version uuid.UUID) ([]TemplateVersionParameterSchema, error) { func (c *Client) TemplateVersionSchema(ctx context.Context, version uuid.UUID) ([]ParameterSchema, error) {
res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/templateversions/%s/schema", version), nil) res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/templateversions/%s/schema", version), nil)
if err != nil { if err != nil {
return nil, err return nil, err
@ -67,7 +63,7 @@ func (c *Client) TemplateVersionSchema(ctx context.Context, version uuid.UUID) (
if res.StatusCode != http.StatusOK { if res.StatusCode != http.StatusOK {
return nil, readBodyAsError(res) return nil, readBodyAsError(res)
} }
var params []TemplateVersionParameterSchema var params []ParameterSchema
return params, json.NewDecoder(res.Body).Decode(&params) return params, json.NewDecoder(res.Body).Decode(&params)
} }

View File

@ -49,7 +49,7 @@ export interface CreateOrganizationRequest {
readonly name: string readonly name: string
} }
// From codersdk/parameters.go:38:6 // From codersdk/parameters.go:61:6
export interface CreateParameterRequest { export interface CreateParameterRequest {
readonly name: string readonly name: string
readonly source_value: string readonly source_value: string
@ -86,7 +86,7 @@ export interface CreateUserRequest {
readonly organization_id: string readonly organization_id: string
} }
// From codersdk/workspaces.go:34:6 // From codersdk/workspaces.go:36:6
export interface CreateWorkspaceBuildRequest { export interface CreateWorkspaceBuildRequest {
readonly template_version_id?: string readonly template_version_id?: string
// This is likely an enum in an external package ("github.com/coder/coder/coderd/database.WorkspaceTransition") // This is likely an enum in an external package ("github.com/coder/coder/coderd/database.WorkspaceTransition")
@ -169,6 +169,30 @@ export interface Parameter {
readonly destination_scheme: string readonly destination_scheme: string
} }
// From codersdk/parameters.go:37:6
export interface ParameterSchema {
readonly id: string
readonly created_at: string
readonly job_id: string
readonly name: string
readonly description: string
// This is likely an enum in an external package ("github.com/coder/coder/coderd/database.ParameterSourceScheme")
readonly default_source_scheme: string
readonly default_source_value: string
readonly allow_override_source: boolean
// This is likely an enum in an external package ("github.com/coder/coder/coderd/database.ParameterDestinationScheme")
readonly default_destination_scheme: string
readonly allow_override_destination: boolean
readonly default_refresh: string
readonly redisplay_value: boolean
readonly validation_error: string
readonly validation_condition: string
// This is likely an enum in an external package ("github.com/coder/coder/coderd/database.ParameterTypeSystem")
readonly validation_type_system: string
readonly validation_value_type: string
readonly validation_contains: string[]
}
// From codersdk/provisionerdaemons.go:23:6 // From codersdk/provisionerdaemons.go:23:6
export interface ProvisionerDaemon { export interface ProvisionerDaemon {
readonly id: string readonly id: string
@ -223,7 +247,7 @@ export interface Template {
readonly description: string readonly description: string
} }
// From codersdk/templateversions.go:17:6 // From codersdk/templateversions.go:16:6
export interface TemplateVersion { export interface TemplateVersion {
readonly id: string readonly id: string
readonly template_id?: string readonly template_id?: string
@ -234,7 +258,7 @@ export interface TemplateVersion {
readonly readme: string readonly readme: string
} }
// From codersdk/templateversions.go:31:6 // From codersdk/templateversions.go:27:6
export interface TemplateVersionParameter { export interface TemplateVersionParameter {
// Named type "github.com/coder/coder/coderd/database.ParameterValue" unknown, using "any" // Named type "github.com/coder/coder/coderd/database.ParameterValue" unknown, using "any"
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -243,29 +267,6 @@ export interface TemplateVersionParameter {
readonly default_source_value: boolean readonly default_source_value: boolean
} }
// From codersdk/templateversions.go:28:6
export interface TemplateVersionParameterSchema {
readonly id: string
readonly created_at: string
readonly job_id: string
readonly name: string
readonly description: string
// This is likely an enum in an external package ("github.com/coder/coder/coderd/database.ParameterSourceScheme")
readonly default_source_scheme: string
readonly default_source_value: string
readonly allow_override_source: boolean
// This is likely an enum in an external package ("github.com/coder/coder/coderd/database.ParameterDestinationScheme")
readonly default_destination_scheme: string
readonly allow_override_destination: boolean
readonly default_refresh: string
readonly redisplay_value: boolean
readonly validation_error: string
readonly validation_condition: string
// This is likely an enum in an external package ("github.com/coder/coder/coderd/database.ParameterTypeSystem")
readonly validation_type_system: string
readonly validation_value_type: string
}
// From codersdk/templates.go:75:6 // From codersdk/templates.go:75:6
export interface TemplateVersionsByTemplateRequest extends Pagination { export interface TemplateVersionsByTemplateRequest extends Pagination {
readonly template_id: string readonly template_id: string
@ -292,12 +293,12 @@ export interface UpdateUserProfileRequest {
readonly username: string readonly username: string
} }
// From codersdk/workspaces.go:102:6 // From codersdk/workspaces.go:134:6
export interface UpdateWorkspaceAutostartRequest { export interface UpdateWorkspaceAutostartRequest {
readonly schedule: string readonly schedule: string
} }
// From codersdk/workspaces.go:122:6 // From codersdk/workspaces.go:154:6
export interface UpdateWorkspaceAutostopRequest { export interface UpdateWorkspaceAutostopRequest {
readonly schedule: string readonly schedule: string
} }
@ -352,7 +353,7 @@ export interface UsersRequest extends Pagination {
readonly status?: string readonly status?: string
} }
// From codersdk/workspaces.go:18:6 // From codersdk/workspaces.go:20:6
export interface Workspace { export interface Workspace {
readonly id: string readonly id: string
readonly created_at: string readonly created_at: string
@ -429,12 +430,12 @@ export interface WorkspaceBuild {
readonly job: ProvisionerJob readonly job: ProvisionerJob
} }
// From codersdk/workspaces.go:55:6 // From codersdk/workspaces.go:57:6
export interface WorkspaceBuildsRequest extends Pagination { export interface WorkspaceBuildsRequest extends Pagination {
readonly WorkspaceID: string readonly WorkspaceID: string
} }
// From codersdk/workspaces.go:141:6 // From codersdk/workspaces.go:173:6
export interface WorkspaceFilter { export interface WorkspaceFilter {
readonly OrganizationID: string readonly OrganizationID: string
readonly Owner: string readonly Owner: string