├── .copywrite.hcl ├── .gitattributes ├── .github ├── CODEOWNERS ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── .go-version ├── LICENSE ├── META.d ├── _summary.yml └── data.yml ├── README.md ├── backend ├── backend.go └── cloud.go ├── earlydecoder ├── backend.go ├── data_source.go ├── decoder.go ├── decoder_test.go ├── ephemeral_resource.go ├── load_module.go ├── provider_requirements.go ├── resource.go ├── schema.go ├── stacks │ ├── decoder.go │ ├── decoder_test.go │ ├── load_stack.go │ ├── provider_requirements.go │ └── schema.go └── tests │ ├── decoder.go │ ├── decoder_test.go │ ├── load_tests.go │ └── schema.go ├── go.mod ├── go.sum ├── internal ├── addr │ └── addr.go ├── detect │ ├── detect.go │ ├── detect_bitbucket.go │ ├── detect_bitbucket_test.go │ ├── detect_gcs.go │ ├── detect_gcs_test.go │ ├── detect_git.go │ ├── detect_git_test.go │ ├── detect_github.go │ ├── detect_github_test.go │ ├── detect_s3.go │ ├── detect_s3_test.go │ ├── detect_ssh.go │ ├── detect_test.go │ ├── get.go │ ├── source.go │ └── source_test.go ├── funcs │ ├── 0.12 │ │ ├── base_functions.go │ │ └── functions.go │ ├── 0.13 │ │ └── functions.go │ ├── 0.14 │ │ └── functions.go │ ├── 0.15 │ │ └── functions.go │ ├── 1.3 │ │ └── functions.go │ └── generated │ │ ├── 1.10.0.go │ │ ├── 1.4.0.go │ │ ├── 1.5.0.go │ │ ├── 1.8.0.go │ │ ├── 1.9.0.go │ │ ├── README.md │ │ ├── doc.go │ │ ├── functions.go │ │ └── gen │ │ └── gen.go ├── references │ ├── 0.12 │ │ └── references.go │ └── 1.10 │ │ └── references.go ├── schema │ ├── 0.12 │ │ ├── connection_block.go │ │ ├── data_block.go │ │ ├── locals_block.go │ │ ├── module_block.go │ │ ├── output_block.go │ │ ├── provider_block.go │ │ ├── provisioner_block.go │ │ ├── provisioners.go │ │ ├── resource_block.go │ │ ├── root.go │ │ ├── terraform_block.go │ │ └── variable_block.go │ ├── 0.13 │ │ ├── module_block.go │ │ ├── provider_block.go │ │ ├── provisioners.go │ │ ├── root.go │ │ └── terraform_block.go │ ├── 0.14 │ │ ├── provisioners.go │ │ ├── root.go │ │ ├── terraform.go │ │ └── variable_block.go │ ├── 0.15 │ │ ├── connection_block.go │ │ ├── provisioners.go │ │ ├── root.go │ │ └── terraform.go │ ├── 1.1 │ │ ├── moved.go │ │ ├── root.go │ │ ├── terraform.go │ │ └── variable_block.go │ ├── 1.10 │ │ ├── ephemeral_block.go │ │ └── root.go │ ├── 1.2 │ │ ├── data.go │ │ ├── resource.go │ │ └── root.go │ ├── 1.3 │ │ ├── connection_block.go │ │ └── root.go │ ├── 1.4 │ │ ├── provisioners.go │ │ └── root.go │ ├── 1.5 │ │ ├── check.go │ │ ├── import.go │ │ └── root.go │ ├── 1.6 │ │ ├── import.go │ │ ├── root.go │ │ └── terraform.go │ ├── 1.7 │ │ ├── removed.go │ │ └── root.go │ ├── 1.8 │ │ ├── root.go │ │ └── terraform.go │ ├── 1.9 │ │ └── root.go │ ├── backends │ │ ├── artifactory.go │ │ ├── azurerm.go │ │ ├── backends.go │ │ ├── consul.go │ │ ├── cos.go │ │ ├── etcdv2.go │ │ ├── etcdv3.go │ │ ├── gcs.go │ │ ├── http.go │ │ ├── kubernetes.go │ │ ├── local.go │ │ ├── manta.go │ │ ├── object_expression.go │ │ ├── oss.go │ │ ├── pg.go │ │ ├── remote.go │ │ ├── s3.go │ │ └── swift.go │ ├── refscope │ │ └── scopes.go │ ├── stacks │ │ └── 1.9 │ │ │ ├── component_block.go │ │ │ ├── deployment_block.go │ │ │ ├── identity_token_block.go │ │ │ ├── locals_block.go │ │ │ ├── orchestrate_block.go │ │ │ ├── output_block.go │ │ │ ├── provider_block.go │ │ │ ├── removed_block.go │ │ │ ├── required_providers_block.go │ │ │ ├── root.go │ │ │ ├── store_block.go │ │ │ └── variable_block.go │ ├── tests │ │ ├── 1.6 │ │ │ ├── provider_block.go │ │ │ ├── root.go │ │ │ ├── run_block.go │ │ │ └── variables_block.go │ │ ├── 1.7 │ │ │ ├── mock_data_block.go │ │ │ ├── mock_provider_block.go │ │ │ ├── mock_resource_block.go │ │ │ ├── override_data_block.go │ │ │ ├── override_module_block.go │ │ │ ├── override_resource_block.go │ │ │ └── root.go │ │ └── 1.9 │ │ │ └── root.go │ └── tokmod │ │ └── token_modifier.go └── versiongen │ ├── gen.go │ └── gen_test.go ├── module ├── meta.go ├── meta_test.go ├── module_calls.go ├── output.go └── variable.go ├── registry └── registry.go ├── schema ├── builtin_references.go ├── convert_json.go ├── convert_json_test.go ├── core_schema.go ├── core_schema_test.go ├── errors.go ├── functions.go ├── functions_merge.go ├── functions_merge_test.go ├── language_ids.go ├── module_schema.go ├── module_schema_test.go ├── provider_schema.go ├── provider_schema_test.go ├── schema_merge.go ├── schema_merge_remote_state_ds.go ├── schema_merge_test.go ├── schema_merge_v012_test.go ├── schema_merge_v013_test.go ├── schema_merge_v015_aliased_test.go ├── schema_merge_v015_test.go ├── semantic_tokens.go ├── stacks │ ├── deploy_schema.go │ ├── deploy_schema_merge.go │ ├── deploy_schema_merge_test.go │ ├── stack_schema.go │ ├── stack_schema_merge.go │ └── stack_schema_merge_test.go ├── testdata │ ├── provider-schema-terraform.json │ ├── provider-schemas-0.12.json │ ├── provider-schemas-0.13.json │ ├── provider-schemas-0.15.json │ ├── test-config-0.12.tf │ ├── test-config-0.13.tf │ ├── test-config-0.15.tf │ └── test-config-remote-module.tf ├── tests │ ├── mock_schema.go │ ├── mock_schema_merge.go │ ├── test_schema.go │ └── test_schema_merge.go ├── variable_schema.go ├── variable_schema_test.go ├── versions.go ├── versions_gen.go └── versions_test.go ├── stack ├── component.go ├── deployment.go ├── meta.go ├── orchestrate.go ├── output.go ├── provider_requirements.go ├── store.go └── variable.go ├── test └── meta.go └── tools └── tools.go /.copywrite.hcl: -------------------------------------------------------------------------------- 1 | schema_version = 1 2 | 3 | project { 4 | license = "MPL-2.0" 5 | 6 | # (OPTIONAL) A list of globs that should not have copyright/license headers. 7 | # Supports doublestar glob patterns for more flexibility in defining which 8 | # files or folders should be ignored 9 | header_ignore = [ 10 | "**/testdata/**" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.go text eol=lf 2 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @hashicorp/tf-editor-experience-engineers 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "gomod" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | labels: ["dependencies"] 8 | # Dependabot only updates hashicorp GHAs, external GHAs are managed by internal tooling (tsccr) 9 | - package-ecosystem: "github-actions" 10 | directory: "/" 11 | schedule: 12 | interval: "daily" 13 | allow: 14 | - dependency-name: "hashicorp/*" 15 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | - pre-release 8 | push: 9 | branches: 10 | - main 11 | - pre-release 12 | 13 | permissions: 14 | contents: read 15 | 16 | env: 17 | GOPROXY: https://proxy.golang.org/ 18 | 19 | jobs: 20 | copywrite: 21 | runs-on: ubuntu-latest 22 | timeout-minutes: 3 23 | steps: 24 | - name: Checkout Repo 25 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 26 | - name: Install copywrite 27 | uses: hashicorp/setup-copywrite@v1.1.3 28 | - name: Validate Header Compliance 29 | run: copywrite headers --plan 30 | 31 | test: 32 | runs-on: ${{ matrix.os }} 33 | timeout-minutes: 5 34 | strategy: 35 | matrix: 36 | os: 37 | - ubuntu-latest 38 | - windows-latest 39 | - macos-latest 40 | steps: 41 | - 42 | name: Checkout 43 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 44 | - 45 | name: Unshallow 46 | run: git fetch --prune --unshallow 47 | - 48 | name: Set up Go 49 | uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0 50 | with: 51 | go-version-file: ".go-version" 52 | - 53 | name: Go mod download 54 | run: go mod download -x 55 | - 56 | name: Go mod verify 57 | run: go mod verify 58 | - 59 | name: Run go fmt 60 | run: go run github.com/mh-cbon/go-fmt-fail ./... 61 | - 62 | name: Run tests 63 | run: go test -v -race -covermode=atomic ./... 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | go.work* 2 | -------------------------------------------------------------------------------- /.go-version: -------------------------------------------------------------------------------- 1 | 1.22 2 | -------------------------------------------------------------------------------- /META.d/_summary.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) HashiCorp, Inc. 2 | # SPDX-License-Identifier: MPL-2.0 3 | 4 | schema: 1.1 5 | partition: tf-ecosystem 6 | category: library 7 | 8 | summary: 9 | owner: team-tf-editor-experience 10 | description: Terraform Language Server Schema 11 | visibility: external 12 | -------------------------------------------------------------------------------- /META.d/data.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) HashiCorp, Inc. 2 | # SPDX-License-Identifier: MPL-2.0 3 | 4 | data_summary: 5 | gdpr: 6 | exempt: true 7 | -------------------------------------------------------------------------------- /backend/backend.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package backend 5 | 6 | type BackendData interface { 7 | Copy() BackendData 8 | Equals(BackendData) bool 9 | } 10 | 11 | type UnknownBackendData struct{} 12 | 13 | func (*UnknownBackendData) Copy() BackendData { 14 | return &UnknownBackendData{} 15 | } 16 | 17 | func (*UnknownBackendData) Equals(d BackendData) bool { 18 | _, ok := d.(*UnknownBackendData) 19 | return ok 20 | } 21 | 22 | type Remote struct { 23 | Hostname string 24 | } 25 | 26 | func (r *Remote) Copy() BackendData { 27 | return &Remote{ 28 | Hostname: r.Hostname, 29 | } 30 | } 31 | 32 | func (r *Remote) Equals(d BackendData) bool { 33 | data, ok := d.(*Remote) 34 | if !ok { 35 | return false 36 | } 37 | 38 | return data.Hostname == r.Hostname 39 | } 40 | -------------------------------------------------------------------------------- /backend/cloud.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package backend 5 | 6 | type Cloud struct { 7 | Hostname string 8 | } 9 | 10 | func (be *Cloud) Equals(b *Cloud) bool { 11 | if be == nil && b == nil { 12 | return true 13 | } 14 | 15 | if be == nil || b == nil { 16 | return false 17 | } 18 | 19 | if be.Hostname != b.Hostname { 20 | return false 21 | } 22 | 23 | return true 24 | } 25 | -------------------------------------------------------------------------------- /earlydecoder/backend.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package earlydecoder 5 | 6 | import ( 7 | "github.com/hashicorp/hcl/v2" 8 | "github.com/hashicorp/terraform-schema/backend" 9 | "github.com/zclconf/go-cty/cty" 10 | ) 11 | 12 | func decodeBackendsBlock(block *hcl.Block) (backend.BackendData, hcl.Diagnostics) { 13 | bType := block.Labels[0] 14 | attrs, diags := block.Body.JustAttributes() 15 | 16 | switch bType { 17 | case "remote": 18 | if attr, ok := attrs["hostname"]; ok { 19 | val, vDiags := attr.Expr.Value(nil) 20 | diags = append(diags, vDiags...) 21 | if val.IsWhollyKnown() && val.Type() == cty.String { 22 | return &backend.Remote{ 23 | Hostname: val.AsString(), 24 | }, nil 25 | } 26 | } 27 | 28 | return &backend.Remote{}, nil 29 | } 30 | 31 | return &backend.UnknownBackendData{}, diags 32 | } 33 | 34 | func decodeCloudBlock(block *hcl.Block) (*backend.Cloud, hcl.Diagnostics) { 35 | attrs, _ := block.Body.JustAttributes() 36 | // Ignore diagnostics which may complain about unknown blocks 37 | 38 | // https://developer.hashicorp.com/terraform/language/settings/terraform-cloud#usage-example 39 | // Required for Terraform Enterprise 40 | // Defaults to app.terraform.io for HCP Terraform 41 | if attr, ok := attrs["hostname"]; ok { 42 | val, vDiags := attr.Expr.Value(nil) 43 | if val.IsWhollyKnown() && val.Type() == cty.String { 44 | return &backend.Cloud{ 45 | Hostname: val.AsString(), 46 | }, vDiags 47 | } 48 | } 49 | 50 | return &backend.Cloud{}, nil 51 | } 52 | -------------------------------------------------------------------------------- /earlydecoder/data_source.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package earlydecoder 5 | 6 | import ( 7 | "fmt" 8 | 9 | "github.com/hashicorp/terraform-schema/module" 10 | ) 11 | 12 | type dataSource struct { 13 | Type string 14 | Name string 15 | Provider module.ProviderRef 16 | } 17 | 18 | // MapKey returns a string that can be used to uniquely identify the receiver 19 | // in a map[string]*dataSource. 20 | func (r *dataSource) MapKey() string { 21 | return fmt.Sprintf("data.%s.%s", r.Type, r.Name) 22 | } 23 | -------------------------------------------------------------------------------- /earlydecoder/ephemeral_resource.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package earlydecoder 5 | 6 | import ( 7 | "fmt" 8 | 9 | "github.com/hashicorp/terraform-schema/module" 10 | ) 11 | 12 | type ephemeralResource struct { 13 | Type string 14 | Name string 15 | Provider module.ProviderRef 16 | } 17 | 18 | // MapKey returns a string that can be used to uniquely identify the receiver 19 | // in a map[string]*ephemeralResource. 20 | func (r *ephemeralResource) MapKey() string { 21 | return fmt.Sprintf("ephemeral.%s.%s", r.Type, r.Name) 22 | } 23 | -------------------------------------------------------------------------------- /earlydecoder/resource.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package earlydecoder 5 | 6 | import ( 7 | "fmt" 8 | "strings" 9 | 10 | "github.com/hashicorp/terraform-schema/module" 11 | ) 12 | 13 | type resource struct { 14 | Type string 15 | Name string 16 | Provider module.ProviderRef 17 | } 18 | 19 | // MapKey returns a string that can be used to uniquely identify the receiver 20 | // in a map[string]*resource. 21 | func (r *resource) MapKey() string { 22 | return fmt.Sprintf("%s.%s", r.Type, r.Name) 23 | } 24 | 25 | func inferProviderNameFromType(typeName string) string { 26 | if underPos := strings.IndexByte(typeName, '_'); underPos != -1 { 27 | return typeName[:underPos] 28 | } 29 | return typeName 30 | } 31 | -------------------------------------------------------------------------------- /earlydecoder/schema.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package earlydecoder 5 | 6 | import ( 7 | "github.com/hashicorp/hcl/v2" 8 | ) 9 | 10 | var rootSchema = &hcl.BodySchema{ 11 | Blocks: []hcl.BlockHeaderSchema{ 12 | { 13 | Type: "terraform", 14 | }, 15 | { 16 | Type: "provider", 17 | LabelNames: []string{"name"}, 18 | }, 19 | { 20 | Type: "resource", 21 | LabelNames: []string{"type", "name"}, 22 | }, 23 | { 24 | Type: "ephemeral", 25 | LabelNames: []string{"type", "name"}, 26 | }, 27 | { 28 | Type: "data", 29 | LabelNames: []string{"type", "name"}, 30 | }, 31 | { 32 | Type: "variable", 33 | LabelNames: []string{"name"}, 34 | }, 35 | { 36 | Type: "output", 37 | LabelNames: []string{"name"}, 38 | }, 39 | { 40 | Type: "module", 41 | LabelNames: []string{"name"}, 42 | }, 43 | }, 44 | } 45 | 46 | var terraformBlockSchema = &hcl.BodySchema{ 47 | Attributes: []hcl.AttributeSchema{ 48 | { 49 | Name: "required_version", 50 | }, 51 | }, 52 | Blocks: []hcl.BlockHeaderSchema{ 53 | { 54 | Type: "required_providers", 55 | }, 56 | { 57 | Type: "cloud", 58 | }, 59 | { 60 | Type: "backend", 61 | LabelNames: []string{"type"}, 62 | }, 63 | }, 64 | } 65 | 66 | var providerConfigSchema = &hcl.BodySchema{ 67 | Attributes: []hcl.AttributeSchema{ 68 | { 69 | Name: "version", 70 | }, 71 | { 72 | Name: "alias", 73 | }, 74 | }, 75 | } 76 | 77 | var resourceSchema = &hcl.BodySchema{ 78 | Attributes: []hcl.AttributeSchema{ 79 | { 80 | Name: "provider", 81 | }, 82 | }, 83 | } 84 | 85 | var variableSchema = &hcl.BodySchema{ 86 | Attributes: []hcl.AttributeSchema{ 87 | { 88 | Name: "description", 89 | }, 90 | { 91 | Name: "type", 92 | }, 93 | { 94 | Name: "sensitive", 95 | }, 96 | { 97 | Name: "default", 98 | }, 99 | }, 100 | } 101 | 102 | var outputSchema = &hcl.BodySchema{ 103 | Attributes: []hcl.AttributeSchema{ 104 | { 105 | Name: "description", 106 | }, 107 | { 108 | Name: "sensitive", 109 | }, 110 | { 111 | Name: "value", 112 | }, 113 | }, 114 | } 115 | 116 | var moduleSchema = &hcl.BodySchema{ 117 | Attributes: []hcl.AttributeSchema{ 118 | { 119 | Name: "source", 120 | }, 121 | { 122 | Name: "version", 123 | }, 124 | }, 125 | } 126 | -------------------------------------------------------------------------------- /earlydecoder/stacks/decoder.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package earlydecoder 5 | 6 | import ( 7 | "sort" 8 | "strings" 9 | 10 | "github.com/hashicorp/hcl/v2" 11 | "github.com/hashicorp/terraform-schema/stack" 12 | ) 13 | 14 | func LoadStack(path string, files map[string]*hcl.File) (*stack.Meta, map[string]hcl.Diagnostics) { 15 | filenames := make([]string, 0) 16 | sdiags := make(map[string]hcl.Diagnostics, 0) 17 | 18 | mod := newDecodedStack() 19 | for filename, f := range files { 20 | filenames = append(filenames, filename) 21 | 22 | if isStackFilename(filename) { 23 | sdiags[filename] = loadStackFromFile(f, mod) 24 | } 25 | } 26 | 27 | sort.Strings(filenames) 28 | 29 | components := make(map[string]stack.Component) 30 | for key, component := range mod.Components { 31 | components[key] = *component 32 | } 33 | 34 | variables := make(map[string]stack.Variable) 35 | for key, variable := range mod.Variables { 36 | variables[key] = *variable 37 | } 38 | 39 | outputs := make(map[string]stack.Output) 40 | for key, output := range mod.Outputs { 41 | outputs[key] = *output 42 | } 43 | 44 | providerRequirements := make(map[string]stack.ProviderRequirement, len(mod.ProviderRequirements)) 45 | for name, req := range mod.ProviderRequirements { 46 | if req.Source == nil || req.VersionConstraints == nil { 47 | continue 48 | } 49 | 50 | providerRequirements[name] = stack.ProviderRequirement{ 51 | Source: *req.Source, 52 | VersionConstraints: *req.VersionConstraints, 53 | } 54 | } 55 | 56 | return &stack.Meta{ 57 | Path: path, 58 | Filenames: filenames, 59 | Components: components, 60 | Variables: variables, 61 | Outputs: outputs, 62 | ProviderRequirements: providerRequirements, 63 | }, sdiags 64 | } 65 | 66 | func isStackFilename(name string) bool { 67 | return strings.HasSuffix(name, ".tfstack.hcl") || 68 | strings.HasSuffix(name, ".tfstack.json") 69 | } 70 | -------------------------------------------------------------------------------- /earlydecoder/stacks/schema.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package earlydecoder 5 | 6 | import ( 7 | "github.com/hashicorp/hcl/v2" 8 | ) 9 | 10 | var rootSchema = &hcl.BodySchema{ 11 | Blocks: []hcl.BlockHeaderSchema{ 12 | { 13 | Type: "component", 14 | LabelNames: []string{"name"}, 15 | }, 16 | { 17 | Type: "required_providers", 18 | }, 19 | { 20 | Type: "variable", 21 | LabelNames: []string{"name"}, 22 | }, 23 | { 24 | Type: "output", 25 | LabelNames: []string{"name"}, 26 | }, 27 | }, 28 | } 29 | 30 | var componentSchema = &hcl.BodySchema{ 31 | Attributes: []hcl.AttributeSchema{ 32 | { 33 | Name: "source", 34 | }, 35 | { 36 | Name: "version", 37 | }, 38 | }, 39 | } 40 | 41 | var variableSchema = &hcl.BodySchema{ 42 | Attributes: []hcl.AttributeSchema{ 43 | { 44 | Name: "description", 45 | }, 46 | { 47 | Name: "type", 48 | }, 49 | { 50 | Name: "default", 51 | }, 52 | { 53 | Name: "sensitive", 54 | }, 55 | }, 56 | } 57 | 58 | var outputSchema = &hcl.BodySchema{ 59 | Attributes: []hcl.AttributeSchema{ 60 | { 61 | Name: "description", 62 | }, 63 | { 64 | Name: "value", 65 | }, 66 | { 67 | Name: "sensitive", 68 | }, 69 | }, 70 | } 71 | -------------------------------------------------------------------------------- /earlydecoder/tests/decoder.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package earlydecoder 5 | 6 | import ( 7 | "sort" 8 | 9 | "github.com/hashicorp/hcl/v2" 10 | tftest "github.com/hashicorp/terraform-schema/test" 11 | ) 12 | 13 | func LoadTest(path string, files map[string]*hcl.File) (*tftest.Meta, hcl.Diagnostics) { 14 | var diags hcl.Diagnostics 15 | filenames := make([]string, 0) 16 | 17 | mod := newDecodedTest() 18 | for filename, f := range files { 19 | filenames = append(filenames, filename) 20 | fDiags := loadTestFromFile(f, mod) 21 | diags = append(diags, fDiags...) 22 | } 23 | 24 | sort.Strings(filenames) 25 | 26 | return &tftest.Meta{ 27 | Path: path, 28 | Filenames: filenames, 29 | }, diags 30 | } 31 | -------------------------------------------------------------------------------- /earlydecoder/tests/decoder_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package earlydecoder 5 | 6 | import ( 7 | "fmt" 8 | "testing" 9 | 10 | "github.com/google/go-cmp/cmp" 11 | "github.com/hashicorp/go-version" 12 | "github.com/hashicorp/hcl/v2" 13 | "github.com/hashicorp/hcl/v2/hclsyntax" 14 | tftest "github.com/hashicorp/terraform-schema/test" 15 | "github.com/zclconf/go-cty-debug/ctydebug" 16 | ) 17 | 18 | type testCase struct { 19 | name string 20 | cfg string 21 | expectedMeta *tftest.Meta 22 | expectedError hcl.Diagnostics 23 | } 24 | 25 | var customComparer = []cmp.Option{ 26 | cmp.Comparer(compareVersionConstraint), 27 | ctydebug.CmpOptions, 28 | } 29 | 30 | func TestLoadTest(t *testing.T) { 31 | path := t.TempDir() 32 | 33 | testCases := []testCase{ 34 | { 35 | "empty config", 36 | ``, 37 | &tftest.Meta{ 38 | Path: path, 39 | Filenames: []string{"test.tftest.hcl"}, 40 | }, 41 | nil, 42 | }, 43 | // TODO: add more test once we do more early decoding 44 | } 45 | 46 | runTestCases(testCases, t, path) 47 | } 48 | 49 | func runTestCases(testCases []testCase, t *testing.T, path string) { 50 | for i, tc := range testCases { 51 | t.Run(fmt.Sprintf("%d-%s", i, tc.name), func(t *testing.T) { 52 | f, diags := hclsyntax.ParseConfig([]byte(tc.cfg), "test.tftest.hcl", hcl.InitialPos) 53 | if len(diags) > 0 { 54 | t.Fatal(diags) 55 | } 56 | files := map[string]*hcl.File{ 57 | "test.tftest.hcl": f, 58 | } 59 | 60 | meta, diags := LoadTest(path, files) 61 | 62 | if diff := cmp.Diff(tc.expectedError, diags, customComparer...); diff != "" { 63 | t.Fatalf("expected errors doesn't match: %s", diff) 64 | } 65 | 66 | if diff := cmp.Diff(tc.expectedMeta, meta, customComparer...); diff != "" { 67 | t.Fatalf("test meta doesn't match: %s", diff) 68 | } 69 | }) 70 | } 71 | } 72 | 73 | func compareVersionConstraint(x, y *version.Constraint) bool { 74 | return x.Equals(y) 75 | } 76 | -------------------------------------------------------------------------------- /earlydecoder/tests/load_tests.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package earlydecoder 5 | 6 | import ( 7 | "github.com/hashicorp/hcl/v2" 8 | ) 9 | 10 | // decodedTest is the type representing a decoded Terraform test. 11 | type decodedTest struct { 12 | } 13 | 14 | func newDecodedTest() *decodedTest { 15 | return &decodedTest{} 16 | } 17 | 18 | // loadTestFromFile reads given file, interprets it and stores in given test 19 | // This is useful for any caller which does tokenization/parsing on its own 20 | // e.g. because it will reuse these parsed files later for more detailed 21 | // interpretation. 22 | func loadTestFromFile(file *hcl.File, _ *decodedTest) hcl.Diagnostics { 23 | var diags hcl.Diagnostics 24 | 25 | content, _, contentDiags := file.Body.PartialContent(rootSchema) 26 | diags = append(diags, contentDiags...) 27 | for _, block := range content.Blocks { 28 | switch block.Type { 29 | // TODO? decode mock_provider blocks (they have a source attribute) 30 | // TODO? decode run -> module blocks (they have a source attribute) 31 | } 32 | } 33 | 34 | return diags 35 | } 36 | -------------------------------------------------------------------------------- /earlydecoder/tests/schema.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package earlydecoder 5 | 6 | import ( 7 | "github.com/hashicorp/hcl/v2" 8 | ) 9 | 10 | var rootSchema = &hcl.BodySchema{ 11 | Blocks: []hcl.BlockHeaderSchema{ 12 | { 13 | Type: "run", 14 | LabelNames: []string{"name"}, 15 | }, 16 | { 17 | Type: "variables", 18 | }, 19 | { 20 | Type: "provider", 21 | LabelNames: []string{"name"}, 22 | }, 23 | }, 24 | } 25 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/hashicorp/terraform-schema 2 | 3 | go 1.22.0 4 | 5 | toolchain go1.22.5 6 | 7 | require ( 8 | github.com/google/go-cmp v0.6.0 9 | github.com/hashicorp/go-cleanhttp v0.5.2 10 | github.com/hashicorp/go-version v1.7.0 11 | github.com/hashicorp/hc-install v0.9.1 12 | github.com/hashicorp/hcl-lang v0.0.0-20250110164351-daa3bfeb1625 13 | github.com/hashicorp/hcl/v2 v2.23.0 14 | github.com/hashicorp/terraform-exec v0.22.0 15 | github.com/hashicorp/terraform-json v0.24.0 16 | github.com/hashicorp/terraform-registry-address v0.2.4 17 | github.com/mh-cbon/go-fmt-fail v0.0.0-20160815164508-67765b3fbcb5 18 | github.com/zclconf/go-cty v1.16.2 19 | github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 20 | ) 21 | 22 | require ( 23 | github.com/ProtonMail/go-crypto v1.1.3 // indirect 24 | github.com/agext/levenshtein v1.2.1 // indirect 25 | github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect 26 | github.com/cloudflare/circl v1.3.7 // indirect 27 | github.com/hashicorp/errwrap v1.0.0 // indirect 28 | github.com/hashicorp/go-multierror v1.1.1 // indirect 29 | github.com/hashicorp/go-retryablehttp v0.7.7 // indirect 30 | github.com/hashicorp/terraform-svchost v0.1.1 // indirect 31 | github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect 32 | golang.org/x/crypto v0.32.0 // indirect 33 | golang.org/x/mod v0.22.0 // indirect 34 | golang.org/x/net v0.34.0 // indirect 35 | golang.org/x/sync v0.10.0 // indirect 36 | golang.org/x/sys v0.29.0 // indirect 37 | golang.org/x/text v0.21.0 // indirect 38 | golang.org/x/tools v0.29.0 // indirect 39 | ) 40 | -------------------------------------------------------------------------------- /internal/addr/addr.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package addr 5 | 6 | import ( 7 | tfaddr "github.com/hashicorp/terraform-registry-address" 8 | ) 9 | 10 | // NewLegacyProvider returns a mock address for a provider. 11 | func NewLegacyProvider(name string) tfaddr.Provider { 12 | return tfaddr.Provider{ 13 | Type: tfaddr.MustParseProviderPart(name), 14 | Namespace: tfaddr.LegacyProviderNamespace, 15 | Hostname: tfaddr.DefaultProviderRegistryHost, 16 | } 17 | } 18 | 19 | // NewDefaultProvider returns the default address of a HashiCorp-maintained, 20 | // Registry-hosted provider. 21 | func NewDefaultProvider(name string) tfaddr.Provider { 22 | return tfaddr.Provider{ 23 | Type: tfaddr.MustParseProviderPart(name), 24 | Namespace: "hashicorp", 25 | Hostname: tfaddr.DefaultProviderRegistryHost, 26 | } 27 | } 28 | 29 | // NewBuiltInProvider returns the address of a "built-in" provider. See 30 | // the docs for Provider.IsBuiltIn for more information. 31 | func NewBuiltInProvider(name string) tfaddr.Provider { 32 | return tfaddr.Provider{ 33 | Type: tfaddr.MustParseProviderPart(name), 34 | Namespace: tfaddr.BuiltInProviderNamespace, 35 | Hostname: tfaddr.BuiltInProviderHost, 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /internal/detect/detect.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package detect 5 | 6 | import ( 7 | "fmt" 8 | "net/url" 9 | "path/filepath" 10 | ) 11 | 12 | var RemoteSourceDetectors = []Detector{ 13 | new(GitHubDetector), 14 | new(GitDetector), 15 | new(BitBucketDetector), 16 | new(GCSDetector), 17 | new(S3Detector), 18 | } 19 | 20 | // Detector defines the interface that an invalid URL or a URL with a blank 21 | // scheme is passed through in order to determine if its shorthand for 22 | // something else well-known. 23 | type Detector interface { 24 | // Detect will detect whether the string matches a known pattern to 25 | // turn it into a proper URL. 26 | Detect(string) (string, bool, error) 27 | } 28 | 29 | // Detect turns a source string into another source string if it is 30 | // detected to be of a known pattern. 31 | // 32 | // The third parameter should be the list of detectors to use in the 33 | // order to try them. If you don't want to configure this, just use 34 | // the global Detectors variable. 35 | // 36 | // This is safe to be called with an already valid source string: Detect 37 | // will just return it. 38 | func Detect(src string, ds []Detector) (string, error) { 39 | getForce, getSrc := getForcedGetter(src) 40 | 41 | // Separate out the subdir if there is one, we don't pass that to detect 42 | getSrc, subDir := SourceDirSubdir(getSrc) 43 | 44 | u, err := url.Parse(getSrc) 45 | if err == nil && u.Scheme != "" { 46 | // Valid URL 47 | return src, nil 48 | } 49 | 50 | for _, d := range ds { 51 | result, ok, err := d.Detect(getSrc) 52 | if err != nil { 53 | return "", err 54 | } 55 | if !ok { 56 | continue 57 | } 58 | 59 | var detectForce string 60 | detectForce, result = getForcedGetter(result) 61 | result, detectSubdir := SourceDirSubdir(result) 62 | 63 | // If we have a subdir from the detection, then prepend it to our 64 | // requested subdir. 65 | if detectSubdir != "" { 66 | if subDir != "" { 67 | subDir = filepath.Join(detectSubdir, subDir) 68 | } else { 69 | subDir = detectSubdir 70 | } 71 | } 72 | 73 | if subDir != "" { 74 | u, err := url.Parse(result) 75 | if err != nil { 76 | return "", fmt.Errorf("error parsing URL: %s", err) 77 | } 78 | u.Path += "//" + subDir 79 | 80 | // a subdir may contain wildcards, but in order to support them we 81 | // have to ensure the path isn't escaped. 82 | u.RawPath = u.Path 83 | 84 | result = u.String() 85 | } 86 | 87 | // Preserve the forced getter if it exists. We try to use the 88 | // original set force first, followed by any force set by the 89 | // detector. 90 | if getForce != "" { 91 | result = fmt.Sprintf("%s::%s", getForce, result) 92 | } else if detectForce != "" { 93 | result = fmt.Sprintf("%s::%s", detectForce, result) 94 | } 95 | 96 | return result, nil 97 | } 98 | 99 | return "", fmt.Errorf("invalid source string: %s", src) 100 | } 101 | -------------------------------------------------------------------------------- /internal/detect/detect_bitbucket.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package detect 5 | 6 | import ( 7 | "encoding/json" 8 | "fmt" 9 | "net/http" 10 | "net/url" 11 | "strings" 12 | ) 13 | 14 | // BitBucketDetector implements Detector to detect BitBucket URLs and turn 15 | // them into URLs that the Git or Hg Getter can understand. 16 | type BitBucketDetector struct{} 17 | 18 | func (d *BitBucketDetector) Detect(src string) (string, bool, error) { 19 | if len(src) == 0 { 20 | return "", false, nil 21 | } 22 | 23 | if strings.HasPrefix(src, "bitbucket.org/") { 24 | return d.detectHTTP(src) 25 | } 26 | 27 | return "", false, nil 28 | } 29 | 30 | func (d *BitBucketDetector) detectHTTP(src string) (string, bool, error) { 31 | u, err := url.Parse("https://" + src) 32 | if err != nil { 33 | return "", true, fmt.Errorf("error parsing BitBucket URL: %s", err) 34 | } 35 | 36 | // We need to get info on this BitBucket repository to determine whether 37 | // it is Git or Hg. 38 | var info struct { 39 | SCM string `json:"scm"` 40 | } 41 | infoUrl := "https://api.bitbucket.org/2.0/repositories" + u.Path 42 | resp, err := http.Get(infoUrl) 43 | if err != nil { 44 | return "", true, fmt.Errorf("error looking up BitBucket URL: %s", err) 45 | } 46 | if resp.StatusCode == 403 { 47 | // A private repo 48 | return "", true, fmt.Errorf( 49 | "shorthand BitBucket URL can't be used for private repos, " + 50 | "please use a full URL") 51 | } 52 | dec := json.NewDecoder(resp.Body) 53 | if err := dec.Decode(&info); err != nil { 54 | return "", true, fmt.Errorf("error looking up BitBucket URL: %s", err) 55 | } 56 | 57 | switch info.SCM { 58 | case "git": 59 | if !strings.HasSuffix(u.Path, ".git") { 60 | u.Path += ".git" 61 | } 62 | 63 | return "git::" + u.String(), true, nil 64 | case "hg": 65 | return "hg::" + u.String(), true, nil 66 | default: 67 | return "", true, fmt.Errorf("unknown BitBucket SCM type: %s", info.SCM) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /internal/detect/detect_bitbucket_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package detect 5 | 6 | import ( 7 | "net/http" 8 | "strings" 9 | "testing" 10 | ) 11 | 12 | const testBBUrl = "https://bitbucket.org/hashicorp/tf-test-git" 13 | 14 | func TestBitBucketDetector(t *testing.T) { 15 | t.Parallel() 16 | 17 | if _, err := http.Get(testBBUrl); err != nil { 18 | t.Log("internet may not be working, skipping BB tests") 19 | t.Skip() 20 | } 21 | 22 | cases := []struct { 23 | Input string 24 | Output string 25 | }{ 26 | // HTTP 27 | { 28 | "bitbucket.org/hashicorp/tf-test-git", 29 | "git::https://bitbucket.org/hashicorp/tf-test-git.git", 30 | }, 31 | { 32 | "bitbucket.org/hashicorp/tf-test-git.git", 33 | "git::https://bitbucket.org/hashicorp/tf-test-git.git", 34 | }, 35 | } 36 | 37 | f := new(BitBucketDetector) 38 | for i, tc := range cases { 39 | var err error 40 | for i := 0; i < 3; i++ { 41 | var output string 42 | var ok bool 43 | output, ok, err = f.Detect(tc.Input) 44 | if err != nil { 45 | if strings.Contains(err.Error(), "invalid character") { 46 | continue 47 | } 48 | 49 | t.Fatalf("err: %s", err) 50 | } 51 | if !ok { 52 | t.Fatal("not ok") 53 | } 54 | 55 | if output != tc.Output { 56 | t.Fatalf("%d: bad: %#v", i, output) 57 | } 58 | 59 | break 60 | } 61 | if i >= 3 { 62 | t.Fatalf("failure from bitbucket: %s", err) 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /internal/detect/detect_gcs.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package detect 5 | 6 | import ( 7 | "fmt" 8 | "net/url" 9 | "strings" 10 | ) 11 | 12 | // GCSDetector implements Detector to detect GCS URLs and turn 13 | // them into URLs that the GCSGetter can understand. 14 | type GCSDetector struct{} 15 | 16 | func (d *GCSDetector) Detect(src string) (string, bool, error) { 17 | if len(src) == 0 { 18 | return "", false, nil 19 | } 20 | 21 | if strings.Contains(src, "googleapis.com/") { 22 | return d.detectHTTP(src) 23 | } 24 | 25 | return "", false, nil 26 | } 27 | 28 | func (d *GCSDetector) detectHTTP(src string) (string, bool, error) { 29 | 30 | parts := strings.Split(src, "/") 31 | if len(parts) < 5 { 32 | return "", false, fmt.Errorf( 33 | "URL is not a valid GCS URL") 34 | } 35 | version := parts[2] 36 | bucket := parts[3] 37 | object := strings.Join(parts[4:], "/") 38 | 39 | url, err := url.Parse(fmt.Sprintf("https://www.googleapis.com/storage/%s/%s/%s", 40 | version, bucket, object)) 41 | if err != nil { 42 | return "", false, fmt.Errorf("error parsing GCS URL: %s", err) 43 | } 44 | 45 | return "gcs::" + url.String(), true, nil 46 | } 47 | -------------------------------------------------------------------------------- /internal/detect/detect_gcs_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package detect 5 | 6 | import ( 7 | "testing" 8 | ) 9 | 10 | func TestGCSDetector(t *testing.T) { 11 | cases := []struct { 12 | Input string 13 | Output string 14 | }{ 15 | { 16 | "www.googleapis.com/storage/v1/bucket/foo", 17 | "gcs::https://www.googleapis.com/storage/v1/bucket/foo", 18 | }, 19 | { 20 | "www.googleapis.com/storage/v1/bucket/foo/bar", 21 | "gcs::https://www.googleapis.com/storage/v1/bucket/foo/bar", 22 | }, 23 | { 24 | "www.googleapis.com/storage/v1/foo/bar.baz", 25 | "gcs::https://www.googleapis.com/storage/v1/foo/bar.baz", 26 | }, 27 | } 28 | 29 | f := new(GCSDetector) 30 | for i, tc := range cases { 31 | output, ok, err := f.Detect(tc.Input) 32 | if err != nil { 33 | t.Fatalf("err: %s", err) 34 | } 35 | if !ok { 36 | t.Fatal("not ok") 37 | } 38 | 39 | if output != tc.Output { 40 | t.Fatalf("%d: bad: %#v", i, output) 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /internal/detect/detect_git.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package detect 5 | 6 | // GitDetector implements Detector to detect Git SSH URLs such as 7 | // git@host.com:dir1/dir2 and converts them to proper URLs. 8 | type GitDetector struct{} 9 | 10 | func (d *GitDetector) Detect(src string) (string, bool, error) { 11 | if len(src) == 0 { 12 | return "", false, nil 13 | } 14 | 15 | u, err := detectSSH(src) 16 | if err != nil { 17 | return "", true, err 18 | } 19 | if u == nil { 20 | return "", false, nil 21 | } 22 | 23 | // We require the username to be "git" to assume that this is a Git URL 24 | if u.User.Username() != "git" { 25 | return "", false, nil 26 | } 27 | 28 | return "git::" + u.String(), true, nil 29 | } 30 | -------------------------------------------------------------------------------- /internal/detect/detect_git_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package detect 5 | 6 | import ( 7 | "testing" 8 | ) 9 | 10 | func TestGitDetector(t *testing.T) { 11 | cases := []struct { 12 | Input string 13 | Output string 14 | }{ 15 | { 16 | "git@github.com:hashicorp/foo.git", 17 | "git::ssh://git@github.com/hashicorp/foo.git", 18 | }, 19 | { 20 | "git@github.com:org/project.git?ref=test-branch", 21 | "git::ssh://git@github.com/org/project.git?ref=test-branch", 22 | }, 23 | { 24 | "git@github.com:hashicorp/foo.git//bar", 25 | "git::ssh://git@github.com/hashicorp/foo.git//bar", 26 | }, 27 | { 28 | "git@github.com:hashicorp/foo.git?foo=bar", 29 | "git::ssh://git@github.com/hashicorp/foo.git?foo=bar", 30 | }, 31 | { 32 | "git@github.xyz.com:org/project.git", 33 | "git::ssh://git@github.xyz.com/org/project.git", 34 | }, 35 | { 36 | "git@github.xyz.com:org/project.git?ref=test-branch", 37 | "git::ssh://git@github.xyz.com/org/project.git?ref=test-branch", 38 | }, 39 | { 40 | "git@github.xyz.com:org/project.git//module/a", 41 | "git::ssh://git@github.xyz.com/org/project.git//module/a", 42 | }, 43 | { 44 | "git@github.xyz.com:org/project.git//module/a?ref=test-branch", 45 | "git::ssh://git@github.xyz.com/org/project.git//module/a?ref=test-branch", 46 | }, 47 | { 48 | // Already in the canonical form, so no rewriting required 49 | // When the ssh: protocol is used explicitly, we recognize it as 50 | // URL form rather than SCP-like form, so the part after the colon 51 | // is a port number, not part of the path. 52 | "git::ssh://git@git.example.com:2222/hashicorp/foo.git", 53 | "git::ssh://git@git.example.com:2222/hashicorp/foo.git", 54 | }, 55 | } 56 | 57 | f := new(GitDetector) 58 | ds := []Detector{f} 59 | for _, tc := range cases { 60 | t.Run(tc.Input, func(t *testing.T) { 61 | output, err := Detect(tc.Input, ds) 62 | if err != nil { 63 | t.Fatalf("unexpected error: %s", err) 64 | } 65 | 66 | if output != tc.Output { 67 | t.Errorf("wrong result\ninput: %s\ngot: %s\nwant: %s", tc.Input, output, tc.Output) 68 | } 69 | }) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /internal/detect/detect_github.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package detect 5 | 6 | import ( 7 | "fmt" 8 | "net/url" 9 | "strings" 10 | ) 11 | 12 | // GitHubDetector implements Detector to detect GitHub URLs and turn 13 | // them into URLs that the Git Getter can understand. 14 | type GitHubDetector struct{} 15 | 16 | func (d *GitHubDetector) Detect(src string) (string, bool, error) { 17 | if len(src) == 0 { 18 | return "", false, nil 19 | } 20 | 21 | if strings.HasPrefix(src, "github.com/") { 22 | return d.detectHTTP(src) 23 | } 24 | 25 | return "", false, nil 26 | } 27 | 28 | func (d *GitHubDetector) detectHTTP(src string) (string, bool, error) { 29 | parts := strings.Split(src, "/") 30 | if len(parts) < 3 { 31 | return "", false, fmt.Errorf( 32 | "GitHub URLs should be github.com/username/repo") 33 | } 34 | 35 | urlStr := fmt.Sprintf("https://%s", strings.Join(parts[:3], "/")) 36 | url, err := url.Parse(urlStr) 37 | if err != nil { 38 | return "", true, fmt.Errorf("error parsing GitHub URL: %s", err) 39 | } 40 | 41 | if !strings.HasSuffix(url.Path, ".git") { 42 | url.Path += ".git" 43 | } 44 | 45 | if len(parts) > 3 { 46 | url.Path += "//" + strings.Join(parts[3:], "/") 47 | } 48 | 49 | return "git::" + url.String(), true, nil 50 | } 51 | -------------------------------------------------------------------------------- /internal/detect/detect_github_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package detect 5 | 6 | import ( 7 | "testing" 8 | ) 9 | 10 | func TestGitHubDetector(t *testing.T) { 11 | cases := []struct { 12 | Input string 13 | Output string 14 | }{ 15 | // HTTP 16 | {"github.com/hashicorp/foo", "git::https://github.com/hashicorp/foo.git"}, 17 | {"github.com/hashicorp/foo.git", "git::https://github.com/hashicorp/foo.git"}, 18 | { 19 | "github.com/hashicorp/foo/bar", 20 | "git::https://github.com/hashicorp/foo.git//bar", 21 | }, 22 | { 23 | "github.com/hashicorp/foo?foo=bar", 24 | "git::https://github.com/hashicorp/foo.git?foo=bar", 25 | }, 26 | { 27 | "github.com/hashicorp/foo.git?foo=bar", 28 | "git::https://github.com/hashicorp/foo.git?foo=bar", 29 | }, 30 | } 31 | 32 | f := new(GitHubDetector) 33 | for i, tc := range cases { 34 | output, ok, err := f.Detect(tc.Input) 35 | if err != nil { 36 | t.Fatalf("err: %s", err) 37 | } 38 | if !ok { 39 | t.Fatal("not ok") 40 | } 41 | 42 | if output != tc.Output { 43 | t.Fatalf("%d: bad: %#v", i, output) 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /internal/detect/detect_s3.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package detect 5 | 6 | import ( 7 | "fmt" 8 | "net/url" 9 | "strings" 10 | ) 11 | 12 | // S3Detector implements Detector to detect S3 URLs and turn 13 | // them into URLs that the S3 getter can understand. 14 | type S3Detector struct{} 15 | 16 | func (d *S3Detector) Detect(src string) (string, bool, error) { 17 | if len(src) == 0 { 18 | return "", false, nil 19 | } 20 | 21 | if strings.Contains(src, ".amazonaws.com/") { 22 | return d.detectHTTP(src) 23 | } 24 | 25 | return "", false, nil 26 | } 27 | 28 | func (d *S3Detector) detectHTTP(src string) (string, bool, error) { 29 | parts := strings.Split(src, "/") 30 | if len(parts) < 2 { 31 | return "", false, fmt.Errorf( 32 | "URL is not a valid S3 URL") 33 | } 34 | 35 | hostParts := strings.Split(parts[0], ".") 36 | if len(hostParts) == 3 { 37 | return d.detectPathStyle(hostParts[0], parts[1:]) 38 | } else if len(hostParts) == 4 { 39 | return d.detectVhostStyle(hostParts[1], hostParts[0], parts[1:]) 40 | } else if len(hostParts) == 5 && hostParts[1] == "s3" { 41 | return d.detectNewVhostStyle(hostParts[2], hostParts[0], parts[1:]) 42 | } else { 43 | return "", false, fmt.Errorf( 44 | "URL is not a valid S3 URL") 45 | } 46 | } 47 | 48 | func (d *S3Detector) detectPathStyle(region string, parts []string) (string, bool, error) { 49 | urlStr := fmt.Sprintf("https://%s.amazonaws.com/%s", region, strings.Join(parts, "/")) 50 | url, err := url.Parse(urlStr) 51 | if err != nil { 52 | return "", false, fmt.Errorf("error parsing S3 URL: %s", err) 53 | } 54 | 55 | return "s3::" + url.String(), true, nil 56 | } 57 | 58 | func (d *S3Detector) detectVhostStyle(region, bucket string, parts []string) (string, bool, error) { 59 | urlStr := fmt.Sprintf("https://%s.amazonaws.com/%s/%s", region, bucket, strings.Join(parts, "/")) 60 | url, err := url.Parse(urlStr) 61 | if err != nil { 62 | return "", false, fmt.Errorf("error parsing S3 URL: %s", err) 63 | } 64 | 65 | return "s3::" + url.String(), true, nil 66 | } 67 | 68 | func (d *S3Detector) detectNewVhostStyle(region, bucket string, parts []string) (string, bool, error) { 69 | urlStr := fmt.Sprintf("https://s3.%s.amazonaws.com/%s/%s", region, bucket, strings.Join(parts, "/")) 70 | url, err := url.Parse(urlStr) 71 | if err != nil { 72 | return "", false, fmt.Errorf("error parsing S3 URL: %s", err) 73 | } 74 | 75 | return "s3::" + url.String(), true, nil 76 | } 77 | -------------------------------------------------------------------------------- /internal/detect/detect_s3_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package detect 5 | 6 | import ( 7 | "testing" 8 | ) 9 | 10 | func TestS3Detector(t *testing.T) { 11 | cases := []struct { 12 | Input string 13 | Output string 14 | }{ 15 | // Virtual hosted style 16 | { 17 | "bucket.s3.amazonaws.com/foo", 18 | "s3::https://s3.amazonaws.com/bucket/foo", 19 | }, 20 | { 21 | "bucket.s3.amazonaws.com/foo/bar", 22 | "s3::https://s3.amazonaws.com/bucket/foo/bar", 23 | }, 24 | { 25 | "bucket.s3.amazonaws.com/foo/bar.baz", 26 | "s3::https://s3.amazonaws.com/bucket/foo/bar.baz", 27 | }, 28 | { 29 | "bucket.s3-eu-west-1.amazonaws.com/foo", 30 | "s3::https://s3-eu-west-1.amazonaws.com/bucket/foo", 31 | }, 32 | { 33 | "bucket.s3-eu-west-1.amazonaws.com/foo/bar", 34 | "s3::https://s3-eu-west-1.amazonaws.com/bucket/foo/bar", 35 | }, 36 | { 37 | "bucket.s3-eu-west-1.amazonaws.com/foo/bar.baz", 38 | "s3::https://s3-eu-west-1.amazonaws.com/bucket/foo/bar.baz", 39 | }, 40 | // 5 parts Virtual hosted-style 41 | { 42 | "bucket.s3.eu-west-1.amazonaws.com/foo/bar.baz", 43 | "s3::https://s3.eu-west-1.amazonaws.com/bucket/foo/bar.baz", 44 | }, 45 | // Path style 46 | { 47 | "s3.amazonaws.com/bucket/foo", 48 | "s3::https://s3.amazonaws.com/bucket/foo", 49 | }, 50 | { 51 | "s3.amazonaws.com/bucket/foo/bar", 52 | "s3::https://s3.amazonaws.com/bucket/foo/bar", 53 | }, 54 | { 55 | "s3.amazonaws.com/bucket/foo/bar.baz", 56 | "s3::https://s3.amazonaws.com/bucket/foo/bar.baz", 57 | }, 58 | { 59 | "s3-eu-west-1.amazonaws.com/bucket/foo", 60 | "s3::https://s3-eu-west-1.amazonaws.com/bucket/foo", 61 | }, 62 | { 63 | "s3-eu-west-1.amazonaws.com/bucket/foo/bar", 64 | "s3::https://s3-eu-west-1.amazonaws.com/bucket/foo/bar", 65 | }, 66 | { 67 | "s3-eu-west-1.amazonaws.com/bucket/foo/bar.baz", 68 | "s3::https://s3-eu-west-1.amazonaws.com/bucket/foo/bar.baz", 69 | }, 70 | // Misc tests 71 | { 72 | "s3-eu-west-1.amazonaws.com/bucket/foo/bar.baz?version=1234", 73 | "s3::https://s3-eu-west-1.amazonaws.com/bucket/foo/bar.baz?version=1234", 74 | }, 75 | } 76 | 77 | f := new(S3Detector) 78 | for i, tc := range cases { 79 | output, ok, err := f.Detect(tc.Input) 80 | if err != nil { 81 | t.Fatalf("err: %s", err) 82 | } 83 | if !ok { 84 | t.Fatal("not ok") 85 | } 86 | 87 | if output != tc.Output { 88 | t.Fatalf("%d: bad: %#v", i, output) 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /internal/detect/detect_ssh.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package detect 5 | 6 | import ( 7 | "fmt" 8 | "net/url" 9 | "regexp" 10 | "strings" 11 | ) 12 | 13 | // Note that we do not have an SSH-getter currently so this file serves 14 | // only to hold the detectSSH helper that is used by other detectors. 15 | 16 | // sshPattern matches SCP-like SSH patterns (user@host:path) 17 | var sshPattern = regexp.MustCompile("^(?:([^@]+)@)?([^:]+):/?(.+)$") 18 | 19 | // detectSSH determines if the src string matches an SSH-like URL and 20 | // converts it into a net.URL compatible string. This returns nil if the 21 | // string doesn't match the SSH pattern. 22 | // 23 | // This function is tested indirectly via detect_git_test.go 24 | func detectSSH(src string) (*url.URL, error) { 25 | matched := sshPattern.FindStringSubmatch(src) 26 | if matched == nil { 27 | return nil, nil 28 | } 29 | 30 | user := matched[1] 31 | host := matched[2] 32 | path := matched[3] 33 | qidx := strings.Index(path, "?") 34 | if qidx == -1 { 35 | qidx = len(path) 36 | } 37 | 38 | var u url.URL 39 | u.Scheme = "ssh" 40 | u.User = url.User(user) 41 | u.Host = host 42 | u.Path = path[0:qidx] 43 | if qidx < len(path) { 44 | q, err := url.ParseQuery(path[qidx+1:]) 45 | if err != nil { 46 | return nil, fmt.Errorf("error parsing GitHub SSH URL: %s", err) 47 | } 48 | u.RawQuery = q.Encode() 49 | } 50 | 51 | return &u, nil 52 | } 53 | -------------------------------------------------------------------------------- /internal/detect/detect_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package detect 5 | 6 | import ( 7 | "fmt" 8 | "testing" 9 | ) 10 | 11 | func TestDetect(t *testing.T) { 12 | cases := []struct { 13 | Input string 14 | Output string 15 | Err bool 16 | }{ 17 | { 18 | 19 | "git::github.com/hashicorp/foo", 20 | "git::https://github.com/hashicorp/foo.git", 21 | false, 22 | }, 23 | { 24 | "git::github.com/hashicorp/foo//bar", 25 | "git::https://github.com/hashicorp/foo.git//bar", 26 | false, 27 | }, 28 | { 29 | "git::https://github.com/hashicorp/consul.git", 30 | "git::https://github.com/hashicorp/consul.git", 31 | false, 32 | }, 33 | { 34 | "git::https://person@someothergit.com/foo/bar", 35 | "git::https://person@someothergit.com/foo/bar", 36 | false, 37 | }, 38 | { 39 | "git::https://person@someothergit.com/foo/bar", 40 | "git::https://person@someothergit.com/foo/bar", 41 | false, 42 | }, 43 | { 44 | "git::ssh://git@my.custom.git/dir1/dir2", 45 | "git::ssh://git@my.custom.git/dir1/dir2", 46 | false, 47 | }, 48 | { 49 | "git::git@my.custom.git:dir1/dir2", 50 | "git::ssh://git@my.custom.git/dir1/dir2", 51 | false, 52 | }, 53 | { 54 | "git::git@my.custom.git:dir1/dir2", 55 | "git::ssh://git@my.custom.git/dir1/dir2", 56 | false, 57 | }, 58 | } 59 | 60 | for i, tc := range cases { 61 | t.Run(fmt.Sprintf("%d %s", i, tc.Input), func(t *testing.T) { 62 | output, err := Detect(tc.Input, RemoteSourceDetectors) 63 | if err != nil != tc.Err { 64 | t.Fatalf("%d: bad err: %s", i, err) 65 | } 66 | if output != tc.Output { 67 | t.Fatalf("%d: bad output: %s\nexpected: %s", i, output, tc.Output) 68 | } 69 | }) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /internal/detect/get.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package detect 5 | 6 | import "regexp" 7 | 8 | // forcedRegexp is the regular expression that finds forced getters. This 9 | // syntax is schema::url, example: git::https://foo.com 10 | var forcedRegexp = regexp.MustCompile(`^([A-Za-z0-9]+)::(.+)$`) 11 | 12 | // getForcedGetter takes a source and returns the tuple of the forced 13 | // getter and the raw URL (without the force syntax). 14 | func getForcedGetter(src string) (string, string) { 15 | var forced string 16 | if ms := forcedRegexp.FindStringSubmatch(src); ms != nil { 17 | forced = ms[1] 18 | src = ms[2] 19 | } 20 | 21 | return forced, src 22 | } 23 | -------------------------------------------------------------------------------- /internal/detect/source.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package detect 5 | 6 | import ( 7 | "fmt" 8 | "path/filepath" 9 | "strings" 10 | ) 11 | 12 | // SourceDirSubdir takes a source URL and returns a tuple of the URL without 13 | // the subdir and the subdir. 14 | // 15 | // ex: 16 | // 17 | // dom.com/path/?q=p => dom.com/path/?q=p, "" 18 | // proto://dom.com/path//*?q=p => proto://dom.com/path?q=p, "*" 19 | // proto://dom.com/path//path2?q=p => proto://dom.com/path?q=p, "path2" 20 | func SourceDirSubdir(src string) (string, string) { 21 | 22 | // URL might contains another url in query parameters 23 | stop := len(src) 24 | if idx := strings.Index(src, "?"); idx > -1 { 25 | stop = idx 26 | } 27 | 28 | // Calculate an offset to avoid accidentally marking the scheme 29 | // as the dir. 30 | var offset int 31 | if idx := strings.Index(src[:stop], "://"); idx > -1 { 32 | offset = idx + 3 33 | } 34 | 35 | // First see if we even have an explicit subdir 36 | idx := strings.Index(src[offset:stop], "//") 37 | if idx == -1 { 38 | return src, "" 39 | } 40 | 41 | idx += offset 42 | subdir := src[idx+2:] 43 | src = src[:idx] 44 | 45 | // Next, check if we have query parameters and push them onto the 46 | // URL. 47 | if idx = strings.Index(subdir, "?"); idx > -1 { 48 | query := subdir[idx:] 49 | subdir = subdir[:idx] 50 | src += query 51 | } 52 | 53 | return src, subdir 54 | } 55 | 56 | // SubdirGlob returns the actual subdir with globbing processed. 57 | // 58 | // dst should be a destination directory that is already populated (the 59 | // download is complete) and subDir should be the set subDir. If subDir 60 | // is an empty string, this returns an empty string. 61 | // 62 | // The returned path is the full absolute path. 63 | func SubdirGlob(dst, subDir string) (string, error) { 64 | pattern := filepath.Join(dst, subDir) 65 | 66 | matches, err := filepath.Glob(pattern) 67 | if err != nil { 68 | return "", err 69 | } 70 | 71 | if len(matches) == 0 { 72 | return "", fmt.Errorf("subdir %q not found", subDir) 73 | } 74 | 75 | if len(matches) > 1 { 76 | return "", fmt.Errorf("subdir %q matches multiple paths", subDir) 77 | } 78 | 79 | return matches[0], nil 80 | } 81 | -------------------------------------------------------------------------------- /internal/detect/source_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package detect 5 | 6 | import ( 7 | "io/ioutil" 8 | "os" 9 | "path/filepath" 10 | "testing" 11 | ) 12 | 13 | func TestSourceDirSubdir(t *testing.T) { 14 | cases := []struct { 15 | Input string 16 | Dir, Sub string 17 | }{ 18 | { 19 | "hashicorp.com", 20 | "hashicorp.com", "", 21 | }, 22 | { 23 | "hashicorp.com//foo", 24 | "hashicorp.com", "foo", 25 | }, 26 | { 27 | "hashicorp.com//foo?bar=baz", 28 | "hashicorp.com?bar=baz", "foo", 29 | }, 30 | { 31 | "https://hashicorp.com/path//*?archive=foo", 32 | "https://hashicorp.com/path?archive=foo", "*", 33 | }, 34 | { 35 | "https://hashicorp.com/path?checksum=file:http://url.com/....iso.sha256", 36 | "https://hashicorp.com/path?checksum=file:http://url.com/....iso.sha256", "", 37 | }, 38 | { 39 | "https://hashicorp.com/path//*?checksum=file:http://url.com/....iso.sha256", 40 | "https://hashicorp.com/path?checksum=file:http://url.com/....iso.sha256", "*", 41 | }, 42 | { 43 | "file://foo//bar", 44 | "file://foo", "bar", 45 | }, 46 | } 47 | 48 | for i, tc := range cases { 49 | adir, asub := SourceDirSubdir(tc.Input) 50 | if adir != tc.Dir { 51 | t.Fatalf("%d: bad dir: %#v", i, adir) 52 | } 53 | if asub != tc.Sub { 54 | t.Fatalf("%d: bad sub: %#v", i, asub) 55 | } 56 | } 57 | } 58 | 59 | func TestSourceSubdirGlob(t *testing.T) { 60 | td, err := ioutil.TempDir("", "subdir-glob") 61 | if err != nil { 62 | t.Fatal(err) 63 | } 64 | defer os.RemoveAll(td) 65 | 66 | if err := os.Mkdir(filepath.Join(td, "subdir"), 0755); err != nil { 67 | t.Fatal(err) 68 | } 69 | 70 | if err := os.Mkdir(filepath.Join(td, "subdir/one"), 0755); err != nil { 71 | t.Fatal(err) 72 | } 73 | 74 | if err := os.Mkdir(filepath.Join(td, "subdir/two"), 0755); err != nil { 75 | t.Fatal(err) 76 | } 77 | 78 | subdir := filepath.Join(td, "subdir") 79 | 80 | // match the exact directory 81 | res, err := SubdirGlob(td, "subdir") 82 | if err != nil { 83 | t.Fatal(err) 84 | } 85 | if res != subdir { 86 | t.Fatalf(`expected "subdir", got: %q`, subdir) 87 | } 88 | 89 | // single match from a wildcard 90 | res, err = SubdirGlob(td, "*") 91 | if err != nil { 92 | t.Fatal(err) 93 | } 94 | if res != subdir { 95 | t.Fatalf(`expected "subdir", got: %q`, subdir) 96 | } 97 | 98 | // multiple matches 99 | res, err = SubdirGlob(td, "subdir/*") 100 | if err == nil { 101 | t.Fatalf("expected multiple matches, got %q", res) 102 | } 103 | 104 | // non-existent 105 | res, err = SubdirGlob(td, "foo") 106 | if err == nil { 107 | t.Fatalf("expected no matches, got %q", res) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /internal/funcs/0.13/functions.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package funcs 5 | 6 | import ( 7 | "github.com/hashicorp/go-version" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | "github.com/zclconf/go-cty/cty" 10 | "github.com/zclconf/go-cty/cty/function" 11 | 12 | funcs_v0_12 "github.com/hashicorp/terraform-schema/internal/funcs/0.12" 13 | ) 14 | 15 | func Functions(v *version.Version) map[string]schema.FunctionSignature { 16 | f := funcs_v0_12.Functions(v) 17 | 18 | f["sum"] = schema.FunctionSignature{ 19 | Params: []function.Parameter{ 20 | { 21 | Name: "list", 22 | Type: cty.DynamicPseudoType, 23 | }, 24 | }, 25 | ReturnType: cty.DynamicPseudoType, 26 | Description: "`sum` takes a list or set of numbers and returns the sum of those numbers.", 27 | } 28 | 29 | return f 30 | } 31 | -------------------------------------------------------------------------------- /internal/funcs/0.14/functions.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package funcs 5 | 6 | import ( 7 | "github.com/hashicorp/go-version" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | "github.com/zclconf/go-cty/cty" 10 | "github.com/zclconf/go-cty/cty/function" 11 | 12 | funcs_v0_13 "github.com/hashicorp/terraform-schema/internal/funcs/0.13" 13 | ) 14 | 15 | func Functions(v *version.Version) map[string]schema.FunctionSignature { 16 | f := funcs_v0_13.Functions(v) 17 | 18 | f["alltrue"] = schema.FunctionSignature{ 19 | Params: []function.Parameter{ 20 | { 21 | Name: "list", 22 | Type: cty.List(cty.Bool), 23 | }, 24 | }, 25 | ReturnType: cty.Bool, 26 | Description: "`alltrue` returns `true` if all elements in a given collection are `true` or `\"true\"`. It also returns `true` if the collection is empty.", 27 | } 28 | f["anytrue"] = schema.FunctionSignature{ 29 | Params: []function.Parameter{ 30 | { 31 | Name: "list", 32 | Type: cty.List(cty.Bool), 33 | }, 34 | }, 35 | ReturnType: cty.Bool, 36 | Description: "`anytrue` returns `true` if any element in a given collection is `true` or `\"true\"`. It also returns `false` if the collection is empty.", 37 | } 38 | f["textdecodebase64"] = schema.FunctionSignature{ 39 | Params: []function.Parameter{ 40 | { 41 | Name: "source", 42 | Type: cty.String, 43 | }, 44 | { 45 | Name: "encoding", 46 | Type: cty.String, 47 | }, 48 | }, 49 | ReturnType: cty.String, 50 | Description: "`textdecodebase64` function decodes a string that was previously Base64-encoded, and then interprets the result as characters in a specified character encoding.", 51 | } 52 | f["textencodebase64"] = schema.FunctionSignature{ 53 | Params: []function.Parameter{ 54 | { 55 | Name: "string", 56 | Type: cty.String, 57 | }, 58 | { 59 | Name: "encoding", 60 | Type: cty.String, 61 | }, 62 | }, 63 | ReturnType: cty.String, 64 | Description: "`textencodebase64` encodes the unicode characters in a given string using a specified character encoding, returning the result base64 encoded because Terraform language strings are always sequences of unicode characters.", 65 | } 66 | 67 | return f 68 | } 69 | -------------------------------------------------------------------------------- /internal/funcs/0.15/functions.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package funcs 5 | 6 | import ( 7 | "github.com/hashicorp/go-version" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | "github.com/zclconf/go-cty/cty" 10 | "github.com/zclconf/go-cty/cty/function" 11 | 12 | funcs_v0_14 "github.com/hashicorp/terraform-schema/internal/funcs/0.14" 13 | ) 14 | 15 | func Functions(v *version.Version) map[string]schema.FunctionSignature { 16 | f := funcs_v0_14.Functions(v) 17 | 18 | f["nonsensitive"] = schema.FunctionSignature{ 19 | Params: []function.Parameter{ 20 | { 21 | Name: "value", 22 | Type: cty.DynamicPseudoType, 23 | }, 24 | }, 25 | ReturnType: cty.DynamicPseudoType, 26 | Description: "`nonsensitive` takes a sensitive value and returns a copy of that value with the sensitive marking removed, thereby exposing the sensitive value.", 27 | } 28 | f["one"] = schema.FunctionSignature{ 29 | Params: []function.Parameter{ 30 | { 31 | Name: "list", 32 | Type: cty.DynamicPseudoType, 33 | }, 34 | }, 35 | ReturnType: cty.DynamicPseudoType, 36 | Description: "`one` takes a list, set, or tuple value with either zero or one elements. If the collection is empty, `one` returns `null`. Otherwise, `one` returns the first element. If there are two or more elements then `one` will return an error.", 37 | } 38 | f["sensitive"] = schema.FunctionSignature{ 39 | Params: []function.Parameter{ 40 | { 41 | Name: "value", 42 | Type: cty.DynamicPseudoType, 43 | }, 44 | }, 45 | ReturnType: cty.DynamicPseudoType, 46 | Description: "`sensitive` takes any value and returns a copy of it marked so that Terraform will treat it as sensitive, with the same meaning and behavior as for [sensitive input variables](/language/values/variables#suppressing-values-in-cli-output).", 47 | } 48 | 49 | delete(f, "list") // list was removed in 0.15 50 | delete(f, "map") // map was removed in 0.15 51 | 52 | return f 53 | } 54 | -------------------------------------------------------------------------------- /internal/funcs/1.3/functions.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package funcs 5 | 6 | import ( 7 | "github.com/hashicorp/go-version" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | "github.com/zclconf/go-cty/cty" 10 | "github.com/zclconf/go-cty/cty/function" 11 | 12 | funcs_v0_15 "github.com/hashicorp/terraform-schema/internal/funcs/0.15" 13 | ) 14 | 15 | func Functions(v *version.Version) map[string]schema.FunctionSignature { 16 | f := funcs_v0_15.Functions(v) 17 | 18 | f["endswith"] = schema.FunctionSignature{ 19 | Params: []function.Parameter{ 20 | { 21 | Name: "str", 22 | Type: cty.String, 23 | }, 24 | { 25 | Name: "suffix", 26 | Type: cty.String, 27 | }, 28 | }, 29 | ReturnType: cty.Bool, 30 | Description: "`endswith` takes two values: a string to check and a suffix string. The function returns true if the first string ends with that exact suffix.", 31 | } 32 | f["startswith"] = schema.FunctionSignature{ 33 | Params: []function.Parameter{ 34 | { 35 | Name: "str", 36 | Type: cty.String, 37 | }, 38 | { 39 | Name: "prefix", 40 | Type: cty.String, 41 | }, 42 | }, 43 | ReturnType: cty.Bool, 44 | Description: "`startswith` takes two values: a string to check and a prefix string. The function returns true if the string begins with that exact prefix.", 45 | } 46 | f["timecmp"] = schema.FunctionSignature{ 47 | Params: []function.Parameter{ 48 | { 49 | Name: "timestamp_a", 50 | Type: cty.String, 51 | }, 52 | { 53 | Name: "timestamp_b", 54 | Type: cty.String, 55 | }, 56 | }, 57 | ReturnType: cty.Number, 58 | Description: "`timecmp` compares two timestamps and returns a number that represents the ordering of the instants those timestamps represent.", 59 | } 60 | 61 | return f 62 | } 63 | -------------------------------------------------------------------------------- /internal/funcs/generated/README.md: -------------------------------------------------------------------------------- 1 | # Generated Terraform function signatures 2 | 3 | This package can generate function signature files for Terraform >= 1.4 automatically. 4 | 5 | It is intended to run whenever HashiCorp releases a new Terraform version. If the Terraform version contains updated function signatures, it generates a new file for that version. When no changes are detected, one should commit the version bump in `gen/gen.go`. 6 | 7 | Pre-releases are accepted with the following caveats: 8 | 9 | - The given pre-release version of Terraform is downloaded 10 | - The pre-release part of the version is omitted in all other contexts (e.g. function name, file name, constraint etc.) 11 | 12 | ... meaning that e.g. `1.5.0-alpha20230504` downloads Terraform `1.5.0-alpha20230504` but assumes compatibility with `1.5.0`. Therefore, generating signatures based on pre-releases should be used with the assumption that those signatures won't change before the final release. 13 | 14 | ## Running 15 | 16 | Update the `terraformVersion` in `gen/gen.go` with the version of the new Terraform release. 17 | 18 | Run `go generate ./internal/funcs/generated`. This command will: 19 | 20 | 1. Install the specified Terraform version. 21 | 1. Run `terraform metadata functions -json` to obtain the function signatures. 22 | 1. Compare a hash of the JSON string to `functionSignatureHash` in `gen/gen.go`. 23 | 1. If there are no changes, we will stop here. 24 | 1. Create a new Go file with the function signatures of that Terraform version. 25 | 1. Regenerate the function signature selection in `functions.go`. 26 | 27 | **If everything looks solid, update the `functionSignatureHash` with the one from the output, and commit all changes.** 28 | -------------------------------------------------------------------------------- /internal/funcs/generated/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | // Package generates function signature files for Terraform >= 1.4 5 | package funcs 6 | 7 | //go:generate go run gen/gen.go 8 | -------------------------------------------------------------------------------- /internal/funcs/generated/functions.go: -------------------------------------------------------------------------------- 1 | // Code generated by "gen"; DO NOT EDIT. 2 | package funcs 3 | 4 | import ( 5 | "github.com/hashicorp/go-version" 6 | "github.com/hashicorp/hcl-lang/schema" 7 | ) 8 | 9 | var ( 10 | v1_10_0 = version.Must(version.NewVersion("1.10.0")) 11 | v1_9_0 = version.Must(version.NewVersion("1.9.0")) 12 | v1_8_0 = version.Must(version.NewVersion("1.8.0")) 13 | v1_5_0 = version.Must(version.NewVersion("1.5.0")) 14 | v1_4_0 = version.Must(version.NewVersion("1.4.0")) 15 | ) 16 | 17 | func Functions(v *version.Version) map[string]schema.FunctionSignature { 18 | if v.GreaterThanOrEqual(v1_10_0) { 19 | return v1_10_0_Functions() 20 | } 21 | if v.GreaterThanOrEqual(v1_9_0) { 22 | return v1_9_0_Functions() 23 | } 24 | if v.GreaterThanOrEqual(v1_8_0) { 25 | return v1_8_0_Functions() 26 | } 27 | if v.GreaterThanOrEqual(v1_5_0) { 28 | return v1_5_0_Functions() 29 | } 30 | if v.GreaterThanOrEqual(v1_4_0) { 31 | return v1_4_0_Functions() 32 | } 33 | 34 | return v1_4_0_Functions() 35 | } 36 | -------------------------------------------------------------------------------- /internal/references/0.12/references.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | package references 4 | 5 | import ( 6 | "github.com/hashicorp/hcl-lang/lang" 7 | "github.com/hashicorp/hcl-lang/reference" 8 | "github.com/hashicorp/terraform-schema/internal/schema/refscope" 9 | "github.com/zclconf/go-cty/cty" 10 | ) 11 | 12 | func BuiltinReferences(modPath string) reference.Targets { 13 | return reference.Targets{ 14 | { 15 | Addr: lang.Address{ 16 | lang.RootStep{Name: "path"}, 17 | lang.AttrStep{Name: "module"}, 18 | }, 19 | ScopeId: refscope.BuiltinScope, 20 | Type: cty.String, 21 | Description: lang.Markdown("The filesystem path of the module where the expression is placed\n\n" + 22 | modPath), 23 | }, 24 | { 25 | Addr: lang.Address{ 26 | lang.RootStep{Name: "path"}, 27 | lang.AttrStep{Name: "root"}, 28 | }, 29 | ScopeId: refscope.BuiltinScope, 30 | Type: cty.String, 31 | Description: lang.Markdown("The filesystem path of the root module of the configuration"), 32 | }, 33 | { 34 | Addr: lang.Address{ 35 | lang.RootStep{Name: "path"}, 36 | lang.AttrStep{Name: "cwd"}, 37 | }, 38 | ScopeId: refscope.BuiltinScope, 39 | Type: cty.String, 40 | Description: lang.Markdown("The filesystem path of the current working directory.\n\n" + 41 | "In normal use of Terraform this is the same as `path.root`, " + 42 | "but some advanced uses of Terraform run it from a directory " + 43 | "other than the root module directory, causing these paths to be different."), 44 | }, 45 | { 46 | Addr: lang.Address{ 47 | lang.RootStep{Name: "terraform"}, 48 | lang.AttrStep{Name: "workspace"}, 49 | }, 50 | ScopeId: refscope.BuiltinScope, 51 | Type: cty.String, 52 | Description: lang.Markdown("The name of the currently selected workspace"), 53 | }, 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /internal/references/1.10/references.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package references 5 | 6 | import ( 7 | "github.com/hashicorp/hcl-lang/lang" 8 | "github.com/hashicorp/hcl-lang/reference" 9 | refs_v0_12 "github.com/hashicorp/terraform-schema/internal/references/0.12" 10 | "github.com/hashicorp/terraform-schema/internal/schema/refscope" 11 | "github.com/zclconf/go-cty/cty" 12 | ) 13 | 14 | func BuiltinReferences(modPath string) reference.Targets { 15 | refs := refs_v0_12.BuiltinReferences(modPath) 16 | 17 | refs = append(refs, reference.Target{ 18 | Addr: lang.Address{ 19 | lang.RootStep{Name: "terraform"}, 20 | lang.AttrStep{Name: "applying"}, 21 | }, 22 | ScopeId: refscope.BuiltinScope, 23 | Type: cty.Bool, 24 | Description: lang.Markdown("True if Terraform is currently in the apply phase (including destroy mode), false otherwise"), 25 | }) 26 | 27 | return refs 28 | } 29 | -------------------------------------------------------------------------------- /internal/schema/0.12/locals_block.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/hcl-lang/lang" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | "github.com/hashicorp/terraform-schema/internal/schema/refscope" 10 | "github.com/hashicorp/terraform-schema/internal/schema/tokmod" 11 | "github.com/zclconf/go-cty/cty" 12 | ) 13 | 14 | func localsBlockSchema() *schema.BlockSchema { 15 | return &schema.BlockSchema{ 16 | SemanticTokenModifiers: lang.SemanticTokenModifiers{tokmod.Locals}, 17 | Description: lang.Markdown("Local values assigning names to expressions, so you can use these multiple times without repetition\n" + 18 | "e.g. `service_name = \"forum\"`"), 19 | Body: &schema.BodySchema{ 20 | AnyAttribute: &schema.AttributeSchema{ 21 | Address: &schema.AttributeAddrSchema{ 22 | Steps: []schema.AddrStep{ 23 | schema.StaticStep{Name: "local"}, 24 | schema.AttrNameStep{}, 25 | }, 26 | ScopeId: refscope.LocalScope, 27 | AsExprType: true, 28 | AsReference: true, 29 | }, 30 | Constraint: schema.AnyExpression{OfType: cty.DynamicPseudoType}, 31 | }, 32 | }, 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /internal/schema/0.12/module_block.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/hcl-lang/lang" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | "github.com/hashicorp/terraform-schema/internal/schema/refscope" 10 | "github.com/hashicorp/terraform-schema/internal/schema/tokmod" 11 | "github.com/zclconf/go-cty/cty" 12 | ) 13 | 14 | func moduleBlockSchema() *schema.BlockSchema { 15 | return &schema.BlockSchema{ 16 | Address: &schema.BlockAddrSchema{ 17 | Steps: []schema.AddrStep{ 18 | schema.StaticStep{Name: "module"}, 19 | schema.LabelStep{Index: 0}, 20 | }, 21 | FriendlyName: "module", 22 | ScopeId: refscope.ModuleScope, 23 | AsReference: true, 24 | }, 25 | SemanticTokenModifiers: lang.SemanticTokenModifiers{tokmod.Module}, 26 | Labels: []*schema.LabelSchema{ 27 | { 28 | Name: "name", 29 | SemanticTokenModifiers: lang.SemanticTokenModifiers{tokmod.Name}, 30 | Description: lang.PlainText("Reference Name"), 31 | }, 32 | }, 33 | Description: lang.PlainText("Module block to call a locally or remotely stored module"), 34 | Body: &schema.BodySchema{ 35 | Attributes: map[string]*schema.AttributeSchema{ 36 | "source": { 37 | Constraint: schema.LiteralType{Type: cty.String}, 38 | Description: lang.Markdown("Source where to load the module from, " + 39 | "a local directory (e.g. `./module`) or a remote address - e.g. " + 40 | "`hashicorp/consul/aws` (Terraform Registry address) or " + 41 | "`github.com/hashicorp/example` (GitHub)"), 42 | IsRequired: true, 43 | IsDepKey: true, 44 | SemanticTokenModifiers: lang.SemanticTokenModifiers{lang.TokenModifierDependent}, 45 | CompletionHooks: lang.CompletionHooks{ 46 | { 47 | Name: "CompleteLocalModuleSources", 48 | }, 49 | { 50 | Name: "CompleteRegistryModuleSources", 51 | }, 52 | }, 53 | }, 54 | "version": { 55 | Constraint: schema.LiteralType{Type: cty.String}, 56 | IsOptional: true, 57 | Description: lang.Markdown("Constraint to set the version of the module, e.g. `~> 1.0`." + 58 | " Only applicable to modules in a module registry."), 59 | CompletionHooks: lang.CompletionHooks{ 60 | { 61 | Name: "CompleteRegistryModuleVersions", 62 | }, 63 | }, 64 | }, 65 | "providers": { 66 | Constraint: schema.Map{ 67 | Name: "map of provider references", 68 | Elem: schema.Reference{OfScopeId: refscope.ProviderScope}, 69 | }, 70 | IsOptional: true, 71 | Description: lang.Markdown("Explicit mapping of providers which the module uses"), 72 | }, 73 | }, 74 | }, 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /internal/schema/0.12/output_block.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/hcl-lang/lang" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | "github.com/hashicorp/terraform-schema/internal/schema/refscope" 10 | "github.com/hashicorp/terraform-schema/internal/schema/tokmod" 11 | "github.com/zclconf/go-cty/cty" 12 | ) 13 | 14 | func outputBlockSchema() *schema.BlockSchema { 15 | return &schema.BlockSchema{ 16 | Address: &schema.BlockAddrSchema{ 17 | Steps: []schema.AddrStep{ 18 | schema.StaticStep{Name: "output"}, 19 | schema.LabelStep{Index: 0}, 20 | }, 21 | FriendlyName: "output", 22 | ScopeId: refscope.OutputScope, 23 | AsReference: true, 24 | }, 25 | SemanticTokenModifiers: lang.SemanticTokenModifiers{tokmod.Output}, 26 | Labels: []*schema.LabelSchema{ 27 | { 28 | Name: "name", 29 | SemanticTokenModifiers: lang.SemanticTokenModifiers{tokmod.Name}, 30 | Description: lang.PlainText("Output Name"), 31 | }, 32 | }, 33 | Description: lang.PlainText("Output value for consumption by another module or a human interacting via the UI"), 34 | Body: &schema.BodySchema{ 35 | Attributes: map[string]*schema.AttributeSchema{ 36 | "description": { 37 | Constraint: schema.LiteralType{Type: cty.String}, 38 | IsOptional: true, 39 | Description: lang.PlainText("Human-readable description of the output (for documentation and UI)"), 40 | }, 41 | "value": { 42 | Constraint: schema.AnyExpression{OfType: cty.DynamicPseudoType}, 43 | IsRequired: true, 44 | Description: lang.PlainText("Value, typically a reference to an attribute of a resource or a data source"), 45 | }, 46 | "sensitive": { 47 | Constraint: schema.LiteralType{Type: cty.Bool}, 48 | DefaultValue: schema.DefaultValue{Value: cty.BoolVal(false)}, 49 | IsOptional: true, 50 | Description: lang.PlainText("Whether the output contains sensitive material and should be hidden in the UI"), 51 | }, 52 | "depends_on": { 53 | Constraint: schema.Set{ 54 | Elem: schema.OneOf{ 55 | schema.Reference{OfScopeId: refscope.DataScope}, 56 | schema.Reference{OfScopeId: refscope.ModuleScope}, 57 | schema.Reference{OfScopeId: refscope.ResourceScope}, 58 | schema.Reference{OfScopeId: refscope.VariableScope}, 59 | schema.Reference{OfScopeId: refscope.LocalScope}, 60 | }, 61 | }, 62 | IsOptional: true, 63 | Description: lang.PlainText("Set of references to hidden dependencies (e.g. resources or data sources)"), 64 | }, 65 | }, 66 | }, 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /internal/schema/0.12/provider_block.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/go-version" 8 | "github.com/hashicorp/hcl-lang/lang" 9 | "github.com/hashicorp/hcl-lang/schema" 10 | "github.com/hashicorp/terraform-schema/internal/schema/refscope" 11 | "github.com/hashicorp/terraform-schema/internal/schema/tokmod" 12 | "github.com/zclconf/go-cty/cty" 13 | ) 14 | 15 | func providerBlockSchema(v *version.Version) *schema.BlockSchema { 16 | return &schema.BlockSchema{ 17 | Address: &schema.BlockAddrSchema{ 18 | Steps: []schema.AddrStep{ 19 | schema.LabelStep{Index: 0}, 20 | schema.AttrValueStep{Name: "alias", IsOptional: true}, 21 | }, 22 | FriendlyName: "provider", 23 | ScopeId: refscope.ProviderScope, 24 | AsReference: true, 25 | }, 26 | SemanticTokenModifiers: lang.SemanticTokenModifiers{tokmod.Provider}, 27 | Labels: []*schema.LabelSchema{ 28 | { 29 | Name: "name", 30 | SemanticTokenModifiers: lang.SemanticTokenModifiers{tokmod.Name, lang.TokenModifierDependent}, 31 | Description: lang.PlainText("Provider Name"), 32 | IsDepKey: true, 33 | Completable: true, 34 | }, 35 | }, 36 | Description: lang.PlainText("A provider block is used to specify a provider configuration"), 37 | Body: &schema.BodySchema{ 38 | Extensions: &schema.BodyExtensions{ 39 | DynamicBlocks: true, 40 | }, 41 | Attributes: map[string]*schema.AttributeSchema{ 42 | "alias": { 43 | Constraint: schema.LiteralType{Type: cty.String}, 44 | IsOptional: true, 45 | Description: lang.Markdown("Alias for using the same provider with different configurations for different resources, e.g. `eu-west`"), 46 | }, 47 | "version": { 48 | Constraint: schema.LiteralType{Type: cty.String}, 49 | IsOptional: true, 50 | Description: lang.Markdown("Specifies a version constraint for the provider, e.g. `~> 1.0`"), 51 | }, 52 | }, 53 | }, 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /internal/schema/0.12/provisioner_block.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/go-version" 8 | "github.com/hashicorp/hcl-lang/lang" 9 | "github.com/hashicorp/hcl-lang/schema" 10 | "github.com/hashicorp/terraform-schema/internal/schema/tokmod" 11 | ) 12 | 13 | func ProvisionerBlock(v *version.Version) *schema.BlockSchema { 14 | return &schema.BlockSchema{ 15 | SemanticTokenModifiers: lang.SemanticTokenModifiers{tokmod.Provisioner}, 16 | Description: lang.Markdown("Provisioner to model specific actions on the local machine or on a remote machine " + 17 | "in order to prepare servers or other infrastructure objects for service"), 18 | Labels: []*schema.LabelSchema{ 19 | { 20 | Name: "type", 21 | SemanticTokenModifiers: lang.SemanticTokenModifiers{tokmod.Type, lang.TokenModifierDependent}, 22 | Description: lang.PlainText("Type of provisioner to use, e.g. `remote-exec` or `file`"), 23 | IsDepKey: true, 24 | Completable: true, 25 | }, 26 | }, 27 | Body: &schema.BodySchema{ 28 | Extensions: &schema.BodyExtensions{ 29 | DynamicBlocks: true, 30 | SelfRefs: true, 31 | }, 32 | HoverURL: "https://www.terraform.io/docs/language/resources/provisioners/syntax.html", 33 | Attributes: map[string]*schema.AttributeSchema{ 34 | "when": { 35 | Constraint: schema.OneOf{ 36 | schema.Keyword{ 37 | Keyword: "create", 38 | Description: lang.Markdown("Run the provisioner when the resource is created"), 39 | }, 40 | schema.Keyword{ 41 | Keyword: "destroy", 42 | Description: lang.Markdown("Run the provisioner when the resource is destroyed"), 43 | }, 44 | }, 45 | IsOptional: true, 46 | Description: lang.Markdown("When to run the provisioner - `create` or `destroy`, defaults to `create` " + 47 | "(i.e. after creation of the resource)"), 48 | }, 49 | "on_failure": { 50 | IsOptional: true, 51 | Constraint: schema.OneOf{ 52 | schema.Keyword{ 53 | Keyword: "fail", 54 | Description: lang.Markdown("Raise an error and stop applying (the default behavior). If this is a creation provisioner, taint the resource."), 55 | }, 56 | schema.Keyword{ 57 | Keyword: "continue", 58 | Description: lang.Markdown("Ignore the error and continue with creation or destruction"), 59 | }, 60 | }, 61 | Description: lang.Markdown("What to do when the provisioner run fails to finish - `fail` (default), " + 62 | "or `continue` (ignore the error)"), 63 | }, 64 | }, 65 | Blocks: map[string]*schema.BlockSchema{ 66 | "connection": ConnectionBlock(v), 67 | }, 68 | }, 69 | DependentBody: ProvisionerDependentBodies(v), 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /internal/schema/0.12/root.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/go-version" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | ) 10 | 11 | var ( 12 | v0_12_2 = version.Must(version.NewVersion("0.12.2")) 13 | v0_12_6 = version.Must(version.NewVersion("0.12.6")) 14 | v0_12_7 = version.Must(version.NewVersion("0.12.7")) 15 | v0_12_18 = version.Must(version.NewVersion("0.12.18")) 16 | v0_12_20 = version.Must(version.NewVersion("0.12.20")) 17 | ) 18 | 19 | func ModuleSchema(v *version.Version) *schema.BodySchema { 20 | return &schema.BodySchema{ 21 | Blocks: map[string]*schema.BlockSchema{ 22 | "data": datasourceBlockSchema(v), 23 | "locals": localsBlockSchema(), 24 | "module": moduleBlockSchema(), 25 | "output": outputBlockSchema(), 26 | "provider": providerBlockSchema(v), 27 | "resource": resourceBlockSchema(v), 28 | "variable": variableBlockSchema(v), 29 | "terraform": terraformBlockSchema(v), 30 | }, 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /internal/schema/0.12/variable_block.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/go-version" 8 | "github.com/hashicorp/hcl-lang/lang" 9 | "github.com/hashicorp/hcl-lang/schema" 10 | "github.com/hashicorp/terraform-schema/internal/schema/refscope" 11 | "github.com/hashicorp/terraform-schema/internal/schema/tokmod" 12 | "github.com/zclconf/go-cty/cty" 13 | ) 14 | 15 | func variableBlockSchema(v *version.Version) *schema.BlockSchema { 16 | bs := &schema.BlockSchema{ 17 | Address: &schema.BlockAddrSchema{ 18 | Steps: []schema.AddrStep{ 19 | schema.StaticStep{Name: "var"}, 20 | schema.LabelStep{Index: 0}, 21 | }, 22 | FriendlyName: "variable", 23 | ScopeId: refscope.VariableScope, 24 | AsReference: true, 25 | AsTypeOf: &schema.BlockAsTypeOf{ 26 | AttributeExpr: "type", 27 | }, 28 | }, 29 | SemanticTokenModifiers: lang.SemanticTokenModifiers{tokmod.Variable}, 30 | Labels: []*schema.LabelSchema{ 31 | { 32 | Name: "name", 33 | SemanticTokenModifiers: lang.SemanticTokenModifiers{tokmod.Name}, 34 | Description: lang.PlainText("Variable Name"), 35 | }, 36 | }, 37 | Description: lang.Markdown("Input variable allowing users to customize aspects of the configuration when used directly " + 38 | "(e.g. via CLI, `tfvars` file or via environment variables), or as a module (via `module` arguments)"), 39 | Body: &schema.BodySchema{ 40 | Attributes: map[string]*schema.AttributeSchema{ 41 | "description": { 42 | Constraint: schema.LiteralType{Type: cty.String}, 43 | IsOptional: true, 44 | Description: lang.Markdown("Description to document the purpose of the variable and what value is expected"), 45 | }, 46 | "type": { 47 | Constraint: schema.TypeDeclaration{}, 48 | IsOptional: true, 49 | Description: lang.Markdown("Type constraint restricting the type of value to accept, e.g. `string` or `list(string)`"), 50 | }, 51 | }, 52 | Blocks: make(map[string]*schema.BlockSchema, 0), 53 | }, 54 | } 55 | 56 | if v.GreaterThanOrEqual(v0_12_20) { 57 | bs.Body.Blocks["validation"] = &schema.BlockSchema{ 58 | Description: lang.Markdown("Custom validation rule to restrict what value is expected for the variable"), 59 | Body: &schema.BodySchema{ 60 | Attributes: map[string]*schema.AttributeSchema{ 61 | "condition": { 62 | Constraint: schema.LiteralType{Type: cty.Bool}, 63 | IsRequired: true, 64 | Description: lang.Markdown("Condition under which a variable value is valid, " + 65 | "e.g. `length(var.example) >= 4` enforces minimum of 4 characters"), 66 | }, 67 | "error_message": { 68 | Constraint: schema.LiteralType{Type: cty.String}, 69 | IsRequired: true, 70 | Description: lang.Markdown("Error message to present when the variable is considered invalid, " + 71 | "i.e. when `condition` evaluates to `false`"), 72 | }, 73 | }, 74 | }, 75 | } 76 | } 77 | 78 | return bs 79 | } 80 | -------------------------------------------------------------------------------- /internal/schema/0.13/provider_block.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/hcl-lang/lang" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | "github.com/zclconf/go-cty/cty" 10 | 11 | "github.com/hashicorp/terraform-schema/internal/schema/refscope" 12 | "github.com/hashicorp/terraform-schema/internal/schema/tokmod" 13 | ) 14 | 15 | func providerBlockSchema() *schema.BlockSchema { 16 | return &schema.BlockSchema{ 17 | Address: &schema.BlockAddrSchema{ 18 | Steps: []schema.AddrStep{ 19 | schema.LabelStep{Index: 0}, 20 | schema.AttrValueStep{Name: "alias", IsOptional: true}, 21 | }, 22 | FriendlyName: "provider", 23 | ScopeId: refscope.ProviderScope, 24 | AsReference: true, 25 | }, 26 | SemanticTokenModifiers: lang.SemanticTokenModifiers{tokmod.Provider}, 27 | Labels: []*schema.LabelSchema{ 28 | { 29 | Name: "name", 30 | SemanticTokenModifiers: lang.SemanticTokenModifiers{tokmod.Name, lang.TokenModifierDependent}, 31 | Description: lang.PlainText("Provider Name"), 32 | IsDepKey: true, 33 | Completable: true, 34 | }, 35 | }, 36 | Description: lang.PlainText("A provider block is used to specify a provider configuration"), 37 | Body: &schema.BodySchema{ 38 | Extensions: &schema.BodyExtensions{ 39 | DynamicBlocks: true, 40 | }, 41 | Attributes: map[string]*schema.AttributeSchema{ 42 | "alias": { 43 | Constraint: schema.LiteralType{Type: cty.String}, 44 | IsOptional: true, 45 | Description: lang.Markdown("Alias for using the same provider with different configurations for different resources, e.g. `eu-west`"), 46 | }, 47 | "version": { 48 | Constraint: schema.LiteralType{Type: cty.String}, 49 | IsOptional: true, 50 | IsDeprecated: true, 51 | Description: lang.Markdown("Specifies a version constraint for the provider. e.g. `~> 1.0`.\n" + 52 | "**DEPRECATED:** Use `required_providers` block to manage provider version instead."), 53 | }, 54 | }, 55 | }, 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /internal/schema/0.13/provisioners.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/go-version" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | 10 | v012_mod "github.com/hashicorp/terraform-schema/internal/schema/0.12" 11 | ) 12 | 13 | // See https://github.com/hashicorp/terraform/blob/v0.13.0/command/internal_plugin_list.go 14 | 15 | var ( 16 | FileProvisioner = v012_mod.FileProvisioner 17 | LocalExecProvisioner = v012_mod.LocalExecProvisioner 18 | RemoteExecProvisioner = v012_mod.RemoteExecProvisioner 19 | ) 20 | 21 | func ConnectionDependentBodies(v *version.Version) map[schema.SchemaKey]*schema.BodySchema { 22 | return v012_mod.ConnectionDependentBodies(v) 23 | } 24 | 25 | func ProvisionerDependentBodies(v *version.Version) map[schema.SchemaKey]*schema.BodySchema { 26 | m := map[schema.SchemaKey]*schema.BodySchema{ 27 | labelKey("file"): FileProvisioner, 28 | labelKey("local-exec"): LocalExecProvisioner, 29 | labelKey("remote-exec"): RemoteExecProvisioner, 30 | } 31 | 32 | // Vendor provisioners are deprecated in 0.13.4+ 33 | // See https://discuss.hashicorp.com/t/notice-terraform-to-begin-deprecation-of-vendor-tool-specific-provisioners-starting-in-terraform-0-13-4/13997 34 | // Some of these provisioners have complex schemas 35 | // but we can at least helpfully list their names 36 | if v.GreaterThanOrEqual(v0_13_4) { 37 | m[labelKey("chef")] = &schema.BodySchema{IsDeprecated: true} 38 | m[labelKey("salt-masterless")] = &schema.BodySchema{IsDeprecated: true} 39 | m[labelKey("habitat")] = &schema.BodySchema{IsDeprecated: true} 40 | m[labelKey("puppet")] = &schema.BodySchema{IsDeprecated: true} 41 | } else { 42 | m[labelKey("chef")] = &schema.BodySchema{} 43 | m[labelKey("salt-masterless")] = &schema.BodySchema{} 44 | m[labelKey("habitat")] = &schema.BodySchema{} 45 | m[labelKey("puppet")] = &schema.BodySchema{} 46 | } 47 | 48 | return m 49 | } 50 | 51 | func labelKey(value string) schema.SchemaKey { 52 | return schema.NewSchemaKey(schema.DependencyKeys{ 53 | Labels: []schema.LabelDependent{{Index: 0, Value: value}}, 54 | }) 55 | } 56 | -------------------------------------------------------------------------------- /internal/schema/0.13/root.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/go-version" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | 10 | v012_mod "github.com/hashicorp/terraform-schema/internal/schema/0.12" 11 | ) 12 | 13 | var v0_13_4 = version.Must(version.NewVersion("0.13.4")) 14 | 15 | func ModuleSchema(v *version.Version) *schema.BodySchema { 16 | bs := v012_mod.ModuleSchema(v) 17 | 18 | bs.Blocks["module"] = moduleBlockSchema() 19 | bs.Blocks["provider"] = providerBlockSchema() 20 | bs.Blocks["terraform"] = terraformBlockSchema(v) 21 | bs.Blocks["resource"].Body.Blocks["provisioner"].DependentBody = ProvisionerDependentBodies(v) 22 | 23 | return bs 24 | } 25 | -------------------------------------------------------------------------------- /internal/schema/0.14/provisioners.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/go-version" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | 10 | v013_mod "github.com/hashicorp/terraform-schema/internal/schema/0.13" 11 | ) 12 | 13 | // See https://github.com/hashicorp/terraform/blob/v0.14.0/command/internal_plugin_list.go 14 | 15 | var ( 16 | FileProvisioner = v013_mod.FileProvisioner 17 | LocalExecProvisioner = v013_mod.LocalExecProvisioner 18 | RemoteExecProvisioner = v013_mod.RemoteExecProvisioner 19 | ) 20 | 21 | func ConnectionDependentBodies(v *version.Version) map[schema.SchemaKey]*schema.BodySchema { 22 | return v013_mod.ConnectionDependentBodies(v) 23 | } 24 | 25 | var ProvisionerDependentBodies = map[schema.SchemaKey]*schema.BodySchema{ 26 | labelKey("file"): FileProvisioner, 27 | labelKey("local-exec"): LocalExecProvisioner, 28 | labelKey("remote-exec"): RemoteExecProvisioner, 29 | 30 | // Vendor provisioners are deprecated in 0.13.4+ 31 | // See https://discuss.hashicorp.com/t/notice-terraform-to-begin-deprecation-of-vendor-tool-specific-provisioners-starting-in-terraform-0-13-4/13997 32 | // Some of these provisioners have complex schemas 33 | // but we can at least helpfully list their names 34 | labelKey("chef"): {IsDeprecated: true}, 35 | labelKey("salt-masterless"): {IsDeprecated: true}, 36 | labelKey("habitat"): {IsDeprecated: true}, 37 | labelKey("puppet"): {IsDeprecated: true}, 38 | } 39 | 40 | func labelKey(value string) schema.SchemaKey { 41 | return schema.NewSchemaKey(schema.DependencyKeys{ 42 | Labels: []schema.LabelDependent{{Index: 0, Value: value}}, 43 | }) 44 | } 45 | -------------------------------------------------------------------------------- /internal/schema/0.14/root.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/go-version" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | 10 | v013_mod "github.com/hashicorp/terraform-schema/internal/schema/0.13" 11 | ) 12 | 13 | func ModuleSchema(v *version.Version) *schema.BodySchema { 14 | bs := v013_mod.ModuleSchema(v) 15 | 16 | bs.Blocks["variable"] = variableBlockSchema() 17 | bs.Blocks["terraform"] = terraformBlockSchema(v) 18 | bs.Blocks["resource"].Body.Blocks["provisioner"].DependentBody = ProvisionerDependentBodies 19 | 20 | return bs 21 | } 22 | -------------------------------------------------------------------------------- /internal/schema/0.15/connection_block.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/go-version" 8 | "github.com/hashicorp/hcl-lang/lang" 9 | "github.com/hashicorp/hcl-lang/schema" 10 | "github.com/zclconf/go-cty/cty" 11 | 12 | v014_mod "github.com/hashicorp/terraform-schema/internal/schema/0.14" 13 | ) 14 | 15 | func ConnectionDependentBodies(v *version.Version) map[schema.SchemaKey]*schema.BodySchema { 16 | bodies := v014_mod.ConnectionDependentBodies(v) 17 | 18 | ssh := schema.NewSchemaKey(schema.DependencyKeys{ 19 | Attributes: []schema.AttributeDependent{ 20 | { 21 | Name: "type", 22 | Expr: schema.ExpressionValue{Static: cty.StringVal("ssh")}, 23 | }, 24 | }, 25 | }) 26 | 27 | // See https://github.com/hashicorp/terraform/commit/5b99a56f 28 | bodies[ssh].Attributes["target_platform"] = &schema.AttributeSchema{ 29 | Constraint: schema.OneOf{ 30 | schema.LiteralValue{Value: cty.StringVal("windows")}, 31 | schema.LiteralValue{Value: cty.StringVal("unix")}, 32 | }, 33 | DefaultValue: schema.DefaultValue{Value: cty.StringVal("unix")}, 34 | IsOptional: true, 35 | Description: lang.Markdown("The target platform to connect to. " + 36 | "Defaults to `unix` if not set. If the platform is set to `windows`, the default `script_path`" + 37 | " is `" + `c:\windows\temp\terraform_%RAND%.cmd` + ", assuming the SSH default shell is `cmd.exe`."), 38 | } 39 | 40 | return bodies 41 | } 42 | -------------------------------------------------------------------------------- /internal/schema/0.15/provisioners.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/go-version" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | 10 | v014_mod "github.com/hashicorp/terraform-schema/internal/schema/0.14" 11 | ) 12 | 13 | var ( 14 | FileProvisioner = v014_mod.FileProvisioner 15 | LocalExecProvisioner = v014_mod.LocalExecProvisioner 16 | RemoteExecProvisioner = v014_mod.RemoteExecProvisioner 17 | ) 18 | 19 | // See https://github.com/hashicorp/terraform/tree/v0.15.0/builtin/provisioners 20 | 21 | func ProvisionerDependentBodies(v *version.Version) map[schema.SchemaKey]*schema.BodySchema { 22 | return map[schema.SchemaKey]*schema.BodySchema{ 23 | labelKey("file"): FileProvisioner, 24 | labelKey("local-exec"): LocalExecProvisioner, 25 | labelKey("remote-exec"): RemoteExecProvisioner, 26 | } 27 | } 28 | 29 | func labelKey(value string) schema.SchemaKey { 30 | return schema.NewSchemaKey(schema.DependencyKeys{ 31 | Labels: []schema.LabelDependent{{Index: 0, Value: value}}, 32 | }) 33 | } 34 | -------------------------------------------------------------------------------- /internal/schema/0.15/root.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/go-version" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | 10 | v014_mod "github.com/hashicorp/terraform-schema/internal/schema/0.14" 11 | ) 12 | 13 | func ModuleSchema(v *version.Version) *schema.BodySchema { 14 | bs := v014_mod.ModuleSchema(v) 15 | bs.Blocks["terraform"] = patchTerraformBlockSchema(bs.Blocks["terraform"]) 16 | bs.Blocks["resource"].Body.Blocks["provisioner"].DependentBody = ProvisionerDependentBodies(v) 17 | bs.Blocks["resource"].Body.Blocks["connection"].DependentBody = ConnectionDependentBodies(v) 18 | bs.Blocks["resource"].Body.Blocks["provisioner"].Body.Blocks["connection"].DependentBody = ConnectionDependentBodies(v) 19 | 20 | return bs 21 | } 22 | -------------------------------------------------------------------------------- /internal/schema/0.15/terraform.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/hcl-lang/lang" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | "github.com/hashicorp/terraform-schema/internal/schema/refscope" 10 | "github.com/zclconf/go-cty/cty" 11 | ) 12 | 13 | func patchTerraformBlockSchema(bs *schema.BlockSchema) *schema.BlockSchema { 14 | bs.Body.Blocks["required_providers"].Body = &schema.BodySchema{ 15 | AnyAttribute: &schema.AttributeSchema{ 16 | Constraint: schema.OneOf{ 17 | schema.Object{ 18 | Attributes: schema.ObjectAttributes{ 19 | "source": &schema.AttributeSchema{ 20 | Constraint: schema.LiteralType{Type: cty.String}, 21 | IsRequired: true, 22 | Description: lang.Markdown("The global source address for the provider " + 23 | "you intend to use, such as `hashicorp/aws`"), 24 | }, 25 | "version": &schema.AttributeSchema{ 26 | Constraint: schema.LiteralType{Type: cty.String}, 27 | IsOptional: true, 28 | Description: lang.Markdown("Version constraint specifying which subset of " + 29 | "available provider versions the module is compatible with, e.g. `~> 1.0`"), 30 | }, 31 | "configuration_aliases": &schema.AttributeSchema{ 32 | IsOptional: true, 33 | Constraint: schema.Set{ 34 | Elem: schema.Reference{ 35 | Address: &schema.ReferenceAddrSchema{ 36 | ScopeId: refscope.ProviderScope, 37 | }, 38 | Name: "provider", 39 | }, 40 | }, 41 | Description: lang.Markdown("Aliases under which to make the provider available, " + 42 | "such as `[ aws.eu-west, aws.us-east ]`"), 43 | }, 44 | }, 45 | }, 46 | schema.LiteralType{Type: cty.String}, 47 | }, 48 | Address: &schema.AttributeAddrSchema{ 49 | Steps: []schema.AddrStep{ 50 | schema.AttrNameStep{}, 51 | }, 52 | FriendlyName: "provider", 53 | AsReference: true, 54 | ScopeId: refscope.ProviderScope, 55 | }, 56 | Description: lang.Markdown("Provider source, version constraint and its aliases"), 57 | }, 58 | } 59 | bs.Body.Attributes["language"] = &schema.AttributeSchema{ 60 | IsOptional: true, 61 | Constraint: schema.Keyword{ 62 | Keyword: "TF2021", 63 | }, 64 | } 65 | return bs 66 | } 67 | -------------------------------------------------------------------------------- /internal/schema/1.1/moved.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/hcl-lang/lang" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | "github.com/hashicorp/terraform-schema/internal/schema/refscope" 10 | ) 11 | 12 | var movedBlockSchema = &schema.BlockSchema{ 13 | Description: lang.Markdown("Refactoring declaration to specify what address to move where"), 14 | Body: &schema.BodySchema{ 15 | HoverURL: "https://www.terraform.io/language/modules/develop/refactoring#moved-block-syntax", 16 | Attributes: map[string]*schema.AttributeSchema{ 17 | "from": { 18 | Constraint: schema.OneOf{ 19 | schema.Reference{OfScopeId: refscope.ModuleScope}, 20 | schema.Reference{OfScopeId: refscope.ResourceScope}, 21 | }, 22 | IsRequired: true, 23 | Description: lang.Markdown("Source address to move away from"), 24 | }, 25 | "to": { 26 | Constraint: schema.OneOf{ 27 | schema.Reference{OfScopeId: refscope.ModuleScope}, 28 | schema.Reference{OfScopeId: refscope.ResourceScope}, 29 | }, 30 | IsRequired: true, 31 | Description: lang.Markdown("Destination address to move to"), 32 | }, 33 | }, 34 | }, 35 | } 36 | -------------------------------------------------------------------------------- /internal/schema/1.1/root.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/go-version" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | 10 | v015_mod "github.com/hashicorp/terraform-schema/internal/schema/0.15" 11 | ) 12 | 13 | func ModuleSchema(v *version.Version) *schema.BodySchema { 14 | bs := v015_mod.ModuleSchema(v) 15 | bs.Blocks["moved"] = movedBlockSchema 16 | bs.Blocks["terraform"] = patchTerraformBlockSchema(bs.Blocks["terraform"]) 17 | bs.Blocks["variable"] = patchVariableBlockSchema(bs.Blocks["variable"]) 18 | 19 | return bs 20 | } 21 | -------------------------------------------------------------------------------- /internal/schema/1.1/terraform.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/hcl-lang/lang" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | "github.com/zclconf/go-cty/cty" 10 | ) 11 | 12 | func patchTerraformBlockSchema(bs *schema.BlockSchema) *schema.BlockSchema { 13 | bs.Body.Blocks["cloud"] = &schema.BlockSchema{ 14 | Description: lang.PlainText("HCP Terraform configuration"), 15 | MaxItems: 1, 16 | Body: &schema.BodySchema{ 17 | Attributes: map[string]*schema.AttributeSchema{ 18 | "hostname": { 19 | Constraint: schema.LiteralType{Type: cty.String}, 20 | IsOptional: true, 21 | Description: lang.Markdown("The Terraform Enterprise hostname to connect to. " + 22 | "This optional argument defaults to `app.terraform.io` for use with HCP Terraform."), 23 | }, 24 | "organization": { 25 | Constraint: schema.LiteralType{Type: cty.String}, 26 | IsOptional: true, 27 | Description: lang.PlainText("The name of the organization containing the targeted workspace(s)."), 28 | }, 29 | "token": { 30 | Constraint: schema.LiteralType{Type: cty.String}, 31 | IsOptional: true, 32 | Description: lang.Markdown("The token used to authenticate with HCP Terraform/Terraform Enterprise. " + 33 | "Typically this argument should not be set, and `terraform login` used instead; " + 34 | "your credentials will then be fetched from your CLI configuration file " + 35 | "or configured credential helper."), 36 | }, 37 | }, 38 | Blocks: map[string]*schema.BlockSchema{ 39 | "workspaces": { 40 | Description: lang.Markdown("Workspace mapping strategy, either workspace `tags` or `name` is required."), 41 | MaxItems: 1, 42 | Body: &schema.BodySchema{ 43 | Attributes: map[string]*schema.AttributeSchema{ 44 | "name": { 45 | Constraint: schema.LiteralType{Type: cty.String}, 46 | IsOptional: true, 47 | Description: lang.Markdown("The name of a single HCP Terraform workspace " + 48 | "to be used with this configuration. When configured only the specified workspace " + 49 | "can be used. This option conflicts with `tags`."), 50 | }, 51 | "tags": { 52 | Constraint: schema.Set{ 53 | Elem: schema.LiteralType{Type: cty.String}, 54 | }, 55 | IsOptional: true, 56 | Description: lang.Markdown("A set of tags used to select remote HCP Terraform workspaces" + 57 | " to be used for this single configuration. New workspaces will automatically be tagged " + 58 | "with these tag values. Generally, this is the primary and recommended strategy to use. " + 59 | "This option conflicts with `name`."), 60 | }, 61 | }, 62 | }, 63 | }, 64 | }, 65 | }, 66 | } 67 | 68 | return bs 69 | } 70 | -------------------------------------------------------------------------------- /internal/schema/1.1/variable_block.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/hcl-lang/lang" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | "github.com/zclconf/go-cty/cty" 10 | ) 11 | 12 | func patchVariableBlockSchema(bs *schema.BlockSchema) *schema.BlockSchema { 13 | bs.Body.Attributes["nullable"] = &schema.AttributeSchema{ 14 | Constraint: schema.LiteralType{Type: cty.Bool}, 15 | DefaultValue: schema.DefaultValue{Value: cty.False}, 16 | IsOptional: true, 17 | Description: lang.Markdown("Specifies whether `null` is a valid value for this variable"), 18 | } 19 | 20 | return bs 21 | } 22 | -------------------------------------------------------------------------------- /internal/schema/1.10/root.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/go-version" 8 | "github.com/hashicorp/hcl-lang/lang" 9 | "github.com/hashicorp/hcl-lang/schema" 10 | "github.com/zclconf/go-cty/cty" 11 | 12 | v1_9_mod "github.com/hashicorp/terraform-schema/internal/schema/1.9" 13 | "github.com/hashicorp/terraform-schema/internal/schema/refscope" 14 | ) 15 | 16 | func ModuleSchema(v *version.Version) *schema.BodySchema { 17 | bs := v1_9_mod.ModuleSchema(v) 18 | 19 | bs.Blocks["variable"].Body.Attributes["ephemeral"] = &schema.AttributeSchema{ 20 | IsOptional: true, 21 | Constraint: schema.LiteralType{Type: cty.Bool}, 22 | Description: lang.PlainText("Whether the value is ephemeral and should not be persisted in the state"), 23 | } 24 | bs.Blocks["output"].Body.Attributes["ephemeral"] = &schema.AttributeSchema{ 25 | IsOptional: true, 26 | Constraint: schema.LiteralType{Type: cty.Bool}, 27 | Description: lang.PlainText("Whether the value is ephemeral and should not be persisted in the state"), 28 | } 29 | 30 | bs.Blocks["ephemeral"] = ephemeralBlockSchema() 31 | 32 | // all the depends_on attributes can refer to ephemeral blocks 33 | constraint := schema.Set{ 34 | Elem: schema.OneOf{ 35 | schema.Reference{OfScopeId: refscope.DataScope}, 36 | schema.Reference{OfScopeId: refscope.ModuleScope}, 37 | schema.Reference{OfScopeId: refscope.ResourceScope}, 38 | schema.Reference{OfScopeId: refscope.EphemeralScope}, // This one is new, but overriding is easier than adding to each list 39 | schema.Reference{OfScopeId: refscope.VariableScope}, 40 | schema.Reference{OfScopeId: refscope.LocalScope}, 41 | }, 42 | } 43 | bs.Blocks["resource"].Body.Attributes["depends_on"].Constraint = constraint 44 | bs.Blocks["data"].Body.Attributes["depends_on"].Constraint = constraint 45 | bs.Blocks["output"].Body.Attributes["depends_on"].Constraint = constraint 46 | bs.Blocks["module"].Body.Attributes["depends_on"].Constraint = constraint 47 | bs.Blocks["check"].Body.Blocks["data"].Body.Attributes["depends_on"].Constraint = constraint 48 | 49 | return bs 50 | } 51 | -------------------------------------------------------------------------------- /internal/schema/1.2/data.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/hcl-lang/lang" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | ) 10 | 11 | func datasourceLifecycleBlock() *schema.BlockSchema { 12 | return &schema.BlockSchema{ 13 | Description: lang.Markdown("Lifecycle customizations to set validity conditions of the datasource"), 14 | Body: &schema.BodySchema{ 15 | Blocks: map[string]*schema.BlockSchema{ 16 | "precondition": { 17 | Body: conditionBody(false), 18 | }, 19 | "postcondition": { 20 | Body: conditionBody(false), 21 | }, 22 | }, 23 | }, 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /internal/schema/1.2/resource.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/hcl-lang/lang" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | "github.com/hashicorp/terraform-schema/internal/schema/refscope" 10 | "github.com/zclconf/go-cty/cty" 11 | ) 12 | 13 | func resourceLifecycleBlock() *schema.BlockSchema { 14 | return &schema.BlockSchema{ 15 | Description: lang.Markdown("Lifecycle customizations to change default resource behaviours during plan or apply"), 16 | Body: &schema.BodySchema{ 17 | Attributes: map[string]*schema.AttributeSchema{ 18 | "create_before_destroy": { 19 | Constraint: schema.LiteralType{Type: cty.Bool}, 20 | DefaultValue: schema.DefaultValue{Value: cty.False}, 21 | IsOptional: true, 22 | Description: lang.Markdown("Whether to reverse the default order of operations (destroy -> create) during apply " + 23 | "when the resource requires replacement (cannot be updated in-place)"), 24 | }, 25 | "prevent_destroy": { 26 | Constraint: schema.LiteralType{Type: cty.Bool}, 27 | DefaultValue: schema.DefaultValue{Value: cty.False}, 28 | IsOptional: true, 29 | Description: lang.Markdown("Whether to prevent accidental destruction of the resource and cause Terraform " + 30 | "to reject with an error any plan that would destroy the resource"), 31 | }, 32 | "ignore_changes": { 33 | Constraint: schema.OneOf{ 34 | schema.Set{ 35 | // TODO: expose reference targets via attribute-only address 36 | }, 37 | schema.Keyword{ 38 | Keyword: "all", 39 | Description: lang.Markdown("Ignore all attributes, which means that Terraform can create" + 40 | " and destroy the remote object but will never propose updates to it"), 41 | }, 42 | }, 43 | IsOptional: true, 44 | Description: lang.Markdown("A set of fields (references) of which to ignore changes to, e.g. `tags`"), 45 | }, 46 | "replace_triggered_by": { 47 | Constraint: schema.Set{ 48 | Elem: schema.Reference{ 49 | OfScopeId: refscope.ResourceScope, 50 | }, 51 | }, 52 | IsOptional: true, 53 | Description: lang.Markdown("Set of references to any other resources which when changed cause " + 54 | "this resource to be proposed for replacement"), 55 | }, 56 | }, 57 | Blocks: map[string]*schema.BlockSchema{ 58 | "precondition": { 59 | Body: conditionBody(false), 60 | }, 61 | "postcondition": { 62 | Body: conditionBody(true), 63 | }, 64 | }, 65 | }, 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /internal/schema/1.2/root.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/go-version" 8 | "github.com/hashicorp/hcl-lang/lang" 9 | "github.com/hashicorp/hcl-lang/schema" 10 | "github.com/zclconf/go-cty/cty" 11 | 12 | v1_1_mod "github.com/hashicorp/terraform-schema/internal/schema/1.1" 13 | ) 14 | 15 | func ModuleSchema(v *version.Version) *schema.BodySchema { 16 | bs := v1_1_mod.ModuleSchema(v) 17 | bs.Blocks["data"].Body.Blocks = map[string]*schema.BlockSchema{ 18 | "lifecycle": datasourceLifecycleBlock(), 19 | } 20 | bs.Blocks["resource"].Body.Blocks["lifecycle"] = resourceLifecycleBlock() 21 | bs.Blocks["output"].Body.Blocks = map[string]*schema.BlockSchema{ 22 | "precondition": { 23 | Body: conditionBody(false), 24 | }, 25 | } 26 | 27 | return bs 28 | } 29 | 30 | func conditionBody(enableSelfRefs bool) *schema.BodySchema { 31 | bs := &schema.BodySchema{ 32 | Attributes: map[string]*schema.AttributeSchema{ 33 | "condition": { 34 | Constraint: schema.AnyExpression{OfType: cty.Bool}, 35 | IsRequired: true, 36 | Description: lang.Markdown("Condition, a boolean expression that should return `true` " + 37 | "if the intended assumption or guarantee is fulfilled or `false` if it is not."), 38 | }, 39 | "error_message": { 40 | Constraint: schema.AnyExpression{OfType: cty.String}, 41 | IsRequired: true, 42 | Description: lang.Markdown("Error message to return if the `condition` isn't met " + 43 | "(evaluates to `false`)."), 44 | }, 45 | }, 46 | } 47 | 48 | if enableSelfRefs { 49 | bs.Extensions = &schema.BodyExtensions{ 50 | SelfRefs: true, 51 | } 52 | } 53 | 54 | return bs 55 | } 56 | -------------------------------------------------------------------------------- /internal/schema/1.3/connection_block.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/go-version" 8 | "github.com/hashicorp/hcl-lang/lang" 9 | "github.com/hashicorp/hcl-lang/schema" 10 | "github.com/zclconf/go-cty/cty" 11 | 12 | v015_mod "github.com/hashicorp/terraform-schema/internal/schema/0.15" 13 | ) 14 | 15 | func ConnectionDependentBodies(v *version.Version) map[schema.SchemaKey]*schema.BodySchema { 16 | bodies := v015_mod.ConnectionDependentBodies(v) 17 | 18 | ssh := schema.NewSchemaKey(schema.DependencyKeys{ 19 | Attributes: []schema.AttributeDependent{ 20 | { 21 | Name: "type", 22 | Expr: schema.ExpressionValue{Static: cty.StringVal("ssh")}, 23 | }, 24 | }, 25 | }) 26 | 27 | // See https://github.com/hashicorp/terraform/commit/4cfb6bc8 28 | bodies[ssh].Attributes["proxy_scheme"] = &schema.AttributeSchema{ 29 | Constraint: schema.OneOf{ 30 | schema.LiteralValue{Value: cty.StringVal("http")}, 31 | schema.LiteralValue{Value: cty.StringVal("https")}, 32 | }, 33 | IsOptional: true, 34 | Description: lang.Markdown("Scheme to use to connect to the proxy (`http` or `https`). " + 35 | "Defaults to `http`."), 36 | } 37 | bodies[ssh].Attributes["proxy_host"] = &schema.AttributeSchema{ 38 | Constraint: schema.AnyExpression{OfType: cty.String}, 39 | IsOptional: true, 40 | Description: lang.Markdown("Host to connect to in order to enable SSH over HTTP connection. " + 41 | "This host will be connected to first, and then the `host` or `bastion_host` connection " + 42 | "will be made from there."), 43 | } 44 | bodies[ssh].Attributes["proxy_port"] = &schema.AttributeSchema{ 45 | Constraint: schema.AnyExpression{OfType: cty.Number}, 46 | IsOptional: true, 47 | Description: lang.Markdown("The port to use connect to the `proxy_host`"), 48 | } 49 | bodies[ssh].Attributes["proxy_user_name"] = &schema.AttributeSchema{ 50 | Constraint: schema.AnyExpression{OfType: cty.String}, 51 | IsOptional: true, 52 | Description: lang.Markdown("The username to use to connect to the `proxy_host`"), 53 | } 54 | bodies[ssh].Attributes["proxy_user_password"] = &schema.AttributeSchema{ 55 | Constraint: schema.AnyExpression{OfType: cty.String}, 56 | IsOptional: true, 57 | Description: lang.Markdown("The password to use to connect to the `proxy_host`"), 58 | } 59 | 60 | return bodies 61 | } 62 | -------------------------------------------------------------------------------- /internal/schema/1.3/root.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/go-version" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | 10 | v1_2_mod "github.com/hashicorp/terraform-schema/internal/schema/1.2" 11 | ) 12 | 13 | func ModuleSchema(v *version.Version) *schema.BodySchema { 14 | bs := v1_2_mod.ModuleSchema(v) 15 | bs.Blocks["resource"].Body.Blocks["connection"].DependentBody = ConnectionDependentBodies(v) 16 | bs.Blocks["resource"].Body.Blocks["provisioner"].Body.Blocks["connection"].DependentBody = ConnectionDependentBodies(v) 17 | 18 | return bs 19 | } 20 | -------------------------------------------------------------------------------- /internal/schema/1.4/provisioners.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/go-version" 8 | "github.com/hashicorp/hcl-lang/lang" 9 | "github.com/hashicorp/hcl-lang/schema" 10 | "github.com/zclconf/go-cty/cty" 11 | 12 | v0_15_mod "github.com/hashicorp/terraform-schema/internal/schema/0.15" 13 | ) 14 | 15 | var ( 16 | FileProvisioner = v0_15_mod.FileProvisioner 17 | LocalExecProvisioner = func() *schema.BodySchema { 18 | // See https: //github.com/hashicorp/terraform/pull/32116/files 19 | bodySchema := v0_15_mod.LocalExecProvisioner 20 | bodySchema.Attributes["quiet"] = &schema.AttributeSchema{ 21 | Constraint: schema.LiteralType{Type: cty.Bool}, 22 | DefaultValue: schema.DefaultValue{Value: cty.False}, 23 | IsOptional: true, 24 | Description: lang.Markdown("Whether to suppress script output"), 25 | } 26 | return bodySchema 27 | }() 28 | RemoteExecProvisioner = v0_15_mod.RemoteExecProvisioner 29 | ) 30 | 31 | func ProvisionerDependentBodies(v *version.Version) map[schema.SchemaKey]*schema.BodySchema { 32 | return map[schema.SchemaKey]*schema.BodySchema{ 33 | labelKey("file"): FileProvisioner, 34 | labelKey("local-exec"): LocalExecProvisioner, 35 | labelKey("remote-exec"): RemoteExecProvisioner, 36 | } 37 | } 38 | 39 | func labelKey(value string) schema.SchemaKey { 40 | return schema.NewSchemaKey(schema.DependencyKeys{ 41 | Labels: []schema.LabelDependent{{Index: 0, Value: value}}, 42 | }) 43 | } 44 | -------------------------------------------------------------------------------- /internal/schema/1.4/root.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/go-version" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | 10 | v1_3_mod "github.com/hashicorp/terraform-schema/internal/schema/1.3" 11 | ) 12 | 13 | func ModuleSchema(v *version.Version) *schema.BodySchema { 14 | bs := v1_3_mod.ModuleSchema(v) 15 | bs.Blocks["resource"].Body.Blocks["provisioner"].DependentBody = ProvisionerDependentBodies(v) 16 | 17 | return bs 18 | } 19 | -------------------------------------------------------------------------------- /internal/schema/1.5/import.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/hcl-lang/lang" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | "github.com/hashicorp/terraform-schema/internal/schema/refscope" 10 | "github.com/zclconf/go-cty/cty" 11 | ) 12 | 13 | func importBlock() *schema.BlockSchema { 14 | return &schema.BlockSchema{ 15 | Description: lang.Markdown("Import resources into Terraform to bring them under Terraform's management"), 16 | Body: &schema.BodySchema{ 17 | HoverURL: "https://developer.hashicorp.com/terraform/language/import", 18 | Attributes: map[string]*schema.AttributeSchema{ 19 | "provider": { 20 | Constraint: schema.Reference{OfScopeId: refscope.ProviderScope}, 21 | IsOptional: true, 22 | Description: lang.Markdown("Reference to a `provider` configuration block, e.g. `mycloud.west` or `mycloud`"), 23 | }, 24 | "id": { 25 | Constraint: schema.LiteralType{Type: cty.String}, 26 | IsRequired: true, 27 | Description: lang.Markdown("ID of the resource to be imported. e.g. `i-abcd1234`"), 28 | }, 29 | "to": { 30 | Constraint: schema.Reference{OfScopeId: refscope.ResourceScope}, 31 | IsRequired: true, 32 | Description: lang.Markdown("An address of the resource instance to import to. e.g. `aws_instance.example` or `module.foo.aws_instance.bar`"), 33 | }, 34 | }, 35 | }, 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /internal/schema/1.5/root.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/go-version" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | 10 | v1_4_mod "github.com/hashicorp/terraform-schema/internal/schema/1.4" 11 | ) 12 | 13 | func ModuleSchema(v *version.Version) *schema.BodySchema { 14 | bs := v1_4_mod.ModuleSchema(v) 15 | bs.Blocks["import"] = importBlock() 16 | bs.Blocks["check"] = checkBlock() 17 | 18 | return bs 19 | } 20 | -------------------------------------------------------------------------------- /internal/schema/1.6/import.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/hcl-lang/lang" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | "github.com/hashicorp/terraform-schema/internal/schema/refscope" 10 | "github.com/zclconf/go-cty/cty" 11 | ) 12 | 13 | func importBlock() *schema.BlockSchema { 14 | return &schema.BlockSchema{ 15 | Description: lang.Markdown("Import resources into Terraform to bring them under Terraform's management"), 16 | Body: &schema.BodySchema{ 17 | HoverURL: "https://developer.hashicorp.com/terraform/language/import", 18 | Attributes: map[string]*schema.AttributeSchema{ 19 | "provider": { 20 | Constraint: schema.Reference{OfScopeId: refscope.ProviderScope}, 21 | IsOptional: true, 22 | Description: lang.Markdown("Reference to a `provider` configuration block, e.g. `mycloud.west` or `mycloud`"), 23 | }, 24 | "id": { 25 | Constraint: schema.AnyExpression{OfType: cty.String}, 26 | IsRequired: true, 27 | Description: lang.Markdown("ID of the resource to be imported. e.g. `i-abcd1234`"), 28 | }, 29 | "to": { 30 | Constraint: schema.Reference{OfScopeId: refscope.ResourceScope}, 31 | IsRequired: true, 32 | Description: lang.Markdown("An address of the resource instance to import to. e.g. `aws_instance.example` or `module.foo.aws_instance.bar`"), 33 | }, 34 | }, 35 | }, 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /internal/schema/1.6/root.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/go-version" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | 10 | v1_5_mod "github.com/hashicorp/terraform-schema/internal/schema/1.5" 11 | ) 12 | 13 | func ModuleSchema(v *version.Version) *schema.BodySchema { 14 | bs := v1_5_mod.ModuleSchema(v) 15 | bs.Blocks["import"] = importBlock() 16 | bs.Blocks["terraform"] = patchTerraformBlockSchema(bs.Blocks["terraform"]) 17 | return bs 18 | } 19 | -------------------------------------------------------------------------------- /internal/schema/1.6/terraform.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/hcl-lang/lang" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | "github.com/zclconf/go-cty/cty" 10 | ) 11 | 12 | func patchTerraformBlockSchema(bs *schema.BlockSchema) *schema.BlockSchema { 13 | bs.Body.Blocks["cloud"].Body.Blocks["workspaces"].Body = &schema.BodySchema{ 14 | Attributes: map[string]*schema.AttributeSchema{ 15 | "name": { 16 | Constraint: schema.LiteralType{Type: cty.String}, 17 | IsOptional: true, 18 | Description: lang.Markdown("The name of a single HCP Terraform workspace " + 19 | "to be used with this configuration. When configured only the specified workspace " + 20 | "can be used. This option conflicts with `tags`."), 21 | }, 22 | "project": { 23 | Constraint: schema.LiteralType{Type: cty.String}, 24 | IsOptional: true, 25 | Description: lang.PlainText("The name of a HCP Terraform project. Workspaces that need creating will be created within this project."), 26 | }, 27 | "tags": { 28 | Constraint: schema.Set{ 29 | Elem: schema.LiteralType{Type: cty.String}, 30 | }, 31 | IsOptional: true, 32 | Description: lang.Markdown("A set of tags used to select remote HCP Terraform workspaces" + 33 | " to be used for this single configuration. New workspaces will automatically be tagged " + 34 | "with these tag values. Generally, this is the primary and recommended strategy to use. " + 35 | "This option conflicts with `name`."), 36 | }, 37 | }, 38 | } 39 | 40 | return bs 41 | } 42 | -------------------------------------------------------------------------------- /internal/schema/1.7/removed.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/hcl-lang/lang" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | "github.com/hashicorp/terraform-schema/internal/schema/refscope" 10 | "github.com/zclconf/go-cty/cty" 11 | ) 12 | 13 | func removedBlock() *schema.BlockSchema { 14 | return &schema.BlockSchema{ 15 | Description: lang.Markdown("Declaration to specify what resource or module to remove from the state"), 16 | Body: &schema.BodySchema{ 17 | HoverURL: "https://developer.hashicorp.com/terraform/language/resources/syntax#removing-resources", 18 | Attributes: map[string]*schema.AttributeSchema{ 19 | "from": { 20 | Constraint: schema.OneOf{ 21 | schema.Reference{OfScopeId: refscope.ModuleScope}, 22 | schema.Reference{OfScopeId: refscope.ResourceScope}, 23 | }, 24 | IsRequired: true, 25 | Description: lang.Markdown("Address of the module or resource to be removed"), 26 | }, 27 | }, 28 | Blocks: map[string]*schema.BlockSchema{ 29 | "lifecycle": { 30 | Description: lang.Markdown("Lifecycle customizations controlling the removal"), 31 | Body: &schema.BodySchema{ 32 | Attributes: map[string]*schema.AttributeSchema{ 33 | "destroy": { 34 | Constraint: schema.LiteralType{Type: cty.Bool}, 35 | IsRequired: true, 36 | Description: lang.Markdown("Whether Terraform will attempt to destroy the objects (`true`) or not, i.e. just remove from state (`false`)."), 37 | }, 38 | }, 39 | }, 40 | MinItems: 1, 41 | MaxItems: 1, 42 | }, 43 | }, 44 | }, 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /internal/schema/1.7/root.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/go-version" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | 10 | v1_6_mod "github.com/hashicorp/terraform-schema/internal/schema/1.6" 11 | ) 12 | 13 | func ModuleSchema(v *version.Version) *schema.BodySchema { 14 | bs := v1_6_mod.ModuleSchema(v) 15 | bs.Blocks["removed"] = removedBlock() 16 | bs.Blocks["import"].Body.Extensions = &schema.BodyExtensions{ 17 | ForEach: true, 18 | } 19 | return bs 20 | } 21 | -------------------------------------------------------------------------------- /internal/schema/1.8/root.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/go-version" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | 10 | v1_7_mod "github.com/hashicorp/terraform-schema/internal/schema/1.7" 11 | ) 12 | 13 | func ModuleSchema(v *version.Version) *schema.BodySchema { 14 | bs := v1_7_mod.ModuleSchema(v) 15 | bs.Blocks["terraform"] = patchTerraformBlockSchema(bs.Blocks["terraform"]) 16 | return bs 17 | } 18 | -------------------------------------------------------------------------------- /internal/schema/1.8/terraform.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/hcl-lang/lang" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | ) 10 | 11 | func patchTerraformBlockSchema(bs *schema.BlockSchema) *schema.BlockSchema { 12 | // removes module_variable_optional_attrs experiment (defined in 0.14) 13 | bs.Body.Attributes["experiments"] = &schema.AttributeSchema{ 14 | Constraint: schema.Set{ 15 | Elem: schema.OneOf{ 16 | schema.Keyword{ 17 | Keyword: "provider_sensitive_attrs", 18 | Name: "feature", 19 | }, 20 | }, 21 | }, 22 | IsOptional: true, 23 | Description: lang.Markdown("A set of experimental language features to enable"), 24 | } 25 | 26 | return bs 27 | } 28 | -------------------------------------------------------------------------------- /internal/schema/1.9/root.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/go-version" 8 | "github.com/hashicorp/hcl-lang/lang" 9 | "github.com/hashicorp/hcl-lang/schema" 10 | 11 | v012_mod "github.com/hashicorp/terraform-schema/internal/schema/0.12" 12 | v1_3_mod "github.com/hashicorp/terraform-schema/internal/schema/1.3" 13 | v1_4_mod "github.com/hashicorp/terraform-schema/internal/schema/1.4" 14 | v1_8_mod "github.com/hashicorp/terraform-schema/internal/schema/1.8" 15 | ) 16 | 17 | func ModuleSchema(v *version.Version) *schema.BodySchema { 18 | bs := v1_8_mod.ModuleSchema(v) 19 | 20 | bs.Blocks["removed"].Body.Blocks["provisioner"] = v012_mod.ProvisionerBlock(v) 21 | bs.Blocks["removed"].Body.Blocks["provisioner"].DependentBody = v1_4_mod.ProvisionerDependentBodies(v) 22 | bs.Blocks["removed"].Body.Blocks["provisioner"].Body.Blocks["connection"].DependentBody = v1_3_mod.ConnectionDependentBodies(v) 23 | bs.Blocks["removed"].Body.Blocks["provisioner"].Body.Attributes["when"] = &schema.AttributeSchema{ 24 | Constraint: schema.OneOf{ 25 | schema.Keyword{ 26 | Keyword: "destroy", 27 | Description: lang.Markdown("Run the provisioner when the resource is destroyed"), 28 | }, 29 | }, 30 | IsOptional: true, 31 | Description: lang.Markdown("When to run the provisioner - `removed` resources can only be destroyed."), 32 | } 33 | 34 | bs.Blocks["removed"].Body.Blocks["connection"] = v012_mod.ConnectionBlock(v) 35 | bs.Blocks["removed"].Body.Blocks["connection"].DependentBody = v1_3_mod.ConnectionDependentBodies(v) 36 | 37 | return bs 38 | } 39 | -------------------------------------------------------------------------------- /internal/schema/backends/artifactory.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package backends 5 | 6 | import ( 7 | "github.com/hashicorp/go-version" 8 | "github.com/hashicorp/hcl-lang/lang" 9 | "github.com/hashicorp/hcl-lang/schema" 10 | "github.com/zclconf/go-cty/cty" 11 | ) 12 | 13 | func artifactoryBackend(v *version.Version) *schema.BodySchema { 14 | // https://github.com/hashicorp/terraform/blob/v0.12.0/backend/remote-state/artifactory/backend.go 15 | // https://github.com/hashicorp/terraform/blob/v1.0.0/internal/backend/remote-state/artifactory/backend.go 16 | // Docs: 17 | // https://github.com/hashicorp/terraform/blob/v1.0.0/website/docs/language/settings/backends/artifactory.html.md 18 | docsUrl := "https://www.terraform.io/docs/language/settings/backends/artifactory.html" 19 | return &schema.BodySchema{ 20 | Description: lang.Markdown("Artifactory"), 21 | HoverURL: docsUrl, 22 | DocsLink: &schema.DocsLink{ 23 | URL: docsUrl, 24 | }, 25 | Attributes: map[string]*schema.AttributeSchema{ 26 | "username": { 27 | Constraint: schema.LiteralType{Type: cty.String}, 28 | IsRequired: true, 29 | Description: lang.Markdown("Username"), 30 | }, 31 | "password": { 32 | Constraint: schema.LiteralType{Type: cty.String}, 33 | IsRequired: true, 34 | Description: lang.Markdown("Password"), 35 | }, 36 | "url": { 37 | Constraint: schema.LiteralType{Type: cty.String}, 38 | IsRequired: true, 39 | Description: lang.Markdown("Artfactory base URL (i.e. URL without repo and subpath)"), 40 | }, 41 | "repo": { 42 | Constraint: schema.LiteralType{Type: cty.String}, 43 | IsRequired: true, 44 | Description: lang.Markdown("The repository name"), 45 | }, 46 | "subpath": { 47 | Constraint: schema.LiteralType{Type: cty.String}, 48 | IsRequired: true, 49 | Description: lang.Markdown("Path within the repository"), 50 | }, 51 | }, 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /internal/schema/backends/etcdv2.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package backends 5 | 6 | import ( 7 | "github.com/hashicorp/go-version" 8 | "github.com/hashicorp/hcl-lang/lang" 9 | "github.com/hashicorp/hcl-lang/schema" 10 | "github.com/zclconf/go-cty/cty" 11 | ) 12 | 13 | func etcdv2Backend(v *version.Version) *schema.BodySchema { 14 | // https://github.com/hashicorp/terraform/blob/v0.12.0/backend/remote-state/etcdv2/backend.go 15 | // https://github.com/hashicorp/terraform/blob/v1.0.0/internal/backend/remote-state/etcdv2/backend.go 16 | docsUrl := "https://www.terraform.io/docs/language/settings/backends/etcd.html" 17 | bodySchema := &schema.BodySchema{ 18 | Description: lang.Markdown("etcd v2.x"), 19 | HoverURL: docsUrl, 20 | DocsLink: &schema.DocsLink{ 21 | URL: docsUrl, 22 | }, 23 | Attributes: map[string]*schema.AttributeSchema{ 24 | "path": { 25 | Constraint: schema.LiteralType{Type: cty.String}, 26 | IsRequired: true, 27 | Description: lang.Markdown("The path where to store the state"), 28 | }, 29 | "endpoints": { 30 | Constraint: schema.LiteralType{Type: cty.String}, 31 | IsRequired: true, 32 | Description: lang.Markdown("A space-separated list of the etcd endpoints"), 33 | }, 34 | "username": { 35 | Constraint: schema.LiteralType{Type: cty.String}, 36 | IsOptional: true, 37 | Description: lang.Markdown("Username"), 38 | }, 39 | "password": { 40 | Constraint: schema.LiteralType{Type: cty.String}, 41 | IsOptional: true, 42 | Description: lang.Markdown("Password"), 43 | }, 44 | }, 45 | } 46 | 47 | return bodySchema 48 | } 49 | -------------------------------------------------------------------------------- /internal/schema/backends/etcdv3.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package backends 5 | 6 | import ( 7 | "github.com/hashicorp/go-version" 8 | "github.com/hashicorp/hcl-lang/lang" 9 | "github.com/hashicorp/hcl-lang/schema" 10 | "github.com/zclconf/go-cty/cty" 11 | ) 12 | 13 | func etcdv3Backend(v *version.Version) *schema.BodySchema { 14 | // https://github.com/hashicorp/terraform/blob/v0.12.0/backend/remote-state/etcdv3/backend.go 15 | // https://github.com/hashicorp/terraform/blob/v1.0.0/internal/backend/remote-state/etcdv3/backend.go 16 | docsUrl := "https://www.terraform.io/docs/language/settings/backends/etcdv3.html" 17 | bodySchema := &schema.BodySchema{ 18 | Description: lang.Markdown("etcd v3"), 19 | HoverURL: docsUrl, 20 | DocsLink: &schema.DocsLink{ 21 | URL: docsUrl, 22 | }, 23 | Attributes: map[string]*schema.AttributeSchema{ 24 | "endpoints": { 25 | Constraint: schema.List{ 26 | Elem: schema.LiteralType{Type: cty.String}, 27 | MinItems: 1, 28 | }, 29 | IsRequired: true, 30 | Description: lang.Markdown("Endpoints for the etcd cluster."), 31 | }, 32 | 33 | "username": { 34 | Constraint: schema.LiteralType{Type: cty.String}, 35 | IsOptional: true, 36 | Description: lang.Markdown("Username used to connect to the etcd cluster."), 37 | }, 38 | 39 | "password": { 40 | Constraint: schema.LiteralType{Type: cty.String}, 41 | IsOptional: true, 42 | Description: lang.Markdown("Password used to connect to the etcd cluster."), 43 | }, 44 | 45 | "prefix": { 46 | Constraint: schema.LiteralType{Type: cty.String}, 47 | IsOptional: true, 48 | Description: lang.Markdown("An optional prefix to be added to keys when to storing state in etcd."), 49 | }, 50 | 51 | "lock": { 52 | Constraint: schema.LiteralType{Type: cty.Bool}, 53 | IsOptional: true, 54 | Description: lang.Markdown("Whether to lock state access."), 55 | }, 56 | 57 | "cacert_path": { 58 | Constraint: schema.LiteralType{Type: cty.String}, 59 | IsOptional: true, 60 | Description: lang.Markdown("The path to a PEM-encoded CA bundle with which to verify certificates of TLS-enabled etcd servers."), 61 | }, 62 | 63 | "cert_path": { 64 | Constraint: schema.LiteralType{Type: cty.String}, 65 | IsOptional: true, 66 | Description: lang.Markdown("The path to a PEM-encoded certificate to provide to etcd for secure client identification."), 67 | }, 68 | 69 | "key_path": { 70 | Constraint: schema.LiteralType{Type: cty.String}, 71 | IsOptional: true, 72 | Description: lang.Markdown("The path to a PEM-encoded key to provide to etcd for secure client identification."), 73 | }, 74 | }, 75 | } 76 | 77 | return bodySchema 78 | } 79 | -------------------------------------------------------------------------------- /internal/schema/backends/local.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package backends 5 | 6 | import ( 7 | "github.com/hashicorp/go-version" 8 | "github.com/hashicorp/hcl-lang/lang" 9 | "github.com/hashicorp/hcl-lang/schema" 10 | "github.com/zclconf/go-cty/cty" 11 | ) 12 | 13 | func localBackend(v *version.Version) *schema.BodySchema { 14 | docsUrl := "https://www.terraform.io/docs/language/settings/backends/local.html" 15 | return &schema.BodySchema{ 16 | Description: lang.Markdown("Local (filesystem) backend, locks state using system APIs"), 17 | HoverURL: docsUrl, 18 | DocsLink: &schema.DocsLink{ 19 | URL: docsUrl, 20 | }, 21 | Attributes: map[string]*schema.AttributeSchema{ 22 | "path": { 23 | Constraint: schema.LiteralType{Type: cty.String}, 24 | Description: lang.Markdown("The path to the tfstate file. This defaults to `terraform.tfstate` relative to the root module."), 25 | IsOptional: true, 26 | }, 27 | "workspace_dir": { 28 | Constraint: schema.LiteralType{Type: cty.String}, 29 | Description: lang.Markdown("The path to non-default workspaces."), 30 | IsOptional: true, 31 | }, 32 | }, 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /internal/schema/backends/object_expression.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package backends 5 | 6 | import "github.com/hashicorp/hcl-lang/schema" 7 | 8 | func objectConstraintFromBodySchema(bs *schema.BodySchema) schema.Object { 9 | if bs == nil { 10 | return schema.Object{} 11 | } 12 | 13 | oe := schema.Object{ 14 | Description: bs.Description, 15 | Attributes: bs.Attributes, 16 | } 17 | 18 | for bType, block := range bs.Blocks { 19 | oe.Attributes[bType] = &schema.AttributeSchema{ 20 | Description: block.Description, 21 | IsDeprecated: block.IsDeprecated, 22 | } 23 | 24 | if block.MinItems > 0 { 25 | oe.Attributes[bType].IsRequired = true 26 | } else { 27 | oe.Attributes[bType].IsOptional = true 28 | } 29 | 30 | switch block.Type { 31 | case schema.BlockTypeObject: 32 | oe.Attributes[bType].Constraint = objectConstraintFromBodySchema(block.Body) 33 | case schema.BlockTypeList: 34 | oe.Attributes[bType].Constraint = schema.List{ 35 | Elem: objectConstraintFromBodySchema(block.Body), 36 | MinItems: block.MinItems, 37 | MaxItems: block.MaxItems, 38 | } 39 | case schema.BlockTypeSet: 40 | oe.Attributes[bType].Constraint = schema.Set{ 41 | Elem: objectConstraintFromBodySchema(block.Body), 42 | MinItems: block.MinItems, 43 | MaxItems: block.MaxItems, 44 | } 45 | case schema.BlockTypeMap: 46 | oe.Attributes[bType].Constraint = schema.Map{ 47 | Elem: objectConstraintFromBodySchema(block.Body), 48 | MinItems: block.MinItems, 49 | MaxItems: block.MaxItems, 50 | } 51 | } 52 | } 53 | 54 | return oe 55 | } 56 | -------------------------------------------------------------------------------- /internal/schema/backends/pg.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package backends 5 | 6 | import ( 7 | "github.com/hashicorp/go-version" 8 | "github.com/hashicorp/hcl-lang/lang" 9 | "github.com/hashicorp/hcl-lang/schema" 10 | "github.com/zclconf/go-cty/cty" 11 | ) 12 | 13 | func pgBackend(v *version.Version) *schema.BodySchema { 14 | // https://github.com/hashicorp/terraform/blob/v0.12.0/backend/remote-state/pg/backend.go 15 | docsUrl := "https://www.terraform.io/docs/language/settings/backends/pg.html" 16 | bodySchema := &schema.BodySchema{ 17 | Description: lang.Markdown("PostgreSQL (v10+)"), 18 | HoverURL: docsUrl, 19 | DocsLink: &schema.DocsLink{ 20 | URL: docsUrl, 21 | }, 22 | Attributes: map[string]*schema.AttributeSchema{ 23 | "conn_str": { 24 | Constraint: schema.LiteralType{Type: cty.String}, 25 | IsRequired: true, 26 | Description: lang.Markdown("Postgres connection string; a `postgres://` URL"), 27 | }, 28 | 29 | "schema_name": { 30 | Constraint: schema.LiteralType{Type: cty.String}, 31 | IsOptional: true, 32 | Description: lang.Markdown("Name of the automatically managed Postgres schema to store state"), 33 | }, 34 | }, 35 | } 36 | 37 | if v.GreaterThanOrEqual(v0_12_8) { 38 | // https://github.com/hashicorp/terraform/commit/be5280e4 39 | bodySchema.Attributes["skip_schema_creation"] = &schema.AttributeSchema{ 40 | Constraint: schema.LiteralType{Type: cty.Bool}, 41 | IsOptional: true, 42 | Description: lang.Markdown("If set to `true`, Terraform won't try to create the Postgres schema"), 43 | } 44 | } 45 | 46 | if v.GreaterThanOrEqual(v0_14_0) { 47 | // https://github.com/hashicorp/terraform/commit/12a0a21c 48 | bodySchema.Attributes["skip_table_creation"] = &schema.AttributeSchema{ 49 | Constraint: schema.LiteralType{Type: cty.Bool}, 50 | IsOptional: true, 51 | Description: lang.Markdown("If set to `true`, Terraform won't try to create the Postgres table"), 52 | } 53 | 54 | bodySchema.Attributes["skip_index_creation"] = &schema.AttributeSchema{ 55 | Constraint: schema.LiteralType{Type: cty.Bool}, 56 | IsOptional: true, 57 | Description: lang.Markdown("If set to `true`, Terraform won't try to create the Postgres index"), 58 | } 59 | } 60 | 61 | return bodySchema 62 | } 63 | -------------------------------------------------------------------------------- /internal/schema/backends/remote.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package backends 5 | 6 | import ( 7 | "github.com/hashicorp/go-version" 8 | "github.com/hashicorp/hcl-lang/lang" 9 | "github.com/hashicorp/hcl-lang/schema" 10 | "github.com/zclconf/go-cty/cty" 11 | ) 12 | 13 | func remoteBackend(v *version.Version) *schema.BodySchema { 14 | docsUrl := "https://www.terraform.io/docs/language/settings/backends/remote.html" 15 | return &schema.BodySchema{ 16 | Description: lang.Markdown("Remote backend to store state and run operations in HCP Terraform."), 17 | HoverURL: docsUrl, 18 | DocsLink: &schema.DocsLink{ 19 | URL: docsUrl, 20 | }, 21 | Attributes: map[string]*schema.AttributeSchema{ 22 | "hostname": { 23 | Constraint: schema.LiteralType{Type: cty.String}, 24 | IsOptional: true, 25 | Description: lang.Markdown("The remote backend hostname to connect to (defaults to `app.terraform.io`)."), 26 | }, 27 | "organization": { 28 | Constraint: schema.LiteralType{Type: cty.String}, 29 | IsRequired: true, 30 | Description: lang.Markdown("The name of the organization containing the targeted workspace(s)."), 31 | }, 32 | "token": { 33 | Constraint: schema.LiteralType{Type: cty.String}, 34 | IsOptional: true, 35 | Description: lang.Markdown("The token used to authenticate with the remote backend. If credentials for the " + 36 | "host are configured in the CLI Config File, then those will be used instead."), 37 | }, 38 | }, 39 | Blocks: map[string]*schema.BlockSchema{ 40 | "workspaces": { 41 | Body: &schema.BodySchema{ 42 | Attributes: map[string]*schema.AttributeSchema{ 43 | "name": { 44 | Constraint: schema.LiteralType{Type: cty.String}, 45 | IsOptional: true, 46 | Description: lang.Markdown("A workspace name used to map the default workspace to a named remote workspace. " + 47 | "When configured only the default workspace can be used. This option conflicts " + 48 | "with `prefix`"), 49 | }, 50 | "prefix": { 51 | Constraint: schema.LiteralType{Type: cty.String}, 52 | IsOptional: true, 53 | Description: lang.Markdown("A prefix used to filter workspaces using a single configuration. New workspaces " + 54 | "will automatically be prefixed with this prefix. If omitted only the default " + 55 | "workspace can be used. This option conflicts with `name`"), 56 | }, 57 | }, 58 | }, 59 | Type: schema.BlockTypeObject, 60 | }, 61 | }, 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /internal/schema/refscope/scopes.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package refscope 5 | 6 | import ( 7 | "github.com/hashicorp/hcl-lang/lang" 8 | ) 9 | 10 | var ( 11 | BuiltinScope = lang.ScopeId("builtin") 12 | DataScope = lang.ScopeId("data") 13 | LocalScope = lang.ScopeId("local") 14 | ModuleScope = lang.ScopeId("module") 15 | OutputScope = lang.ScopeId("output") 16 | ProviderScope = lang.ScopeId("provider") 17 | ResourceScope = lang.ScopeId("resource") 18 | EphemeralScope = lang.ScopeId("ephemeral") 19 | VariableScope = lang.ScopeId("variable") 20 | 21 | ComponentScope = lang.ScopeId("component") 22 | IdentityTokenScope = lang.ScopeId("identity_token") 23 | StoreScope = lang.ScopeId("store") 24 | OrchestrateContext = lang.ScopeId("orchestrate_context") 25 | ) 26 | -------------------------------------------------------------------------------- /internal/schema/stacks/1.9/deployment_block.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/hcl-lang/lang" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | "github.com/hashicorp/terraform-schema/internal/schema/tokmod" 10 | "github.com/zclconf/go-cty/cty" 11 | ) 12 | 13 | func deploymentBlockSchema() *schema.BlockSchema { 14 | return &schema.BlockSchema{ 15 | Description: lang.Markdown("Deployment"), 16 | Labels: []*schema.LabelSchema{ 17 | { 18 | Name: "name", 19 | SemanticTokenModifiers: lang.SemanticTokenModifiers{tokmod.Name, lang.TokenModifierDependent}, 20 | Description: lang.PlainText("Deployment Name"), 21 | IsDepKey: true, 22 | Completable: true, 23 | }, 24 | }, 25 | Body: &schema.BodySchema{ 26 | Extensions: &schema.BodyExtensions{ 27 | DynamicBlocks: true, 28 | }, 29 | Attributes: map[string]*schema.AttributeSchema{ 30 | "inputs": { 31 | Description: lang.Markdown("A mapping of stack variable names to values for this deployment. The keys of this map must correspond to the names of variables defined for the stack. The values must be valid HCL literals meeting the type constraint of those variables. Values are also expressions, currently with access to identity token references only"), 32 | IsOptional: true, 33 | Constraint: schema.Map{ 34 | Name: "map of variable references", 35 | Elem: schema.AnyExpression{OfType: cty.DynamicPseudoType}, 36 | }, 37 | }, 38 | }, 39 | }, 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /internal/schema/stacks/1.9/identity_token_block.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/hcl-lang/lang" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | "github.com/hashicorp/terraform-schema/internal/schema/refscope" 10 | "github.com/hashicorp/terraform-schema/internal/schema/tokmod" 11 | "github.com/zclconf/go-cty/cty" 12 | ) 13 | 14 | func identityTokenBlockSchema() *schema.BlockSchema { 15 | return &schema.BlockSchema{ 16 | Description: lang.PlainText("An identity token block is a definition of a JSON Web Token (JWT) that will be generated for a given deployment if referenced in the inputs for that deployment block. The block label defines the token name, which must be unique within the stack."), 17 | Address: &schema.BlockAddrSchema{ 18 | Steps: []schema.AddrStep{ 19 | schema.StaticStep{Name: "identity_token"}, 20 | schema.LabelStep{Index: 0}, 21 | }, 22 | FriendlyName: "identity_token", 23 | ScopeId: refscope.IdentityTokenScope, 24 | AsReference: true, 25 | InferBody: true, 26 | BodyAsData: true, 27 | }, 28 | Labels: []*schema.LabelSchema{ 29 | { 30 | Name: "name", 31 | SemanticTokenModifiers: lang.SemanticTokenModifiers{tokmod.Name, lang.TokenModifierDependent}, 32 | Description: lang.PlainText("Identity name"), 33 | IsDepKey: true, 34 | Completable: true, 35 | }, 36 | }, 37 | Body: &schema.BodySchema{ 38 | Attributes: map[string]*schema.AttributeSchema{ 39 | "audience": { 40 | Description: lang.Markdown("The audience(s) that tokens generated with this configuration block will be generated with. Audience(s) are the resource(s)/server(s) that the token is intended for. With an audience claim, the cloud service authorizing the workload can be confident that the token is being presented intentionally to that service"), 41 | IsOptional: true, 42 | Constraint: schema.List{ 43 | // TODO: Is a list correct for this attribute? 44 | Elem: schema.AnyExpression{OfType: cty.String}, 45 | }, 46 | }, 47 | "jwt": { 48 | Description: lang.Markdown("Token that will be generated that you can pass to a given provider's configuration for OIDC/JWT authentication"), 49 | IsComputed: true, 50 | Constraint: schema.AnyExpression{OfType: cty.String}, 51 | }, 52 | "jwt_filename": { 53 | Description: lang.Markdown("Path to the token that will be generated on the filesystem that you can pass to a given provider's configuration for OIDC/JWT authentication"), 54 | IsComputed: true, 55 | Constraint: schema.AnyExpression{OfType: cty.String}, 56 | }, 57 | }, 58 | }, 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /internal/schema/stacks/1.9/locals_block.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/hcl-lang/lang" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | "github.com/hashicorp/terraform-schema/internal/schema/refscope" 10 | "github.com/hashicorp/terraform-schema/internal/schema/tokmod" 11 | "github.com/zclconf/go-cty/cty" 12 | ) 13 | 14 | func localsBlockSchema() *schema.BlockSchema { 15 | return &schema.BlockSchema{ 16 | SemanticTokenModifiers: lang.SemanticTokenModifiers{tokmod.Locals}, 17 | Description: lang.Markdown("Local values assigning names to expressions, so you can use these multiple times without repetition\n" + 18 | "e.g. `service_name = \"forum\"`"), 19 | Body: &schema.BodySchema{ 20 | AnyAttribute: &schema.AttributeSchema{ 21 | Address: &schema.AttributeAddrSchema{ 22 | Steps: []schema.AddrStep{ 23 | schema.StaticStep{Name: "local"}, 24 | schema.AttrNameStep{}, 25 | }, 26 | ScopeId: refscope.LocalScope, 27 | AsExprType: true, 28 | AsReference: true, 29 | }, 30 | Constraint: schema.AnyExpression{OfType: cty.DynamicPseudoType}, 31 | }, 32 | }, 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /internal/schema/stacks/1.9/output_block.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/hcl-lang/lang" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | "github.com/hashicorp/terraform-schema/internal/schema/refscope" 10 | "github.com/hashicorp/terraform-schema/internal/schema/tokmod" 11 | "github.com/zclconf/go-cty/cty" 12 | ) 13 | 14 | func outputBlockSchema() *schema.BlockSchema { 15 | return &schema.BlockSchema{ 16 | SemanticTokenModifiers: lang.SemanticTokenModifiers{tokmod.Output}, 17 | Address: &schema.BlockAddrSchema{ 18 | Steps: []schema.AddrStep{ 19 | schema.StaticStep{Name: "output"}, 20 | schema.LabelStep{Index: 0}, 21 | }, 22 | FriendlyName: "output", 23 | ScopeId: refscope.OutputScope, 24 | AsReference: true, 25 | }, 26 | Labels: []*schema.LabelSchema{ 27 | { 28 | Name: "name", 29 | SemanticTokenModifiers: lang.SemanticTokenModifiers{tokmod.Name}, 30 | Description: lang.PlainText("Output Name"), 31 | }, 32 | }, 33 | Description: lang.PlainText("Output value for consumption by another component or a human interacting via the UI"), 34 | Body: &schema.BodySchema{ 35 | Attributes: map[string]*schema.AttributeSchema{ 36 | "description": { 37 | Constraint: schema.LiteralType{Type: cty.String}, 38 | IsOptional: true, 39 | Description: lang.PlainText("Human-readable description of the output (for documentation and UI)"), 40 | }, 41 | "value": { 42 | Constraint: schema.AnyExpression{OfType: cty.DynamicPseudoType}, 43 | IsRequired: true, 44 | Description: lang.PlainText("Value, typically a reference to an attribute of a resource or a data source"), 45 | }, 46 | "type": { 47 | Constraint: schema.TypeDeclaration{}, 48 | IsRequired: true, 49 | Description: lang.PlainText("Type of the output value"), 50 | }, 51 | "sensitive": { 52 | Constraint: schema.LiteralType{Type: cty.Bool}, 53 | DefaultValue: schema.DefaultValue{Value: cty.False}, 54 | IsOptional: true, 55 | Description: lang.Markdown("Whether the output contains sensitive material and should be hidden in the UI"), 56 | }, 57 | "ephemeral": { 58 | Constraint: schema.LiteralType{Type: cty.Bool}, 59 | IsOptional: true, 60 | Description: lang.PlainText("Whether the value is ephemeral and should not be persisted in the state"), 61 | }, 62 | }, 63 | }, 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /internal/schema/stacks/1.9/provider_block.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/hcl-lang/lang" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | "github.com/hashicorp/terraform-schema/internal/schema/refscope" 10 | "github.com/hashicorp/terraform-schema/internal/schema/tokmod" 11 | ) 12 | 13 | func providerBlockSchema() *schema.BlockSchema { 14 | return &schema.BlockSchema{ 15 | Description: lang.PlainText("A Stack provider block is used to specify a provider configuration"), 16 | SemanticTokenModifiers: lang.SemanticTokenModifiers{tokmod.Provider}, 17 | Address: &schema.BlockAddrSchema{ 18 | Steps: []schema.AddrStep{ 19 | schema.StaticStep{Name: "provider"}, 20 | schema.LabelStep{Index: 0}, 21 | schema.LabelStep{Index: 1}, 22 | }, 23 | FriendlyName: "provider", 24 | ScopeId: refscope.ProviderScope, 25 | AsReference: true, 26 | }, 27 | Labels: []*schema.LabelSchema{ 28 | { 29 | Name: "type", 30 | SemanticTokenModifiers: lang.SemanticTokenModifiers{tokmod.Type, lang.TokenModifierDependent}, 31 | Description: lang.PlainText("Provider Type"), 32 | IsDepKey: true, 33 | Completable: true, 34 | }, 35 | { 36 | Name: "name", 37 | SemanticTokenModifiers: lang.SemanticTokenModifiers{tokmod.Name}, 38 | Description: lang.PlainText("Provider Name"), 39 | }, 40 | }, 41 | Body: &schema.BodySchema{ 42 | Extensions: &schema.BodyExtensions{ 43 | ForEach: true, 44 | }, 45 | Blocks: map[string]*schema.BlockSchema{ 46 | "config": { 47 | Description: lang.Markdown("Provider configuration"), 48 | MaxItems: 1, 49 | }, 50 | }, 51 | }, 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /internal/schema/stacks/1.9/removed_block.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/hcl-lang/lang" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | "github.com/hashicorp/terraform-schema/internal/schema/refscope" 10 | "github.com/zclconf/go-cty/cty" 11 | ) 12 | 13 | func removedBlockSchema() *schema.BlockSchema { 14 | return &schema.BlockSchema{ 15 | Description: lang.Markdown("Declaration to specify what component to remove from the stack"), 16 | Body: &schema.BodySchema{ 17 | Extensions: &schema.BodyExtensions{ 18 | ForEach: true, 19 | }, 20 | Attributes: map[string]*schema.AttributeSchema{ 21 | "from": { 22 | Description: lang.Markdown("Address of the component to be removed"), 23 | IsRequired: true, 24 | Constraint: schema.Reference{OfScopeId: refscope.ComponentScope}, // TODO: this component would not exist in the config anymore, only in state (i.e. the component block has to be removed from the config) 25 | }, 26 | "source": { 27 | Description: lang.Markdown("The Terraform module location to load the Component from: a local directory (e.g. `./modules`), a git repository (e.g. `github.com/acme/infra/core`, `git::https://vcs.acme.com/acme/infra//core`), or a registry module (e.g. `acme-public/coreinfra/aws`, `app.terraform.io/acme/core-infra/aws`)"), 28 | IsRequired: true, 29 | IsDepKey: true, 30 | Constraint: schema.LiteralType{Type: cty.String}, 31 | SemanticTokenModifiers: lang.SemanticTokenModifiers{lang.TokenModifierDependent}, 32 | CompletionHooks: lang.CompletionHooks{ 33 | {Name: "CompleteLocalModuleSources"}, 34 | }, 35 | }, 36 | "version": { 37 | Description: lang.Markdown("Accepts a comma-separated list of version constraints for registry modules"), 38 | IsOptional: true, 39 | Constraint: schema.List{ 40 | Elem: schema.AnyExpression{OfType: cty.String}, // TODO: comma separated list 41 | }, 42 | }, 43 | "providers": { 44 | Description: lang.Markdown(" A mapping of provider names to providers declared in the stack configuration. Providers must be declared in the top level of the stack and passed into each component in the stack. Components cannot configure their own providers"), 45 | IsOptional: true, 46 | Constraint: schema.Map{ 47 | Name: "map of provider references", 48 | Elem: schema.Reference{OfScopeId: refscope.ProviderScope}, 49 | }, 50 | }, 51 | }, 52 | }, 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /internal/schema/stacks/1.9/required_providers_block.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/hcl-lang/lang" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | "github.com/hashicorp/terraform-schema/internal/schema/refscope" 10 | "github.com/zclconf/go-cty/cty" 11 | ) 12 | 13 | func requiredProvidersBlockSchema() *schema.BlockSchema { 14 | return &schema.BlockSchema{ 15 | Description: lang.Markdown("What provider version to use within this configuration and where to source it from"), 16 | Body: &schema.BodySchema{ 17 | AnyAttribute: &schema.AttributeSchema{ 18 | Constraint: schema.OneOf{ 19 | schema.Object{ 20 | Attributes: schema.ObjectAttributes{ 21 | "source": &schema.AttributeSchema{ 22 | Constraint: schema.LiteralType{Type: cty.String}, 23 | IsRequired: true, 24 | Description: lang.Markdown("The global source address for the provider " + 25 | "you intend to use, such as `hashicorp/aws`"), 26 | }, 27 | "version": &schema.AttributeSchema{ 28 | Constraint: schema.LiteralType{Type: cty.String}, 29 | IsOptional: true, 30 | Description: lang.Markdown("Version constraint specifying which subset of " + 31 | "available provider versions the module is compatible with, e.g. `~> 1.0`"), 32 | }, 33 | }, 34 | }, 35 | schema.LiteralType{Type: cty.String}, 36 | }, 37 | Address: &schema.AttributeAddrSchema{ 38 | Steps: []schema.AddrStep{ 39 | schema.AttrNameStep{}, 40 | }, 41 | FriendlyName: "provider", 42 | AsReference: true, 43 | ScopeId: refscope.ProviderScope, 44 | }, 45 | Description: lang.Markdown("Provider source, version constraint"), 46 | }, 47 | }, 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /internal/schema/stacks/1.9/root.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/go-version" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | ) 10 | 11 | // StackSchema returns the static schema for a stack 12 | // configuration (*.tfstack.hcl) file. 13 | func StackSchema(_ *version.Version) *schema.BodySchema { 14 | return &schema.BodySchema{ 15 | Blocks: map[string]*schema.BlockSchema{ 16 | "component": componentBlockSchema(), 17 | "provider": providerBlockSchema(), 18 | "required_providers": requiredProvidersBlockSchema(), 19 | "variable": variableBlockSchema(), 20 | "output": outputBlockSchema(), 21 | "locals": localsBlockSchema(), 22 | "removed": removedBlockSchema(), 23 | }, 24 | } 25 | } 26 | 27 | // DeploymentSchema returns the static schema for a deployment 28 | // configuration (*.tfdeploy.hcl) file. 29 | func DeploymentSchema(_ *version.Version) *schema.BodySchema { 30 | return &schema.BodySchema{ 31 | Blocks: map[string]*schema.BlockSchema{ 32 | "deployment": deploymentBlockSchema(), 33 | "identity_token": identityTokenBlockSchema(), 34 | "orchestrate": orchestrateBlockSchema(), 35 | "store": storeBlockSchema(), 36 | "locals": localsBlockSchema(), 37 | }, 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /internal/schema/stacks/1.9/store_block.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/hcl-lang/lang" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | "github.com/hashicorp/terraform-schema/internal/schema/refscope" 10 | "github.com/hashicorp/terraform-schema/internal/schema/tokmod" 11 | "github.com/zclconf/go-cty/cty" 12 | ) 13 | 14 | func storeBlockSchema() *schema.BlockSchema { 15 | return &schema.BlockSchema{ 16 | Description: lang.PlainText("A store block allows to retrieve credentials at plan and apply time. These credentials can be used as inputs to deployment blocks."), 17 | Address: &schema.BlockAddrSchema{ 18 | Steps: []schema.AddrStep{ 19 | schema.StaticStep{Name: "store"}, 20 | schema.LabelStep{Index: 0}, 21 | schema.LabelStep{Index: 1}, 22 | }, 23 | FriendlyName: "store", 24 | ScopeId: refscope.StoreScope, 25 | AsReference: true, 26 | SupportUnknownNestedRefs: true, 27 | }, 28 | Labels: []*schema.LabelSchema{ 29 | { 30 | Name: "type", 31 | SemanticTokenModifiers: lang.SemanticTokenModifiers{tokmod.Type, lang.TokenModifierDependent}, 32 | Description: lang.PlainText("Store type"), 33 | IsDepKey: true, 34 | Completable: true, 35 | }, 36 | { 37 | Name: "name", 38 | SemanticTokenModifiers: lang.SemanticTokenModifiers{tokmod.Name}, 39 | Description: lang.PlainText("Store name"), 40 | }, 41 | }, 42 | DependentBody: map[schema.SchemaKey]*schema.BodySchema{ 43 | schema.NewSchemaKey(schema.DependencyKeys{ 44 | Labels: []schema.LabelDependent{ 45 | {Index: 0, Value: "tfvars"}, 46 | }, 47 | }): { 48 | Attributes: map[string]*schema.AttributeSchema{ 49 | "path": { 50 | IsRequired: true, 51 | Constraint: schema.LiteralType{Type: cty.String}, 52 | Description: lang.Markdown("The path to the tfvars file."), 53 | }, 54 | }, 55 | }, 56 | schema.NewSchemaKey(schema.DependencyKeys{ 57 | Labels: []schema.LabelDependent{ 58 | {Index: 0, Value: "varset"}, 59 | }, 60 | }): { 61 | Attributes: map[string]*schema.AttributeSchema{ 62 | "id": { 63 | IsRequired: true, 64 | Constraint: schema.LiteralType{Type: cty.String}, 65 | Description: lang.Markdown("The id of the varset. In the form of `varset-QKpocVOC3uQQxVrF`."), 66 | }, 67 | "category": { 68 | IsRequired: true, 69 | Constraint: schema.OneOf{ 70 | schema.LiteralValue{Value: cty.StringVal("terraform")}, 71 | schema.LiteralValue{Value: cty.StringVal("env")}, 72 | }, 73 | Description: lang.Markdown("The category argument specifies whether to use Terraform or environment variables from the variable set."), 74 | }, 75 | }, 76 | }, 77 | }, 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /internal/schema/stacks/1.9/variable_block.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/hcl-lang/lang" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | "github.com/hashicorp/terraform-schema/internal/schema/refscope" 10 | "github.com/hashicorp/terraform-schema/internal/schema/tokmod" 11 | "github.com/zclconf/go-cty/cty" 12 | ) 13 | 14 | func variableBlockSchema() *schema.BlockSchema { 15 | return &schema.BlockSchema{ 16 | Address: &schema.BlockAddrSchema{ 17 | Steps: []schema.AddrStep{ 18 | schema.StaticStep{Name: "var"}, 19 | schema.LabelStep{Index: 0}, 20 | }, 21 | FriendlyName: "variable", 22 | ScopeId: refscope.VariableScope, 23 | AsReference: true, 24 | AsTypeOf: &schema.BlockAsTypeOf{ 25 | AttributeExpr: "type", 26 | }, 27 | }, 28 | SemanticTokenModifiers: lang.SemanticTokenModifiers{tokmod.Variable}, 29 | Labels: []*schema.LabelSchema{ 30 | { 31 | Name: "name", 32 | SemanticTokenModifiers: lang.SemanticTokenModifiers{tokmod.Name}, 33 | Description: lang.PlainText("Variable Name"), 34 | }, 35 | }, 36 | Description: lang.Markdown("Stack variable allowing users to customize aspects of the stack that differ between deployments" + 37 | "(e.g. different instance sizes, regions, etc.)"), 38 | Body: &schema.BodySchema{ 39 | Attributes: map[string]*schema.AttributeSchema{ 40 | "description": { 41 | Constraint: schema.LiteralType{Type: cty.String}, 42 | IsOptional: true, 43 | Description: lang.Markdown("Description to document the purpose of the variable and what value is expected"), 44 | }, 45 | "sensitive": { 46 | Constraint: schema.LiteralType{Type: cty.Bool}, 47 | DefaultValue: schema.DefaultValue{Value: cty.False}, 48 | IsOptional: true, 49 | Description: lang.Markdown("Whether the variable contains sensitive material and should be hidden in the UI"), 50 | }, 51 | "ephemeral": { 52 | Constraint: schema.LiteralType{Type: cty.Bool}, 53 | IsOptional: true, 54 | Description: lang.PlainText("Whether the value is ephemeral and should not be persisted in the state"), 55 | }, 56 | "type": { 57 | Constraint: schema.TypeDeclaration{}, 58 | IsOptional: true, 59 | Description: lang.Markdown("Type constraint restricting the type of value to accept, e.g. `string` or `list(string)`"), 60 | }, 61 | "default": { 62 | Constraint: schema.TypeDeclaration{}, 63 | IsOptional: true, 64 | Description: lang.Markdown("A literal expression of an appropriate type for the variable"), 65 | }, 66 | }, 67 | }, 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /internal/schema/tests/1.6/provider_block.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/hcl-lang/lang" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | "github.com/hashicorp/terraform-schema/internal/schema/refscope" 10 | "github.com/hashicorp/terraform-schema/internal/schema/tokmod" 11 | "github.com/zclconf/go-cty/cty" 12 | ) 13 | 14 | func providerBlockSchema() *schema.BlockSchema { 15 | return &schema.BlockSchema{ 16 | Address: &schema.BlockAddrSchema{ 17 | Steps: []schema.AddrStep{ 18 | schema.LabelStep{Index: 0}, 19 | schema.AttrValueStep{Name: "alias", IsOptional: true}, 20 | }, 21 | FriendlyName: "provider", 22 | ScopeId: refscope.ProviderScope, 23 | AsReference: true, 24 | }, 25 | SemanticTokenModifiers: lang.SemanticTokenModifiers{tokmod.Provider}, 26 | Labels: []*schema.LabelSchema{ 27 | { 28 | Name: "name", 29 | SemanticTokenModifiers: lang.SemanticTokenModifiers{tokmod.Name, lang.TokenModifierDependent}, 30 | Description: lang.PlainText("Provider Name"), 31 | IsDepKey: true, 32 | Completable: true, 33 | }, 34 | }, 35 | Description: lang.PlainText("A provider block is used to specify a provider configuration"), 36 | Body: &schema.BodySchema{ 37 | Extensions: &schema.BodyExtensions{ 38 | DynamicBlocks: true, 39 | }, 40 | Attributes: map[string]*schema.AttributeSchema{ 41 | "alias": { 42 | Constraint: schema.LiteralType{Type: cty.String}, 43 | IsOptional: true, 44 | Description: lang.Markdown("Alias for using the same provider with different configurations for different resources, e.g. `eu-west`"), 45 | }, 46 | "version": { 47 | Constraint: schema.LiteralType{Type: cty.String}, 48 | IsOptional: true, 49 | IsDeprecated: true, 50 | Description: lang.Markdown("Specifies a version constraint for the provider. e.g. `~> 1.0`.\n" + 51 | "**DEPRECATED:** Use `required_providers` block to manage provider version instead."), 52 | }, 53 | }, 54 | }, 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /internal/schema/tests/1.6/root.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/go-version" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | ) 10 | 11 | // TestSchema returns the static schema for a test 12 | // configuration (*.tftest.hcl) file. 13 | func TestSchema(_ *version.Version) *schema.BodySchema { 14 | return &schema.BodySchema{ 15 | Blocks: map[string]*schema.BlockSchema{ 16 | "run": runBlockSchema(), 17 | "provider": providerBlockSchema(), 18 | "variables": variablesBlockSchema(), 19 | }, 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /internal/schema/tests/1.6/variables_block.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/hcl-lang/lang" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | "github.com/hashicorp/terraform-schema/internal/schema/refscope" 10 | "github.com/hashicorp/terraform-schema/internal/schema/tokmod" 11 | "github.com/zclconf/go-cty/cty" 12 | ) 13 | 14 | func variablesBlockSchema() *schema.BlockSchema { 15 | return &schema.BlockSchema{ 16 | MaxItems: 1, 17 | SemanticTokenModifiers: lang.SemanticTokenModifiers{tokmod.Variables}, 18 | Description: lang.Markdown("Provides values for input variables within your configuration directly from your test files"), 19 | Body: &schema.BodySchema{ 20 | AnyAttribute: &schema.AttributeSchema{ 21 | Address: &schema.AttributeAddrSchema{ 22 | Steps: []schema.AddrStep{ 23 | schema.StaticStep{Name: "var"}, 24 | schema.AttrNameStep{}, 25 | }, 26 | ScopeId: refscope.VariableScope, 27 | AsExprType: true, 28 | AsReference: true, 29 | }, 30 | Constraint: schema.AnyExpression{OfType: cty.DynamicPseudoType}, 31 | }, 32 | }, 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /internal/schema/tests/1.7/mock_data_block.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/hcl-lang/lang" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | "github.com/hashicorp/terraform-schema/internal/schema/tokmod" 10 | ) 11 | 12 | func mockDataBlockSchema() *schema.BlockSchema { 13 | return &schema.BlockSchema{ 14 | Labels: []*schema.LabelSchema{ 15 | { 16 | Name: "type", 17 | Description: lang.PlainText("Data Source Type"), 18 | SemanticTokenModifiers: lang.SemanticTokenModifiers{tokmod.Type, lang.TokenModifierDependent}, 19 | IsDepKey: true, 20 | Completable: true, 21 | }, 22 | }, 23 | Description: lang.PlainText("Allows to specify specific values for targeted data sources"), 24 | Body: &schema.BodySchema{ 25 | Attributes: map[string]*schema.AttributeSchema{ 26 | "defaults": { 27 | Constraint: schema.Object{ 28 | Attributes: schema.ObjectAttributes{}, 29 | }, 30 | IsOptional: true, 31 | Description: lang.Markdown("Specify the values that should be returned for specific attributes"), 32 | }, 33 | }, 34 | }, 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /internal/schema/tests/1.7/mock_provider_block.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/hcl-lang/lang" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | "github.com/hashicorp/terraform-schema/internal/schema/tokmod" 10 | "github.com/zclconf/go-cty/cty" 11 | ) 12 | 13 | func mockProviderBlockSchema() *schema.BlockSchema { 14 | return &schema.BlockSchema{ 15 | SemanticTokenModifiers: lang.SemanticTokenModifiers{tokmod.Provider}, 16 | Labels: []*schema.LabelSchema{ 17 | { 18 | Name: "name", 19 | SemanticTokenModifiers: lang.SemanticTokenModifiers{tokmod.Name, lang.TokenModifierDependent}, 20 | Description: lang.PlainText("Provider Name"), 21 | IsDepKey: true, 22 | Completable: true, 23 | }, 24 | }, 25 | Description: lang.PlainText("In Terraform tests, you can mock a provider with the mock_provider block. Mock providers return the same schema as the original provider and you can pass the mocked provider to your tests in place of the matching provider. All resources and data sources retrieved by a mock provider will set the relevant values from the configuration, and generate fake data for any computed attributes."), 26 | Body: &schema.BodySchema{ 27 | Attributes: map[string]*schema.AttributeSchema{ 28 | "alias": { 29 | Constraint: schema.LiteralType{Type: cty.String}, 30 | IsOptional: true, 31 | Description: lang.Markdown("Alias for using the same provider with different configurations for different resources, e.g. `mock`"), 32 | }, 33 | "source": { 34 | Constraint: schema.LiteralType{Type: cty.String}, 35 | IsOptional: true, 36 | Description: lang.Markdown("Path to a directory that includes dedicated mock data files (*.tfmock.hcl). These can be used to share mock provider data between tests. You can combine the source attribute with directly nested mock_resource and mock_data blocks. If the source location and a directly nested block describe the same resource or data source then the directly nested block takes precedence."), 37 | }, 38 | }, 39 | Blocks: map[string]*schema.BlockSchema{ 40 | "mock_resource": mockResourceBlockSchema(), 41 | "mock_data": mockDataBlockSchema(), 42 | "override_resource": overrideResourceBlockSchema(), 43 | "override_data": overrideDataBlockSchema(), 44 | }, 45 | }, 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /internal/schema/tests/1.7/mock_resource_block.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/hcl-lang/lang" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | "github.com/hashicorp/terraform-schema/internal/schema/tokmod" 10 | ) 11 | 12 | func mockResourceBlockSchema() *schema.BlockSchema { 13 | return &schema.BlockSchema{ 14 | Labels: []*schema.LabelSchema{ 15 | { 16 | Name: "type", 17 | SemanticTokenModifiers: lang.SemanticTokenModifiers{tokmod.Type, lang.TokenModifierDependent}, 18 | Description: lang.PlainText("Resource Type"), 19 | IsDepKey: true, 20 | Completable: true, 21 | }, 22 | }, 23 | Description: lang.PlainText("Allows to specify specific values for targeted resources"), 24 | Body: &schema.BodySchema{ 25 | Attributes: map[string]*schema.AttributeSchema{ 26 | "defaults": { 27 | Constraint: schema.Object{ 28 | Attributes: schema.ObjectAttributes{}, 29 | }, 30 | IsOptional: true, 31 | Description: lang.Markdown("Specify the values that should be returned for specific attributes"), 32 | }, 33 | }, 34 | }, 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /internal/schema/tests/1.7/override_data_block.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/hcl-lang/lang" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | "github.com/hashicorp/terraform-schema/internal/schema/refscope" 10 | ) 11 | 12 | func overrideDataBlockSchema() *schema.BlockSchema { 13 | return &schema.BlockSchema{ 14 | Description: lang.PlainText("Allows overriding the values of a specific data source in the targeted configuration"), 15 | Body: &schema.BodySchema{ 16 | Attributes: map[string]*schema.AttributeSchema{ 17 | "target": { 18 | Constraint: schema.Reference{ 19 | OfScopeId: refscope.DataScope, 20 | }, 21 | IsRequired: true, 22 | Description: lang.Markdown("Reference to the data source to override"), 23 | }, 24 | "values": { 25 | Constraint: schema.Object{ 26 | Attributes: schema.ObjectAttributes{}, 27 | }, 28 | IsOptional: true, 29 | Description: lang.Markdown("Specify the values that should be returned for specific attributes"), 30 | }, 31 | }, 32 | }, 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /internal/schema/tests/1.7/override_module_block.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/hcl-lang/lang" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | "github.com/hashicorp/terraform-schema/internal/schema/refscope" 10 | ) 11 | 12 | func overrideModuleBlockSchema() *schema.BlockSchema { 13 | return &schema.BlockSchema{ 14 | Description: lang.PlainText("Allows overriding the outputs of a specific module in the targeted configuration"), 15 | Body: &schema.BodySchema{ 16 | Attributes: map[string]*schema.AttributeSchema{ 17 | "target": { 18 | Constraint: schema.Reference{ 19 | OfScopeId: refscope.ModuleScope, 20 | }, 21 | IsRequired: true, 22 | Description: lang.Markdown("Reference to the module to override"), 23 | }, 24 | "outputs": { 25 | Constraint: schema.Object{ 26 | Attributes: schema.ObjectAttributes{}, 27 | }, 28 | IsOptional: true, 29 | Description: lang.Markdown("Specify the values that should be returned for specific attributes"), 30 | }, 31 | }, 32 | }, 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /internal/schema/tests/1.7/override_resource_block.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/hcl-lang/lang" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | "github.com/hashicorp/terraform-schema/internal/schema/refscope" 10 | ) 11 | 12 | func overrideResourceBlockSchema() *schema.BlockSchema { 13 | return &schema.BlockSchema{ 14 | Description: lang.PlainText("Allows overriding the values of a specific resource in the targeted configuration"), 15 | Body: &schema.BodySchema{ 16 | Attributes: map[string]*schema.AttributeSchema{ 17 | "target": { 18 | Constraint: schema.Reference{ 19 | OfScopeId: refscope.ResourceScope, 20 | }, 21 | IsRequired: true, 22 | Description: lang.Markdown("Reference to the resource to override"), 23 | }, 24 | "values": { 25 | Constraint: schema.Object{ 26 | Attributes: schema.ObjectAttributes{}, 27 | }, 28 | IsOptional: true, 29 | Description: lang.Markdown("Specify the values that should be returned for specific attributes"), 30 | }, 31 | }, 32 | }, 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /internal/schema/tests/1.7/root.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/go-version" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | 10 | v1_6_test "github.com/hashicorp/terraform-schema/internal/schema/tests/1.6" 11 | ) 12 | 13 | // TestSchema returns the static schema for a test 14 | // configuration (*.tftest.hcl) file. 15 | func TestSchema(v *version.Version) *schema.BodySchema { 16 | bs := v1_6_test.TestSchema(v) 17 | 18 | bs.Blocks["mock_provider"] = mockProviderBlockSchema() 19 | bs.Blocks["override_resource"] = overrideResourceBlockSchema() 20 | bs.Blocks["override_data"] = overrideDataBlockSchema() 21 | bs.Blocks["override_module"] = overrideModuleBlockSchema() 22 | 23 | bs.Blocks["run"].Body.Blocks["override_resource"] = overrideResourceBlockSchema() 24 | bs.Blocks["run"].Body.Blocks["override_data"] = overrideDataBlockSchema() 25 | bs.Blocks["run"].Body.Blocks["override_module"] = overrideModuleBlockSchema() 26 | 27 | return bs 28 | } 29 | 30 | // MockSchema returns the static schema for a mock 31 | // configuration (*.tfmock.hcl) file. 32 | func MockSchema(_ *version.Version) *schema.BodySchema { 33 | return &schema.BodySchema{ 34 | Blocks: map[string]*schema.BlockSchema{ 35 | "mock_resource": mockResourceBlockSchema(), 36 | "mock_data": mockDataBlockSchema(), 37 | "override_resource": overrideResourceBlockSchema(), 38 | "override_data": overrideDataBlockSchema(), 39 | }, 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /internal/schema/tests/1.9/root.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/go-version" 8 | "github.com/hashicorp/hcl-lang/lang" 9 | "github.com/hashicorp/hcl-lang/schema" 10 | "github.com/zclconf/go-cty/cty" 11 | 12 | v1_7_test "github.com/hashicorp/terraform-schema/internal/schema/tests/1.7" 13 | ) 14 | 15 | // TestSchema returns the static schema for a test 16 | // configuration (*.tftest.hcl) file. 17 | func TestSchema(v *version.Version) *schema.BodySchema { 18 | bs := v1_7_test.TestSchema(v) 19 | 20 | // Removes the version attribute 21 | bs.Blocks["provider"].Body.Attributes = map[string]*schema.AttributeSchema{ 22 | "alias": { 23 | Constraint: schema.LiteralType{Type: cty.String}, 24 | IsOptional: true, 25 | Description: lang.Markdown("Alias for using the same provider with different configurations for different resources, e.g. `eu-west`"), 26 | }, 27 | } 28 | 29 | return bs 30 | } 31 | -------------------------------------------------------------------------------- /internal/schema/tokmod/token_modifier.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package tokmod 5 | 6 | import ( 7 | "github.com/hashicorp/hcl-lang/lang" 8 | ) 9 | 10 | var ( 11 | Data = lang.SemanticTokenModifier("terraform-data") 12 | Locals = lang.SemanticTokenModifier("terraform-locals") 13 | Module = lang.SemanticTokenModifier("terraform-module") 14 | Output = lang.SemanticTokenModifier("terraform-output") 15 | Provider = lang.SemanticTokenModifier("terraform-provider") 16 | Resource = lang.SemanticTokenModifier("terraform-resource") 17 | Ephemeral = lang.SemanticTokenModifier("terraform-ephemeral") 18 | Provisioner = lang.SemanticTokenModifier("terraform-provisioner") 19 | Connection = lang.SemanticTokenModifier("terraform-connection") 20 | Variable = lang.SemanticTokenModifier("terraform-variable") 21 | Terraform = lang.SemanticTokenModifier("terraform-terraform") 22 | Backend = lang.SemanticTokenModifier("terraform-backend") 23 | Name = lang.SemanticTokenModifier("terraform-name") 24 | Type = lang.SemanticTokenModifier("terraform-type") 25 | RequiredProviders = lang.SemanticTokenModifier("terraform-requiredProviders") 26 | Run = lang.SemanticTokenModifier("terraform-run") 27 | Variables = lang.SemanticTokenModifier("terraform-variables") 28 | ) 29 | 30 | var SupportedModifiers = []lang.SemanticTokenModifier{ 31 | Backend, 32 | Connection, 33 | Data, 34 | Locals, 35 | Module, 36 | Name, 37 | Output, 38 | Provider, 39 | Provisioner, 40 | RequiredProviders, 41 | Resource, 42 | Run, 43 | Terraform, 44 | Type, 45 | Variable, 46 | Variables, 47 | } 48 | -------------------------------------------------------------------------------- /internal/versiongen/gen_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package main 5 | 6 | import ( 7 | "testing" 8 | "time" 9 | 10 | "github.com/google/go-cmp/cmp" 11 | "github.com/hashicorp/go-version" 12 | ) 13 | 14 | func TestGetTerraformReleases(t *testing.T) { 15 | releases, err := GetTerraformReleases() 16 | if err != nil { 17 | t.Fatal(err) 18 | } 19 | 20 | minExpectedLength := 234 21 | if len(releases) < minExpectedLength { 22 | t.Fatalf("expected >= %d releases, %d given", minExpectedLength, len(releases)) 23 | } 24 | 25 | // The oldest release should really be 0.1.0. We're however getting 26 | // releases sorted by dates and those dates were backfilled as part 27 | // of some older data migrations where the original dates were lost. 28 | expectedDate := time.Date(2017, 3, 1, 17, 36, 49, 0, time.UTC) 29 | expectedOldestRelease := release{ 30 | Version: version.Must(version.NewVersion("0.6.4")), 31 | Created: &expectedDate, 32 | } 33 | oldestRelease := releases[len(releases)-1] 34 | if diff := cmp.Diff(expectedOldestRelease, oldestRelease); diff != "" { 35 | t.Fatalf("unexpected oldest release: %s", diff) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /module/meta.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package module 5 | 6 | import ( 7 | "github.com/hashicorp/go-version" 8 | tfaddr "github.com/hashicorp/terraform-registry-address" 9 | "github.com/hashicorp/terraform-schema/backend" 10 | ) 11 | 12 | type Meta struct { 13 | Path string 14 | Filenames []string 15 | 16 | CoreRequirements version.Constraints 17 | Backend *Backend 18 | Cloud *backend.Cloud 19 | ProviderReferences map[ProviderRef]tfaddr.Provider 20 | ProviderRequirements ProviderRequirements 21 | Variables map[string]Variable 22 | Outputs map[string]Output 23 | ModuleCalls map[string]DeclaredModuleCall 24 | } 25 | 26 | type ProviderRequirements map[tfaddr.Provider]version.Constraints 27 | 28 | func (pr ProviderRequirements) Equals(reqs ProviderRequirements) bool { 29 | if len(pr) != len(reqs) { 30 | return false 31 | } 32 | 33 | for pAddr, vCons := range pr { 34 | c, ok := reqs[pAddr] 35 | if !ok { 36 | return false 37 | } 38 | if !vCons.Equals(c) { 39 | return false 40 | } 41 | } 42 | 43 | return true 44 | } 45 | 46 | type Backend struct { 47 | Type string 48 | Data backend.BackendData 49 | } 50 | 51 | func (be *Backend) Equals(b *Backend) bool { 52 | if be == nil && b == nil { 53 | return true 54 | } 55 | 56 | if be == nil || b == nil { 57 | return false 58 | } 59 | 60 | if be.Type != b.Type { 61 | return false 62 | } 63 | 64 | return be.Data.Equals(b.Data) 65 | } 66 | 67 | type ProviderRef struct { 68 | LocalName string 69 | 70 | // If not empty, Alias identifies which non-default (aliased) provider 71 | // configuration this address refers to. 72 | Alias string 73 | } 74 | -------------------------------------------------------------------------------- /module/output.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package module 5 | 6 | import ( 7 | "github.com/zclconf/go-cty/cty" 8 | ) 9 | 10 | type Output struct { 11 | Description string 12 | IsSensitive bool 13 | Value cty.Value 14 | } 15 | -------------------------------------------------------------------------------- /module/variable.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package module 5 | 6 | import ( 7 | "github.com/hashicorp/hcl/v2/ext/typeexpr" 8 | "github.com/zclconf/go-cty/cty" 9 | ) 10 | 11 | type Variable struct { 12 | Description string 13 | Type cty.Type 14 | 15 | // In case the version it is before 0.14 sensitive will always be false 16 | // that was actually the default value for prior versions 17 | IsSensitive bool 18 | 19 | // DefaultValue represents default value if one is defined 20 | // and is decodable without errors, else cty.NilVal 21 | DefaultValue cty.Value 22 | 23 | // TypeDefaults represents any default values for optional object 24 | // attributes assuming Type is of cty.Object and has defaults. 25 | // 26 | // Any relationships between DefaultValue & TypeDefaults are left 27 | // for downstream to deal with using e.g. TypeDefaults.Apply(). 28 | TypeDefaults *typeexpr.Defaults 29 | } 30 | -------------------------------------------------------------------------------- /registry/registry.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package registry 5 | 6 | import ( 7 | "github.com/hashicorp/go-version" 8 | "github.com/hashicorp/hcl-lang/lang" 9 | "github.com/zclconf/go-cty/cty" 10 | ) 11 | 12 | type ModuleData struct { 13 | Version *version.Version 14 | Inputs []Input 15 | Outputs []Output 16 | } 17 | 18 | type Input struct { 19 | Name string 20 | Type cty.Type 21 | Description lang.MarkupContent 22 | Default cty.Value 23 | Required bool 24 | } 25 | 26 | type Output struct { 27 | Name string 28 | Description lang.MarkupContent 29 | } 30 | -------------------------------------------------------------------------------- /schema/builtin_references.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/go-version" 8 | "github.com/hashicorp/hcl-lang/reference" 9 | 10 | refs_v0_12 "github.com/hashicorp/terraform-schema/internal/references/0.12" 11 | refs_v1_10 "github.com/hashicorp/terraform-schema/internal/references/1.10" 12 | ) 13 | 14 | // BuiltinReferencesForVersion returns known "built-in" reference targets 15 | // (range-less references available within any module) 16 | func BuiltinReferencesForVersion(v *version.Version, modPath string) reference.Targets { 17 | ver := v.Core() 18 | 19 | if ver.GreaterThanOrEqual(v1_10) { 20 | return refs_v1_10.BuiltinReferences(modPath) 21 | } 22 | 23 | return refs_v0_12.BuiltinReferences(modPath) 24 | } 25 | -------------------------------------------------------------------------------- /schema/errors.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "fmt" 8 | 9 | "github.com/hashicorp/go-version" 10 | ) 11 | 12 | type CoreSchemaRequiredErr struct{} 13 | 14 | func (e CoreSchemaRequiredErr) Error() string { 15 | return "core schema required (none provided)" 16 | } 17 | 18 | type coreFunctionsRequiredErr struct{} 19 | 20 | func (e coreFunctionsRequiredErr) Error() string { 21 | return "core functions required (none provided)" 22 | } 23 | 24 | type NoCompatibleSchemaErr struct { 25 | Version *version.Version 26 | Constraints version.Constraints 27 | } 28 | 29 | func (e NoCompatibleSchemaErr) Error() string { 30 | if e.Version != nil { 31 | return fmt.Sprintf("no compatible schema found for %s", e.Version) 32 | } 33 | if e.Constraints != nil && len(e.Constraints) > 0 { 34 | return fmt.Sprintf("no compatible schema found for %s", e.Constraints) 35 | } 36 | return "no compatible schema found" 37 | } 38 | -------------------------------------------------------------------------------- /schema/functions.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "fmt" 8 | 9 | "github.com/hashicorp/go-version" 10 | "github.com/hashicorp/hcl-lang/schema" 11 | 12 | funcs_v0_12 "github.com/hashicorp/terraform-schema/internal/funcs/0.12" 13 | funcs_v0_13 "github.com/hashicorp/terraform-schema/internal/funcs/0.13" 14 | funcs_v0_14 "github.com/hashicorp/terraform-schema/internal/funcs/0.14" 15 | funcs_v0_15 "github.com/hashicorp/terraform-schema/internal/funcs/0.15" 16 | funcs_v1_3 "github.com/hashicorp/terraform-schema/internal/funcs/1.3" 17 | funcs_generated "github.com/hashicorp/terraform-schema/internal/funcs/generated" 18 | ) 19 | 20 | func FunctionsForVersion(v *version.Version) (map[string]schema.FunctionSignature, error) { 21 | ver := v.Core() 22 | if ver.GreaterThanOrEqual(v1_4) { 23 | return funcs_generated.Functions(ver), nil 24 | } 25 | if ver.GreaterThanOrEqual(v1_3) { 26 | return funcs_v1_3.Functions(ver), nil 27 | } 28 | if ver.GreaterThanOrEqual(v0_15) { 29 | return funcs_v0_15.Functions(ver), nil 30 | } 31 | if ver.GreaterThanOrEqual(v0_14) { 32 | return funcs_v0_14.Functions(ver), nil 33 | } 34 | if ver.GreaterThanOrEqual(v0_13) { 35 | return funcs_v0_13.Functions(ver), nil 36 | } 37 | 38 | // Return the 0.12 functions for any version <= 0.12 39 | return funcs_v0_12.Functions(ver), nil 40 | } 41 | 42 | func FunctionsForConstraint(vc version.Constraints) (map[string]schema.FunctionSignature, error) { 43 | for _, v := range terraformVersions { 44 | if vc.Check(v) { 45 | return FunctionsForVersion(v) 46 | } 47 | } 48 | 49 | return nil, fmt.Errorf("no compatible functions found for %s", vc) 50 | } 51 | -------------------------------------------------------------------------------- /schema/functions_merge.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "fmt" 8 | 9 | "github.com/hashicorp/go-version" 10 | "github.com/hashicorp/hcl-lang/schema" 11 | tfaddr "github.com/hashicorp/terraform-registry-address" 12 | tfmod "github.com/hashicorp/terraform-schema/module" 13 | ) 14 | 15 | // FunctionsStateReader exposes a set of methods to read data from the internal language server state 16 | // for function merging 17 | type FunctionsStateReader interface { 18 | // ProviderSchema returns the schema for a provider we have stored in memory. The can come 19 | // from different sources. 20 | ProviderSchema(modPath string, addr tfaddr.Provider, vc version.Constraints) (*ProviderSchema, error) 21 | } 22 | 23 | type FunctionsMerger struct { 24 | coreFunctions map[string]schema.FunctionSignature 25 | terraformVersion *version.Version 26 | stateReader FunctionsStateReader 27 | } 28 | 29 | func NewFunctionsMerger(coreFunctions map[string]schema.FunctionSignature) *FunctionsMerger { 30 | return &FunctionsMerger{ 31 | coreFunctions: coreFunctions, 32 | } 33 | } 34 | 35 | func (m *FunctionsMerger) SetStateReader(mr FunctionsStateReader) { 36 | m.stateReader = mr 37 | } 38 | 39 | func (m *FunctionsMerger) SetTerraformVersion(v *version.Version) { 40 | m.terraformVersion = v 41 | } 42 | 43 | func (m *FunctionsMerger) FunctionsForModule(meta *tfmod.Meta) (map[string]schema.FunctionSignature, error) { 44 | if m.coreFunctions == nil { 45 | return nil, coreFunctionsRequiredErr{} 46 | } 47 | 48 | if meta == nil { 49 | return m.coreFunctions, nil 50 | } 51 | 52 | if m.stateReader == nil { 53 | return m.coreFunctions, nil 54 | } 55 | 56 | if m.terraformVersion.LessThan(v1_8) { 57 | return m.coreFunctions, nil 58 | } 59 | 60 | mergedFunctions := make(map[string]schema.FunctionSignature, len(m.coreFunctions)) 61 | for fName, fSig := range m.coreFunctions { 62 | mergedFunctions[fName] = *fSig.Copy() 63 | } 64 | 65 | providerRefs := ProviderReferences(meta.ProviderReferences) 66 | 67 | for pAddr, pVersionCons := range meta.ProviderRequirements { 68 | pSchema, err := m.stateReader.ProviderSchema(meta.Path, pAddr, pVersionCons) 69 | if err != nil { 70 | continue 71 | } 72 | 73 | refs := providerRefs.ReferencesOfProvider(pAddr) 74 | 75 | for _, localRef := range refs { 76 | for fName, fSig := range pSchema.Functions { 77 | mergedFunctions[fmt.Sprintf("provider::%s::%s", localRef.LocalName, fName)] = *fSig.Copy() 78 | } 79 | } 80 | } 81 | 82 | return mergedFunctions, nil 83 | } 84 | -------------------------------------------------------------------------------- /schema/language_ids.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | const ( 7 | ModuleLanguageID = "terraform" 8 | VariablesLanguageID = "terraform-vars" 9 | StackLanguageID = "terraform-stack" 10 | ) 11 | -------------------------------------------------------------------------------- /schema/provider_schema.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/go-version" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | tfaddr "github.com/hashicorp/terraform-registry-address" 10 | ) 11 | 12 | type ProviderSchema struct { 13 | Provider *schema.BodySchema 14 | Resources map[string]*schema.BodySchema 15 | EphemeralResources map[string]*schema.BodySchema 16 | DataSources map[string]*schema.BodySchema 17 | Functions map[string]*schema.FunctionSignature 18 | } 19 | 20 | func (ps *ProviderSchema) Copy() *ProviderSchema { 21 | if ps == nil { 22 | return nil 23 | } 24 | 25 | newPs := &ProviderSchema{ 26 | Provider: ps.Provider.Copy(), 27 | } 28 | 29 | if ps.Resources != nil { 30 | newPs.Resources = make(map[string]*schema.BodySchema, len(ps.Resources)) 31 | for name, rSchema := range ps.Resources { 32 | newPs.Resources[name] = rSchema.Copy() 33 | } 34 | } 35 | 36 | if ps.EphemeralResources != nil { 37 | newPs.EphemeralResources = make(map[string]*schema.BodySchema, len(ps.EphemeralResources)) 38 | for name, erSchema := range ps.EphemeralResources { 39 | newPs.EphemeralResources[name] = erSchema.Copy() 40 | } 41 | } 42 | 43 | if ps.DataSources != nil { 44 | newPs.DataSources = make(map[string]*schema.BodySchema, len(ps.DataSources)) 45 | for name, rSchema := range ps.DataSources { 46 | newPs.DataSources[name] = rSchema.Copy() 47 | } 48 | } 49 | 50 | if ps.Functions != nil { 51 | newPs.Functions = make(map[string]*schema.FunctionSignature, len(ps.Functions)) 52 | for name, fSig := range ps.Functions { 53 | newPs.Functions[name] = fSig.Copy() 54 | } 55 | } 56 | 57 | return newPs 58 | } 59 | 60 | func (ps *ProviderSchema) SetProviderVersion(pAddr tfaddr.Provider, v *version.Version) { 61 | if ps.Provider != nil { 62 | ps.Provider.Detail = detailForSrcAddr(pAddr, v) 63 | ps.Provider.HoverURL = urlForProvider(pAddr, v) 64 | ps.Provider.DocsLink = docsLinkForProvider(pAddr, v) 65 | } 66 | for _, rSchema := range ps.Resources { 67 | rSchema.Detail = detailForSrcAddr(pAddr, v) 68 | } 69 | for _, erSchema := range ps.EphemeralResources { 70 | erSchema.Detail = detailForSrcAddr(pAddr, v) 71 | } 72 | for _, dsSchema := range ps.DataSources { 73 | dsSchema.Detail = detailForSrcAddr(pAddr, v) 74 | } 75 | for _, fSig := range ps.Functions { 76 | fSig.Detail = detailForSrcAddr(pAddr, v) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /schema/schema_merge_remote_state_ds.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "strings" 8 | 9 | "github.com/hashicorp/hcl-lang/lang" 10 | "github.com/hashicorp/hcl-lang/schema" 11 | tfaddr "github.com/hashicorp/terraform-registry-address" 12 | "github.com/hashicorp/terraform-schema/internal/addr" 13 | "github.com/hashicorp/terraform-schema/internal/schema/backends" 14 | "github.com/hashicorp/terraform-schema/module" 15 | "github.com/zclconf/go-cty/cty" 16 | ) 17 | 18 | const remoteStateDsName = "terraform_remote_state" 19 | 20 | func isRemoteStateDataSource(pAddr tfaddr.Provider, dsName string) bool { 21 | return (pAddr.Equals(addr.NewBuiltInProvider("terraform")) || 22 | pAddr.Equals(addr.NewDefaultProvider("terraform")) || 23 | pAddr.Equals(addr.NewLegacyProvider("terraform"))) && 24 | dsName == remoteStateDsName 25 | } 26 | 27 | func (sm *SchemaMerger) dependentBodyForRemoteStateDataSource(originalBodySchema *schema.BodySchema, providerAddr lang.Address, localRef module.ProviderRef) map[schema.SchemaKey]*schema.BodySchema { 28 | m := make(map[schema.SchemaKey]*schema.BodySchema, 0) 29 | backendsAsCons := backends.ConfigsAsObjectConstraint(sm.terraformVersion) 30 | 31 | for backendType, objConstraint := range backendsAsCons { 32 | depKeys := schema.DependencyKeys{ 33 | Labels: []schema.LabelDependent{ 34 | {Index: 0, Value: remoteStateDsName}, 35 | }, 36 | Attributes: []schema.AttributeDependent{ 37 | { 38 | Name: "provider", 39 | Expr: schema.ExpressionValue{ 40 | Address: providerAddr, 41 | }, 42 | }, 43 | { 44 | Name: "backend", 45 | Expr: schema.ExpressionValue{ 46 | Static: cty.StringVal(backendType), 47 | }, 48 | }, 49 | }, 50 | } 51 | 52 | dsSchema := originalBodySchema.Copy() 53 | dsSchema.Attributes["backend"] = &schema.AttributeSchema{ 54 | Constraint: backends.BackendTypesAsOneOfConstraint(sm.terraformVersion), 55 | IsRequired: true, 56 | SemanticTokenModifiers: lang.SemanticTokenModifiers{lang.TokenModifierDependent}, 57 | } 58 | dsSchema.Attributes["config"] = &schema.AttributeSchema{ 59 | Constraint: objConstraint, 60 | IsOptional: true, 61 | } 62 | 63 | m[schema.NewSchemaKey(depKeys)] = dsSchema 64 | 65 | // No explicit association is required 66 | // if the resource prefix matches provider name 67 | if strings.HasPrefix(remoteStateDsName, localRef.LocalName+"_") { 68 | depKeys := schema.DependencyKeys{ 69 | Labels: []schema.LabelDependent{ 70 | {Index: 0, Value: remoteStateDsName}, 71 | }, 72 | Attributes: []schema.AttributeDependent{ 73 | { 74 | Name: "backend", 75 | Expr: schema.ExpressionValue{ 76 | Static: cty.StringVal(backendType), 77 | }, 78 | }, 79 | }, 80 | } 81 | m[schema.NewSchemaKey(depKeys)] = dsSchema 82 | } 83 | } 84 | 85 | return m 86 | } 87 | -------------------------------------------------------------------------------- /schema/semantic_tokens.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/terraform-schema/internal/schema/tokmod" 8 | ) 9 | 10 | var SemanticTokenModifiers = tokmod.SupportedModifiers 11 | -------------------------------------------------------------------------------- /schema/stacks/deploy_schema.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/go-version" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | stack_1_9 "github.com/hashicorp/terraform-schema/internal/schema/stacks/1.9" 10 | ) 11 | 12 | // CoreDeploySchemaForVersion finds a schema for deployment configuration files 13 | // that is relevant for the given Terraform version. 14 | // It will return an error if such schema cannot be found. 15 | func CoreDeploySchemaForVersion(v *version.Version) (*schema.BodySchema, error) { 16 | return stack_1_9.DeploymentSchema(v), nil 17 | } 18 | -------------------------------------------------------------------------------- /schema/stacks/deploy_schema_merge.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/hcl-lang/lang" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | "github.com/hashicorp/terraform-schema/internal/schema/refscope" 10 | tfschema "github.com/hashicorp/terraform-schema/schema" 11 | "github.com/hashicorp/terraform-schema/stack" 12 | "github.com/zclconf/go-cty/cty" 13 | ) 14 | 15 | type DeploySchemaMerger struct { 16 | coreSchema *schema.BodySchema 17 | } 18 | 19 | func NewDeploySchemaMerger(coreSchema *schema.BodySchema) *DeploySchemaMerger { 20 | return &DeploySchemaMerger{ 21 | coreSchema: coreSchema, 22 | } 23 | } 24 | 25 | // SchemaForDeployment returns the schema for a deployment block 26 | func (m *DeploySchemaMerger) SchemaForDeployment(meta *stack.Meta) (*schema.BodySchema, error) { 27 | if m.coreSchema == nil { 28 | return nil, tfschema.CoreSchemaRequiredErr{} 29 | } 30 | 31 | mergedSchema := m.coreSchema.Copy() 32 | 33 | constr := constraintForDeploymentInputs(*meta) 34 | 35 | // TODO: isOptional should be set to true if at least one input is required 36 | mergedSchema.Blocks["deployment"].Body.Attributes["inputs"].Constraint = constr 37 | 38 | return mergedSchema, nil 39 | } 40 | 41 | func constraintForDeploymentInputs(stackMeta stack.Meta) schema.Constraint { 42 | inputs := make(map[string]*schema.AttributeSchema, 0) 43 | 44 | for name, variable := range stackMeta.Variables { 45 | varType := variable.Type 46 | if varType == cty.NilType { 47 | varType = cty.DynamicPseudoType 48 | } 49 | aSchema := StackVarToAttribute(variable) 50 | aSchema.Constraint = tfschema.ConvertAttributeTypeToConstraint(varType) 51 | 52 | aSchema.OriginForTarget = &schema.PathTarget{ 53 | Address: schema.Address{ 54 | schema.StaticStep{Name: "var"}, 55 | schema.AttrNameStep{}, 56 | }, 57 | Path: lang.Path{ 58 | Path: stackMeta.Path, 59 | LanguageID: tfschema.StackLanguageID, 60 | }, 61 | Constraints: schema.Constraints{ 62 | ScopeId: refscope.VariableScope, 63 | Type: varType, 64 | }, 65 | } 66 | 67 | inputs[name] = aSchema 68 | } 69 | 70 | return schema.Object{ 71 | Attributes: inputs, 72 | } 73 | } 74 | 75 | func StackVarToAttribute(stackVar stack.Variable) *schema.AttributeSchema { 76 | aSchema := &schema.AttributeSchema{ 77 | IsSensitive: stackVar.IsSensitive, 78 | } 79 | 80 | if stackVar.Description != "" { 81 | aSchema.Description = lang.PlainText(stackVar.Description) 82 | } 83 | 84 | if stackVar.DefaultValue == cty.NilVal { 85 | aSchema.IsRequired = true 86 | } else { 87 | aSchema.IsOptional = true 88 | } 89 | 90 | return aSchema 91 | } 92 | -------------------------------------------------------------------------------- /schema/stacks/stack_schema.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/go-version" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | stack_1_9 "github.com/hashicorp/terraform-schema/internal/schema/stacks/1.9" 10 | ) 11 | 12 | // CoreStackSchemaForVersion finds a schema for stack configuration files 13 | // that is relevant for the given Terraform version. 14 | // It will return an error if such schema cannot be found. 15 | func CoreStackSchemaForVersion(v *version.Version) (*schema.BodySchema, error) { 16 | return stack_1_9.StackSchema(v), nil 17 | } 18 | -------------------------------------------------------------------------------- /schema/testdata/provider-schema-terraform.json: -------------------------------------------------------------------------------- 1 | {"format_version":"1.0","provider_schemas":{"terraform.io/builtin/terraform":{"data_source_schemas":{"terraform_remote_state":{"version":0,"block":{"attributes":{"backend":{"type":"string","description":"The remote backend to use, e.g. `remote` or `http`.","description_kind":"markdown","required":true},"config":{"type":"dynamic","description":"The configuration of the remote backend. Although this is optional, most backends require some configuration.\n\nThe object can use any arguments that would be valid in the equivalent `terraform { backend \"\u003cTYPE\u003e\" { ... } }` block.","description_kind":"markdown","optional":true},"defaults":{"type":"dynamic","description":"Default values for outputs, in case the state file is empty or lacks a required output.","description_kind":"markdown","optional":true},"outputs":{"type":"dynamic","description":"An object containing every root-level output in the remote state.","description_kind":"markdown","computed":true},"workspace":{"type":"string","description":"The Terraform workspace to use, if the backend supports workspaces.","description_kind":"markdown","optional":true}},"description_kind":"plain"}}}}}} -------------------------------------------------------------------------------- /schema/testdata/test-config-0.12.tf: -------------------------------------------------------------------------------- 1 | # terraform init && terraform providers schema -json 2 | # with v0.12 generates relevant JSON file 3 | terraform { 4 | required_providers { 5 | random = "3.0.0" 6 | null = "3.0.0" 7 | } 8 | } 9 | 10 | provider "null" { 11 | alias = "foobar" 12 | } 13 | 14 | resource "null_resource" "name" { 15 | 16 | } 17 | 18 | resource "random_string" "name" { 19 | 20 | } 21 | 22 | data "terraform_remote_state" "vpc" { 23 | 24 | } 25 | -------------------------------------------------------------------------------- /schema/testdata/test-config-0.13.tf: -------------------------------------------------------------------------------- 1 | # terraform init && terraform providers schema -json 2 | # with v0.13 generates relevant JSON file 3 | terraform { 4 | required_providers { 5 | rand = { 6 | source = "hashicorp/random" 7 | version = "3.0.0" 8 | } 9 | null = { 10 | source = "hashicorp/null" 11 | version = "3.0.0" 12 | } 13 | grafana = { 14 | source = "grafana/grafana" 15 | version = "1.6.0" 16 | } 17 | } 18 | } 19 | 20 | provider "null" { 21 | alias = "foobar" 22 | } 23 | 24 | resource "random_string" "name" { 25 | provider = rand 26 | } 27 | 28 | resource "null_resource" "name" { 29 | 30 | } 31 | 32 | data "terraform_remote_state" "vpc" { 33 | 34 | } 35 | 36 | resource "grafana_alert_notification" "slack" { 37 | 38 | } 39 | -------------------------------------------------------------------------------- /schema/testdata/test-config-0.15.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | hashicup = { 4 | source = "hashicorp/hashicup" 5 | version = "0.0.0" 6 | } 7 | } 8 | } 9 | 10 | module "example" { 11 | source = "./source" 12 | } 13 | -------------------------------------------------------------------------------- /schema/testdata/test-config-remote-module.tf: -------------------------------------------------------------------------------- 1 | module "remote-example" { 2 | source = "namespace/foo/bar" 3 | } 4 | -------------------------------------------------------------------------------- /schema/tests/mock_schema.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/go-version" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | test_v1_7 "github.com/hashicorp/terraform-schema/internal/schema/tests/1.7" 10 | tfschema "github.com/hashicorp/terraform-schema/schema" 11 | ) 12 | 13 | // CoreMockSchemaForVersion finds a schema for mock configuration files 14 | // that is relevant for the given Terraform version. 15 | // It will return an error if such schema cannot be found. 16 | func CoreMockSchemaForVersion(v *version.Version) (*schema.BodySchema, error) { 17 | ver := v.Core() 18 | 19 | if ver.GreaterThanOrEqual(v1_7) { 20 | return test_v1_7.MockSchema(ver), nil 21 | } 22 | 23 | return nil, tfschema.NoCompatibleSchemaErr{Version: ver} 24 | } 25 | -------------------------------------------------------------------------------- /schema/tests/mock_schema_merge.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/hcl-lang/schema" 8 | tfschema "github.com/hashicorp/terraform-schema/schema" 9 | tftest "github.com/hashicorp/terraform-schema/test" 10 | ) 11 | 12 | type MockSchemaMerger struct { 13 | coreSchema *schema.BodySchema 14 | stateReader StateReader 15 | } 16 | 17 | func NewMockSchemaMerger(coreSchema *schema.BodySchema) *MockSchemaMerger { 18 | return &MockSchemaMerger{ 19 | coreSchema: coreSchema, 20 | } 21 | } 22 | 23 | func (m *MockSchemaMerger) SetStateReader(mr StateReader) { 24 | m.stateReader = mr 25 | } 26 | 27 | func (m *MockSchemaMerger) SchemaForMock(meta *tftest.Meta) (*schema.BodySchema, error) { 28 | if m.coreSchema == nil { 29 | return nil, tfschema.CoreSchemaRequiredErr{} 30 | } 31 | 32 | if meta == nil { 33 | return m.coreSchema, nil 34 | } 35 | 36 | if m.stateReader == nil { 37 | return m.coreSchema, nil 38 | } 39 | 40 | mergedSchema := m.coreSchema.Copy() 41 | 42 | // TODO merge mock_resource blocks - use the label as dependency key TFECO-7471 43 | // TODO merge mock_data blocks - use the label as dependency key TFECO-7472 44 | 45 | return mergedSchema, nil 46 | } 47 | -------------------------------------------------------------------------------- /schema/tests/test_schema.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/go-version" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | test_v1_6 "github.com/hashicorp/terraform-schema/internal/schema/tests/1.6" 10 | test_v1_7 "github.com/hashicorp/terraform-schema/internal/schema/tests/1.7" 11 | test_v1_9 "github.com/hashicorp/terraform-schema/internal/schema/tests/1.9" 12 | tfschema "github.com/hashicorp/terraform-schema/schema" 13 | ) 14 | 15 | var ( 16 | v1_6 = version.Must(version.NewVersion("1.6")) 17 | v1_7 = version.Must(version.NewVersion("1.7")) 18 | v1_9 = version.Must(version.NewVersion("1.9")) 19 | ) 20 | 21 | // CoreTestSchemaForVersion finds a schema for test configuration files 22 | // that is relevant for the given Terraform version. 23 | // It will return an error if such schema cannot be found. 24 | func CoreTestSchemaForVersion(v *version.Version) (*schema.BodySchema, error) { 25 | ver := v.Core() 26 | 27 | if ver.GreaterThanOrEqual(v1_9) { 28 | return test_v1_9.TestSchema(ver), nil 29 | } 30 | if ver.GreaterThanOrEqual(v1_7) { 31 | return test_v1_7.TestSchema(ver), nil 32 | } 33 | if ver.GreaterThanOrEqual(v1_6) { 34 | return test_v1_6.TestSchema(ver), nil 35 | } 36 | 37 | return nil, tfschema.NoCompatibleSchemaErr{Version: ver} 38 | } 39 | -------------------------------------------------------------------------------- /schema/tests/test_schema_merge.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/go-version" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | tfaddr "github.com/hashicorp/terraform-registry-address" 10 | tfmod "github.com/hashicorp/terraform-schema/module" 11 | tfschema "github.com/hashicorp/terraform-schema/schema" 12 | tftest "github.com/hashicorp/terraform-schema/test" 13 | ) 14 | 15 | type TestSchemaMerger struct { 16 | coreSchema *schema.BodySchema 17 | stateReader StateReader 18 | } 19 | 20 | // StateReader exposes a set of methods to read data from the internal language server state 21 | type StateReader interface { 22 | // ProviderSchema returns the schema for a provider we have stored in memory. The can come 23 | // from different sources. 24 | ProviderSchema(modPath string, addr tfaddr.Provider, vc version.Constraints) (*tfschema.ProviderSchema, error) 25 | 26 | // LocalModuleMeta returns the module meta data for a local module. This is the result 27 | // of the [earlydecoder] when processing module files 28 | LocalModuleMeta(modPath string) (*tfmod.Meta, error) 29 | } 30 | 31 | func NewTestSchemaMerger(coreSchema *schema.BodySchema) *TestSchemaMerger { 32 | return &TestSchemaMerger{ 33 | coreSchema: coreSchema, 34 | } 35 | } 36 | 37 | func (m *TestSchemaMerger) SetStateReader(mr StateReader) { 38 | m.stateReader = mr 39 | } 40 | 41 | func (m *TestSchemaMerger) SchemaForTest(meta *tftest.Meta) (*schema.BodySchema, error) { 42 | if m.coreSchema == nil { 43 | return nil, tfschema.CoreSchemaRequiredErr{} 44 | } 45 | 46 | if meta == nil { 47 | return m.coreSchema, nil 48 | } 49 | 50 | if m.stateReader == nil { 51 | return m.coreSchema, nil 52 | } 53 | 54 | mergedSchema := m.coreSchema.Copy() 55 | 56 | // TODO merge mock_provider blocks - use the label as dependency key AND the source if defined TFECO-7476 57 | // TODO merge nested mock_resource blocks - use the label as dependency key TFECO-7474 58 | // TODO merge nested mock_data blocks - use the label as dependency key TFECO-7475 59 | // TODO merge run - module blocks - use the source as dependency key TFECO-7477 60 | // TODO merge variables - source them from the Terraform module meta TFECO-7478 61 | // TODO merge provider - source them from the Terraform module meta requirements TFECO-7522 62 | 63 | return mergedSchema, nil 64 | } 65 | -------------------------------------------------------------------------------- /schema/variable_schema.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "github.com/hashicorp/hcl-lang/lang" 8 | "github.com/hashicorp/hcl-lang/schema" 9 | "github.com/hashicorp/terraform-schema/internal/schema/refscope" 10 | "github.com/hashicorp/terraform-schema/module" 11 | "github.com/zclconf/go-cty/cty" 12 | ) 13 | 14 | // AnySchemaForVariableCollection returns a schema for collecting all 15 | // variables in a variable file. It doesn't check if a variable has 16 | // been defined in the module or not. 17 | // 18 | // We can use this schema to collect variable references without waiting 19 | // on the module metadata. 20 | func AnySchemaForVariableCollection(modPath string) *schema.BodySchema { 21 | return &schema.BodySchema{ 22 | AnyAttribute: &schema.AttributeSchema{ 23 | OriginForTarget: &schema.PathTarget{ 24 | Address: schema.Address{ 25 | schema.StaticStep{Name: "var"}, 26 | schema.AttrNameStep{}, 27 | }, 28 | Path: lang.Path{ 29 | Path: modPath, 30 | LanguageID: ModuleLanguageID, 31 | }, 32 | Constraints: schema.Constraints{ 33 | ScopeId: refscope.VariableScope, 34 | Type: cty.DynamicPseudoType, 35 | }, 36 | }, 37 | Constraint: schema.AnyExpression{OfType: cty.DynamicPseudoType}, 38 | }, 39 | } 40 | } 41 | 42 | func SchemaForVariables(vars map[string]module.Variable, modPath string) (*schema.BodySchema, error) { 43 | attributes := make(map[string]*schema.AttributeSchema) 44 | 45 | for name, modVar := range vars { 46 | aSchema := ModuleVarToAttribute(modVar) 47 | varType := modVar.Type 48 | aSchema.Constraint = schema.LiteralType{Type: varType} 49 | aSchema.OriginForTarget = &schema.PathTarget{ 50 | Address: schema.Address{ 51 | schema.StaticStep{Name: "var"}, 52 | schema.AttrNameStep{}, 53 | }, 54 | Path: lang.Path{ 55 | Path: modPath, 56 | LanguageID: ModuleLanguageID, 57 | }, 58 | Constraints: schema.Constraints{ 59 | ScopeId: refscope.VariableScope, 60 | Type: varType, 61 | }, 62 | } 63 | 64 | attributes[name] = aSchema 65 | } 66 | 67 | return &schema.BodySchema{ 68 | Attributes: attributes, 69 | }, nil 70 | } 71 | 72 | func ModuleVarToAttribute(modVar module.Variable) *schema.AttributeSchema { 73 | aSchema := &schema.AttributeSchema{ 74 | IsSensitive: modVar.IsSensitive, 75 | } 76 | 77 | if modVar.Description != "" { 78 | aSchema.Description = lang.PlainText(modVar.Description) 79 | } 80 | 81 | if modVar.DefaultValue == cty.NilVal { 82 | aSchema.IsRequired = true 83 | } else { 84 | aSchema.IsOptional = true 85 | } 86 | 87 | return aSchema 88 | } 89 | -------------------------------------------------------------------------------- /schema/versions.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import "github.com/hashicorp/go-version" 7 | 8 | // ResolveVersion returns Terraform version for which we have schema available 9 | // based on either given version and/or constraint. 10 | // Lack of constraint and version implies latest known version. 11 | // 12 | //go:generate go run ../internal/versiongen -w ./versions_gen.go 13 | func ResolveVersion(tfVersion *version.Version, tfCons version.Constraints) *version.Version { 14 | if tfVersion != nil { 15 | coreVersion := tfVersion.Core() 16 | if coreVersion.LessThan(OldestAvailableVersion) { 17 | return OldestAvailableVersion 18 | } 19 | if coreVersion.GreaterThan(LatestAvailableVersion) { 20 | // We could simply return coreVersion or tfVersion here 21 | // but we ensure the return version is actually known, i.e. 22 | // that we actually have schema for it. 23 | // 24 | // Also we strip the pre-release part as it simplifies 25 | // the version comparisons downstream (we don't care 26 | // about differences between individual pre-releases 27 | // of the same patch version). 28 | for _, v := range terraformVersions { 29 | if tfVersion.Equal(v) { 30 | return coreVersion 31 | } 32 | } 33 | return LatestAvailableVersion 34 | } 35 | if tfCons.Check(coreVersion) { 36 | return coreVersion 37 | } 38 | } 39 | 40 | for _, v := range terraformVersions { 41 | if len(tfCons) > 0 && tfCons.Check(v) && v.LessThan(OldestAvailableVersion) { 42 | return OldestAvailableVersion 43 | } 44 | if tfVersion != nil && tfVersion.Core().Equal(v) { 45 | return tfVersion.Core() 46 | } 47 | if len(tfCons) > 0 && tfCons.Check(v) { 48 | return v 49 | } 50 | } 51 | 52 | return LatestAvailableVersion 53 | } 54 | -------------------------------------------------------------------------------- /schema/versions_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package schema 5 | 6 | import ( 7 | "fmt" 8 | "testing" 9 | 10 | "github.com/hashicorp/go-version" 11 | ) 12 | 13 | func TestResolveVersion(t *testing.T) { 14 | testCases := []struct { 15 | installedVersion *version.Version 16 | constraint version.Constraints 17 | expectedVersion *version.Version 18 | }{ 19 | { 20 | nil, 21 | version.MustConstraints(version.NewConstraint(">= 0.12, < 0.13")), 22 | version.Must(version.NewVersion("0.12.31")), 23 | }, 24 | { 25 | nil, 26 | version.Constraints{}, 27 | LatestAvailableVersion, 28 | }, 29 | { 30 | nil, 31 | version.MustConstraints(version.NewConstraint("< 0.12")), 32 | OldestAvailableVersion, 33 | }, 34 | { 35 | version.Must(version.NewVersion("0.11.0")), 36 | nil, 37 | OldestAvailableVersion, 38 | }, 39 | { 40 | nil, 41 | version.MustConstraints(version.NewConstraint("> 999.999.999")), 42 | LatestAvailableVersion, 43 | }, 44 | { 45 | version.Must(version.NewVersion("999.999.999")), 46 | nil, 47 | LatestAvailableVersion, 48 | }, 49 | { 50 | version.Must(version.NewVersion("1.5.3")), 51 | nil, 52 | version.Must(version.NewVersion("1.5.3")), 53 | }, 54 | { 55 | version.Must(version.NewVersion("1.5.3")), 56 | version.MustConstraints(version.NewConstraint("> 999")), 57 | version.Must(version.NewVersion("1.5.3")), 58 | }, 59 | { 60 | version.Must(version.NewVersion("1.5.3")), 61 | nil, 62 | version.Must(version.NewVersion("1.5.3")), 63 | }, 64 | { 65 | version.Must(version.NewVersion("1.7.0-alpha20231025")), 66 | nil, 67 | version.Must(version.NewVersion("1.7.0")), 68 | }, 69 | { 70 | version.Must(version.NewVersion("1.6.0-beta2")), 71 | nil, 72 | version.Must(version.NewVersion("1.6.0")), 73 | }, 74 | } 75 | 76 | for i, tc := range testCases { 77 | t.Run(fmt.Sprintf("%d-%s", i, tc.constraint.String()), func(t *testing.T) { 78 | resolvedVersion := ResolveVersion(tc.installedVersion, tc.constraint) 79 | if !tc.expectedVersion.Equal(resolvedVersion) { 80 | t.Fatalf("unexpected version: %q, expected: %q", resolvedVersion, tc.expectedVersion) 81 | } 82 | }) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /stack/component.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package stack 5 | 6 | import ( 7 | "github.com/hashicorp/go-version" 8 | "github.com/hashicorp/terraform-schema/module" 9 | ) 10 | 11 | type Component struct { 12 | Source string 13 | SourceAddr module.ModuleSourceAddr 14 | Version version.Constraints 15 | } 16 | -------------------------------------------------------------------------------- /stack/deployment.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package stack 5 | 6 | import "github.com/zclconf/go-cty/cty" 7 | 8 | type Deployment struct { 9 | Inputs map[string]cty.Value 10 | } 11 | -------------------------------------------------------------------------------- /stack/meta.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package stack 5 | 6 | type Meta struct { 7 | Path string 8 | Filenames []string 9 | 10 | Components map[string]Component 11 | Variables map[string]Variable 12 | Outputs map[string]Output 13 | ProviderRequirements map[string]ProviderRequirement 14 | } 15 | -------------------------------------------------------------------------------- /stack/orchestrate.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package stack 5 | 6 | import "github.com/hashicorp/hcl/v2" 7 | 8 | type OrchestrationRule struct { 9 | Type string 10 | Range hcl.Range 11 | } 12 | -------------------------------------------------------------------------------- /stack/output.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package stack 5 | 6 | import ( 7 | "github.com/zclconf/go-cty/cty" 8 | ) 9 | 10 | type Output struct { 11 | Description string 12 | IsSensitive bool 13 | Value cty.Value 14 | } 15 | -------------------------------------------------------------------------------- /stack/provider_requirements.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package stack 5 | 6 | import ( 7 | "github.com/hashicorp/go-version" 8 | tfaddr "github.com/hashicorp/terraform-registry-address" 9 | ) 10 | 11 | type ProviderRequirement struct { 12 | Source tfaddr.Provider 13 | VersionConstraints version.Constraints 14 | } 15 | -------------------------------------------------------------------------------- /stack/store.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package stack 5 | 6 | type Store struct { 7 | Type string 8 | } 9 | -------------------------------------------------------------------------------- /stack/variable.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package stack 5 | 6 | import ( 7 | "github.com/hashicorp/hcl/v2/ext/typeexpr" 8 | "github.com/zclconf/go-cty/cty" 9 | ) 10 | 11 | type Variable struct { 12 | Description string 13 | Type cty.Type 14 | 15 | IsSensitive bool 16 | 17 | // DefaultValue represents default value if one is defined 18 | // and is decodable without errors, else cty.NilVal 19 | DefaultValue cty.Value 20 | 21 | // TypeDefaults represents any default values for optional object 22 | // attributes assuming Type is of cty.Object and has defaults. 23 | // 24 | // Any relationships between DefaultValue & TypeDefaults are left 25 | // for downstream to deal with using e.g. TypeDefaults.Apply(). 26 | TypeDefaults *typeexpr.Defaults 27 | } 28 | -------------------------------------------------------------------------------- /test/meta.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package stack 5 | 6 | type Meta struct { 7 | Path string 8 | Filenames []string 9 | } 10 | -------------------------------------------------------------------------------- /tools/tools.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | // +build tools 5 | 6 | package tools 7 | 8 | import ( 9 | _ "github.com/mh-cbon/go-fmt-fail" 10 | ) 11 | --------------------------------------------------------------------------------