File terraschema-0.2.0.obscpio of Package terraschema
07070100000000000081A400000000000000000000000167BF3238000008C7000000000000000000000000000000000000002100000000terraschema-0.2.0/.golangci.yamlrun:
timeout: 240s
tests: true
linters-settings:
lll:
line-length: 128
goimports:
local-prefixes: "github.com/HewlettPackard"
godox:
keywords:
- OPTIMIZE
exhaustive:
default-signifies-exhaustive: true
funlen:
lines: 90
statements: 60
cyclop:
max-complexity: 14
revive:
rules:
- name: exported
arguments:
- disableStutteringCheck
- name: var-naming
# Suppress warnings about Id "initialism" - i.e. make Id and ID valid
arguments: [["ID"]]
linters:
disable-all: true
enable:
- asasalint
- asciicheck
- bidichk
- bodyclose
- containedctx
- cyclop
- decorder
- dogsled
- dupl
- durationcheck
- errcheck
- errchkjson
- errname
- errorlint
- exhaustive
- funlen
- gocognit
- gocritic
- gocyclo
- godox
- gofmt
- gofumpt
- goheader
- goimports
- gomoddirectives
- gomodguard
- goprintffuncname
- gosec
- gosimple
- govet
- grouper
- importas
- ineffassign
- lll
- maintidx
- makezero
- misspell
- nakedret
- nestif
- nilerr
- nilnil
- nlreturn
- noctx
- nonamedreturns
- nosprintfhostport
- paralleltest
- prealloc
- predeclared
- revive
- staticcheck
- stylecheck
- tenv
- thelper
- tparallel
- typecheck
- unconvert
- unparam
- unused
- usestdlibvars
- whitespace
issues:
include:
# Match function comment to function name
- EXC0012
- EXC0013
- EXC0014
- EXC0015
exclude:
# We want to allow exporting const/var/type without comments
# but enforce comments for functions/methods.
# TODO: Remove method and function from the below (when we can)
- "exported (const|var|type|method|function) (.+) should have comment (.+) or be unexported"
- "exported (const|var|type|method|function) (.+) should have comment or be unexported"
# don't read the vendor folder.
exclude-dirs-use-default: true
exclude-rules:
# ignore function length for tests as look up tables typically exceed.
- linters:
- funlen
path: _test\.go
max-same-issues: 0
07070100000001000081A400000000000000000000000167BF32380000044A000000000000000000000000000000000000001A00000000terraschema-0.2.0/LICENSEMIT License
Copyright (c) 2024 Hewlett Packard Enterprise Development LP
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
07070100000002000081A400000000000000000000000167BF32380000043B000000000000000000000000000000000000001B00000000terraschema-0.2.0/Makefile# Copyright 2024 Hewlett Packard Enterprise Development LP
.DEFAULT_GOAL := terraschema
.PHONY: test
test:
go test ./...
.PHONY: lint
lint:
golangci-lint run
.PHONY: all
all: test lint
.PHONY: terraschema
terraschema:
@go build .
.PHONY: clean
clean:
@rm -f terraschema
.PHONY: test-data
test-data:
@for name in `ls test/modules`; do \
go run . -i test/modules/$$name -o test/expected/$$name/schema.json --overwrite --allow-empty --ignore-variable ignored --ignore-variable also_ignored; \
go run . -i test/modules/$$name -o test/expected/$$name/variables.json --overwrite --allow-empty --export-variables --ignore-variable ignored --ignore-variable also_ignored; \
go run . -i test/modules/$$name -o test/expected/$$name/schema-disallow-additional.json --overwrite --allow-empty --disallow-additional-properties --ignore-variable ignored --ignore-variable also_ignored; \
go run . -i test/modules/$$name -o test/expected/$$name/schema-nullable-all.json --overwrite --allow-empty --nullable-all --ignore-variable ignored --ignore-variable also_ignored; \
done
07070100000003000081A400000000000000000000000167BF323800004131000000000000000000000000000000000000001C00000000terraschema-0.2.0/README.md# TerraSchema
TerraSchema (or `terraschema`) is a CLI tool which scans Terraform configuration (`.tf`)
files, parses a list of variables along with their type and validation rules, and converts
them to a schema which complies with
[JSON Schema Draft-07](https://json-schema.org/draft-07/json-schema-release-notes).
### Installation
To install this application, do
```
$ go install github.com/HewlettPackard/terraschema@latest
```
or alternatively, download the correct binary for your PC from the [releases](https://github.com/HewlettPackard/terraschema/releases) tab.
### Motivation
JSON Schema files can be used to validate a `.tfvars.json` file without the need to run `terraform plan`.
Some applications can also use JSON schema to generate a web form to enter variables, such as
[react-jsonschema-form](https://github.com/rjsf-team/react-jsonschema-form). To validate a JSON file
against a schema, [santhosh-tekuri/jsonschema](https://github.com/santhosh-tekuri/jsonschema) can be
used, for example:
```
$ go install github.com/santhosh-tekuri/jsonschema/cmd/jv@latest
$ jv schema.json input.tfvars.json
```
Once a valid `.tfvars.json` file has been created, it can then be used in terraform with
```
$ terraform plan -var-file="input.tfvars.json"
$ terraform apply -var-file="input.tfvars.json"
$ terraform destroy -var-file="input.tfvars.json"
```
(see [Terraform Input Variables](https://developer.hashicorp.com/terraform/language/values/variables#variable-definitions-tfvars-files)).
In the majority of use-cases, any input file which validates against the JSON Schema
generated by this application will also be a valid input file to the terraform module itself.
The main exceptions to this are certain classes of validation rules, which JSON Schema
do not directly support (see Custom Validation Rules).
# CLI Usage
The default behaviour of TerraSchema is to scan the current directory for Terraform
configuration files, and create a file called `schema.json` at the same location. It
returns an error if no Terraform configuration files are present, or if no variables
are defined within those files. Variable are marked as `required` in the schema only
if they don't have a default value set, and additional variables are permitted.
Note: an `input.tfvars.json` file with additional variables (ones which don't correspond
to an existing variable in the Terraform configuration) will generate a warning when
running Terraform.
### Flags
- `-h`, `--help`: Print help instructions.
- `-i`, `--input`: The root directory of the terraform module. Note: only files contained
in the root folder will be scanned. This is consistent with the behaviour of terraform modules.
- `-o`, `--output`: The output file for the schema file. Should be in the form `path/to/schema.json`.
- `--allow-empty`: Allow an empty schema (`{}`) to be created if no `.tf` files or variables are found.
- `--disallow-additional-properties`: Set additionalProperties to false in the root object and nested objects
(see [JSON Schema definition](https://json-schema.org/understanding-json-schema/reference/object#additionalproperties)).
- `--nullable-all`: Change the default value for `nullable` in a Variable block to 'true'. This is to make the behaviour more closely reflect Terraform's own validation. See 'Nullable Variables' below.
- `--overwrite`: Allow overwriting an existing file at the output location.
- `--debug`: Print debug logs for variable retrieval and errors related to custom validation rules.
- `--stdout`: Print schema to stdout and prevent all other logging unless an error occurs. Does not create a file.
Overrides `--debug` and `--output`.
- `--export-variables`: Export the variables in JSON format directly and do not create a JSON Schema. This provides similar functionality to applications such as terraform-docs, where the input variables can be output to a machine-readable format such as JSON. The `type` field is converted to a type constraint based on the type definition, and the `default` field is translated to its literal value. `condition` inside each `validation` block is left as a string, because it is difficult to represent arbitrary (ie unevaluated) HCL Expressions in JSON.
- `--escape-json`: Escape special characters in the JSON (`<`,`>` and `&`) so that the schema can be used in a web context. By default, this behaviour is disabled so the JSON file can be read more easily, though it does not effect external programs such as `jq`.
- `--ignore-variable=<VAR_NAME>`: Ignore a variable with the name `VAR_NAME` in the schema. This can be used to exclude variables which are not intended to be used in the schema, such as those which are only used in the module itself. This flag can be used multiple times to ignore multiple variables.
# Design
### Parsing Terraform Configuration Files
Parsing Terraform files is done using the [HCL package](https://github.com/hashicorp/hcl). Initially, the plan was to use an existing application such as [terraform-docs](https://github.com/terraform-docs/terraform-docs/) to preform the parsing step, but some of the fields of the `variable` block weren't implemented, such as validation rules.
TerraSchema parses each Terraform configuration file as a HCL (HashiCorp Configuration Language) file and picks out any blocks which match the definition of an input variable in Terraform. A typical `variable` block looks like this:
```hcl
variable "age" {
type = number
default = 10
description = "Your age"
nullable = false
sensitive = false
validation {
condition = var.age >= 0
error_message = "Age must not be negative"
}
}
```
Note: All of these fields are optional.
Note: Multiple `validation` blocks may be specified in one `variable` block. In this case, terraschema will try and apply each of the validation conditions to the variable.
This `variable` is translated into the following format in the `reader` package, so that it can be used by the rest of the application:
```Go
type VariableBlock struct {
Type hcl.Expression // or nil
Default hcl.Expression // or nil
Description *string
Nullable *bool
Sensitive *bool
Validation []struct{
Condition hcl.Expression
ErrorMessage string
}
}
```
Empty expressions (such as `Type` and `Default`) are filtered out by the `reader` package after unmarshalling the `variable` block by setting them to nil.
This struct is then passed to the JSON Schema package so that it can create a schema based on these variable definitions.
Here is an example schema generate from a module with only the variable listed above. More examples of generated schema files can be found in the `test` folder.
```JSON
{
"$schema": "http://json-schema.org/draft-07/schema#",
// can be overridden with `--disallow-additional-properties`
"additionalProperties": true,
"properties": {
"age": {
"description": "Your age",
"default": 10,
"minimum": 0,
"type": "number",
},
},
"required": [] // only variables without a default are required, unless `--require-all` is set
}
```
Alternatively, if the program is run with the `--export-variables` flag, the returned JSON will be in the form:
```JSON
{
"age": {
"description": "Your age",
"default": 10,
"sensitive": false,
"nullable": false,
"validation": [
{
"condition": "var.age >= 0",
"error_message": "Age must not be negative"
}
],
"type": "number"
}
}
```
### Translating Types to JSON Schema
Translation of types to Terraform is done in 2 steps. The first step is to take the `hcl.Expression` for the type from the `VariableBlock` struct, and use [go-cty](https://github.com/zclconf/go-cty/) to convert it to a 'type constraint', which is a JSON blob representing all the information about the type in a more machine-readable format.
The second phase is taking that type information and converting it to a JSON Schema definition. All types used by Terraform currently are supported here. Here is how each of them is represented. Also see [Terraform Input Variables](https://developer.hashicorp.com/terraform/language/values/variables#type-constraints) for more information on Terraform input variable types.
#### string
```json
{
"type": "string"
}
```
#### number
```json
{
"type": "number"
}
```
#### bool
```json
{
"type": "boolean"
}
```
#### list(\<TYPE>)
```json
{
"type": "array",
"items": {
"type": "<TYPE>"
}
}
```
#### set(\<TYPE>)
```json
{
"type": "array",
"items": {
"type": "<TYPE>"
},
"uniqueItems": true
}
```
#### map(\<TYPE>)
```json
{
"type": "object",
"additionalProperties": {
"type": "<TYPE>"
}
}
```
#### object({\<NAME> = \<TYPE>,... })
```json
{
// can be overridden with `--disallow-additional-properties`
"additionalProperties": true,
"type": "object",
"properties": {
"<NAME>": {
"type": "<TYPE>"
},
...
},
"required": [
"<NAME>",
...
]
}
```
#### tuple(\<TYPE 1>, ... \<TYPE N>)
```json
{
"type": "array",
"items": [
{
"type": "<TYPE 1>"
},
...
{
"type": "<TYPE N>"
}
],
"minItems": N,
"maxItems": N
}
```
Additionally, any nesting of these types is also valid, and will create a schema according to these rules.
---
Issue: [Optional Type Attributes](https://developer.hashicorp.com/terraform/language/expressions/type-constraints#optional-object-type-attributes) are not fully supported by go-cty (as of v1.15.0), and the program will error if it encounters a type of the form
```hcl
type = optional(<TYPE>,<DEFAULT-VALUE>)
```
with the following error:
```
Invalid type specification; Optional attribute modifier expects only one argument: the attribute type.
```
Optional declarations of the form `optional(<TYPE>)` are supported.
### Custom Validation Rules
A subset of common validation patterns have been implemented. If a validation rule is present and can't be converted to an existing rule, then the application will print a warning. The current list of valid validation rules for a variable with the name `name` is as follows:
| Condition | Variable Type | JSON Output |
| -------------------------------------------------------- | ---------------------- | -------------------------------------------------- |
| **Enum conditions** | | |
| `var.name == 1 \|\| 2 == var.name \|\| ...` | any | `{"enum": [1, 2, ...]}` |
| `contains([1,2,...], var.name)` | any | `{"enum": [1, 2, ...]}` |
| **Regex conditions** | | |
| `can(regex("<pattern>", var.name))` | `string` | `{"pattern": "<pattern>"}` |
| **Number value comparison conditions** | | |
| `var.name < 10 && var.name > 0 && ...` | `number` | `{"exclusiveMinimum": 0", "exclusiveMaximum": 10}` |
| `var.name <= 10 && var.name >= 0 && ...` | `number` | `{"minimum": 0, "maximum": 10"}` |
| **String length comparison conditions** | | |
| `length(var.name) < 10 && length(var.name) > 0 && ...` | `string` | `{"minLength": 1,"maxLength": 9}` |
| `length(var.name) <= 10 && length(var.name) >= 0 && ...` | `string` | `{"minLength": 0, "maxLength": 10, }` |
| `length(var.name) == 5 && ...` | `string` | `{"minLength": 5, "maxLength": 5"}` |
| **Object length comparison conditions** | | |
| `length(var.name) < 10 && length(var.name) > 0 && ...` | `map`, `object` | `{"minProperties": 1,"maxProperties": 9}` |
| `length(var.name) <= 10 && length(var.name) >= 0 && ...` | `map`, `object` | `{"minProperties": 0, "maxProperties": 10, }` |
| `length(var.name) == 5 && ...` | `map`, `object` | `{"minProperties": 5, "maxProperties": 5"}` |
| **Array length comparison conditions** | | |
| `length(var.name) < 10 && length(var.name) > 0 && ...` | `list`, `tuple`, `set` | `{"minItems": 1,"maxItems": 9}` |
| `length(var.name) <= 10 && length(var.name) >= 0 && ...` | `list`, `tuple`, `set` | `{"minItems": 0, "maxItems": 10, }` |
| `length(var.name) == 5 && ...` | `list`, `tuple`, `set` | `{"minItems": 5, "maxItems": 5"}` |
### Nullable Variables
If `nullable` is true in the `variable` block, then the JSON Schema will be modified to look like this. This method is primarily chosen for compatibility with react-jsonschema-form.
```JSON
"<NAME>": {
"anyOf": [
{
"title": "null",
"type": "null"
},
{
"title": "<TYPE>",
"type": "<TYPE>"
}
],
"description": "<DESCRIPTION>",
"default": "<DEFAULT>",
"title": "<NAME>: Select a type"
},
```
This is actually a slight behaviour change from the validator used by terraform. If `nullable` is unset, then terraform treats them as `nullable` by default. I chose not to implement that default behaviour here and instead am making the Terraform module author specify `nullable = true`. This is because otherwise schema definitions for simple programs would have to become a lot more verbose just to handle this case.
For behaviour more consistent with Terraform, the flag `--nullable-all` can be used to reset the default value for nullable to be true. Note: this rule only applies to variables which have not explicitly set the value of nullable themselves. See [Terraform documentation on nullable](https://developer.hashicorp.com/terraform/language/values/variables#disallowing-null-input-values
).
As an example, here is a Terraform configuration file which does not specify `nullable`:
```hcl
variable "name" {
type = string
nullable = false
}
variable "age" {
type = number
default = 10
}
```
Without `--nullable-all`, this would result in the following JSON Schema file:
```json
{
"additionalProperties": true,
"properties": {
"age": {
"default": 10,
"type": "number"
},
"name": {
"type": "string"
}
},
"required": [
"age",
"name"
]
}
```
And if `--nullable-all` is set to true, then the 'default' value for nullable will be true, so the schema will change to reflect this:
```json
{
"additionalProperties": true,
"properties": {
"age": {
"anyOf": [
{
"title": "null",
"type": "null"
},
{
"title": "number",
"type": "number"
}
],
"default": 10,
"title": "age: Select a type"
},
"name": {
"type": "string"
}
},
"required": [
"age",
"name"
]
}
```
`name` is not affected here since it has `nullable = false` in its HCL definition.
### Default Handling
Default handling is relatively straightforward. The default specified in Terraform is rendered to a JSON object, and added to the default field in the JSON Schema. Type checking is not performed on the default value. This is in line with how the JSON Schema creators generally expect this field to be used. See their notes on [annotations](https://json-schema.org/understanding-json-schema/reference/annotations#:~:text=The%20default%20keyword%20specifies%20a%20default%20value.).
07070100000004000041ED00000000000000000000000267BF323800000000000000000000000000000000000000000000001600000000terraschema-0.2.0/cmd07070100000005000081A400000000000000000000000167BF323800001DC1000000000000000000000000000000000000001D00000000terraschema-0.2.0/cmd/cmd.go// (C) Copyright 2024 Hewlett Packard Enterprise Development LP
package cmd
import (
"bytes"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/spf13/cobra"
tsjson "github.com/HewlettPackard/terraschema/pkg/json"
"github.com/HewlettPackard/terraschema/pkg/jsonschema"
)
var (
disallowAdditionalProperties bool
overwrite bool
allowEmpty bool
requireAll bool
outputStdOut bool
nullableAll bool
inputPath string
outputPath string
debugOut bool
exportVariables bool
escapeJSON bool
ignoreVariables []string
)
// rootCmd is the base command for terraschema
var rootCmd = &cobra.Command{
Use: "terraschema",
Example: "terraschema -i /path/to/module -o /path/to/schema.json",
Short: "Generate JSON schema from HCL Variable Blocks in a Terraform/OpenTofu module",
Long: "TerraSchema is a CLI tool which scans Terraform configuration ('.tf') " +
"files, parses a list of variables along with their type and validation rules, and converts " +
"them to a schema which complies with JSON Schema Draft-07.\nThe default behaviour is to scan " +
"the current directory and output a schema file called 'schema.json' in the same location. " +
"\nFor more information see https://github.com/HewlettPackard/terraschema.",
PreRunE: preRunCommand,
RunE: runCommand,
SilenceUsage: true,
}
// Execute command with the following flags:
// - disallow-additional-properties: disallow additional properties in schema (default is false)
// - overwrite: overwrite an existing file (default is false for safety reasons)
// - stdout: suppress errors and output schema to stdout (generally not recommended)
// - output: file, default is ./schema.json. Allow creation of directories.
// - input: folder, default is .
// - allow-empty: if no variables are found, print empty schema and exit with 0
// - require-all: require all variables to be present in the schema, even if a default value is specified
// - debug: output logs to track variables retrieved from each file, and get more verbose logs from custom validation rules
func Execute() error {
return rootCmd.Execute()
}
func init() {
rootCmd.Flags().BoolVar(&disallowAdditionalProperties, "disallow-additional-properties", false,
"set additionalProperties to false in the JSON Schema and in nested objects",
)
rootCmd.Flags().BoolVar(&allowEmpty, "allow-empty", false,
"allow an empty JSON Schema if no variables are found",
)
rootCmd.Flags().BoolVar(&requireAll, "require-all", false,
"set all variables to be 'required' in the JSON Schema, even if a default\n"+
"value is specified",
)
rootCmd.Flags().StringVarP(&inputPath, "input", "i", ".",
"input folder containing a Terraform module",
)
rootCmd.Flags().StringVarP(&outputPath, "output", "o", "schema.json",
"output path for the JSON Schema file",
)
rootCmd.Flags().BoolVar(&overwrite, "overwrite", false,
"allow overwriting an existing file",
)
rootCmd.Flags().BoolVar(&outputStdOut, "stdout", false,
"output JSON Schema content to stdout instead of a file and disable any other logging\n"+
"unless an error occurs. Overrides 'debug' and 'output.",
)
rootCmd.Flags().BoolVar(&debugOut, "debug", false,
"output debug logs, may useful for troubleshooting issues relating to translating\n"+
"validation rules. Does not work with --stdout",
)
rootCmd.Flags().BoolVar(&nullableAll, "nullable-all", false,
"make all variables nullable unless nullable set to false explicitly, to make behavior consistent with Terraform",
)
rootCmd.Flags().BoolVar(&exportVariables, "export-variables", false,
"export variables to a JSON file or stdout instead of creating a schema",
)
rootCmd.Flags().BoolVar(&escapeJSON, "escape-json", false,
"escape JSON special characters in the output, so that the Schema can be used in a\n"+
"web context",
)
rootCmd.Flags().StringSliceVar(&ignoreVariables, "ignore-variable", []string{},
"ignore a variable by name when generating schema or exporting variables,\n"+
"repeating this argument allows you to ignore multiple variables",
)
rootCmd.SetFlagErrorFunc(func(cmd *cobra.Command, err error) error {
_ = rootCmd.Usage()
return err
})
}
func preRunCommand(cmd *cobra.Command, args []string) error {
err := inputFileChecks()
if err != nil {
return err
}
if !outputStdOut {
return outputFileChecks()
}
return nil
}
func inputFileChecks() error {
_, err := filepath.Abs(inputPath) // absolute path
if err != nil {
return fmt.Errorf("could not get absolute path for %q: %w", inputPath, err)
}
folder, err := os.Stat(inputPath)
if err != nil {
return fmt.Errorf("could not access directory %q: %w", inputPath, err)
}
if !folder.IsDir() {
return fmt.Errorf("input path %q is not a directory", inputPath)
}
return nil
}
func outputFileChecks() error {
_, err := filepath.Abs(outputPath) // absolute path
if err != nil {
return fmt.Errorf("could not get absolute path for %q: %w", outputPath, err)
}
outputFile, err := os.Stat(outputPath)
if err == nil {
if overwrite {
if outputFile.IsDir() {
return fmt.Errorf(
"output path %q is an existing directory, please specify a file path",
outputPath,
)
}
} else {
return fmt.Errorf("output path %q already exists, use --overwrite to overwrite", outputPath)
}
}
if !strings.HasSuffix(outputPath, ".json") {
fmt.Printf("Warning: output path %q does not have a .json extension, continuing\n", outputPath)
}
return nil
}
func runCommand(cmd *cobra.Command, args []string) error {
var outputMap any
var err error
jsonIndent := "\t"
if exportVariables {
outputMap, err = tsjson.ExportVariables(inputPath, tsjson.ExportVariablesOptions{
AllowEmpty: allowEmpty,
SuppressLogging: outputStdOut,
DebugOut: debugOut && !outputStdOut,
EscapeJSON: escapeJSON,
Indent: jsonIndent,
IgnoreVariables: ignoreVariables,
})
if err != nil {
return fmt.Errorf("error exporting variables: %w", err)
}
} else {
outputMap, err = jsonschema.CreateSchema(inputPath, jsonschema.CreateSchemaOptions{
RequireAll: requireAll,
AllowAdditionalProperties: !disallowAdditionalProperties,
AllowEmpty: allowEmpty,
DebugOut: debugOut && !outputStdOut,
SuppressLogging: outputStdOut,
NullableAll: nullableAll,
IgnoreVariables: ignoreVariables,
})
if err != nil {
return fmt.Errorf("error creating schema: %w", err)
}
}
buffer := &bytes.Buffer{}
encoder := json.NewEncoder(buffer)
encoder.SetEscapeHTML(escapeJSON)
encoder.SetIndent("", jsonIndent)
err = encoder.Encode(outputMap)
if err != nil {
return fmt.Errorf("error marshalling schema: %w", err)
}
jsonOutput := buffer.Bytes()
if outputStdOut {
fmt.Println(string(jsonOutput))
return nil
}
// create folder path for output file if it doesn't exist
err = os.MkdirAll(filepath.Dir(outputPath), 0o755)
if err != nil {
return fmt.Errorf("error creating folder for %q: %w", outputPath, err)
}
// Create a file with 644 file permissions. If this causes issues, we can use 600 instead later.
//nolint:gosec
err = os.WriteFile(outputPath, jsonOutput, 0o644)
if err != nil {
return fmt.Errorf("error writing schema to %q: %w", outputPath, err)
}
fmt.Printf("Info: schema written to %q\n", outputPath)
return nil
}
07070100000006000081A400000000000000000000000167BF323800000372000000000000000000000000000000000000001900000000terraschema-0.2.0/go.modmodule github.com/HewlettPackard/terraschema
go 1.22.6
require (
github.com/google/go-cmp v0.7.0
github.com/hashicorp/hcl/v2 v2.23.0
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1
github.com/spf13/cobra v1.9.1
github.com/stretchr/testify v1.10.0
github.com/zclconf/go-cty v1.16.2
)
require (
github.com/agext/levenshtein v1.2.1 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/spf13/pflag v1.0.6 // indirect
golang.org/x/mod v0.8.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/text v0.11.0 // indirect
golang.org/x/tools v0.6.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
07070100000007000081A400000000000000000000000167BF323800000FF6000000000000000000000000000000000000001900000000terraschema-0.2.0/go.sumgithub.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8=
github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/hashicorp/hcl/v2 v2.23.0 h1:Fphj1/gCylPxHutVSEOf2fBOh1VE4AuLV7+kbJf3qos=
github.com/hashicorp/hcl/v2 v2.23.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM=
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4=
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/zclconf/go-cty v1.16.2 h1:LAJSwc3v81IRBZyUVQDUdZ7hs3SYs9jv0eZJDWHD/70=
github.com/zclconf/go-cty v1.16.2/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo=
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
07070100000008000081A400000000000000000000000167BF3238000000AA000000000000000000000000000000000000001A00000000terraschema-0.2.0/main.go// (C) Copyright 2024 Hewlett Packard Enterprise Development LP
package main
import (
"github.com/HewlettPackard/terraschema/cmd"
)
func main() {
_ = cmd.Execute()
}
07070100000009000041ED00000000000000000000000267BF323800000000000000000000000000000000000000000000001600000000terraschema-0.2.0/pkg0707010000000A000041ED00000000000000000000000267BF323800000000000000000000000000000000000000000000001B00000000terraschema-0.2.0/pkg/json0707010000000B000081A400000000000000000000000167BF323800000D9E000000000000000000000000000000000000002300000000terraschema-0.2.0/pkg/json/json.gopackage json
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"slices"
"github.com/HewlettPackard/terraschema/pkg/model"
"github.com/HewlettPackard/terraschema/pkg/reader"
)
type ExportVariablesOptions struct {
AllowEmpty bool
DebugOut bool
SuppressLogging bool
// this option is used to escape HTML characters in the output JSON. Since these schema files
// aren't intended to be used directly in a web context, this is set to false by default.
EscapeJSON bool
Indent string
IgnoreVariables []string
}
type MarshallableVariableBlock struct {
model.TranslatedVariable
EscapeHTML bool
Indent string
}
var _ json.Marshaler = MarshallableVariableBlock{}
type JSONVariableBlock struct {
Default *any `json:"default,omitempty"`
Description *string `json:"description,omitempty"`
Nullable *bool `json:"nullable,omitempty"`
Sensitive *bool `json:"sensitive,omitempty"`
Validations []JSONValidationBlock `json:"validation,omitempty"`
Type *any `json:"type,omitempty"`
}
type JSONValidationBlock struct {
Condition string `json:"condition"`
ErrorMessage string `json:"error_message"`
}
func ExportVariables(path string, options ExportVariablesOptions) (map[string]MarshallableVariableBlock, error) {
jsonMap := make(map[string]MarshallableVariableBlock)
varMap, err := reader.GetVarMap(path, options.DebugOut)
if err != nil {
if options.AllowEmpty && (errors.Is(err, reader.ErrFilesNotFound) || errors.Is(err, reader.ErrNoVariablesFound)) {
if !options.SuppressLogging {
fmt.Printf("Warning: directory %q: %v, creating empty variables file\n", path, err)
}
return jsonMap, nil
} else {
return jsonMap, fmt.Errorf("error reading tf files at %q: %w", path, err)
}
}
for k, v := range varMap {
if slices.Contains(options.IgnoreVariables, k) {
continue
}
jsonMap[k] = MarshallableVariableBlock{v, options.EscapeJSON, options.Indent}
}
return jsonMap, nil
}
func (j MarshallableVariableBlock) MarshalJSON() ([]byte, error) {
translatedBlock := JSONVariableBlock{
Description: j.Variable.Description,
Nullable: j.Variable.Nullable,
Sensitive: j.Variable.Sensitive,
}
translatedType, err := reader.GetTypeConstraint(j.Variable.Type)
if err != nil {
return nil, fmt.Errorf("error marshalling type constraint: %w", err)
}
translatedBlock.Type = &translatedType
translatedDefault, err := reader.ExpressionToJSONObject(j.Variable.Default)
if err != nil {
return nil, fmt.Errorf("error marshalling default expression: %w", err)
}
translatedBlock.Default = &translatedDefault
if len(j.Variable.Validations) != 0 {
if len(j.ConditionsAsString) != len(j.Variable.Validations) {
return nil, errors.New("mismatch between the number of validation blocks and conditions")
}
translatedBlock.Validations = make([]JSONValidationBlock, len(j.Variable.Validations))
for i := range translatedBlock.Validations {
translatedBlock.Validations[i] = JSONValidationBlock{
Condition: j.ConditionsAsString[i],
ErrorMessage: j.Variable.Validations[i].ErrorMessage,
}
}
}
buffer := &bytes.Buffer{}
encoder := json.NewEncoder(buffer)
encoder.SetEscapeHTML(j.EscapeHTML)
encoder.SetIndent("", j.Indent)
err = encoder.Encode(translatedBlock)
if err != nil {
return nil, fmt.Errorf("error marshalling variable block: %w", err)
}
return buffer.Bytes(), nil
}
0707010000000C000081A400000000000000000000000167BF32380000057F000000000000000000000000000000000000002800000000terraschema-0.2.0/pkg/json/json_test.gopackage json
import (
"encoding/json"
"os"
"path/filepath"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
)
func TestCreateSchema(t *testing.T) {
t.Parallel()
tfPath := "../../test/modules"
schemaPath := "../../test/expected"
testCases := []string{
"empty",
"simple",
"simple-types",
"complex-types",
"custom-validation",
"ignore-variables",
}
for i := range testCases {
name := testCases[i]
t.Run(name, func(t *testing.T) {
t.Parallel()
expected, err := os.ReadFile(filepath.Join(schemaPath, name, "variables.json"))
require.NoError(t, err)
result, err := ExportVariables(filepath.Join(tfPath, name), ExportVariablesOptions{
AllowEmpty: true,
DebugOut: true,
SuppressLogging: false,
EscapeJSON: false,
Indent: "\t",
IgnoreVariables: []string{"ignored", "also_ignored"},
})
require.NoError(t, err)
// marshal and unmarshal to ensure that the map is in the correct format
buf, err := json.Marshal(result)
require.NoError(t, err)
var gotMap map[string]any
err = json.Unmarshal(buf, &gotMap)
require.NoError(t, err)
var expectedMap map[string]any
err = json.Unmarshal(expected, &expectedMap)
require.NoError(t, err)
if d := cmp.Diff(expectedMap, gotMap); d != "" {
t.Errorf("Schema has incorrect value (-want,+got):\n%s", d)
}
})
}
}
0707010000000D000041ED00000000000000000000000267BF323800000000000000000000000000000000000000000000002100000000terraschema-0.2.0/pkg/jsonschema0707010000000E000081A400000000000000000000000167BF32380000118B000000000000000000000000000000000000003000000000terraschema-0.2.0/pkg/jsonschema/json-schema.go// (C) Copyright 2024 Hewlett Packard Enterprise Development LP
package jsonschema
import (
"errors"
"fmt"
"slices"
"github.com/HewlettPackard/terraschema/pkg/model"
"github.com/HewlettPackard/terraschema/pkg/reader"
)
type CreateSchemaOptions struct {
RequireAll bool
AllowAdditionalProperties bool
AllowEmpty bool
DebugOut bool
SuppressLogging bool
NullableAll bool
IgnoreVariables []string
}
func CreateSchema(path string, options CreateSchemaOptions) (map[string]any, error) {
schemaOut := make(map[string]any)
varMap, err := reader.GetVarMap(path, options.DebugOut)
if err != nil {
if options.AllowEmpty && (errors.Is(err, reader.ErrFilesNotFound) || errors.Is(err, reader.ErrNoVariablesFound)) {
if !options.SuppressLogging {
fmt.Printf("Warning: directory %q: %v, creating empty schema file\n", path, err)
}
return schemaOut, nil
} else {
return schemaOut, fmt.Errorf("error reading tf files at %q: %w", path, err)
}
}
schemaOut["$schema"] = "http://json-schema.org/draft-07/schema#"
schemaOut["type"] = "object"
schemaOut["additionalProperties"] = options.AllowAdditionalProperties
properties := make(map[string]any)
requiredArray := []any{}
for name, variable := range varMap {
if slices.Contains(options.IgnoreVariables, name) {
continue
}
if variable.Required && !options.RequireAll {
requiredArray = append(requiredArray, name)
}
if options.RequireAll {
requiredArray = append(requiredArray, name)
}
node, err := createNode(name, variable, options)
if err != nil {
return schemaOut, fmt.Errorf("error creating node for %q: %w", name, err)
}
properties[name] = node
}
schemaOut["properties"] = properties
slices.SortFunc(requiredArray, sortInterfaceAlphabetical) // get required in alphabetical order
schemaOut["required"] = requiredArray
return schemaOut, nil
}
//nolint:cyclop
func createNode(name string, v model.TranslatedVariable, options CreateSchemaOptions) (map[string]any, error) {
tc, err := reader.GetTypeConstraint(v.Variable.Type)
if err != nil {
return nil, fmt.Errorf("getting type constraint for %q: %w", name, err)
}
// The default value for nullable is the value of NullableAll. For the purpose of keeping the JSON Schema relatively
// clean, this is normally set to false. Setting the default value to true is consistent with Terraform behavior.
nullableTranslatedValue := options.NullableAll
if v.Variable.Nullable != nil {
nullableTranslatedValue = *v.Variable.Nullable
}
node, err := getNodeFromType(name, tc, nullableTranslatedValue, options)
if err != nil {
return nil, fmt.Errorf("%q: %w", name, err)
}
if v.Variable.Default != nil {
def, err := reader.ExpressionToJSONObject(v.Variable.Default)
if err != nil {
return nil, fmt.Errorf("error converting default value to JSON object: %w", err)
}
node["default"] = def
}
// Apply all specified validation rules in the order specified in the HCL config.
for i, validation := range v.Variable.Validations {
err = parseConditionToNode(validation.Condition, v.ConditionsAsString[i], name, &node)
// if an error occurs, log it and continue.
if err != nil && !options.SuppressLogging {
fmt.Printf("Warning: couldn't apply validation for %q with condition %q: %v\n",
name,
v.ConditionsAsString[i],
err,
)
// if the debug flag is set, print all the errors returned by each of the rules as they try to apply to the condition.
var validationError ValidationApplyError
if ok := errors.As(err, &validationError); ok && options.DebugOut {
fmt.Printf("Debug: condition located at %q\n", validation.Condition.Range().String())
fmt.Println("Debug: the following errors occurred:")
for k, v := range validationError.ErrorMap {
fmt.Printf("\t%s: %v\n", k, v)
}
}
}
}
if v.Variable.Description != nil {
node["description"] = *v.Variable.Description
}
// if nullable is true, then we need to unset the definition for "type" here, since it was only added to
// satisfy the validation rules and is not actually a part of the schema.
if nullableTranslatedValue {
delete(node, "type")
}
return node, nil
}
func sortInterfaceAlphabetical(a, b any) int {
aString, ok := a.(string)
if !ok {
return 0
}
bString, ok := b.(string)
if !ok {
return 0
}
if aString < bString {
return -1
}
if aString > bString {
return 1
}
return 0
}
0707010000000F000081A400000000000000000000000167BF323800003FE1000000000000000000000000000000000000003500000000terraschema-0.2.0/pkg/jsonschema/json-schema_test.go// (C) Copyright 2024 Hewlett Packard Enterprise Development LP
package jsonschema
import (
"encoding/json"
"errors"
"os"
"path/filepath"
"slices"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/santhosh-tekuri/jsonschema/v5"
"github.com/stretchr/testify/require"
)
func TestCreateSchema(t *testing.T) {
t.Parallel()
tfPath := "../../test/modules"
schemaPath := "../../test/expected"
testCases := []string{
"empty",
"simple",
"simple-types",
"complex-types",
"custom-validation",
"ignore-variables",
}
for i := range testCases {
name := testCases[i]
t.Run(name, func(t *testing.T) {
t.Parallel()
expected, err := os.ReadFile(filepath.Join(schemaPath, name, "schema.json"))
require.NoError(t, err)
result, err := CreateSchema(filepath.Join(tfPath, name), CreateSchemaOptions{
RequireAll: false,
AllowAdditionalProperties: true,
AllowEmpty: true,
NullableAll: false,
IgnoreVariables: []string{"ignored", "also_ignored"},
})
require.NoError(t, err)
var expectedMap map[string]any
err = json.Unmarshal(expected, &expectedMap)
require.NoError(t, err)
if d := cmp.Diff(expectedMap, result); d != "" {
t.Errorf("Schema has incorrect value (-want,+got):\n%s", d)
}
})
}
}
type errorLocation struct {
name string
nestedLocations []errorLocation
}
func getErrorLocationsFromValidationErr(t *testing.T, valErr *jsonschema.ValidationError) []errorLocation {
t.Helper()
if len(valErr.Causes) == 0 {
return nil
}
keywordLocations := []errorLocation{}
for _, cause := range valErr.Causes {
newLocation := errorLocation{
name: cause.KeywordLocation,
nestedLocations: getErrorLocationsFromValidationErr(t, cause),
}
keywordLocations = append(keywordLocations, newLocation)
}
slices.SortFunc(keywordLocations, func(a, b errorLocation) int {
if a.name < b.name {
return -1
}
if a.name == b.name {
return 0
}
return 1
})
return keywordLocations
}
//nolint:funlen,maintidx
func TestSampleInput(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
schemaPath string
filePath string
keywordLocations []errorLocation
}{
// Minimum input
{
name: "empty minimum input",
filePath: "../../test/expected/empty/sample-input/test-input-min.json",
schemaPath: "../../test/expected/empty/schema.json",
keywordLocations: nil,
},
{
name: "simple minimum input",
filePath: "../../test/expected/simple/sample-input/test-input-min.json",
schemaPath: "../../test/expected/simple/schema.json",
keywordLocations: nil,
},
{
name: "simple-types minimum input",
filePath: "../../test/expected/simple-types/sample-input/test-input-min.json",
schemaPath: "../../test/expected/simple-types/schema.json",
keywordLocations: nil,
},
{
name: "complex-types minimum input",
filePath: "../../test/expected/complex-types/sample-input/test-input-min.json",
schemaPath: "../../test/expected/complex-types/schema.json",
keywordLocations: nil,
},
{
name: "custom-validation minimum input",
filePath: "../../test/expected/custom-validation/sample-input/test-input-min.json",
schemaPath: "../../test/expected/custom-validation/schema.json",
keywordLocations: nil,
},
// Maximum input plus an unknown variable, additionalProperties is true
{
name: "simple full input",
filePath: "../../test/expected/simple/sample-input/test-input-all.json",
schemaPath: "../../test/expected/simple/schema.json",
keywordLocations: nil,
},
{
name: "simple-types full input",
filePath: "../../test/expected/simple-types/sample-input/test-input-all.json",
schemaPath: "../../test/expected/simple-types/schema.json",
keywordLocations: nil,
},
{
name: "complex-types full input",
filePath: "../../test/expected/complex-types/sample-input/test-input-all.json",
schemaPath: "../../test/expected/complex-types/schema.json",
keywordLocations: nil,
},
{
name: "custom-validation full input",
filePath: "../../test/expected/custom-validation/sample-input/test-input-all.json",
schemaPath: "../../test/expected/custom-validation/schema.json",
keywordLocations: nil,
},
// Maximum input plus an unknown variable, additionalProperties is false
{
name: "simple full input additionalProperties false",
filePath: "../../test/expected/simple/sample-input/test-input-all.json",
schemaPath: "../../test/expected/simple/schema-disallow-additional.json",
keywordLocations: []errorLocation{
{name: "/additionalProperties"},
},
},
{
name: "simple-types full input additionalProperties false",
filePath: "../../test/expected/simple-types/sample-input/test-input-all.json",
schemaPath: "../../test/expected/simple-types/schema-disallow-additional.json",
keywordLocations: []errorLocation{
{name: "/additionalProperties"},
},
},
{
name: "complex-types full input additionalProperties false",
filePath: "../../test/expected/complex-types/sample-input/test-input-all.json",
schemaPath: "../../test/expected/complex-types/schema-disallow-additional.json",
keywordLocations: []errorLocation{
{name: "/additionalProperties"},
},
},
{
name: "custom-validation full input additionalProperties false",
filePath: "../../test/expected/custom-validation/sample-input/test-input-all.json",
schemaPath: "../../test/expected/custom-validation/schema-disallow-additional.json",
keywordLocations: []errorLocation{
{name: "/additionalProperties"},
{name: "/properties/an_object_maximum_minimum_items/additionalProperties"},
},
},
// bad input on all fields
{
name: "simple bad input",
filePath: "../../test/expected/simple/sample-input/test-input-bad.json",
schemaPath: "../../test/expected/simple/schema.json",
keywordLocations: []errorLocation{
{name: "/properties/name/type"},
{name: "/required"},
},
},
{
name: "simple-types bad input",
filePath: "../../test/expected/simple-types/sample-input/test-input-bad.json",
schemaPath: "../../test/expected/simple-types/schema.json",
keywordLocations: []errorLocation{
{name: "/properties/a_bool/type"},
{name: "/properties/a_list/items/type"},
{
name: "/properties/a_map_of_strings",
nestedLocations: []errorLocation{
{name: "/properties/a_map_of_strings/additionalProperties/type"},
{name: "/properties/a_map_of_strings/additionalProperties/type"},
},
},
{
name: "/properties/a_nullable_string/anyOf",
nestedLocations: []errorLocation{
{name: "/properties/a_nullable_string/anyOf/0/type"},
{name: "/properties/a_nullable_string/anyOf/1/type"},
},
},
{name: "/properties/a_set/uniqueItems"},
{name: "/properties/a_string/type"},
{name: "/properties/a_tuple/minItems"},
{
name: "/properties/an_object",
nestedLocations: []errorLocation{
{name: "/properties/an_object/properties/c/type"},
{name: "/properties/an_object/required"},
},
},
{name: "/required"},
},
},
{
name: "complex-types bad input",
filePath: "../../test/expected/complex-types/sample-input/test-input-bad.json",
schemaPath: "../../test/expected/complex-types/schema.json",
keywordLocations: []errorLocation{
{
name: "/properties/a_very_complicated_object",
nestedLocations: []errorLocation{
{name: "/properties/a_very_complicated_object/properties/a/type"},
{name: "/properties/a_very_complicated_object/properties/b/minItems"},
{name: "/properties/a_very_complicated_object/properties/c/additionalProperties/type"},
{
name: "/properties/a_very_complicated_object/properties/d",
nestedLocations: []errorLocation{
{
name: "/properties/a_very_complicated_object/properties/d/properties/a",
nestedLocations: []errorLocation{
{name: "/properties/a_very_complicated_object/properties/d/properties/a/items/items/type"},
{name: "/properties/a_very_complicated_object/properties/d/properties/a/items/type"},
},
},
{name: "/properties/a_very_complicated_object/properties/d/properties/b/type"},
},
},
{name: "/properties/a_very_complicated_object/properties/e/items/1/type"},
{
name: "/properties/a_very_complicated_object/properties/f",
nestedLocations: []errorLocation{
{name: "/properties/a_very_complicated_object/properties/f/items/items/type"},
{name: "/properties/a_very_complicated_object/properties/f/uniqueItems"},
},
},
},
},
{
name: "/properties/an_object_with_optional",
nestedLocations: []errorLocation{
{name: "/properties/an_object_with_optional/properties/a/type"},
{name: "/properties/an_object_with_optional/properties/b/type"},
{name: "/properties/an_object_with_optional/properties/d/type"},
{name: "/properties/an_object_with_optional/required"},
},
},
},
},
{
name: "custom-validation bad input",
filePath: "../../test/expected/custom-validation/sample-input/test-input-bad.json",
schemaPath: "../../test/expected/custom-validation/schema.json",
keywordLocations: []errorLocation{
{name: "/properties/a_list_maximum_minimum_length/minItems"},
{name: "/properties/a_map_maximum_minimum_entries/minProperties"},
{name: "/properties/a_number_enum_kind_1/type"},
{name: "/properties/a_number_enum_kind_2/enum"},
{name: "/properties/a_number_exclusive_maximum_minimum/exclusiveMaximum"},
{name: "/properties/a_number_maximum_minimum/maximum"},
{
name: "/properties/a_set_maximum_minimum_items",
nestedLocations: []errorLocation{
{name: "/properties/a_set_maximum_minimum_items/maxItems"},
{name: "/properties/a_set_maximum_minimum_items/uniqueItems"},
},
},
{name: "/properties/a_string_enum_escaped_characters_kind_1/enum"},
{name: "/properties/a_string_enum_escaped_characters_kind_2/enum"},
{name: "/properties/a_string_enum_kind_1/enum"},
{name: "/properties/a_string_enum_kind_2/type"},
{name: "/properties/a_string_maximum_minimum_length/maxLength"},
{name: "/properties/a_string_multiple_validation_conditions/minLength"},
{name: "/properties/a_string_pattern_1/pattern"},
{name: "/properties/a_string_pattern_2/pattern"},
{
name: "/properties/an_object_maximum_minimum_items",
nestedLocations: []errorLocation{
{name: "/properties/an_object_maximum_minimum_items/maxProperties"},
{name: "/properties/an_object_maximum_minimum_items/properties/name/type"},
},
},
},
},
// null input on all fields, nullableAll is false
{
name: "simple null input nullableAll false",
filePath: "../../test/expected/simple/sample-input/test-input-null.json",
schemaPath: "../../test/expected/simple/schema.json",
keywordLocations: []errorLocation{
{name: "/properties/age/type"},
{name: "/properties/name/type"},
},
},
{
name: "simple-types null input nullableAll false",
filePath: "../../test/expected/simple-types/sample-input/test-input-null.json",
schemaPath: "../../test/expected/simple-types/schema.json",
keywordLocations: []errorLocation{
{name: "/properties/a_bool/type"},
{name: "/properties/a_list/type"},
{name: "/properties/a_map_of_strings/type"},
{name: "/properties/a_number/type"},
{name: "/properties/a_set/type"},
{name: "/properties/a_string/type"},
{name: "/properties/a_tuple/type"},
{name: "/properties/a_variable_in_another_file/type"},
{name: "/properties/an_object/type"},
},
},
{
name: "complex-types null input nullableAll false",
filePath: "../../test/expected/complex-types/sample-input/test-input-null.json",
schemaPath: "../../test/expected/complex-types/schema.json",
keywordLocations: []errorLocation{
{name: "/properties/a_very_complicated_object/type"},
{name: "/properties/an_object_with_optional/type"},
},
},
{
name: "custom-validation null input nullableAll false",
filePath: "../../test/expected/custom-validation/sample-input/test-input-null.json",
schemaPath: "../../test/expected/custom-validation/schema.json",
keywordLocations: []errorLocation{
{name: "/properties/a_list_maximum_minimum_length/type"},
{name: "/properties/a_map_maximum_minimum_entries/type"},
{name: "/properties/a_number_enum_kind_1/type"},
{name: "/properties/a_number_enum_kind_2/type"},
{name: "/properties/a_number_exclusive_maximum_minimum/type"},
{name: "/properties/a_number_maximum_minimum/type"},
{name: "/properties/a_set_maximum_minimum_items/type"},
{name: "/properties/a_string_enum_escaped_characters_kind_1/type"},
{name: "/properties/a_string_enum_escaped_characters_kind_2/type"},
{name: "/properties/a_string_enum_kind_1/type"},
{name: "/properties/a_string_enum_kind_2/type"},
{name: "/properties/a_string_length_over_defined/type"},
{name: "/properties/a_string_maximum_minimum_length/type"},
{name: "/properties/a_string_multiple_validation_conditions/type"},
{name: "/properties/a_string_pattern_1/type"},
{name: "/properties/a_string_pattern_2/type"},
{name: "/properties/a_string_set_length/type"},
{name: "/properties/an_object_maximum_minimum_items/type"},
},
},
// null input on all fields, nullableAll is true
{
name: "simple null input nullableAll true",
filePath: "../../test/expected/simple/sample-input/test-input-null.json",
schemaPath: "../../test/expected/simple/schema-nullable-all.json",
keywordLocations: nil,
},
{
name: "simple-types null input nullableAll true",
filePath: "../../test/expected/simple-types/sample-input/test-input-null.json",
schemaPath: "../../test/expected/simple-types/schema-nullable-all.json",
keywordLocations: nil,
},
{
name: "complex-types null input nullableAll true",
filePath: "../../test/expected/complex-types/sample-input/test-input-null.json",
schemaPath: "../../test/expected/complex-types/schema-nullable-all.json",
keywordLocations: nil,
},
{
// of note: custom validation still applies to nullable fields, and sometimes 'null' doesn't satisfy the
// condition, meaning these fields effectively can't be null.
// This seems to primarily be the case for "enum" fields. Other fields tend to ignore this error, in JSON Schema.
name: "custom-validation null input nullableAll true",
filePath: "../../test/expected/custom-validation/sample-input/test-input-null.json",
schemaPath: "../../test/expected/custom-validation/schema-nullable-all.json",
keywordLocations: []errorLocation{
{name: "/properties/a_number_enum_kind_1/enum"},
{name: "/properties/a_number_enum_kind_2/enum"},
{name: "/properties/a_string_enum_kind_1/enum"},
{name: "/properties/a_string_enum_kind_2/enum"},
},
},
}
for i := range testCases {
tc := testCases[i]
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
path, err := filepath.Abs(tc.schemaPath)
require.NoError(t, err)
c := jsonschema.NewCompiler()
f, err := os.Open(path)
require.NoError(t, err)
err = c.AddResource("file://"+path, f)
require.NoError(t, err)
s, err := c.Compile("file://" + path)
require.NoError(t, err)
input, err := os.ReadFile(tc.filePath)
require.NoError(t, err)
var m any
err = json.Unmarshal(input, &m)
require.NoError(t, err)
var keywordLocations []errorLocation
err = s.Validate(m)
if err != nil {
valErr := &jsonschema.ValidationError{}
ok := errors.As(err, &valErr)
require.True(t, ok)
keywordLocations = getErrorLocationsFromValidationErr(t, valErr)
}
require.Equal(t, tc.keywordLocations, keywordLocations)
})
}
}
07070100000010000081A400000000000000000000000167BF32380000175D000000000000000000000000000000000000002900000000terraschema-0.2.0/pkg/jsonschema/type.go// (C) Copyright 2024 Hewlett Packard Enterprise Development LP
package jsonschema
import (
"fmt"
"slices"
)
var simpleTypeMap = map[string]string{
"string": "string",
"number": "number",
"bool": "boolean",
}
func getNodeFromType(name string, typeInterface any, nullable bool, options CreateSchemaOptions) (map[string]any, error) {
if nullable {
return getNullableNode(name, typeInterface, options)
}
switch t := typeInterface.(type) {
case string:
if simpleType, ok := simpleTypeMap[t]; ok {
return map[string]any{"type": simpleType}, nil
} else if t == "any" {
return map[string]any{}, nil
} else {
return nil, fmt.Errorf("unsupported type %q", t)
}
case []any:
return getNodeFromSlice(t, options)
default:
return nil, fmt.Errorf("unsupported type for %#v", typeInterface)
}
}
func getNullableNode(name string, typeInterface any, options CreateSchemaOptions) (map[string]any, error) {
node := make(map[string]any)
if typeInterface == nil {
return node, nil
}
internalNode, err := getNodeFromType(name, typeInterface, false, options)
if err != nil {
return nil, err
}
title, ok := internalNode["type"].(string)
if !ok {
return nil, fmt.Errorf("could not get type %v as a string", internalNode["type"])
}
internalNode["title"] = title
node["anyOf"] = []any{
map[string]any{"type": "null", "title": "null"},
internalNode,
}
node["title"] = fmt.Sprintf("%s: Select a type", name)
// this is here until the validation rules are applied, because otherwise they error since "type" is undefined.
// This sets validation to apply to the top level, which implies that validation must pass even if the value is null.
node["type"] = internalNode["type"]
return node, nil
}
func getNodeFromSlice(in []any, options CreateSchemaOptions) (map[string]any, error) {
switch in[0] {
// "object" affects additionalProperties, properties, type and required
case "object":
return getObject(in, options)
// "map" affects additionalProperties and type.
case "map":
return getMap(in, options)
// "list" affects items, type
case "list":
return getList(in, options)
// "set" affects items, type, uniqueItems
case "set":
return getSet(in, options)
// "tuple" affects items, type, maxItems, minItems
case "tuple":
return getTuple(in, options)
default:
panic("unknown type")
}
}
func getObject(in []any, options CreateSchemaOptions) (map[string]any, error) {
node := map[string]any{
"type": "object",
}
node["additionalProperties"] = options.AllowAdditionalProperties
if len(in) != 2 && len(in) != 3 {
return nil, fmt.Errorf("object type must have one or two additional elements, %v", in)
}
inMap, ok := in[1].(map[string]any)
if !ok {
return nil, fmt.Errorf("object's first additional element must be a map, %v", in[1])
}
optionals := make(map[string]bool)
if len(in) == 3 {
optionalsSlice, ok := in[2].([]any)
if !ok {
return nil, fmt.Errorf("object's second additional element must be a list of strings, %v", in[2])
}
for _, val := range optionalsSlice {
valAsString, ok := val.(string)
if !ok {
return nil, fmt.Errorf("object's second additional element must be a list of strings, %v", in[2])
}
if _, ok := inMap[valAsString]; !ok {
return nil, fmt.Errorf(
"an object declared as optional is not in the object itself. This should not be possible: %v", in,
)
}
optionals[valAsString] = true
}
}
required := []any{}
properties := make(map[string]any)
for key, val := range inMap {
newNode, err := getNodeFromType("", val, false, options)
if err != nil {
return nil, fmt.Errorf("object property %q: %w", key, err)
}
properties[key] = newNode
// if the variable of the sub-object is marked as optional but RequireAll is true, then it is required.
if !optionals[key] || options.RequireAll {
required = append(required, key)
}
}
node["properties"] = properties
slices.SortFunc(required, sortInterfaceAlphabetical)
node["required"] = required
return node, nil
}
func getMap(in []any, options CreateSchemaOptions) (map[string]any, error) {
node := map[string]any{
"type": "object",
}
if len(in) != 2 {
return nil, fmt.Errorf("map type must have exactly one additional element, %v", in)
}
newNode, err := getNodeFromType("", in[1], false, options)
if err != nil {
return nil, fmt.Errorf("map: %w", err)
}
node["additionalProperties"] = newNode
return node, nil
}
func getList(in []any, options CreateSchemaOptions) (map[string]any, error) {
node := map[string]any{
"type": "array",
}
if len(in) != 2 {
return nil, fmt.Errorf("list type must have exactly one additional element, %v", in)
}
newNode, err := getNodeFromType("", in[1], false, options)
if err != nil {
return nil, fmt.Errorf("list: %w", err)
}
node["items"] = newNode
return node, nil
}
func getSet(in []any, options CreateSchemaOptions) (map[string]any, error) {
node := map[string]any{
"type": "array",
"uniqueItems": true,
}
if len(in) != 2 {
return nil, fmt.Errorf("set type must have exactly one additional element, %v", in)
}
newNode, err := getNodeFromType("", in[1], false, options)
if err != nil {
return nil, fmt.Errorf("set: %w", err)
}
node["items"] = newNode
return node, nil
}
func getTuple(in []any, options CreateSchemaOptions) (map[string]any, error) {
node := map[string]any{
"type": "array",
}
if len(in) != 2 {
return nil, fmt.Errorf("tuple type must have exactly one additional element, %v", in)
}
items := []any{}
typeSlice, ok := in[1].([]any)
if !ok {
return nil, fmt.Errorf("tuple's second argument must be an array, %v", in)
}
for _, val := range typeSlice {
newNode, err := getNodeFromType("", val, false, options)
if err != nil {
return nil, fmt.Errorf("tuple: %w", err)
}
items = append(items, newNode)
}
node["items"] = items
node["minItems"] = float64(len(items))
node["maxItems"] = float64(len(items))
return node, nil
}
07070100000011000081A400000000000000000000000167BF323800000E37000000000000000000000000000000000000002F00000000terraschema-0.2.0/pkg/jsonschema/validation.go// (C) Copyright 2024 Hewlett Packard Enterprise Development LP
package jsonschema
import (
"fmt"
"github.com/hashicorp/hcl/v2"
"github.com/HewlettPackard/terraschema/pkg/reader"
)
type conditionMutator func(hcl.Expression, string, string) (map[string]any, error)
var ErrConditionNotApplied = fmt.Errorf("no translation rules are supported for this condition")
type ValidationApplyError struct {
error
ErrorMap map[string]error
}
func parseConditionToNode(ex hcl.Expression, _ string, name string, m *map[string]any) error {
if m == nil {
return fmt.Errorf("node is nil")
}
t, ok := (*m)["type"].(string)
if !ok {
return fmt.Errorf("cannot apply validation, type is not defined for %v", *m)
}
functions := map[string]conditionMutator{
"contains([...],var.input_parameter)": contains,
"var == \"a\" || var == \"b\"": isOneOf,
"a <>= (variable or variable length) (&& ...)": comparison,
"can(regex(\"...\",var.input_parameter))": canRegex,
}
errorMap := make(map[string]error)
for fnName, fn := range functions {
updatedNode, err := fn(ex, name, t)
if err == nil {
// apply updated node to m:
for k, v := range updatedNode {
(*m)[k] = v
}
return nil
}
errorMap[fnName] = err
}
return ValidationApplyError{ErrConditionNotApplied, errorMap}
}
func isOneOf(ex hcl.Expression, name string, _ string) (map[string]any, error) {
enum := []any{}
err := walkIsOneOf(ex, name, &enum)
if err != nil {
return nil, err
}
if len(enum) == 0 {
return nil, fmt.Errorf("no options found")
}
return map[string]any{"enum": enum}, nil
}
func contains(ex hcl.Expression, name string, _ string) (map[string]any, error) {
args, ok := argumentsOfCall(ex, "contains", 2)
if !ok {
return nil, fmt.Errorf("condition is not a 'contains()' function")
}
l, d := hcl.ExprList(args[0])
if d.HasErrors() {
return nil, fmt.Errorf("first argument is not a list")
}
if !isExpressionVarName(args[1], name) {
return nil, fmt.Errorf("second argument is not a direct reference to the input variable")
}
newEnum := []any{}
for _, val := range l {
simple, err := reader.ExpressionToJSONObject(val)
if err != nil {
return nil, fmt.Errorf("value in list could not be converted to JSON")
}
newEnum = append(newEnum, simple)
}
return map[string]any{"enum": newEnum}, nil
}
func comparison(ex hcl.Expression, name string, t string) (map[string]any, error) {
allowedTypes := map[string]bool{
"object": true,
"array": true,
"number": true,
"string": true,
}
if !allowedTypes[t] {
return nil, fmt.Errorf("rule can only be applied to object, array, number or string types, not %q", t)
}
node := map[string]any{"type": t}
err := walkComparison(ex, name, &node, t)
if err != nil {
return nil, err
}
return node, nil
}
func canRegex(ex hcl.Expression, name string, t string) (map[string]any, error) {
if t != "string" {
return nil, fmt.Errorf("rule can only be applied to string types, not %q", t)
}
canArgs, ok := argumentsOfCall(ex, "can", 1)
if !ok {
return nil, fmt.Errorf("condition is not a 'can()' function")
}
regexArgs, ok := argumentsOfCall(canArgs[0], "regex", 2)
if !ok {
return nil, fmt.Errorf("condition is not a 'can(regex())' function")
}
if !isExpressionVarName(regexArgs[1], name) {
return nil, fmt.Errorf("second argument is not a direct reference to the input variable")
}
patternJSON, err := reader.ExpressionToJSONObject(regexArgs[0])
if err != nil {
return nil, fmt.Errorf("pattern could not be converted to JSON: %w", err)
}
return map[string]any{"pattern": patternJSON}, nil
}
07070100000012000081A400000000000000000000000167BF3238000021E7000000000000000000000000000000000000003400000000terraschema-0.2.0/pkg/jsonschema/validation_util.go// (C) Copyright 2024 Hewlett Packard Enterprise Development LP
package jsonschema
import (
"fmt"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/zclconf/go-cty/cty/gocty"
"github.com/HewlettPackard/terraschema/pkg/reader"
)
func isExpressionVarName(ex hcl.Expression, name string) bool {
newEx, ok := ex.(*hclsyntax.ScopeTraversalExpr)
if !ok {
return false
}
if newEx == nil {
return false
}
t := newEx.Traversal
if len(t) != 2 {
return false
}
root, ok := t[0].(hcl.TraverseRoot)
if !ok {
return false
}
attr, ok := t[1].(hcl.TraverseAttr)
if !ok {
return false
}
return root.Name == "var" && attr.Name == name
}
func isExpressionLengthVarName(ex hcl.Expression, name string) bool {
args, ok := argumentsOfCall(ex, "length", 1)
if !ok {
return false
}
return isExpressionVarName(args[0], name)
}
func argumentsOfCall(ex hcl.Expression, functionName string, args int) ([]hcl.Expression, bool) {
call, d := hcl.ExprCall(ex)
if d.HasErrors() {
return nil, false
}
if call.Name != functionName {
return nil, false
}
if len(call.Arguments) != args {
return nil, false
}
return call.Arguments, true
}
func walkComparison(ex hcl.Expression, name string, node *map[string]any, nodeType string) error {
var err error
switch ex := ex.(type) {
case *hclsyntax.BinaryOpExpr:
switch ex.Op {
case hclsyntax.OpLogicalAnd: // &&
err = walkComparison(ex.LHS, name, node, nodeType)
if err != nil {
return err
}
return walkComparison(ex.RHS, name, node, nodeType)
case hclsyntax.OpGreaterThan,
hclsyntax.OpGreaterThanOrEqual,
hclsyntax.OpLessThanOrEqual,
hclsyntax.OpLessThan,
hclsyntax.OpEqual:
return parseComparisonExpression(ex, name, node, nodeType)
default:
return fmt.Errorf("operator is not one of && <=, >=, <, >, ==")
}
case *hclsyntax.ParenthesesExpr:
return walkComparison(ex.Expression, name, node, nodeType)
default:
return fmt.Errorf("could not evaluate expression")
}
}
func parseComparisonExpression(ex *hclsyntax.BinaryOpExpr, name string, node *map[string]any, nodeType string) error {
if isExpressionVarName(ex.RHS, name) || isExpressionLengthVarName(ex.RHS, name) {
// swap the LHS and RHS
ex.LHS, ex.RHS = ex.RHS, ex.LHS
ex.Op = flipSign(ex.Op)
if ex.Op == nil {
return fmt.Errorf("could not flip sign")
}
}
// parse the right hand side as a constant numeric value. Can't reference other variables since that would require
// making a more complex JSON schema (eg with "if" and "then" properties).
val, d := ex.RHS.Value(&hcl.EvalContext{})
if d.HasErrors() {
return fmt.Errorf("could not evaluate expression as a constant value: %w", d)
}
var num float64
err := gocty.FromCtyValue(val, &num)
if err != nil {
return fmt.Errorf("could not convert value to number: %w", err)
}
// valid comparisons:
// var <> number when type(var) == number
// len(var) <> number when type(var) == array,object,string
// len(var) == number when type(var) == array,object,string
condition1 := isExpressionVarName(ex.LHS, name) &&
nodeType == "number" &&
ex.Op != hclsyntax.OpEqual // don't compare a number to a number with ==. Use const if needed later.
condition2 := isExpressionLengthVarName(ex.LHS, name) &&
(nodeType == "array" || nodeType == "object" || nodeType == "string")
if condition1 || condition2 {
return performOp(ex.Op, node, num, nodeType)
} else {
return fmt.Errorf("operation not supported for type %q op %v", nodeType, ex.Op)
}
}
func performOp(op *hclsyntax.Operation, node *map[string]any, num float64, nodeType string) error {
// bundle the operation and type name together
type operationWithTypeName struct {
operation *hclsyntax.Operation
typeName string
}
// get the field name and whether the field is exclusive, which is a term i use to
// describe whole number quantities paired with "must be greater than this" etc., so
// in that case I have to add 1 to the value before updating "minItems" or similar.
type fieldInfo struct {
minField string
maxField string
exclusive bool
}
// making a lookup table here so I can match an op and a type to its corresponding field
// in a JSON schema.
fieldMap := map[operationWithTypeName]fieldInfo{
{hclsyntax.OpGreaterThan, "number"}: {"exclusiveMinimum", "", false},
{hclsyntax.OpGreaterThanOrEqual, "number"}: {"minimum", "", false},
{hclsyntax.OpLessThan, "number"}: {"", "exclusiveMaximum", false},
{hclsyntax.OpLessThanOrEqual, "number"}: {"", "maximum", false},
{hclsyntax.OpEqual, "number"}: {"minimum", "maximum", false},
{hclsyntax.OpGreaterThan, "array"}: {"minItems", "", true},
{hclsyntax.OpGreaterThanOrEqual, "array"}: {"minItems", "", false},
{hclsyntax.OpLessThan, "array"}: {"", "maxItems", true},
{hclsyntax.OpLessThanOrEqual, "array"}: {"", "maxItems", false},
{hclsyntax.OpEqual, "array"}: {"minItems", "maxItems", false},
{hclsyntax.OpGreaterThan, "object"}: {"minProperties", "", true},
{hclsyntax.OpGreaterThanOrEqual, "object"}: {"minProperties", "", false},
{hclsyntax.OpLessThan, "object"}: {"", "maxProperties", true},
{hclsyntax.OpLessThanOrEqual, "object"}: {"", "maxProperties", false},
{hclsyntax.OpEqual, "object"}: {"minProperties", "maxProperties", false},
{hclsyntax.OpGreaterThan, "string"}: {"minLength", "", true},
{hclsyntax.OpGreaterThanOrEqual, "string"}: {"minLength", "", false},
{hclsyntax.OpLessThan, "string"}: {"", "maxLength", true},
{hclsyntax.OpLessThanOrEqual, "string"}: {"", "maxLength", false},
{hclsyntax.OpEqual, "string"}: {"minLength", "maxLength", false},
}
info, ok := fieldMap[operationWithTypeName{op, nodeType}]
if !ok {
return fmt.Errorf("operation not supported for type %q op %v", nodeType, op)
}
if info.minField != "" {
if info.exclusive {
num += 1
}
if canUpdateField(node, num, info.minField, false) {
(*node)[info.minField] = num
}
}
if info.maxField != "" {
if info.exclusive {
num -= 1
}
if canUpdateField(node, num, info.maxField, true) {
(*node)[info.maxField] = num
}
}
return nil
}
// each of the minimum and maximum fields must pass this validation step before they are updated
// the field must not exist
// or
// - if the field exists but can't be converted to a float, do not update it.
// - if mustDecrease is true, the new value must be less than the current value
// - if mustDecrease is false, the new value must be greater than the current value
func canUpdateField(node *map[string]any, num float64, fieldName string, mustDecrease bool) bool {
if node == nil {
// would result in nil pointer dereference
return false
}
field, ok := (*node)[fieldName]
if !ok {
// can update field if it doesn't exist
return true
}
current, ok := field.(float64)
if !ok {
// field exists but isn't a float64
return false
}
if mustDecrease {
return num < current
}
return num > current
}
func flipSign(sign *hclsyntax.Operation) *hclsyntax.Operation {
flip := map[*hclsyntax.Operation]*hclsyntax.Operation{
hclsyntax.OpGreaterThan: hclsyntax.OpLessThan,
hclsyntax.OpGreaterThanOrEqual: hclsyntax.OpLessThanOrEqual,
hclsyntax.OpLessThan: hclsyntax.OpGreaterThan,
hclsyntax.OpLessThanOrEqual: hclsyntax.OpGreaterThanOrEqual,
hclsyntax.OpEqual: hclsyntax.OpEqual,
}
// nil if sign is not in the map.
newSign := flip[sign]
return newSign
}
func walkIsOneOf(ex hcl.Expression, name string, enum *[]any) error {
switch ex := ex.(type) {
case *hclsyntax.BinaryOpExpr:
switch ex.Op {
case hclsyntax.OpLogicalOr: // ||
err := walkIsOneOf(ex.LHS, name, enum)
if err != nil {
return err
}
return walkIsOneOf(ex.RHS, name, enum)
case hclsyntax.OpEqual: // ==
return parseEqualityExpression(ex, name, enum)
default:
return fmt.Errorf("operator is not || or ==")
}
case *hclsyntax.ParenthesesExpr:
return walkIsOneOf(ex.Expression, name, enum)
default:
return fmt.Errorf("could not evaluate expression")
}
}
func parseEqualityExpression(ex *hclsyntax.BinaryOpExpr, name string, enum *[]any) error {
if isExpressionVarName(ex.RHS, name) {
// swap the LHS and RHS
ex.LHS, ex.RHS = ex.RHS, ex.LHS
}
if isExpressionVarName(ex.LHS, name) {
object, err := reader.ExpressionToJSONObject(ex.RHS)
if err != nil {
return fmt.Errorf("value could not be converted to JSON: %w", err)
}
*enum = append(*enum, object)
return nil
}
return fmt.Errorf("variable name not found in expression")
}
07070100000013000041ED00000000000000000000000267BF323800000000000000000000000000000000000000000000001C00000000terraschema-0.2.0/pkg/model07070100000014000081A400000000000000000000000167BF32380000080B000000000000000000000000000000000000002500000000terraschema-0.2.0/pkg/model/model.go// (C) Copyright 2024 Hewlett Packard Enterprise Development LP
package model
import (
"github.com/hashicorp/hcl/v2"
)
// VariableBlock represents a Terraform variable block. It contains all fields that can be present in a variable block.
// The variable name is stored separately.
type VariableBlock struct {
Default hcl.Expression `hcl:"default,optional"`
Description *string `hcl:"description,optional"`
Nullable *bool `hcl:"nullable,optional"`
// Sensitive is ignored.
Sensitive *bool `hcl:"sensitive,optional"`
// Validations blocks can be used to add extra rules to the JSON schema, as long as their conditions
// are written in a certain format.
Validations []ValidationBlock `hcl:"validation,block"`
Type hcl.Expression `hcl:"type,optional"`
}
type ValidationBlock struct {
Condition hcl.Expression `hcl:"condition,attr"`
// ErrorMessage is ignored.
ErrorMessage string `hcl:"error_message,attr"`
}
// TranslatedVariable contains the Variable struct, as well as some extra information that can be used for debugging.
// This is done here because it can be difficult to extract this information from pure hcl.Expressions as present in
// the Variable struct without further context. Required is used internally, and the others are for debugging.
type TranslatedVariable struct {
// if the variable has validation blocks, this stores their conditions as a string. This is useful for debugging.
ConditionsAsString []string
// DefaultAsString is the default value of the variable, as a string. This is useful for debugging complex default values.
DefaultAsString *string
// TypeAsString is the type of the variable, as a string. This is useful for debugging complex types, such as objects.
// A nil value for type implies no type has been specified, so the terraform code accepts "any" type.
TypeAsString *string
// Required is true if and only if the variable has no default value.
Required bool
// The variable block used to generate the other fields in this struct.
Variable VariableBlock
}
07070100000015000041ED00000000000000000000000267BF323800000000000000000000000000000000000000000000001D00000000terraschema-0.2.0/pkg/reader07070100000016000081A400000000000000000000000167BF323800000F4F000000000000000000000000000000000000002700000000terraschema-0.2.0/pkg/reader/reader.go// (C) Copyright 2024 Hewlett Packard Enterprise Development LP
package reader
import (
"fmt"
"path/filepath"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/gohcl"
"github.com/hashicorp/hcl/v2/hclparse"
"github.com/HewlettPackard/terraschema/pkg/model"
)
var fileSchema = &hcl.BodySchema{
Blocks: []hcl.BlockHeaderSchema{
{
Type: "variable",
LabelNames: []string{"name"},
},
},
}
var (
ErrFilesNotFound = fmt.Errorf("no .tf files found")
ErrNoVariablesFound = fmt.Errorf("tf files don't contain any variables")
)
// GetVarMap reads all .tf files in a directory and returns a map of variable names to their translated values.
// For the purpose of this application, all that matters is the model.VariableBlock contained in this, which
// contains a direct unmarshal of the block itself using the hcl package. The rest of the information is for
// debugging purposes, and to simplify the process of deciding if a variable is 'required' later. Note: in 'strict'
// mode, all variables are required, regardless of whether they have a default value or not.
func GetVarMap(path string, debugOut bool) (map[string]model.TranslatedVariable, error) {
// read all tf files in directory
files, err := filepath.Glob(filepath.Join(path, "*.tf"))
if err != nil {
return nil, err
}
if len(files) == 0 {
return nil, ErrFilesNotFound
}
if debugOut {
fmt.Printf("Debug: found the following files in %q:\n", path)
}
parser := hclparse.NewParser()
varMap := make(map[string]model.TranslatedVariable)
for _, fileName := range files {
if debugOut {
fmt.Printf("\t%q, with variable(s):\n", fileName)
}
file, d := parser.ParseHCLFile(fileName)
if d.HasErrors() {
return nil, d
}
blocks, _, d := file.Body.PartialContent(fileSchema)
if d.HasErrors() {
return nil, d
}
for _, block := range blocks.Blocks {
name, translated, err := getTranslatedVariableFromBlock(block, file)
if err != nil {
return nil, fmt.Errorf("error getting parsing %q: %w", name, err)
}
varMap[name] = translated
if debugOut {
fmt.Printf("\t\t%s\n", name)
}
}
}
if len(varMap) == 0 {
return nil, ErrNoVariablesFound
}
return varMap, nil
}
func getTranslatedVariableFromBlock(block *hcl.Block, file *hcl.File) (string, model.TranslatedVariable, error) {
name := block.Labels[0]
variable := model.VariableBlock{}
d := gohcl.DecodeBody(block.Body, nil, &variable)
if d.HasErrors() {
return name, model.TranslatedVariable{}, d
}
variable.Default = filterMissingExpression(variable.Default)
variable.Type = filterMissingExpression(variable.Type)
out := model.TranslatedVariable{Variable: variable, Required: true}
// Get type, default, and condition as strings and add them to the translated variable struct.
// This is to make the code easier to debug, since hcl.Expressions are difficult to read out of context.
// check if 'default' exists
if variable.Default != nil {
defaultAsString := printToString(variable.Default, file)
out.DefaultAsString = &defaultAsString
out.Required = false
}
// check if 'type' exists
if variable.Type != nil {
typeAsString := printToString(variable.Type, file)
out.TypeAsString = &typeAsString
}
// plaintext print all condition expressions into the ConditionsAsString field.
out.ConditionsAsString = make([]string, len(variable.Validations))
for i, validation := range variable.Validations {
out.ConditionsAsString[i] = printToString(validation.Condition, file)
}
return name, out, nil
}
func filterMissingExpression(in hcl.Expression) hcl.Expression {
// if the start and the end range are the same, this means the field is not
// real, so it can be removed.
if in.Range().Start.Byte == in.Range().End.Byte {
return nil
}
return in
}
func printToString(in hcl.Expression, f *hcl.File) string {
out := string(in.Range().SliceBytes(f.Bytes))
return out
}
07070100000017000081A400000000000000000000000167BF32380000037A000000000000000000000000000000000000002C00000000terraschema-0.2.0/pkg/reader/reader_test.go// (C) Copyright 2024 Hewlett Packard Enterprise Development LP
package reader
import (
"errors"
"path/filepath"
"testing"
)
func TestGetVarMap_Required(t *testing.T) {
t.Parallel()
tfPath := "../../test/modules"
testCases := []string{
"empty",
"simple",
"simple-types",
"complex-types",
"custom-validation",
}
for i := range testCases {
name := testCases[i]
t.Run(name, func(t *testing.T) {
t.Parallel()
varMap, err := GetVarMap(filepath.Join(tfPath, name), true)
if err != nil && !errors.Is(err, ErrFilesNotFound) {
t.Errorf("Error reading tf files: %v", err)
}
for k, v := range varMap {
if v.Required && v.Variable.Default != nil {
t.Errorf("Variable %q is required but has a default", k)
}
if !v.Required && v.Variable.Default == nil {
t.Errorf("Variable %q is not required but has no default", k)
}
}
})
}
}
07070100000018000081A400000000000000000000000167BF3238000005A5000000000000000000000000000000000000003500000000terraschema-0.2.0/pkg/reader/type-constraint_test.go// (C) Copyright 2024 Hewlett Packard Enterprise Development LP
package reader
import (
"encoding/json"
"errors"
"os"
"path/filepath"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
)
func TestGetTypeConstraint(t *testing.T) {
t.Parallel()
tfPath := "../../test/modules"
expectedPath := "../../test/expected/"
testCases := []string{
"empty",
"simple",
"simple-types",
"complex-types",
"custom-validation",
}
for i := range testCases {
name := testCases[i]
t.Run(name, func(t *testing.T) {
t.Parallel()
expected, err := os.ReadFile(filepath.Join(expectedPath, name, "variables.json"))
require.NoError(t, err)
varMap, err := GetVarMap(filepath.Join(tfPath, name), true)
if err != nil && !errors.Is(err, ErrFilesNotFound) {
t.Errorf("Error reading tf files: %v", err)
}
var expectedMap map[string]any
err = json.Unmarshal(expected, &expectedMap)
require.NoError(t, err)
require.Equal(t, len(varMap), len(expectedMap))
for key, val := range varMap {
expectedVal, ok := expectedMap[key].(map[string]any)["type"]
if !ok {
t.Errorf("Variable %q not found in expected map", key)
}
constraint, err := GetTypeConstraint(val.Variable.Type)
require.NoError(t, err)
if d := cmp.Diff(expectedVal, constraint); d != "" {
t.Errorf("Variable %q has incorrect type constraint (-want,+got):\n%s", key, d)
}
}
})
}
}
07070100000019000081A400000000000000000000000167BF3238000003F4000000000000000000000000000000000000002F00000000terraschema-0.2.0/pkg/reader/type-contraint.go// (C) Copyright 2024 Hewlett Packard Enterprise Development LP
package reader
import (
"encoding/json"
"fmt"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/ext/typeexpr"
)
// GetTypeConstraint converts the expression into a type constraint and marshals it to JSON.
// Returns the JSON output as a []byte.
// More info on exactly how this works is here:
// https://pkg.go.dev/github.com/zclconf/go-cty@v1.14.4/cty#Type.MarshalJSON
func GetTypeConstraint(in hcl.Expression) (any, error) {
if in == nil {
return "any", nil
}
t, d := typeexpr.TypeConstraint(in)
if d.HasErrors() {
return nil, fmt.Errorf("could not parse type constraint from expression: %w", d)
}
typeJSON, err := t.MarshalJSON()
if err != nil {
return nil, fmt.Errorf("could not marshal constraint to JSON: %w", err)
}
var typeInterface any
err = json.Unmarshal(typeJSON, &typeInterface)
if err != nil {
return nil, fmt.Errorf("could not unmarshal type from JSON: %w", err)
}
return typeInterface, nil
}
0707010000001A000081A400000000000000000000000167BF3238000003F1000000000000000000000000000000000000002600000000terraschema-0.2.0/pkg/reader/value.go// (C) Copyright 2024 Hewlett Packard Enterprise Development LP
package reader
import (
"encoding/json"
"github.com/hashicorp/hcl/v2"
ctyjson "github.com/zclconf/go-cty/cty/json"
)
// ExpressionToJSONObject converts an HCL expression to an `any` type so that can be marshaled to JSON later.
func ExpressionToJSONObject(in hcl.Expression) (any, error) {
if in == nil {
return nil, nil //nolint:nilnil
}
v, d := in.Value(&hcl.EvalContext{})
if d.HasErrors() {
return nil, d
}
// convert the value to a simple JSON value, so that it can
// be reliably marshaled to JSON. Then, unmarshal it to an
// `any` type so that it can be passed around the code without
// the additional type information that was unmarshaled by the
// hcl package.
simpleObject := ctyjson.SimpleJSONValue{Value: v}
simpleAsJSON, err := simpleObject.MarshalJSON()
if err != nil {
return nil, err
}
var out any
err = json.Unmarshal(simpleAsJSON, &out)
if err != nil {
return nil, err
}
return out, nil
}
0707010000001B000081A400000000000000000000000167BF32380000067E000000000000000000000000000000000000002B00000000terraschema-0.2.0/pkg/reader/value_test.go// (C) Copyright 2024 Hewlett Packard Enterprise Development LP
package reader
import (
"encoding/json"
"errors"
"os"
"path/filepath"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
)
func TestExpressionToJSONObject_Default(t *testing.T) {
t.Parallel()
tfPath := "../../test/modules"
expectedPath := "../../test/expected/"
testCases := []string{
"empty",
"simple",
"simple-types",
"complex-types",
"custom-validation",
}
for i := range testCases {
name := testCases[i]
t.Run(name, func(t *testing.T) {
t.Parallel()
expected, err := os.ReadFile(filepath.Join(expectedPath, name, "variables.json"))
require.NoError(t, err)
var expectedMap map[string]any
err = json.Unmarshal(expected, &expectedMap)
require.NoError(t, err)
defaults := make(map[string]any)
varMap, err := GetVarMap(filepath.Join(tfPath, name), true)
if err != nil && !errors.Is(err, ErrFilesNotFound) {
t.Errorf("error reading tf files: %v", err)
}
for key, val := range varMap {
if val.Variable.Default == nil {
continue
}
defaults[key], err = ExpressionToJSONObject(val.Variable.Default)
require.NoError(t, err)
}
if len(expectedMap) != len(varMap) {
t.Errorf("Expected %d variables, got %d", len(expectedMap), len(varMap))
}
for key, val := range defaults {
expectedVal, ok := expectedMap[key].(map[string]any)["default"]
if !ok {
t.Errorf("Variable %q not found in expected map", key)
}
if d := cmp.Diff(expectedVal, val); d != "" {
t.Errorf("Variable %q has incorrect default (-want,+got):\n%s", key, d)
}
}
})
}
}
0707010000001C000041ED00000000000000000000000267BF323800000000000000000000000000000000000000000000001700000000terraschema-0.2.0/test0707010000001D000041ED00000000000000000000000267BF323800000000000000000000000000000000000000000000002000000000terraschema-0.2.0/test/expected0707010000001E000041ED00000000000000000000000267BF323800000000000000000000000000000000000000000000002E00000000terraschema-0.2.0/test/expected/complex-types0707010000001F000041ED00000000000000000000000267BF323800000000000000000000000000000000000000000000003B00000000terraschema-0.2.0/test/expected/complex-types/sample-input07070100000020000081A400000000000000000000000167BF323800000444000000000000000000000000000000000000004F00000000terraschema-0.2.0/test/expected/complex-types/sample-input/test-input-all.json{
"$schema": "../schema.json",
"a_very_complicated_object": {
"a": "string",
"b": [
[
"string",
"string",
"string"
],
true
],
"c": {
"a": [
"string"
],
"b": [
"b"
]
},
"d": {
"a": [
[
"string",
"string"
],
[
"string",
"string"
]
],
"b": 1
},
"e": [
"string",
1
],
"f": [
[
"string"
],
[
"string-2"
],
[
"string",
"string-2"
]
]
},
"an_object_with_optional": {
"a": "d",
"b": 2,
"c": false,
"d": "Optional string"
},
"something_else": "string"
}07070100000021000081A400000000000000000000000167BF323800000412000000000000000000000000000000000000004F00000000terraschema-0.2.0/test/expected/complex-types/sample-input/test-input-bad.json{
"$schema": "../schema.json",
"new_field": "file which does not follow the JSON schema.",
"a_very_complicated_object": {
"a": {},
"b": [
[
"a",
"b",
"c"
]
],
"c": {
"a": [
"a"
],
"b": "b"
},
"d": {
"a": [
[
"a",
"b"
],
[
"c",
"d",
1
],
1
],
"b": false
},
"e": [
"a",
false
],
"f": [
[
"a"
],
[
"b"
],
[
"a"
],
[
1
]
]
},
"an_object_with_optional": {
"a": true,
"b": "a",
"d": 1
}
}07070100000022000081A400000000000000000000000167BF323800000023000000000000000000000000000000000000004F00000000terraschema-0.2.0/test/expected/complex-types/sample-input/test-input-min.json{
"$schema": "../schema.json"
}07070100000023000081A400000000000000000000000167BF32380000006F000000000000000000000000000000000000005000000000terraschema-0.2.0/test/expected/complex-types/sample-input/test-input-null.json{
"$schema": "../schema.json",
"a_very_complicated_object": null,
"an_object_with_optional": null
}07070100000024000081A400000000000000000000000167BF323800000965000000000000000000000000000000000000004E00000000terraschema-0.2.0/test/expected/complex-types/schema-disallow-additional.json{
"$schema": "http://json-schema.org/draft-07/schema#",
"additionalProperties": false,
"properties": {
"a_very_complicated_object": {
"additionalProperties": false,
"default": {
"b": [
[
"a",
"b",
"c"
],
true
],
"c": {
"a": [
"a"
],
"b": [
"b"
]
},
"d": {
"a": [
[
"a",
"b"
],
[
"c",
"d"
]
],
"b": 1
},
"e": [
"a",
1
],
"f": [
[
"a"
],
[
"b"
],
[
"a",
"b"
]
]
},
"description": "This is a very complicated object",
"properties": {
"a": {
"type": "string"
},
"b": {
"items": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "boolean"
}
],
"maxItems": 2,
"minItems": 2,
"type": "array"
},
"c": {
"additionalProperties": {
"items": {
"type": "string"
},
"type": "array"
},
"type": "object"
},
"d": {
"additionalProperties": false,
"properties": {
"a": {
"items": {
"items": {
"type": "string"
},
"type": "array"
},
"type": "array"
},
"b": {
"type": "number"
}
},
"required": [
"a",
"b"
],
"type": "object"
},
"e": {
"items": [
{
"type": "string"
},
{
"type": "number"
}
],
"maxItems": 2,
"minItems": 2,
"type": "array"
},
"f": {
"items": {
"items": {
"type": "string"
},
"type": "array"
},
"type": "array",
"uniqueItems": true
}
},
"required": [
"b",
"c",
"d",
"e",
"f"
],
"type": "object"
},
"an_object_with_optional": {
"additionalProperties": false,
"default": {
"a": "a",
"b": 1,
"c": true
},
"description": "This is an object variable with an optional field",
"properties": {
"a": {
"type": "string"
},
"b": {
"type": "number"
},
"c": {
"type": "boolean"
},
"d": {
"type": "string"
}
},
"required": [
"a",
"b",
"c"
],
"type": "object"
}
},
"required": [],
"type": "object"
}
07070100000025000081A400000000000000000000000167BF323800000B7D000000000000000000000000000000000000004700000000terraschema-0.2.0/test/expected/complex-types/schema-nullable-all.json{
"$schema": "http://json-schema.org/draft-07/schema#",
"additionalProperties": true,
"properties": {
"a_very_complicated_object": {
"anyOf": [
{
"title": "null",
"type": "null"
},
{
"additionalProperties": true,
"properties": {
"a": {
"type": "string"
},
"b": {
"items": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "boolean"
}
],
"maxItems": 2,
"minItems": 2,
"type": "array"
},
"c": {
"additionalProperties": {
"items": {
"type": "string"
},
"type": "array"
},
"type": "object"
},
"d": {
"additionalProperties": true,
"properties": {
"a": {
"items": {
"items": {
"type": "string"
},
"type": "array"
},
"type": "array"
},
"b": {
"type": "number"
}
},
"required": [
"a",
"b"
],
"type": "object"
},
"e": {
"items": [
{
"type": "string"
},
{
"type": "number"
}
],
"maxItems": 2,
"minItems": 2,
"type": "array"
},
"f": {
"items": {
"items": {
"type": "string"
},
"type": "array"
},
"type": "array",
"uniqueItems": true
}
},
"required": [
"b",
"c",
"d",
"e",
"f"
],
"title": "object",
"type": "object"
}
],
"default": {
"b": [
[
"a",
"b",
"c"
],
true
],
"c": {
"a": [
"a"
],
"b": [
"b"
]
},
"d": {
"a": [
[
"a",
"b"
],
[
"c",
"d"
]
],
"b": 1
},
"e": [
"a",
1
],
"f": [
[
"a"
],
[
"b"
],
[
"a",
"b"
]
]
},
"description": "This is a very complicated object",
"title": "a_very_complicated_object: Select a type"
},
"an_object_with_optional": {
"anyOf": [
{
"title": "null",
"type": "null"
},
{
"additionalProperties": true,
"properties": {
"a": {
"type": "string"
},
"b": {
"type": "number"
},
"c": {
"type": "boolean"
},
"d": {
"type": "string"
}
},
"required": [
"a",
"b",
"c"
],
"title": "object",
"type": "object"
}
],
"default": {
"a": "a",
"b": 1,
"c": true
},
"description": "This is an object variable with an optional field",
"title": "an_object_with_optional: Select a type"
}
},
"required": [],
"type": "object"
}
07070100000026000081A400000000000000000000000167BF323800000961000000000000000000000000000000000000003A00000000terraschema-0.2.0/test/expected/complex-types/schema.json{
"$schema": "http://json-schema.org/draft-07/schema#",
"additionalProperties": true,
"properties": {
"a_very_complicated_object": {
"additionalProperties": true,
"default": {
"b": [
[
"a",
"b",
"c"
],
true
],
"c": {
"a": [
"a"
],
"b": [
"b"
]
},
"d": {
"a": [
[
"a",
"b"
],
[
"c",
"d"
]
],
"b": 1
},
"e": [
"a",
1
],
"f": [
[
"a"
],
[
"b"
],
[
"a",
"b"
]
]
},
"description": "This is a very complicated object",
"properties": {
"a": {
"type": "string"
},
"b": {
"items": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "boolean"
}
],
"maxItems": 2,
"minItems": 2,
"type": "array"
},
"c": {
"additionalProperties": {
"items": {
"type": "string"
},
"type": "array"
},
"type": "object"
},
"d": {
"additionalProperties": true,
"properties": {
"a": {
"items": {
"items": {
"type": "string"
},
"type": "array"
},
"type": "array"
},
"b": {
"type": "number"
}
},
"required": [
"a",
"b"
],
"type": "object"
},
"e": {
"items": [
{
"type": "string"
},
{
"type": "number"
}
],
"maxItems": 2,
"minItems": 2,
"type": "array"
},
"f": {
"items": {
"items": {
"type": "string"
},
"type": "array"
},
"type": "array",
"uniqueItems": true
}
},
"required": [
"b",
"c",
"d",
"e",
"f"
],
"type": "object"
},
"an_object_with_optional": {
"additionalProperties": true,
"default": {
"a": "a",
"b": 1,
"c": true
},
"description": "This is an object variable with an optional field",
"properties": {
"a": {
"type": "string"
},
"b": {
"type": "number"
},
"c": {
"type": "boolean"
},
"d": {
"type": "string"
}
},
"required": [
"a",
"b",
"c"
],
"type": "object"
}
},
"required": [],
"type": "object"
}
07070100000027000081A400000000000000000000000167BF32380000053B000000000000000000000000000000000000003D00000000terraschema-0.2.0/test/expected/complex-types/variables.json{
"a_very_complicated_object": {
"default": {
"b": [
[
"a",
"b",
"c"
],
true
],
"c": {
"a": [
"a"
],
"b": [
"b"
]
},
"d": {
"a": [
[
"a",
"b"
],
[
"c",
"d"
]
],
"b": 1
},
"e": [
"a",
1
],
"f": [
[
"a"
],
[
"b"
],
[
"a",
"b"
]
]
},
"description": "This is a very complicated object",
"type": [
"object",
{
"a": "string",
"b": [
"tuple",
[
[
"list",
"string"
],
"bool"
]
],
"c": [
"map",
[
"list",
"string"
]
],
"d": [
"object",
{
"a": [
"list",
[
"list",
"string"
]
],
"b": "number"
}
],
"e": [
"tuple",
[
"string",
"number"
]
],
"f": [
"set",
[
"list",
"string"
]
]
},
[
"a"
]
]
},
"an_object_with_optional": {
"default": {
"a": "a",
"b": 1,
"c": true
},
"description": "This is an object variable with an optional field",
"type": [
"object",
{
"a": "string",
"b": "number",
"c": "bool",
"d": "string"
},
[
"d"
]
]
}
}
07070100000028000041ED00000000000000000000000267BF323800000000000000000000000000000000000000000000003200000000terraschema-0.2.0/test/expected/custom-validation07070100000029000041ED00000000000000000000000267BF323800000000000000000000000000000000000000000000003F00000000terraschema-0.2.0/test/expected/custom-validation/sample-input0707010000002A000081A400000000000000000000000167BF3238000003F1000000000000000000000000000000000000005300000000terraschema-0.2.0/test/expected/custom-validation/sample-input/test-input-all.json{
"$schema": "../schema.json",
"a_list_maximum_minimum_length": [
"a",
"b"
],
"a_map_maximum_minimum_entries": {
"a": "a",
"b": "b"
},
"a_number_enum_kind_1": 1,
"a_number_enum_kind_2": 2,
"a_number_exclusive_maximum_minimum": 2,
"a_number_maximum_minimum": 1,
"a_set_maximum_minimum_items": [
"a",
"b"
],
"a_string_enum_escaped_characters_kind_1": "${abc}",
"a_string_enum_escaped_characters_kind_2": "\"",
"a_string_enum_kind_1": "a",
"a_string_enum_kind_2": "b",
"a_string_length_over_defined": "abcd",
"a_string_maximum_minimum_length": "a",
"a_string_pattern_1": "1.1.1.1",
"a_string_pattern_2": "#ae27ff",
"a_string_set_length": "abcd",
"an_object_maximum_minimum_items": {
"name": "a",
"other_name": "this is an additional property, terraform will ignore it"
},
"a_string_multiple_validation_conditions": "abcd",
"something_else": "string"
}
0707010000002B000081A400000000000000000000000167BF32380000038A000000000000000000000000000000000000005300000000terraschema-0.2.0/test/expected/custom-validation/sample-input/test-input-bad.json{
"$schema": "../schema.json",
"a_list_maximum_minimum_length": [],
"a_map_maximum_minimum_entries": {},
"a_number_enum_kind_1": "1",
"a_number_enum_kind_2": 5,
"a_number_exclusive_maximum_minimum": 100,
"a_number_maximum_minimum": 100,
"a_set_maximum_minimum_items": [
"a",
"a",
"a",
"a",
"a",
"a",
"a",
"a",
"a",
"a"
],
"a_string_enum_escaped_characters_kind_1": "\\\\",
"a_string_enum_escaped_characters_kind_2": "\\t",
"a_string_enum_kind_1": "d",
"a_string_enum_kind_2": 1,
"a_string_maximum_minimum_length": "abcdefghij",
"a_string_pattern_1": "1.1.1.a",
"a_string_pattern_2": "#bcdefg",
"an_object_maximum_minimum_items": {
"name": false,
"number": 1,
"large": false
},
"a_string_multiple_validation_conditions": "a"
}
0707010000002C000081A400000000000000000000000167BF323800000023000000000000000000000000000000000000005300000000terraschema-0.2.0/test/expected/custom-validation/sample-input/test-input-min.json{
"$schema": "../schema.json"
}0707010000002D000081A400000000000000000000000167BF323800000305000000000000000000000000000000000000005400000000terraschema-0.2.0/test/expected/custom-validation/sample-input/test-input-null.json{
"$schema": "../schema.json",
"a_list_maximum_minimum_length": null,
"a_map_maximum_minimum_entries": null,
"a_number_enum_kind_1": null,
"a_number_enum_kind_2": null,
"a_number_exclusive_maximum_minimum": null,
"a_number_maximum_minimum": null,
"a_set_maximum_minimum_items": null,
"a_string_enum_escaped_characters_kind_1": null,
"a_string_enum_escaped_characters_kind_2": null,
"a_string_enum_kind_1": null,
"a_string_enum_kind_2": null,
"a_string_length_over_defined": null,
"a_string_maximum_minimum_length": null,
"a_string_pattern_1": null,
"a_string_pattern_2": null,
"a_string_set_length": null,
"an_object_maximum_minimum_items": null,
"a_string_multiple_validation_conditions": null
}
0707010000002E000081A400000000000000000000000167BF323800001192000000000000000000000000000000000000005200000000terraschema-0.2.0/test/expected/custom-validation/schema-disallow-additional.json{
"$schema": "http://json-schema.org/draft-07/schema#",
"additionalProperties": false,
"properties": {
"a_list_maximum_minimum_length": {
"default": [
"a"
],
"description": "A list variable that must have a length greater than 0 and less than 10",
"items": {
"type": "string"
},
"maxItems": 9,
"minItems": 1,
"type": "array"
},
"a_map_maximum_minimum_entries": {
"additionalProperties": {
"type": "string"
},
"default": {
"a": "a"
},
"description": "A map variable that must have greater than 0 and less than 10 entries",
"maxProperties": 9,
"minProperties": 1,
"type": "object"
},
"a_number_enum_kind_1": {
"default": 1,
"description": "A number variable that must be one of the values 1, 2, or 3",
"enum": [
1,
2,
3
],
"type": "number"
},
"a_number_enum_kind_2": {
"default": 1,
"description": "A number variable that must be one of the values 1, 2, or 3",
"enum": [
1,
2,
3
],
"type": "number"
},
"a_number_exclusive_maximum_minimum": {
"default": 1,
"description": "A number variable that must be greater than 0 and less than 10",
"exclusiveMaximum": 10,
"exclusiveMinimum": 0,
"type": "number"
},
"a_number_maximum_minimum": {
"default": 0,
"description": "A number variable that must be between 0 and 10 (inclusive)",
"maximum": 10,
"minimum": 0,
"type": "number"
},
"a_set_maximum_minimum_items": {
"default": [
"a"
],
"description": "A set variable that must have a length greater than 0 and less than 10",
"items": {
"type": "string"
},
"maxItems": 9,
"minItems": 1,
"type": "array",
"uniqueItems": true
},
"a_string_enum_escaped_characters_kind_1": {
"default": "\\",
"description": "A string variable that must some complicated escaped characters",
"enum": [
"\\",
"\"",
"\\\"",
"${abc}",
"\n",
"\t",
"10%",
"10%%",
"$a",
"$$a",
"\r",
"\\r",
null,
"<",
">",
"&"
],
"type": "string"
},
"a_string_enum_escaped_characters_kind_2": {
"default": "\"",
"description": "A string variable that must some complicated escaped characters",
"enum": [
"\\",
"\"",
"\\\"",
"${abc}",
"\n",
"\t",
"10%",
"10%%",
"$a",
"$$a",
"\r",
"\\r",
null,
"<",
">",
"&"
],
"type": "string"
},
"a_string_enum_kind_1": {
"default": "a",
"description": "A string variable that must be one of the values 'a', 'b', or 'c'",
"enum": [
"a",
"b",
"c"
],
"type": "string"
},
"a_string_enum_kind_2": {
"default": "a",
"description": "A string variable that must be one of the values 'a', 'b', or 'c'",
"enum": [
"a",
"b",
"c"
],
"type": "string"
},
"a_string_length_over_defined": {
"default": "a",
"description": "A string variable that must have length 4",
"maxLength": 4,
"minLength": 4,
"type": "string"
},
"a_string_maximum_minimum_length": {
"default": "a",
"description": "A string variable that must have a length less than 10 and greater than 0",
"maxLength": 9,
"minLength": 1,
"type": "string"
},
"a_string_multiple_validation_conditions": {
"default": "hello",
"description": "A string which has a minimum and maximum length, defined as 2 separate validation blocks",
"maxLength": 7,
"minLength": 2,
"type": "string"
},
"a_string_pattern_1": {
"default": "1.1.1.1",
"description": "A string variable that must be a valid IPv4 address",
"pattern": "^[0-9]{1,3}(\\.[0-9]{1,3}){3}$",
"type": "string"
},
"a_string_pattern_2": {
"default": "#000000",
"description": "string that must be a valid colour hex code in the form #RRGGBB",
"pattern": "^#[0-9a-fA-F]{6}$",
"type": "string"
},
"a_string_set_length": {
"default": "abcd",
"description": "A string variable that must have length 4",
"maxLength": 4,
"minLength": 4,
"type": "string"
},
"an_object_maximum_minimum_items": {
"additionalProperties": false,
"default": {
"name": "a",
"other_field": "b"
},
"description": "An object variable that must have fewer than 3 properties",
"maxProperties": 2,
"minProperties": 1,
"properties": {
"name": {
"type": "string"
}
},
"required": [
"name"
],
"type": "object"
}
},
"required": [],
"type": "object"
}
0707010000002F000081A400000000000000000000000167BF323800001DA7000000000000000000000000000000000000004B00000000terraschema-0.2.0/test/expected/custom-validation/schema-nullable-all.json{
"$schema": "http://json-schema.org/draft-07/schema#",
"additionalProperties": true,
"properties": {
"a_list_maximum_minimum_length": {
"anyOf": [
{
"title": "null",
"type": "null"
},
{
"items": {
"type": "string"
},
"title": "array",
"type": "array"
}
],
"default": [
"a"
],
"description": "A list variable that must have a length greater than 0 and less than 10",
"maxItems": 9,
"minItems": 1,
"title": "a_list_maximum_minimum_length: Select a type"
},
"a_map_maximum_minimum_entries": {
"anyOf": [
{
"title": "null",
"type": "null"
},
{
"additionalProperties": {
"type": "string"
},
"title": "object",
"type": "object"
}
],
"default": {
"a": "a"
},
"description": "A map variable that must have greater than 0 and less than 10 entries",
"maxProperties": 9,
"minProperties": 1,
"title": "a_map_maximum_minimum_entries: Select a type"
},
"a_number_enum_kind_1": {
"anyOf": [
{
"title": "null",
"type": "null"
},
{
"title": "number",
"type": "number"
}
],
"default": 1,
"description": "A number variable that must be one of the values 1, 2, or 3",
"enum": [
1,
2,
3
],
"title": "a_number_enum_kind_1: Select a type"
},
"a_number_enum_kind_2": {
"anyOf": [
{
"title": "null",
"type": "null"
},
{
"title": "number",
"type": "number"
}
],
"default": 1,
"description": "A number variable that must be one of the values 1, 2, or 3",
"enum": [
1,
2,
3
],
"title": "a_number_enum_kind_2: Select a type"
},
"a_number_exclusive_maximum_minimum": {
"anyOf": [
{
"title": "null",
"type": "null"
},
{
"title": "number",
"type": "number"
}
],
"default": 1,
"description": "A number variable that must be greater than 0 and less than 10",
"exclusiveMaximum": 10,
"exclusiveMinimum": 0,
"title": "a_number_exclusive_maximum_minimum: Select a type"
},
"a_number_maximum_minimum": {
"anyOf": [
{
"title": "null",
"type": "null"
},
{
"title": "number",
"type": "number"
}
],
"default": 0,
"description": "A number variable that must be between 0 and 10 (inclusive)",
"maximum": 10,
"minimum": 0,
"title": "a_number_maximum_minimum: Select a type"
},
"a_set_maximum_minimum_items": {
"anyOf": [
{
"title": "null",
"type": "null"
},
{
"items": {
"type": "string"
},
"title": "array",
"type": "array",
"uniqueItems": true
}
],
"default": [
"a"
],
"description": "A set variable that must have a length greater than 0 and less than 10",
"maxItems": 9,
"minItems": 1,
"title": "a_set_maximum_minimum_items: Select a type"
},
"a_string_enum_escaped_characters_kind_1": {
"anyOf": [
{
"title": "null",
"type": "null"
},
{
"title": "string",
"type": "string"
}
],
"default": "\\",
"description": "A string variable that must some complicated escaped characters",
"enum": [
"\\",
"\"",
"\\\"",
"${abc}",
"\n",
"\t",
"10%",
"10%%",
"$a",
"$$a",
"\r",
"\\r",
null,
"<",
">",
"&"
],
"title": "a_string_enum_escaped_characters_kind_1: Select a type"
},
"a_string_enum_escaped_characters_kind_2": {
"anyOf": [
{
"title": "null",
"type": "null"
},
{
"title": "string",
"type": "string"
}
],
"default": "\"",
"description": "A string variable that must some complicated escaped characters",
"enum": [
"\\",
"\"",
"\\\"",
"${abc}",
"\n",
"\t",
"10%",
"10%%",
"$a",
"$$a",
"\r",
"\\r",
null,
"<",
">",
"&"
],
"title": "a_string_enum_escaped_characters_kind_2: Select a type"
},
"a_string_enum_kind_1": {
"anyOf": [
{
"title": "null",
"type": "null"
},
{
"title": "string",
"type": "string"
}
],
"default": "a",
"description": "A string variable that must be one of the values 'a', 'b', or 'c'",
"enum": [
"a",
"b",
"c"
],
"title": "a_string_enum_kind_1: Select a type"
},
"a_string_enum_kind_2": {
"anyOf": [
{
"title": "null",
"type": "null"
},
{
"title": "string",
"type": "string"
}
],
"default": "a",
"description": "A string variable that must be one of the values 'a', 'b', or 'c'",
"enum": [
"a",
"b",
"c"
],
"title": "a_string_enum_kind_2: Select a type"
},
"a_string_length_over_defined": {
"anyOf": [
{
"title": "null",
"type": "null"
},
{
"title": "string",
"type": "string"
}
],
"default": "a",
"description": "A string variable that must have length 4",
"maxLength": 4,
"minLength": 4,
"title": "a_string_length_over_defined: Select a type"
},
"a_string_maximum_minimum_length": {
"anyOf": [
{
"title": "null",
"type": "null"
},
{
"title": "string",
"type": "string"
}
],
"default": "a",
"description": "A string variable that must have a length less than 10 and greater than 0",
"maxLength": 9,
"minLength": 1,
"title": "a_string_maximum_minimum_length: Select a type"
},
"a_string_multiple_validation_conditions": {
"anyOf": [
{
"title": "null",
"type": "null"
},
{
"title": "string",
"type": "string"
}
],
"default": "hello",
"description": "A string which has a minimum and maximum length, defined as 2 separate validation blocks",
"maxLength": 7,
"minLength": 2,
"title": "a_string_multiple_validation_conditions: Select a type"
},
"a_string_pattern_1": {
"anyOf": [
{
"title": "null",
"type": "null"
},
{
"title": "string",
"type": "string"
}
],
"default": "1.1.1.1",
"description": "A string variable that must be a valid IPv4 address",
"pattern": "^[0-9]{1,3}(\\.[0-9]{1,3}){3}$",
"title": "a_string_pattern_1: Select a type"
},
"a_string_pattern_2": {
"anyOf": [
{
"title": "null",
"type": "null"
},
{
"title": "string",
"type": "string"
}
],
"default": "#000000",
"description": "string that must be a valid colour hex code in the form #RRGGBB",
"pattern": "^#[0-9a-fA-F]{6}$",
"title": "a_string_pattern_2: Select a type"
},
"a_string_set_length": {
"anyOf": [
{
"title": "null",
"type": "null"
},
{
"title": "string",
"type": "string"
}
],
"default": "abcd",
"description": "A string variable that must have length 4",
"maxLength": 4,
"minLength": 4,
"title": "a_string_set_length: Select a type"
},
"an_object_maximum_minimum_items": {
"anyOf": [
{
"title": "null",
"type": "null"
},
{
"additionalProperties": true,
"properties": {
"name": {
"type": "string"
}
},
"required": [
"name"
],
"title": "object",
"type": "object"
}
],
"default": {
"name": "a",
"other_field": "b"
},
"description": "An object variable that must have fewer than 3 properties",
"maxProperties": 2,
"minProperties": 1,
"title": "an_object_maximum_minimum_items: Select a type"
}
},
"required": [],
"type": "object"
}
07070100000030000081A400000000000000000000000167BF323800001190000000000000000000000000000000000000003E00000000terraschema-0.2.0/test/expected/custom-validation/schema.json{
"$schema": "http://json-schema.org/draft-07/schema#",
"additionalProperties": true,
"properties": {
"a_list_maximum_minimum_length": {
"default": [
"a"
],
"description": "A list variable that must have a length greater than 0 and less than 10",
"items": {
"type": "string"
},
"maxItems": 9,
"minItems": 1,
"type": "array"
},
"a_map_maximum_minimum_entries": {
"additionalProperties": {
"type": "string"
},
"default": {
"a": "a"
},
"description": "A map variable that must have greater than 0 and less than 10 entries",
"maxProperties": 9,
"minProperties": 1,
"type": "object"
},
"a_number_enum_kind_1": {
"default": 1,
"description": "A number variable that must be one of the values 1, 2, or 3",
"enum": [
1,
2,
3
],
"type": "number"
},
"a_number_enum_kind_2": {
"default": 1,
"description": "A number variable that must be one of the values 1, 2, or 3",
"enum": [
1,
2,
3
],
"type": "number"
},
"a_number_exclusive_maximum_minimum": {
"default": 1,
"description": "A number variable that must be greater than 0 and less than 10",
"exclusiveMaximum": 10,
"exclusiveMinimum": 0,
"type": "number"
},
"a_number_maximum_minimum": {
"default": 0,
"description": "A number variable that must be between 0 and 10 (inclusive)",
"maximum": 10,
"minimum": 0,
"type": "number"
},
"a_set_maximum_minimum_items": {
"default": [
"a"
],
"description": "A set variable that must have a length greater than 0 and less than 10",
"items": {
"type": "string"
},
"maxItems": 9,
"minItems": 1,
"type": "array",
"uniqueItems": true
},
"a_string_enum_escaped_characters_kind_1": {
"default": "\\",
"description": "A string variable that must some complicated escaped characters",
"enum": [
"\\",
"\"",
"\\\"",
"${abc}",
"\n",
"\t",
"10%",
"10%%",
"$a",
"$$a",
"\r",
"\\r",
null,
"<",
">",
"&"
],
"type": "string"
},
"a_string_enum_escaped_characters_kind_2": {
"default": "\"",
"description": "A string variable that must some complicated escaped characters",
"enum": [
"\\",
"\"",
"\\\"",
"${abc}",
"\n",
"\t",
"10%",
"10%%",
"$a",
"$$a",
"\r",
"\\r",
null,
"<",
">",
"&"
],
"type": "string"
},
"a_string_enum_kind_1": {
"default": "a",
"description": "A string variable that must be one of the values 'a', 'b', or 'c'",
"enum": [
"a",
"b",
"c"
],
"type": "string"
},
"a_string_enum_kind_2": {
"default": "a",
"description": "A string variable that must be one of the values 'a', 'b', or 'c'",
"enum": [
"a",
"b",
"c"
],
"type": "string"
},
"a_string_length_over_defined": {
"default": "a",
"description": "A string variable that must have length 4",
"maxLength": 4,
"minLength": 4,
"type": "string"
},
"a_string_maximum_minimum_length": {
"default": "a",
"description": "A string variable that must have a length less than 10 and greater than 0",
"maxLength": 9,
"minLength": 1,
"type": "string"
},
"a_string_multiple_validation_conditions": {
"default": "hello",
"description": "A string which has a minimum and maximum length, defined as 2 separate validation blocks",
"maxLength": 7,
"minLength": 2,
"type": "string"
},
"a_string_pattern_1": {
"default": "1.1.1.1",
"description": "A string variable that must be a valid IPv4 address",
"pattern": "^[0-9]{1,3}(\\.[0-9]{1,3}){3}$",
"type": "string"
},
"a_string_pattern_2": {
"default": "#000000",
"description": "string that must be a valid colour hex code in the form #RRGGBB",
"pattern": "^#[0-9a-fA-F]{6}$",
"type": "string"
},
"a_string_set_length": {
"default": "abcd",
"description": "A string variable that must have length 4",
"maxLength": 4,
"minLength": 4,
"type": "string"
},
"an_object_maximum_minimum_items": {
"additionalProperties": true,
"default": {
"name": "a",
"other_field": "b"
},
"description": "An object variable that must have fewer than 3 properties",
"maxProperties": 2,
"minProperties": 1,
"properties": {
"name": {
"type": "string"
}
},
"required": [
"name"
],
"type": "object"
}
},
"required": [],
"type": "object"
}
07070100000031000081A400000000000000000000000167BF323800002017000000000000000000000000000000000000004100000000terraschema-0.2.0/test/expected/custom-validation/variables.json{
"a_list_maximum_minimum_length": {
"default": [
"a"
],
"description": "A list variable that must have a length greater than 0 and less than 10",
"validation": [
{
"condition": "length(var.a_list_maximum_minimum_length) > 0 && length(var.a_list_maximum_minimum_length) < 10",
"error_message": "a_list_maximum_minimum_length must have a length greater than 0 and less than 10"
}
],
"type": [
"list",
"string"
]
},
"a_map_maximum_minimum_entries": {
"default": {
"a": "a"
},
"description": "A map variable that must have greater than 0 and less than 10 entries",
"validation": [
{
"condition": "length(var.a_map_maximum_minimum_entries) > 0 && length(var.a_map_maximum_minimum_entries)< 10",
"error_message": "a_map_maximum_minimum_entries must greater than 0 and less than 10 entries"
}
],
"type": [
"map",
"string"
]
},
"a_number_enum_kind_1": {
"default": 1,
"description": "A number variable that must be one of the values 1, 2, or 3",
"validation": [
{
"condition": "contains([1, 2, 3], var.a_number_enum_kind_1)",
"error_message": "Invalid value for a_number_enum_kind_1"
}
],
"type": "number"
},
"a_number_enum_kind_2": {
"default": 1,
"description": "A number variable that must be one of the values 1, 2, or 3",
"validation": [
{
"condition": "var.a_number_enum_kind_2 == 1 || var.a_number_enum_kind_2 == 2 || var.a_number_enum_kind_2 == 3",
"error_message": "Invalid value for a_number_enum_kind_2"
}
],
"type": "number"
},
"a_number_exclusive_maximum_minimum": {
"default": 1,
"description": "A number variable that must be greater than 0 and less than 10",
"validation": [
{
"condition": "var.a_number_exclusive_maximum_minimum > 0 && var.a_number_exclusive_maximum_minimum < 10",
"error_message": "a_number_exclusive_maximum_minimum must be less than 10 and greater than 0"
}
],
"type": "number"
},
"a_number_maximum_minimum": {
"default": 0,
"description": "A number variable that must be between 0 and 10 (inclusive)",
"validation": [
{
"condition": "var.a_number_maximum_minimum >= 0 && var.a_number_maximum_minimum <= 10",
"error_message": "a_number_maximum_minimum must be less than or equal to 10 and greater than or equal to 0"
}
],
"type": "number"
},
"a_set_maximum_minimum_items": {
"default": [
"a"
],
"description": "A set variable that must have a length greater than 0 and less than 10",
"validation": [
{
"condition": "0 < length(var.a_set_maximum_minimum_items) && 10 > length(var.a_set_maximum_minimum_items)",
"error_message": "a_set_maximum_minimum_items must have a length greater than 0 and less than 10"
}
],
"type": [
"set",
"string"
]
},
"a_string_enum_escaped_characters_kind_1": {
"default": "\\",
"description": "A string variable that must some complicated escaped characters",
"validation": [
{
"condition": "contains([\"\\\\\", \"\\\"\", \"\\\\\\\"\", \"$${abc}\",\"\\n\",\"\\t\",\"10%\",\"10%%\",\"$a\",\"$$a\",\"\\r\",\"\\\\r\", null, \"<\", \">\", \"&\"], var.a_string_enum_escaped_characters_kind_1)",
"error_message": "Invalid value for a_string_enum_escaped_characters"
}
],
"type": "string"
},
"a_string_enum_escaped_characters_kind_2": {
"default": "\"",
"description": "A string variable that must some complicated escaped characters",
"validation": [
{
"condition": "var.a_string_enum_escaped_characters_kind_2 == \"\\\\\" || var.a_string_enum_escaped_characters_kind_2 == \"\\\"\" || var.a_string_enum_escaped_characters_kind_2 == \"\\\\\\\"\" || var.a_string_enum_escaped_characters_kind_2 == \"$${abc}\" || var.a_string_enum_escaped_characters_kind_2 == \"\\n\" || var.a_string_enum_escaped_characters_kind_2 == \"\\t\" || var.a_string_enum_escaped_characters_kind_2 == \"10%\" || var.a_string_enum_escaped_characters_kind_2 == \"10%%\" || var.a_string_enum_escaped_characters_kind_2 == \"$a\" || var.a_string_enum_escaped_characters_kind_2 == \"$$a\" || var.a_string_enum_escaped_characters_kind_2 == \"\\r\" || var.a_string_enum_escaped_characters_kind_2 == \"\\\\r\" || var.a_string_enum_escaped_characters_kind_2 == null || var.a_string_enum_escaped_characters_kind_2 == \"<\" || var.a_string_enum_escaped_characters_kind_2 == \">\" || var.a_string_enum_escaped_characters_kind_2 == \"&\"",
"error_message": "Invalid value for a_string_enum_escaped_characters"
}
],
"type": "string"
},
"a_string_enum_kind_1": {
"default": "a",
"description": "A string variable that must be one of the values 'a', 'b', or 'c'",
"validation": [
{
"condition": "contains([\"a\", \"b\", \"c\"], var.a_string_enum_kind_1)",
"error_message": "Invalid value for a_string_enum_kind_1"
}
],
"type": "string"
},
"a_string_enum_kind_2": {
"default": "a",
"description": "A string variable that must be one of the values 'a', 'b', or 'c'",
"validation": [
{
"condition": "var.a_string_enum_kind_2 == \"a\" || var.a_string_enum_kind_2 == \"b\" || var.a_string_enum_kind_2 == \"c\"",
"error_message": "Invalid value for a_string_enum_kind_2"
}
],
"type": "string"
},
"a_string_length_over_defined": {
"default": "a",
"description": "A string variable that must have length 4",
"validation": [
{
"condition": "2<length(var.a_string_length_over_defined)&&length(var.a_string_length_over_defined) == 4&& 7>length(var.a_string_length_over_defined)",
"error_message": "a_string_set_length must have length 4"
}
],
"type": "string"
},
"a_string_maximum_minimum_length": {
"default": "a",
"description": "A string variable that must have a length less than 10 and greater than 0",
"validation": [
{
"condition": "0<length(var.a_string_maximum_minimum_length)&&length(var.a_string_maximum_minimum_length)<10",
"error_message": "a_string_maximum_minimum_length must have a length less than 10 and greater than 0"
}
],
"type": "string"
},
"a_string_multiple_validation_conditions": {
"default": "hello",
"description": "A string which has a minimum and maximum length, defined as 2 separate validation blocks",
"validation": [
{
"condition": "length(var.a_string_multiple_validation_conditions) < 8",
"error_message": "Must be fewer than 8 characters"
},
{
"condition": "length(var.a_string_multiple_validation_conditions) >= 1",
"error_message": "Must have greater than or equal to 1 character (note: redundant check for test)"
},
{
"condition": "length(var.a_string_multiple_validation_conditions) >= 2",
"error_message": "Must have greater than or equal to 2 characters"
}
],
"type": "string"
},
"a_string_pattern_1": {
"default": "1.1.1.1",
"description": "A string variable that must be a valid IPv4 address",
"validation": [
{
"condition": "can( regex( \"^[0-9]{1,3}(\\\\.[0-9]{1,3}){3}$\" , var.a_string_pattern_1 ) )",
"error_message": "a_string_pattern_1 must be an IPv4 address"
}
],
"type": "string"
},
"a_string_pattern_2": {
"default": "#000000",
"description": "string that must be a valid colour hex code in the form #RRGGBB",
"validation": [
{
"condition": "can(regex(\"^#[0-9a-fA-F]{6}$\",var.a_string_pattern_2))",
"error_message": "a_string_pattern_2 must be a valid colour hex code in the form #RRGGBB"
}
],
"type": "string"
},
"a_string_set_length": {
"default": "abcd",
"description": "A string variable that must have length 4",
"validation": [
{
"condition": "4==length(var.a_string_set_length)",
"error_message": "a_string_set_length must have length 4"
}
],
"type": "string"
},
"an_object_maximum_minimum_items": {
"default": {
"name": "a",
"other_field": "b"
},
"description": "An object variable that must have fewer than 3 properties",
"validation": [
{
"condition": "length(var.an_object_maximum_minimum_items) > 0 && length(var.an_object_maximum_minimum_items) < 3",
"error_message": "an_object_maximum_minimum_items must have fewer than 3 properties"
}
],
"type": [
"object",
{
"name": "string"
}
]
}
}
07070100000032000041ED00000000000000000000000267BF323800000000000000000000000000000000000000000000002600000000terraschema-0.2.0/test/expected/empty07070100000033000041ED00000000000000000000000267BF323800000000000000000000000000000000000000000000003300000000terraschema-0.2.0/test/expected/empty/sample-input07070100000034000081A400000000000000000000000167BF323800000023000000000000000000000000000000000000004700000000terraschema-0.2.0/test/expected/empty/sample-input/test-input-min.json{
"$schema": "../schema.json"
}07070100000035000081A400000000000000000000000167BF323800000003000000000000000000000000000000000000004600000000terraschema-0.2.0/test/expected/empty/schema-disallow-additional.json{}
07070100000036000081A400000000000000000000000167BF323800000003000000000000000000000000000000000000003F00000000terraschema-0.2.0/test/expected/empty/schema-nullable-all.json{}
07070100000037000081A400000000000000000000000167BF323800000003000000000000000000000000000000000000003200000000terraschema-0.2.0/test/expected/empty/schema.json{}
07070100000038000081A400000000000000000000000167BF323800000003000000000000000000000000000000000000003500000000terraschema-0.2.0/test/expected/empty/variables.json{}
07070100000039000041ED00000000000000000000000267BF323800000000000000000000000000000000000000000000003100000000terraschema-0.2.0/test/expected/ignore-variables0707010000003A000081A400000000000000000000000167BF323800000091000000000000000000000000000000000000005100000000terraschema-0.2.0/test/expected/ignore-variables/schema-disallow-additional.json{
"$schema": "http://json-schema.org/draft-07/schema#",
"additionalProperties": false,
"properties": {},
"required": [],
"type": "object"
}
0707010000003B000081A400000000000000000000000167BF323800000090000000000000000000000000000000000000004A00000000terraschema-0.2.0/test/expected/ignore-variables/schema-nullable-all.json{
"$schema": "http://json-schema.org/draft-07/schema#",
"additionalProperties": true,
"properties": {},
"required": [],
"type": "object"
}
0707010000003C000081A400000000000000000000000167BF323800000090000000000000000000000000000000000000003D00000000terraschema-0.2.0/test/expected/ignore-variables/schema.json{
"$schema": "http://json-schema.org/draft-07/schema#",
"additionalProperties": true,
"properties": {},
"required": [],
"type": "object"
}
0707010000003D000081A400000000000000000000000167BF323800000003000000000000000000000000000000000000004000000000terraschema-0.2.0/test/expected/ignore-variables/variables.json{}
0707010000003E000041ED00000000000000000000000267BF323800000000000000000000000000000000000000000000002700000000terraschema-0.2.0/test/expected/simple0707010000003F000041ED00000000000000000000000267BF323800000000000000000000000000000000000000000000002D00000000terraschema-0.2.0/test/expected/simple-types07070100000040000041ED00000000000000000000000267BF323800000000000000000000000000000000000000000000003A00000000terraschema-0.2.0/test/expected/simple-types/sample-input07070100000041000081A400000000000000000000000167BF32380000024B000000000000000000000000000000000000004E00000000terraschema-0.2.0/test/expected/simple-types/sample-input/test-input-all.json{
"$schema": "../schema.json",
"a_bool": true,
"a_list": [
"d",
"e",
"f"
],
"a_map_of_strings": {
"d": "d",
"e": "e",
"f": "f"
},
"a_nullable_string": "Hello, World!",
"a_number": 42,
"a_set": [
"d",
"e",
"f"
],
"a_string": "Another string",
"a_tuple": [
"d",
2,
false
],
"a_variable_in_another_file": "Yet another string",
"an_object": {
"a": "d",
"b": 2,
"c": false
},
"something_else": "string"
}07070100000042000081A400000000000000000000000167BF323800000228000000000000000000000000000000000000004E00000000terraschema-0.2.0/test/expected/simple-types/sample-input/test-input-bad.json{
"$schema": "../schema.json",
"a_bool": 1,
"a_list": [
1,
"e",
"f"
],
"a_map_of_strings": {
"d": 1,
"e": false,
"f": "f"
},
"a_nullable_string": 0,
"a_set": [
"d",
"d",
"f"
],
"a_string": false,
"a_tuple": [
"d",
2
],
"a_variable_in_another_file": "Yet another string",
"an_object": {
"a": "d",
"c": 1,
"d": "extra fields in objects are allowed, but are ignored by terraform"
}
}07070100000043000081A400000000000000000000000167BF323800000058000000000000000000000000000000000000004E00000000terraschema-0.2.0/test/expected/simple-types/sample-input/test-input-min.json{
"$schema": "../schema.json",
"a_nullable_string": null,
"a_number": -5.2
}07070100000044000081A400000000000000000000000167BF32380000011B000000000000000000000000000000000000004F00000000terraschema-0.2.0/test/expected/simple-types/sample-input/test-input-null.json{
"$schema": "../schema.json",
"a_bool": null,
"a_list": null,
"a_map_of_strings": null,
"a_nullable_string": null,
"a_number": null,
"a_set": null,
"a_string": null,
"a_tuple": null,
"a_variable_in_another_file": null,
"an_object": null
}07070100000045000081A400000000000000000000000167BF323800000817000000000000000000000000000000000000004D00000000terraschema-0.2.0/test/expected/simple-types/schema-disallow-additional.json{
"$schema": "http://json-schema.org/draft-07/schema#",
"additionalProperties": false,
"properties": {
"a_bool": {
"default": false,
"description": "This is a boolean",
"type": "boolean"
},
"a_list": {
"default": [
"a",
"b",
"c"
],
"description": "This is a list of strings",
"items": {
"type": "string"
},
"type": "array"
},
"a_map_of_strings": {
"additionalProperties": {
"type": "string"
},
"default": {
"a": "a",
"b": "b",
"c": "c"
},
"description": "This is a map of strings",
"type": "object"
},
"a_nullable_string": {
"anyOf": [
{
"title": "null",
"type": "null"
},
{
"title": "string",
"type": "string"
}
],
"description": "This is a nullable string",
"title": "a_nullable_string: Select a type"
},
"a_number": {
"description": "This is a number",
"type": "number"
},
"a_set": {
"default": [
"a",
"b",
"c"
],
"description": "This is a set of strings",
"items": {
"type": "string"
},
"type": "array",
"uniqueItems": true
},
"a_string": {
"default": "a string",
"description": "This is a string",
"type": "string"
},
"a_tuple": {
"default": [
"a",
1,
true
],
"description": "This is a tuple",
"items": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
}
],
"maxItems": 3,
"minItems": 3,
"type": "array"
},
"a_variable_in_another_file": {
"default": "",
"description": "a string",
"type": "string"
},
"an_object": {
"additionalProperties": false,
"default": {
"a": "a",
"b": 1,
"c": true
},
"description": "This is an object",
"properties": {
"a": {
"type": "string"
},
"b": {
"type": "number"
},
"c": {
"type": "boolean"
}
},
"required": [
"a",
"b",
"c"
],
"type": "object"
}
},
"required": [
"a_nullable_string",
"a_number"
],
"type": "object"
}
07070100000046000081A400000000000000000000000167BF323800000DC5000000000000000000000000000000000000004600000000terraschema-0.2.0/test/expected/simple-types/schema-nullable-all.json{
"$schema": "http://json-schema.org/draft-07/schema#",
"additionalProperties": true,
"properties": {
"a_bool": {
"anyOf": [
{
"title": "null",
"type": "null"
},
{
"title": "boolean",
"type": "boolean"
}
],
"default": false,
"description": "This is a boolean",
"title": "a_bool: Select a type"
},
"a_list": {
"anyOf": [
{
"title": "null",
"type": "null"
},
{
"items": {
"type": "string"
},
"title": "array",
"type": "array"
}
],
"default": [
"a",
"b",
"c"
],
"description": "This is a list of strings",
"title": "a_list: Select a type"
},
"a_map_of_strings": {
"anyOf": [
{
"title": "null",
"type": "null"
},
{
"additionalProperties": {
"type": "string"
},
"title": "object",
"type": "object"
}
],
"default": {
"a": "a",
"b": "b",
"c": "c"
},
"description": "This is a map of strings",
"title": "a_map_of_strings: Select a type"
},
"a_nullable_string": {
"anyOf": [
{
"title": "null",
"type": "null"
},
{
"title": "string",
"type": "string"
}
],
"description": "This is a nullable string",
"title": "a_nullable_string: Select a type"
},
"a_number": {
"anyOf": [
{
"title": "null",
"type": "null"
},
{
"title": "number",
"type": "number"
}
],
"description": "This is a number",
"title": "a_number: Select a type"
},
"a_set": {
"anyOf": [
{
"title": "null",
"type": "null"
},
{
"items": {
"type": "string"
},
"title": "array",
"type": "array",
"uniqueItems": true
}
],
"default": [
"a",
"b",
"c"
],
"description": "This is a set of strings",
"title": "a_set: Select a type"
},
"a_string": {
"anyOf": [
{
"title": "null",
"type": "null"
},
{
"title": "string",
"type": "string"
}
],
"default": "a string",
"description": "This is a string",
"title": "a_string: Select a type"
},
"a_tuple": {
"anyOf": [
{
"title": "null",
"type": "null"
},
{
"items": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
}
],
"maxItems": 3,
"minItems": 3,
"title": "array",
"type": "array"
}
],
"default": [
"a",
1,
true
],
"description": "This is a tuple",
"title": "a_tuple: Select a type"
},
"a_variable_in_another_file": {
"anyOf": [
{
"title": "null",
"type": "null"
},
{
"title": "string",
"type": "string"
}
],
"default": "",
"description": "a string",
"title": "a_variable_in_another_file: Select a type"
},
"an_object": {
"anyOf": [
{
"title": "null",
"type": "null"
},
{
"additionalProperties": true,
"properties": {
"a": {
"type": "string"
},
"b": {
"type": "number"
},
"c": {
"type": "boolean"
}
},
"required": [
"a",
"b",
"c"
],
"title": "object",
"type": "object"
}
],
"default": {
"a": "a",
"b": 1,
"c": true
},
"description": "This is an object",
"title": "an_object: Select a type"
}
},
"required": [
"a_nullable_string",
"a_number"
],
"type": "object"
}
07070100000047000081A400000000000000000000000167BF323800000815000000000000000000000000000000000000003900000000terraschema-0.2.0/test/expected/simple-types/schema.json{
"$schema": "http://json-schema.org/draft-07/schema#",
"additionalProperties": true,
"properties": {
"a_bool": {
"default": false,
"description": "This is a boolean",
"type": "boolean"
},
"a_list": {
"default": [
"a",
"b",
"c"
],
"description": "This is a list of strings",
"items": {
"type": "string"
},
"type": "array"
},
"a_map_of_strings": {
"additionalProperties": {
"type": "string"
},
"default": {
"a": "a",
"b": "b",
"c": "c"
},
"description": "This is a map of strings",
"type": "object"
},
"a_nullable_string": {
"anyOf": [
{
"title": "null",
"type": "null"
},
{
"title": "string",
"type": "string"
}
],
"description": "This is a nullable string",
"title": "a_nullable_string: Select a type"
},
"a_number": {
"description": "This is a number",
"type": "number"
},
"a_set": {
"default": [
"a",
"b",
"c"
],
"description": "This is a set of strings",
"items": {
"type": "string"
},
"type": "array",
"uniqueItems": true
},
"a_string": {
"default": "a string",
"description": "This is a string",
"type": "string"
},
"a_tuple": {
"default": [
"a",
1,
true
],
"description": "This is a tuple",
"items": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
}
],
"maxItems": 3,
"minItems": 3,
"type": "array"
},
"a_variable_in_another_file": {
"default": "",
"description": "a string",
"type": "string"
},
"an_object": {
"additionalProperties": true,
"default": {
"a": "a",
"b": 1,
"c": true
},
"description": "This is an object",
"properties": {
"a": {
"type": "string"
},
"b": {
"type": "number"
},
"c": {
"type": "boolean"
}
},
"required": [
"a",
"b",
"c"
],
"type": "object"
}
},
"required": [
"a_nullable_string",
"a_number"
],
"type": "object"
}
07070100000048000081A400000000000000000000000167BF32380000054F000000000000000000000000000000000000003C00000000terraschema-0.2.0/test/expected/simple-types/variables.json{
"a_bool": {
"default": false,
"description": "This is a boolean",
"type": "bool"
},
"a_list": {
"default": [
"a",
"b",
"c"
],
"description": "This is a list of strings",
"type": [
"list",
"string"
]
},
"a_map_of_strings": {
"default": {
"a": "a",
"b": "b",
"c": "c"
},
"description": "This is a map of strings",
"type": [
"map",
"string"
]
},
"a_nullable_string": {
"default": null,
"description": "This is a nullable string",
"nullable": true,
"type": "string"
},
"a_number": {
"default": null,
"description": "This is a number",
"type": "number"
},
"a_set": {
"default": [
"a",
"b",
"c"
],
"description": "This is a set of strings",
"type": [
"set",
"string"
]
},
"a_string": {
"default": "a string",
"description": "This is a string",
"type": "string"
},
"a_tuple": {
"default": [
"a",
1,
true
],
"description": "This is a tuple",
"type": [
"tuple",
[
"string",
"number",
"bool"
]
]
},
"a_variable_in_another_file": {
"default": "",
"description": "a string",
"type": "string"
},
"an_object": {
"default": {
"a": "a",
"b": 1,
"c": true
},
"description": "This is an object",
"type": [
"object",
{
"a": "string",
"b": "number",
"c": "bool"
}
]
}
}
07070100000049000041ED00000000000000000000000267BF323800000000000000000000000000000000000000000000003400000000terraschema-0.2.0/test/expected/simple/sample-input0707010000004A000081A400000000000000000000000167BF323800000069000000000000000000000000000000000000004800000000terraschema-0.2.0/test/expected/simple/sample-input/test-input-all.json{
"$schema": "../schema.json",
"name": "Aisling",
"age": 24,
"something_else": "string"
}0707010000004B000081A400000000000000000000000167BF323800000032000000000000000000000000000000000000004800000000terraschema-0.2.0/test/expected/simple/sample-input/test-input-bad.json{
"$schema": "../schema.json",
"name": 1
}0707010000004C000081A400000000000000000000000167BF323800000032000000000000000000000000000000000000004800000000terraschema-0.2.0/test/expected/simple/sample-input/test-input-min.json{
"$schema": "../schema.json",
"age": 10
}0707010000004D000081A400000000000000000000000167BF323800000046000000000000000000000000000000000000004900000000terraschema-0.2.0/test/expected/simple/sample-input/test-input-null.json{
"$schema": "../schema.json",
"name": null,
"age": null
}0707010000004E000081A400000000000000000000000167BF323800000145000000000000000000000000000000000000004700000000terraschema-0.2.0/test/expected/simple/schema-disallow-additional.json{
"$schema": "http://json-schema.org/draft-07/schema#",
"additionalProperties": false,
"properties": {
"age": {
"description": "Your age. Required.",
"type": "number"
},
"name": {
"default": "world",
"description": "Your name.",
"type": "string"
}
},
"required": [
"age"
],
"type": "object"
}
0707010000004F000081A400000000000000000000000167BF323800000269000000000000000000000000000000000000004000000000terraschema-0.2.0/test/expected/simple/schema-nullable-all.json{
"$schema": "http://json-schema.org/draft-07/schema#",
"additionalProperties": true,
"properties": {
"age": {
"anyOf": [
{
"title": "null",
"type": "null"
},
{
"title": "number",
"type": "number"
}
],
"description": "Your age. Required.",
"title": "age: Select a type"
},
"name": {
"anyOf": [
{
"title": "null",
"type": "null"
},
{
"title": "string",
"type": "string"
}
],
"default": "world",
"description": "Your name.",
"title": "name: Select a type"
}
},
"required": [
"age"
],
"type": "object"
}
07070100000050000081A400000000000000000000000167BF323800000144000000000000000000000000000000000000003300000000terraschema-0.2.0/test/expected/simple/schema.json{
"$schema": "http://json-schema.org/draft-07/schema#",
"additionalProperties": true,
"properties": {
"age": {
"description": "Your age. Required.",
"type": "number"
},
"name": {
"default": "world",
"description": "Your name.",
"type": "string"
}
},
"required": [
"age"
],
"type": "object"
}
07070100000051000081A400000000000000000000000167BF3238000000B6000000000000000000000000000000000000003600000000terraschema-0.2.0/test/expected/simple/variables.json{
"age": {
"default": null,
"description": "Your age. Required.",
"type": "number"
},
"name": {
"default": "world",
"description": "Your name.",
"type": "string"
}
}
07070100000052000041ED00000000000000000000000267BF323800000000000000000000000000000000000000000000001F00000000terraschema-0.2.0/test/modules07070100000053000041ED00000000000000000000000267BF323800000000000000000000000000000000000000000000002D00000000terraschema-0.2.0/test/modules/complex-types07070100000054000081A400000000000000000000000167BF3238000003E3000000000000000000000000000000000000003A00000000terraschema-0.2.0/test/modules/complex-types/variables.tf# Copyright 2024 Hewlett Packard Enterprise Development LP
variable "an_object_with_optional" {
type = object({
a = string
b = number
c = bool
d = optional(string)
})
default = {
a = "a"
b = 1
c = true
}
description = "This is an object variable with an optional field"
}
variable "a_very_complicated_object" {
type = object({
a = optional(string)
b = tuple([list(string), bool])
c = map(list(string))
d = object({
a = list(list(string))
b = number
})
e = tuple([string, number])
f = set(list(string))
})
default = {
b = [["a", "b", "c"], true]
c = {
a = ["a"]
b = ["b"]
}
d = {
a = [["a", "b"], ["c", "d"]]
b = 1
}
e = ["a", 1]
f = [["a"], ["b"], ["a", "b"]]
}
description = "This is a very complicated object"
}07070100000055000041ED00000000000000000000000267BF323800000000000000000000000000000000000000000000003100000000terraschema-0.2.0/test/modules/custom-validation07070100000056000081A400000000000000000000000167BF323800001F37000000000000000000000000000000000000003E00000000terraschema-0.2.0/test/modules/custom-validation/variables.tf# Copyright 2024 Hewlett Packard Enterprise Development LP
variable "a_string_enum_kind_1" {
type = string
default = "a"
description = "A string variable that must be one of the values 'a', 'b', or 'c'"
validation {
condition = contains(["a", "b", "c"], var.a_string_enum_kind_1)
error_message = "Invalid value for a_string_enum_kind_1"
}
}
variable "a_string_enum_kind_2" {
type = string
default = "a"
description = "A string variable that must be one of the values 'a', 'b', or 'c'"
validation {
condition = var.a_string_enum_kind_2 == "a" || var.a_string_enum_kind_2 == "b" || var.a_string_enum_kind_2 == "c"
error_message = "Invalid value for a_string_enum_kind_2"
}
}
variable "a_number_enum_kind_1" {
type = number
default = 1
description = "A number variable that must be one of the values 1, 2, or 3"
validation {
condition = contains([1, 2, 3], var.a_number_enum_kind_1)
error_message = "Invalid value for a_number_enum_kind_1"
}
}
variable "a_number_enum_kind_2" {
type = number
default = 1
description = "A number variable that must be one of the values 1, 2, or 3"
validation {
condition = var.a_number_enum_kind_2 == 1 || var.a_number_enum_kind_2 == 2 || var.a_number_enum_kind_2 == 3
error_message = "Invalid value for a_number_enum_kind_2"
}
}
variable "a_number_exclusive_maximum_minimum" {
type = number
default = 1
description = "A number variable that must be greater than 0 and less than 10"
validation {
condition = var.a_number_exclusive_maximum_minimum > 0 && var.a_number_exclusive_maximum_minimum < 10
error_message = "a_number_exclusive_maximum_minimum must be less than 10 and greater than 0"
}
}
variable "a_number_maximum_minimum" {
type = number
default = 0
description = "A number variable that must be between 0 and 10 (inclusive)"
validation {
condition = var.a_number_maximum_minimum >= 0 && var.a_number_maximum_minimum <= 10
error_message = "a_number_maximum_minimum must be less than or equal to 10 and greater than or equal to 0"
}
}
variable "a_list_maximum_minimum_length" {
type = list(string)
default = [ "a" ]
description = "A list variable that must have a length greater than 0 and less than 10"
validation {
condition = length(var.a_list_maximum_minimum_length) > 0 && length(var.a_list_maximum_minimum_length) < 10
error_message = "a_list_maximum_minimum_length must have a length greater than 0 and less than 10"
}
}
variable "an_object_maximum_minimum_items" {
type = object({
name = string
})
description = "An object variable that must have fewer than 3 properties"
validation {
condition = length(var.an_object_maximum_minimum_items) > 0 && length(var.an_object_maximum_minimum_items) < 3
error_message = "an_object_maximum_minimum_items must have fewer than 3 properties"
}
default = {
name = "a"
other_field = "b"
}
}
variable "a_map_maximum_minimum_entries" {
type = map(string)
description = "A map variable that must have greater than 0 and less than 10 entries"
validation {
condition = length(var.a_map_maximum_minimum_entries) > 0 && length(var.a_map_maximum_minimum_entries)< 10
error_message = "a_map_maximum_minimum_entries must greater than 0 and less than 10 entries"
}
default = {
"a" = "a"
}
}
variable "a_set_maximum_minimum_items" {
type = set(string)
description = "A set variable that must have a length greater than 0 and less than 10"
validation {
condition = 0 < length(var.a_set_maximum_minimum_items) && 10 > length(var.a_set_maximum_minimum_items)
error_message = "a_set_maximum_minimum_items must have a length greater than 0 and less than 10"
}
default = ["a"]
}
variable "a_string_maximum_minimum_length" {
type = string
description = "A string variable that must have a length less than 10 and greater than 0"
validation {
condition =0<length(var.a_string_maximum_minimum_length)&&length(var.a_string_maximum_minimum_length)<10
error_message = "a_string_maximum_minimum_length must have a length less than 10 and greater than 0"
}
default = "a"
}
variable "a_string_set_length" {
type = string
description = "A string variable that must have length 4"
validation {
condition = 4==length(var.a_string_set_length)
error_message = "a_string_set_length must have length 4"
}
default = "abcd"
}
variable "a_string_length_over_defined" {
type = string
description = "A string variable that must have length 4"
validation {
condition = 2<length(var.a_string_length_over_defined)&&length(var.a_string_length_over_defined) == 4&& 7>length(var.a_string_length_over_defined)
error_message = "a_string_set_length must have length 4"
}
default = "a"
}
variable "a_string_pattern_1" {
type = string
description = "A string variable that must be a valid IPv4 address"
validation {
condition = can( regex( "^[0-9]{1,3}(\\.[0-9]{1,3}){3}$" , var.a_string_pattern_1 ) )
error_message = "a_string_pattern_1 must be an IPv4 address"
}
default = "1.1.1.1"
}
variable "a_string_pattern_2" {
type = string
description = "string that must be a valid colour hex code in the form #RRGGBB"
validation {
condition =can(regex("^#[0-9a-fA-F]{6}$",var.a_string_pattern_2))
error_message = "a_string_pattern_2 must be a valid colour hex code in the form #RRGGBB"
}
default = "#000000"
}
variable "a_string_enum_escaped_characters_kind_1" {
type = string
description = "A string variable that must some complicated escaped characters"
validation {
condition = contains(["\\", "\"", "\\\"", "$${abc}","\n","\t","10%","10%%","$a","$$a","\r","\\r", null, "<", ">", "&"], var.a_string_enum_escaped_characters_kind_1)
error_message = "Invalid value for a_string_enum_escaped_characters"
}
default = "\\"
}
variable "a_string_enum_escaped_characters_kind_2" {
type = string
description = "A string variable that must some complicated escaped characters"
validation {
condition = var.a_string_enum_escaped_characters_kind_2 == "\\" || var.a_string_enum_escaped_characters_kind_2 == "\"" || var.a_string_enum_escaped_characters_kind_2 == "\\\"" || var.a_string_enum_escaped_characters_kind_2 == "$${abc}" || var.a_string_enum_escaped_characters_kind_2 == "\n" || var.a_string_enum_escaped_characters_kind_2 == "\t" || var.a_string_enum_escaped_characters_kind_2 == "10%" || var.a_string_enum_escaped_characters_kind_2 == "10%%" || var.a_string_enum_escaped_characters_kind_2 == "$a" || var.a_string_enum_escaped_characters_kind_2 == "$$a" || var.a_string_enum_escaped_characters_kind_2 == "\r" || var.a_string_enum_escaped_characters_kind_2 == "\\r" || var.a_string_enum_escaped_characters_kind_2 == null || var.a_string_enum_escaped_characters_kind_2 == "<" || var.a_string_enum_escaped_characters_kind_2 == ">" || var.a_string_enum_escaped_characters_kind_2 == "&"
error_message = "Invalid value for a_string_enum_escaped_characters"
}
default = "\""
}
variable "a_string_multiple_validation_conditions" {
type = string
description = "A string which has a minimum and maximum length, defined as 2 separate validation blocks"
validation {
condition = length(var.a_string_multiple_validation_conditions) < 8
error_message = "Must be fewer than 8 characters"
}
validation {
condition = length(var.a_string_multiple_validation_conditions) >= 1
error_message = "Must have greater than or equal to 1 character (note: redundant check for test)"
}
validation {
condition = length(var.a_string_multiple_validation_conditions) >= 2
error_message = "Must have greater than or equal to 2 characters"
}
default = "hello"
}
07070100000057000041ED00000000000000000000000267BF323800000000000000000000000000000000000000000000002500000000terraschema-0.2.0/test/modules/empty07070100000058000081A400000000000000000000000167BF323800000000000000000000000000000000000000000000002E00000000terraschema-0.2.0/test/modules/empty/.gitkeep07070100000059000041ED00000000000000000000000267BF323800000000000000000000000000000000000000000000003000000000terraschema-0.2.0/test/modules/ignore-variables0707010000005A000081A400000000000000000000000167BF3238000000B5000000000000000000000000000000000000003D00000000terraschema-0.2.0/test/modules/ignore-variables/variables.tfvariable "ignored" {
type = number
description = "Your age. Required."
}
variable "also_ignored" {
type = number
description = "Your age. Required."
}0707010000005B000041ED00000000000000000000000267BF323800000000000000000000000000000000000000000000002600000000terraschema-0.2.0/test/modules/simple0707010000005C000041ED00000000000000000000000267BF323800000000000000000000000000000000000000000000002C00000000terraschema-0.2.0/test/modules/simple-types0707010000005D000081A400000000000000000000000167BF32380000003A000000000000000000000000000000000000003C00000000terraschema-0.2.0/test/modules/simple-types/no-variables.tf# Copyright 2024 Hewlett Packard Enterprise Development LP0707010000005E000081A400000000000000000000000167BF32380000009F000000000000000000000000000000000000003F00000000terraschema-0.2.0/test/modules/simple-types/other-variables.tf# Copyright 2024 Hewlett Packard Enterprise Development LP
variable "a_variable_in_another_file" {
type = string
description = "a string"
default = ""
}0707010000005F000081A400000000000000000000000167BF3238000004C3000000000000000000000000000000000000003900000000terraschema-0.2.0/test/modules/simple-types/variables.tf# Copyright 2024 Hewlett Packard Enterprise Development LP
variable "a_string" {
type = string
default = "a string"
description = "This is a string"
}
variable "a_number" {
type = number
description = "This is a number"
}
variable "a_bool" {
type = bool
default = false
description = "This is a boolean"
}
variable "a_nullable_string" {
type = string
nullable = true
description = "This is a nullable string"
}
variable "a_list" {
type = list(string)
default = ["a", "b", "c"]
description = "This is a list of strings"
}
variable "a_map_of_strings" {
type = map(string)
default = {
a = "a"
b = "b"
c = "c"
}
description = "This is a map of strings"
}
variable "an_object" {
type = object({
a = string
b = number
c = bool
})
default = {
a = "a"
b = 1
c = true
}
description = "This is an object"
}
variable "a_tuple" {
type = tuple([string, number, bool])
default = ["a", 1, true]
description = "This is a tuple"
}
variable "a_set" {
type = set(string)
default = ["a", "b", "c"]
description = "This is a set of strings"
}
07070100000060000081A400000000000000000000000167BF3238000000E2000000000000000000000000000000000000002E00000000terraschema-0.2.0/test/modules/simple/main.tf# Copyright 2024 Hewlett Packard Enterprise Development LP
terraform {
required_version = ">= 0.13.0"
}
output "hello" {
value = "hello"
}
output "name" {
value = var.name
}
output "age" {
value = var.age
}07070100000061000041ED00000000000000000000000267BF323800000000000000000000000000000000000000000000003100000000terraschema-0.2.0/test/modules/simple/sub-module07070100000062000081A400000000000000000000000167BF3238000000C7000000000000000000000000000000000000003E00000000terraschema-0.2.0/test/modules/simple/sub-module/variables.tf# Copyright 2024 Hewlett Packard Enterprise Development LP
variable "should_be_ignored" {
type = string
description = "Variables in sub-modules are not read."
default = "nothing to see here"
}07070100000063000081A400000000000000000000000167BF3238000000F2000000000000000000000000000000000000003300000000terraschema-0.2.0/test/modules/simple/variables.tf# Copyright 2024 Hewlett Packard Enterprise Development LP
variable "name" {
type = string
description = "Your name."
default = "world"
}
variable "age" {
type = number
description = "Your age. Required."
}07070100000064000081A400000000000000000000000167BF3238000000DB000000000000000000000000000000000000003A00000000terraschema-0.2.0/test/modules/simple/wrong-file-type.hcl# Copyright 2024 Hewlett Packard Enterprise Development LP
variable "a_variable_in_the_wrong_file" {
type = string
description = "A string. This should not show up in the schema, and is ignored by terraform."
}07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!332 blocks