├── examples ├── .gitignore ├── replaced_when │ ├── file_inexistence_check │ │ ├── findme │ │ └── main.tf │ └── create_update_delete_detection │ │ └── main.tf ├── temp_dir │ └── main.tf ├── unknown_proposer │ └── main.tf ├── promise │ └── main.tf ├── is_known │ ├── test_known.tf │ ├── test_unknown.tf │ ├── terraform.tf │ └── test_known_with_nested_unknown.tf ├── is_fully_known │ ├── test_known.tf │ ├── test_unknown.tf │ ├── terraform.tf │ └── test_known_with_nested_unknown.tf └── stash │ └── main.tf ├── tools └── tools.go ├── internal ├── fwkproviderconfig │ ├── provider_meta_schema.go │ ├── provider_config_schema.go │ ├── attribute_validator_plan_known.go │ └── attribute_guid_seed_addition.go ├── goproviderconfig │ ├── attribute_proposed_unknown.go │ ├── attribute_guid_seed.go │ ├── provider_config_schema.go │ ├── provider_meta_schema.go │ └── attribute_guid_seed_addition.go ├── uuid │ └── main.go ├── fwkprovider │ ├── temp_dir_data.go │ ├── provider.go │ ├── unknown_proposer_resource.go │ ├── os_path.go │ └── replaced_when_resource.go ├── schema │ └── main.go └── guid │ └── main.go ├── docs ├── resources │ ├── stash.md │ ├── unknown_proposer.md │ ├── promise.md │ ├── replaced_when.md │ ├── os_path.md │ ├── is_known.md │ └── is_fully_known.md ├── data-sources │ └── temp_dir.md └── index.md ├── README.md ├── .gitignore ├── promise └── provider │ ├── provider.go │ ├── provider_schema.go │ ├── resource_import.go │ ├── provider_configure.go │ ├── schema_resource.go │ ├── resource_type_validate.go │ ├── resource_read.go │ ├── provider_server.go │ ├── resource_apply.go │ └── resource_plan.go ├── stash └── provider │ ├── provider.go │ ├── provider_schema.go │ ├── resource_plan.go │ ├── resource_import.go │ ├── resource_apply.go │ ├── provider_configure.go │ ├── schema_resource.go │ ├── resource_type_validate.go │ ├── resource_read.go │ └── provider_server.go ├── isknown ├── provider │ ├── resource_apply.go │ ├── provider_schema.go │ ├── resource_read.go │ ├── resource_import.go │ ├── resource_schema.go │ ├── provider_configure.go │ ├── provider_server.go │ ├── provider.go │ ├── resource_type_validate.go │ └── resource_plan.go └── common │ ├── provider_resource_schema.go │ └── resource_read.go ├── isfullyknown └── provider │ └── provider.go ├── main.go ├── .github └── workflows │ └── release.yaml ├── .goreleaser.yml ├── Makefile ├── go.mod ├── LICENSE └── go.sum /examples/.gitignore: -------------------------------------------------------------------------------- 1 | **/.terraform.lock.hcl -------------------------------------------------------------------------------- /examples/replaced_when/file_inexistence_check/findme: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tools/tools.go: -------------------------------------------------------------------------------- 1 | //go:build tools 2 | 3 | package tools 4 | 5 | import ( 6 | // document generation 7 | _ "github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs" 8 | ) 9 | -------------------------------------------------------------------------------- /examples/temp_dir/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | value = { 4 | source = "github.com/pseudo-dynamic/value" 5 | version = "0.1.0" 6 | } 7 | } 8 | } 9 | 10 | data "value_temp_dir" "default" {} 11 | 12 | output "temp_dir" { 13 | value = data.value_temp_dir.default.path 14 | } 15 | -------------------------------------------------------------------------------- /examples/unknown_proposer/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | value = { 4 | source = "github.com/pseudo-dynamic/value" 5 | version = "0.1.0" 6 | } 7 | } 8 | } 9 | 10 | resource "value_unknown_proposer" "default" {} 11 | 12 | output "proposed_unknown" { 13 | value = value_unknown_proposer.default.value 14 | } 15 | -------------------------------------------------------------------------------- /examples/promise/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | value = { 4 | source = "github.com/pseudo-dynamic/value" 5 | version = "0.1.0" 6 | } 7 | } 8 | } 9 | 10 | resource "value_promise" "default" { 11 | value = { 12 | timestamp = timestamp() 13 | } 14 | } 15 | 16 | output "result" { 17 | value = value_promise.default.result 18 | } 19 | -------------------------------------------------------------------------------- /internal/fwkproviderconfig/provider_meta_schema.go: -------------------------------------------------------------------------------- 1 | package fwkproviderconfig 2 | 3 | import ( 4 | "github.com/hashicorp/terraform-plugin-framework/tfsdk" 5 | ) 6 | 7 | func GetProviderMetaSchema() *tfsdk.Schema { 8 | return &tfsdk.Schema{ 9 | Version: 0, 10 | Attributes: map[string]tfsdk.Attribute{ 11 | GuidSeedAdditionAttributeName: getGuidSeedAdditionAttribute(), 12 | }, 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /internal/fwkproviderconfig/provider_config_schema.go: -------------------------------------------------------------------------------- 1 | package fwkproviderconfig 2 | 3 | import ( 4 | "github.com/hashicorp/terraform-plugin-framework/tfsdk" 5 | ) 6 | 7 | func GetProviderConfigSchema() *tfsdk.Schema { 8 | return &tfsdk.Schema{ 9 | Version: 0, 10 | Attributes: map[string]tfsdk.Attribute{ 11 | GuidSeedAdditionAttributeName: getGuidSeedAdditionAttribute(), 12 | }, 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/is_known/test_known.tf: -------------------------------------------------------------------------------- 1 | // This example is complete but there are additional features implemented in terraform.tf! 2 | 3 | resource "value_unknown_proposer" "known" {} 4 | 5 | resource "value_is_known" "known" { 6 | value = "test" 7 | guid_seed = "known" 8 | proposed_unknown = value_unknown_proposer.known.value 9 | } 10 | 11 | output "is_known_value" { 12 | value = { 13 | known = value_is_known.known.result 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /internal/goproviderconfig/attribute_proposed_unknown.go: -------------------------------------------------------------------------------- 1 | package goproviderconfig 2 | 3 | func GetProposedUnknownAttributeDescription() string { 4 | return "It is very crucial that this field is **not** filled by any " + 5 | "custom value except the one produced by `value_unknown_proposer` (resource). " + 6 | "This has the reason as its `value` is **always** unknown during the plan phase. " + 7 | "On this behaviour this resource must rely and it cannot check if you do not so! " 8 | } 9 | -------------------------------------------------------------------------------- /examples/is_fully_known/test_known.tf: -------------------------------------------------------------------------------- 1 | // This example is complete but there are additional features implemented in terraform.tf! 2 | 3 | resource "value_unknown_proposer" "known" {} 4 | 5 | resource "value_is_fully_known" "known" { 6 | value = "test" 7 | guid_seed = "known" 8 | proposed_unknown = value_unknown_proposer.known.value 9 | } 10 | 11 | output "is_known_value" { 12 | value = { 13 | fully_known = value_is_fully_known.known.result 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /docs/resources/stash.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "value_stash Resource - terraform-provider-value" 4 | subcategory: "" 5 | description: |- 6 | Allows you to manage any kind of value as resource. 7 | --- 8 | 9 | # value_stash (Resource) 10 | 11 | Allows you to manage any kind of value as resource. 12 | 13 | 14 | 15 | 16 | ## Schema 17 | 18 | ### Optional 19 | 20 | - `value` (Dynamic) The value to store. 21 | 22 | 23 | -------------------------------------------------------------------------------- /internal/uuid/main.go: -------------------------------------------------------------------------------- 1 | package uuid 2 | 3 | import ( 4 | "hash/crc64" 5 | "math/rand" 6 | 7 | "github.com/google/uuid" 8 | ) 9 | 10 | func DeterministicUuidFromString(seed string) uuid.UUID { 11 | hash := crc64.New(crc64.MakeTable(crc64.ISO)) 12 | hash.Write([]byte(seed)) 13 | hashSumUnsigned := hash.Sum64() 14 | hashSumSigned := int64(hashSumUnsigned) 15 | randomGenerator := rand.New(rand.NewSource(hashSumSigned)) 16 | deterministicUuid, _ := uuid.NewRandomFromReader(randomGenerator) 17 | return deterministicUuid 18 | } 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Value Provider for Terraform [![latest release tag](https://img.shields.io/github/v/tag/pseudo-dynamic/terraform-provider-value?label=release)](https://github.com/pseudo-dynamic/terraform-provider-value/releases) [![license](https://img.shields.io/github/license/hashicorp/terraform-provider-kubernetes.svg)](./LICENSE) 2 | 3 | A terraform provider providing resources to work with values. :smile: 4 | 5 | - [Documentation](./docs/) 6 | - [Resources](./docs/resources/) 7 | - [Data Sources](./docs/data-sources/) 8 | - [Examples](./examples/) -------------------------------------------------------------------------------- /examples/stash/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | value = { 4 | source = "github.com/pseudo-dynamic/value" 5 | version = "0.1.0" 6 | } 7 | } 8 | } 9 | 10 | resource "value_stash" "default" { 11 | value = [ 12 | { 13 | coffee = { 14 | id = 3 15 | } 16 | 17 | quantity = 9 18 | }, 19 | { 20 | coffee = { 21 | id = 1 22 | } 23 | 24 | quantity = 2 25 | } 26 | ] 27 | } 28 | 29 | output "value" { 30 | value = value_stash.default.value 31 | } 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.dll 2 | *.exe 3 | .DS_Store 4 | example.tf 5 | kubeconfig 6 | terraform.tfplan 7 | terraform.tfstate 8 | bin/ 9 | modules-dev/ 10 | /pkg/ 11 | website/.vagrant 12 | website/.bundle 13 | website/build 14 | website/node_modules 15 | .vagrant/ 16 | *.backup 17 | ./*.tfstate 18 | .terraform/ 19 | *.log 20 | *.bak 21 | *~ 22 | .*.swp 23 | .idea 24 | .vscode/ 25 | *.iml 26 | *.test 27 | *.iml 28 | 29 | website/vendor 30 | terraform-provider-value 31 | 32 | # Test exclusions 33 | !command/test-fixtures/**/*.tfstate 34 | !command/test-fixtures/**/.terraform/ 35 | -------------------------------------------------------------------------------- /examples/is_known/test_unknown.tf: -------------------------------------------------------------------------------- 1 | // This example is complete but there are additional features implemented in terraform.tf! 2 | 3 | resource "value_unknown_proposer" "unknown" {} 4 | 5 | resource "value_promise" "unknown" { 6 | value = "test" 7 | } 8 | 9 | resource "value_is_known" "unknown" { 10 | value = value_promise.unknown.result 11 | guid_seed = "unknown" 12 | proposed_unknown = value_unknown_proposer.unknown.value 13 | } 14 | 15 | output "is_unknown_value" { 16 | value = { 17 | known = value_is_known.unknown.result 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /docs/data-sources/temp_dir.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "value_temp_dir Data Source - terraform-provider-value" 4 | subcategory: "" 5 | description: |- 6 | Simply returns the OS-dependent temporary directory (e.g. /tmp). 7 | --- 8 | 9 | # value_temp_dir (Data Source) 10 | 11 | Simply returns the OS-dependent temporary directory (e.g. /tmp). 12 | 13 | 14 | 15 | 16 | ## Schema 17 | 18 | ### Read-Only 19 | 20 | - `path` (String) The OS-dependent temporary directory. 21 | 22 | 23 | -------------------------------------------------------------------------------- /examples/is_fully_known/test_unknown.tf: -------------------------------------------------------------------------------- 1 | // This example is complete but there are additional features implemented in terraform.tf! 2 | 3 | resource "value_unknown_proposer" "unknown" {} 4 | 5 | resource "value_promise" "unknown" { 6 | value = "test" 7 | } 8 | 9 | resource "value_is_fully_known" "unknown" { 10 | value = value_promise.unknown.result 11 | guid_seed = "unknown" 12 | proposed_unknown = value_unknown_proposer.unknown.value 13 | } 14 | 15 | output "is_unknown_value" { 16 | value = { 17 | fully_known = value_is_fully_known.unknown.result 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/is_known/terraform.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | value = { 4 | source = "github.com/pseudo-dynamic/value" 5 | version = "0.1.0" 6 | } 7 | } 8 | 9 | provider_meta "value" { 10 | // Module-scoped seed addition. 11 | // {workdir} -> a placeholder (see docs) 12 | guid_seed_addition = "module(is-known)" 13 | } 14 | } 15 | 16 | provider "value" { 17 | // Project-wide seed addition. 18 | // Won't overwrite module-scoped seed addition, 19 | // instead both serve are now considered as seed addition. 20 | guid_seed_addition = "{workdir}" 21 | } 22 | -------------------------------------------------------------------------------- /promise/provider/provider.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/hashicorp/go-hclog" 7 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 8 | ) 9 | 10 | // Provider 11 | func Provider() func() tfprotov6.ProviderServer { 12 | var logLevel string 13 | logLevel, ok := os.LookupEnv("TF_LOG") 14 | 15 | if !ok { 16 | logLevel = "info" 17 | } 18 | 19 | return func() tfprotov6.ProviderServer { 20 | return &(RawProviderServer{logger: hclog.New(&hclog.LoggerOptions{ 21 | Level: hclog.LevelFromString(logLevel), 22 | Output: os.Stderr, 23 | })}) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/is_fully_known/terraform.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | value = { 4 | source = "github.com/pseudo-dynamic/value" 5 | version = "0.1.0" 6 | } 7 | } 8 | 9 | provider_meta "value" { 10 | // Module-scoped seed addition. 11 | // {workdir} -> a placeholder (see docs) 12 | guid_seed_addition = "module(is-fully-known)" 13 | } 14 | } 15 | 16 | provider "value" { 17 | // Project-wide seed addition. 18 | // Won't overwrite module-scoped seed addition, 19 | // instead both serve are now considered as seed addition. 20 | guid_seed_addition = "{workdir}" 21 | } 22 | -------------------------------------------------------------------------------- /stash/provider/provider.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/hashicorp/go-hclog" 7 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 8 | ) 9 | 10 | // Provider 11 | func Provider() func() tfprotov6.ProviderServer { 12 | var logLevel string 13 | logLevel, ok := os.LookupEnv("TF_LOG") 14 | 15 | if !ok { 16 | logLevel = "info" 17 | } 18 | 19 | return func() tfprotov6.ProviderServer { 20 | logger := hclog.New(&hclog.LoggerOptions{ 21 | Level: hclog.LevelFromString(logLevel), 22 | Output: os.Stderr, 23 | }) 24 | 25 | return &(RawProviderServer{ 26 | logger: logger, 27 | }) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /isknown/provider/resource_apply.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | // "fmt" 6 | // "time" 7 | 8 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 9 | ) 10 | 11 | // ApplyResourceChange function 12 | func (s *UserProviderServer) ApplyResourceChange(ctx context.Context, req *tfprotov6.ApplyResourceChangeRequest) (*tfprotov6.ApplyResourceChangeResponse, error) { 13 | resp := &tfprotov6.ApplyResourceChangeResponse{} 14 | execDiag := s.canExecute() 15 | 16 | if len(execDiag) > 0 { 17 | resp.Diagnostics = append(resp.Diagnostics, execDiag...) 18 | return resp, nil 19 | } 20 | 21 | resp.NewState = req.PlannedState 22 | return resp, nil 23 | } 24 | -------------------------------------------------------------------------------- /isknown/provider/provider_schema.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 7 | 8 | "github.com/pseudo-dynamic/terraform-provider-value/internal/goproviderconfig" 9 | ) 10 | 11 | // GetProviderSchema function 12 | func (s *UserProviderServer) GetProviderSchema(ctx context.Context, req *tfprotov6.GetProviderSchemaRequest) (*tfprotov6.GetProviderSchemaResponse, error) { 13 | return &tfprotov6.GetProviderSchemaResponse{ 14 | Provider: goproviderconfig.GetProviderConfigSchema(), 15 | ResourceSchemas: getDocumentedProviderResourceSchema(s.resourceSchemaParams), 16 | ProviderMeta: goproviderconfig.GetProviderMetaSchema(), 17 | }, nil 18 | } 19 | -------------------------------------------------------------------------------- /examples/is_known/test_known_with_nested_unknown.tf: -------------------------------------------------------------------------------- 1 | // This example is complete but there are additional features implemented in terraform.tf! 2 | 3 | resource "value_unknown_proposer" "known_with_nested_unknown" {} 4 | 5 | resource "value_promise" "known_with_nested_unknown" { 6 | value = "test" 7 | } 8 | 9 | resource "value_is_known" "known_with_nested_unknown" { 10 | value = { 11 | nested = value_promise.known_with_nested_unknown.result 12 | } 13 | 14 | guid_seed = "nested_unknown" 15 | proposed_unknown = value_unknown_proposer.known_with_nested_unknown.value 16 | } 17 | 18 | output "is_known_with_nested_unknown_value" { 19 | value = { 20 | known = value_is_known.known_with_nested_unknown.result 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/is_fully_known/test_known_with_nested_unknown.tf: -------------------------------------------------------------------------------- 1 | // This example is complete but there are additional features implemented in terraform.tf! 2 | 3 | resource "value_unknown_proposer" "known_with_nested_unknown" {} 4 | 5 | resource "value_promise" "known_with_nested_unknown" { 6 | value = "test" 7 | } 8 | 9 | resource "value_is_fully_known" "known_with_nested_unknown" { 10 | value = { 11 | nested = value_promise.known_with_nested_unknown.result 12 | } 13 | 14 | guid_seed = "nested_unknown" 15 | proposed_unknown = value_unknown_proposer.known_with_nested_unknown.value 16 | } 17 | 18 | output "is_known_with_nested_unknown_value" { 19 | value = { 20 | fully_known = value_is_fully_known.known_with_nested_unknown.result 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /stash/provider/provider_schema.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | "log" 6 | 7 | "github.com/pseudo-dynamic/terraform-provider-value/internal/goproviderconfig" 8 | 9 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 10 | ) 11 | 12 | // GetProviderSchema function 13 | func (s *RawProviderServer) GetProviderSchema(ctx context.Context, req *tfprotov6.GetProviderSchemaRequest) (*tfprotov6.GetProviderSchemaResponse, error) { 14 | cfgSchema := goproviderconfig.GetProviderConfigSchema() 15 | resSchema := GetProviderResourceSchema() 16 | 17 | log.Println("--------------------------GetProviderSchema Called------------------------------") 18 | 19 | return &tfprotov6.GetProviderSchemaResponse{ 20 | Provider: cfgSchema, 21 | ResourceSchemas: resSchema, 22 | }, nil 23 | } 24 | -------------------------------------------------------------------------------- /promise/provider/provider_schema.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | "log" 6 | 7 | "github.com/pseudo-dynamic/terraform-provider-value/internal/goproviderconfig" 8 | 9 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 10 | ) 11 | 12 | // GetProviderSchema function 13 | func (s *RawProviderServer) GetProviderSchema(ctx context.Context, req *tfprotov6.GetProviderSchemaRequest) (*tfprotov6.GetProviderSchemaResponse, error) { 14 | cfgSchema := goproviderconfig.GetProviderConfigSchema() 15 | resSchema := GetProviderResourceSchema() 16 | 17 | log.Println("--------------------------GetProviderSchema Called------------------------------") 18 | 19 | return &tfprotov6.GetProviderSchemaResponse{ 20 | Provider: cfgSchema, 21 | ResourceSchemas: resSchema, 22 | }, nil 23 | } 24 | -------------------------------------------------------------------------------- /docs/resources/unknown_proposer.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "value_unknown_proposer Resource - terraform-provider-value" 4 | subcategory: "" 5 | description: |- 6 | This resource is very obscure and misbehaving and you really should only use it for value_is_known.proposed_unknown or value_is_fully_known.proposed_unknown. 7 | --- 8 | 9 | # value_unknown_proposer (Resource) 10 | 11 | This resource is very obscure and misbehaving and you really should only use it for `value_is_known.proposed_unknown` or `value_is_fully_known.proposed_unknown`. 12 | 13 | 14 | 15 | 16 | ## Schema 17 | 18 | ### Read-Only 19 | 20 | - `value` (Boolean) This value will **always** be unknown during the plan phase but always true after apply phase. 21 | 22 | 23 | -------------------------------------------------------------------------------- /isknown/provider/resource_read.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 7 | "github.com/pseudo-dynamic/terraform-provider-value/isknown/common" 8 | ) 9 | 10 | // ReadResource function 11 | func (s *UserProviderServer) ReadResource(ctx context.Context, req *tfprotov6.ReadResourceRequest) (*tfprotov6.ReadResourceResponse, error) { 12 | resp := &tfprotov6.ReadResourceResponse{} 13 | execDiag := s.canExecute() 14 | 15 | if len(execDiag) > 0 { 16 | resp.Diagnostics = append(resp.Diagnostics, execDiag...) 17 | return resp, nil 18 | } 19 | 20 | resourceType := getResourceType(req.TypeName) 21 | 22 | if _, _, canReadCurrentState := common.TryReadResource(req.CurrentState, resourceType, resp); !canReadCurrentState { 23 | return resp, nil 24 | } 25 | 26 | resp.NewState = req.CurrentState 27 | return resp, nil 28 | } 29 | -------------------------------------------------------------------------------- /examples/replaced_when/create_update_delete_detection/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | value = { 4 | source = "github.com/pseudo-dynamic/value" 5 | version = "0.1.0" 6 | } 7 | } 8 | } 9 | 10 | # resource "value_promise" "bool" { 11 | # value = true 12 | # } 13 | 14 | resource "value_replaced_when" "true" { 15 | count = 1 16 | condition = true 17 | } 18 | 19 | resource "value_stash" "replacement_trigger" { 20 | // This workaround detects not only resource creation 21 | // and every change of value but also the deletion of 22 | // resource. 23 | value = try(value_replaced_when.true[0].value, null) 24 | } 25 | 26 | resource "value_stash" "replaced" { 27 | lifecycle { 28 | // Replace me whenever value_replaced_when.true[0].value 29 | // changes or value_replaced_when[0] gets deleted. 30 | replace_triggered_by = [ 31 | value_stash.replacement_trigger.value 32 | ] 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /docs/resources/promise.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "value_promise Resource - terraform-provider-value" 4 | subcategory: "" 5 | description: |- 6 | Allows you to treat a value as unknown. This is desirable when you want postconditions first being evaluated during apply phase. 7 | --- 8 | 9 | # value_promise (Resource) 10 | 11 | Allows you to treat a value as unknown. This is desirable when you want postconditions first being evaluated during apply phase. 12 | 13 | 14 | 15 | 16 | ## Schema 17 | 18 | ### Required 19 | 20 | - `value` (Dynamic) The value to promise. Any (nested) change to `value` results into `result` to be marked as "(known after apply)" 21 | 22 | ### Read-Only 23 | 24 | - `result` (Dynamic) `result` is as soon as you apply set to `value`. Every change of `value` results into `result` to be marked as "(known after apply)" 25 | 26 | 27 | -------------------------------------------------------------------------------- /stash/provider/resource_plan.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 7 | ) 8 | 9 | // PlanResourceChange function 10 | func (s *RawProviderServer) PlanResourceChange(ctx context.Context, request *tfprotov6.PlanResourceChangeRequest) (*tfprotov6.PlanResourceChangeResponse, error) { 11 | response := &tfprotov6.PlanResourceChangeResponse{} 12 | execDiag := s.canExecute() 13 | 14 | if len(execDiag) > 0 { 15 | response.Diagnostics = append(response.Diagnostics, execDiag...) 16 | return response, nil 17 | } 18 | 19 | _, err := GetResourceType(request.TypeName) 20 | 21 | if err != nil { 22 | response.Diagnostics = append(response.Diagnostics, &tfprotov6.Diagnostic{ 23 | Severity: tfprotov6.DiagnosticSeverityError, 24 | Summary: "Failed to determine planned resource type", 25 | Detail: err.Error(), 26 | }) 27 | 28 | return response, nil 29 | } 30 | 31 | response.PlannedState = request.ProposedNewState 32 | return response, nil 33 | } 34 | -------------------------------------------------------------------------------- /internal/goproviderconfig/attribute_guid_seed.go: -------------------------------------------------------------------------------- 1 | package goproviderconfig 2 | 3 | func GetGuidSeedAttributeDescription(resourceName string) string { 4 | return "Attention! The seed is being used to determine resource uniqueness prior (first plan phase) " + 5 | "and during apply phase (second plan phase). Very important to state is that the **seed must be fully " + 6 | "known during the plan phase**, otherwise, an error is thrown. Within one terraform plan & apply the " + 7 | "**seed of every \"" + resourceName + "\" must be unique**! I really recommend you to use the " + 8 | "provider configuration and/or provider_meta configuration to increase resource uniqueness. " + 9 | "Besides `guid_seed`, the provider block seed addition, the provider_meta block seed addition " + 10 | "and the resource type itself will become part of the final seed. Under certain circumstances you " + 11 | "may face problems if you run terraform concurrenctly. If you do so, then I recommend you to " + 12 | "pass-through a random value via a user (environment) variable that you then add to the seed." 13 | } 14 | -------------------------------------------------------------------------------- /isknown/provider/resource_import.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 7 | ) 8 | 9 | // ImportResourceState function 10 | func (s *UserProviderServer) ImportResourceState(ctx context.Context, req *tfprotov6.ImportResourceStateRequest) (*tfprotov6.ImportResourceStateResponse, error) { 11 | // Terraform only gives us the schema name of the resource and an ID string, as passed by the user on the command line. 12 | // The ID should be a combination of a Kubernetes GVK and a namespace/name type of resource identifier. 13 | // Without the user supplying the GRV there is no way to fully identify the resource when making the Get API call to K8s. 14 | // Presumably the Kubernetes API machinery already has a standard for expressing such a group. We should look there first. 15 | resp := &tfprotov6.ImportResourceStateResponse{} 16 | 17 | resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ 18 | Severity: tfprotov6.DiagnosticSeverityError, 19 | Summary: "Import not supported", 20 | }) 21 | 22 | return resp, nil 23 | } 24 | -------------------------------------------------------------------------------- /promise/provider/resource_import.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 7 | ) 8 | 9 | // ImportResourceState function 10 | func (s *RawProviderServer) ImportResourceState(ctx context.Context, req *tfprotov6.ImportResourceStateRequest) (*tfprotov6.ImportResourceStateResponse, error) { 11 | // Terraform only gives us the schema name of the resource and an ID string, as passed by the user on the command line. 12 | // The ID should be a combination of a Kubernetes GVK and a namespace/name type of resource identifier. 13 | // Without the user supplying the GRV there is no way to fully identify the resource when making the Get API call to K8s. 14 | // Presumably the Kubernetes API machinery already has a standard for expressing such a group. We should look there first. 15 | resp := &tfprotov6.ImportResourceStateResponse{} 16 | 17 | resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ 18 | Severity: tfprotov6.DiagnosticSeverityError, 19 | Summary: "Import not supported", 20 | }) 21 | 22 | return resp, nil 23 | } 24 | -------------------------------------------------------------------------------- /stash/provider/resource_import.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 7 | ) 8 | 9 | // ImportResourceState function 10 | func (s *RawProviderServer) ImportResourceState(ctx context.Context, req *tfprotov6.ImportResourceStateRequest) (*tfprotov6.ImportResourceStateResponse, error) { 11 | // Terraform only gives us the schema name of the resource and an ID string, as passed by the user on the command line. 12 | // The ID should be a combination of a Kubernetes GVK and a namespace/name type of resource identifier. 13 | // Without the user supplying the GRV there is no way to fully identify the resource when making the Get API call to K8s. 14 | // Presumably the Kubernetes API machinery already has a standard for expressing such a group. We should look there first. 15 | resp := &tfprotov6.ImportResourceStateResponse{} 16 | 17 | resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ 18 | Severity: tfprotov6.DiagnosticSeverityError, 19 | Summary: "Import not supported", 20 | }) 21 | 22 | return resp, nil 23 | } 24 | -------------------------------------------------------------------------------- /internal/goproviderconfig/provider_config_schema.go: -------------------------------------------------------------------------------- 1 | package goproviderconfig 2 | 3 | import ( 4 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 5 | "github.com/hashicorp/terraform-plugin-go/tftypes" 6 | "github.com/pseudo-dynamic/terraform-provider-value/internal/schema" 7 | ) 8 | 9 | // GetProviderConfigType returns the tftypes.Type of a resource of type 'name' 10 | func GetProviderConfigType() tftypes.Type { 11 | sch := GetProviderConfigSchema() 12 | return schema.GetObjectTypeFromSchema(sch) 13 | } 14 | 15 | func GetProviderConfigSchema() *tfprotov6.Schema { 16 | return &tfprotov6.Schema{ 17 | Version: 0, 18 | Block: &tfprotov6.SchemaBlock{ 19 | Attributes: []*tfprotov6.SchemaAttribute{ 20 | GetGuidSeedAdditionSchemaAttribute(GetGuidSeedAdditionAttributeDescription()), 21 | }, 22 | }, 23 | } 24 | } 25 | 26 | func TryExtractProviderConfigGuidSeedAddition(providerConfig *tfprotov6.DynamicValue) (string, []*tfprotov6.Diagnostic, bool) { 27 | providerConfigType := GetProviderConfigType() 28 | return TryUnmarshalDynamicValueThenExtractGuidSeedAddition(providerConfig, providerConfigType) 29 | } 30 | -------------------------------------------------------------------------------- /stash/provider/resource_apply.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | // "fmt" 6 | // "time" 7 | 8 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 9 | // "github.com/hashicorp/terraform-plugin-go/tftypes" 10 | ) 11 | 12 | // ApplyResourceChange function 13 | func (s *RawProviderServer) ApplyResourceChange(ctx context.Context, request *tfprotov6.ApplyResourceChangeRequest) (*tfprotov6.ApplyResourceChangeResponse, error) { 14 | response := &tfprotov6.ApplyResourceChangeResponse{} 15 | execDiag := s.canExecute() 16 | 17 | if len(execDiag) > 0 { 18 | response.Diagnostics = append(response.Diagnostics, execDiag...) 19 | return response, nil 20 | } 21 | 22 | _, err := GetResourceType(request.TypeName) 23 | 24 | if err != nil { 25 | response.Diagnostics = append(response.Diagnostics, &tfprotov6.Diagnostic{ 26 | Severity: tfprotov6.DiagnosticSeverityError, 27 | Summary: "Failed to determine planned resource type", 28 | Detail: err.Error(), 29 | }) 30 | 31 | return response, nil 32 | } 33 | 34 | response.NewState = request.PlannedState 35 | return response, nil 36 | } 37 | -------------------------------------------------------------------------------- /isfullyknown/provider/provider.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 5 | 6 | "github.com/pseudo-dynamic/terraform-provider-value/isknown/common" 7 | isknown "github.com/pseudo-dynamic/terraform-provider-value/isknown/provider" 8 | ) 9 | 10 | func Provider() func() tfprotov6.ProviderServer { 11 | return isknown.ProviderConstructor(isknown.ProviderParameters{ 12 | CheckFullyKnown: true, 13 | }, common.ProviderResourceSchemaParameters{ 14 | ResourceName: "value_is_fully_known", 15 | ResourceDescription: "Allows you to have a access to `result` during plan phase that " + 16 | "states whether `value` or any nested attribute is marked as \"(known after apply)\" or not.", 17 | ValueDescription: "The `value` and if existing, nested attributes, are tested against \"(known after apply)\"", 18 | ResultDescription: "States whether `value` or any nested attribute is marked as \"(known after apply)\" or not. If `value` is an aggregate " + 19 | "type, not only the top level of the aggregate type is checked; elements and attributes " + 20 | "are checked too.", 21 | }) 22 | } 23 | -------------------------------------------------------------------------------- /promise/provider/provider_configure.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 8 | "golang.org/x/mod/semver" 9 | ) 10 | 11 | const minTFVersion string = "v0.14.8" 12 | 13 | // ValidateProviderConfig function 14 | func (s *RawProviderServer) ValidateProviderConfig(ctx context.Context, req *tfprotov6.ValidateProviderConfigRequest) (*tfprotov6.ValidateProviderConfigResponse, error) { 15 | resp := &tfprotov6.ValidateProviderConfigResponse{PreparedConfig: req.Config} 16 | return resp, nil 17 | } 18 | 19 | // ConfigureProvider function 20 | func (s *RawProviderServer) ConfigureProvider(ctx context.Context, req *tfprotov6.ConfigureProviderRequest) (*tfprotov6.ConfigureProviderResponse, error) { 21 | return &tfprotov6.ConfigureProviderResponse{}, nil 22 | } 23 | 24 | func (s *RawProviderServer) canExecute() (resp []*tfprotov6.Diagnostic) { 25 | if semver.IsValid(s.hostTFVersion) && semver.Compare(s.hostTFVersion, minTFVersion) < 0 { 26 | resp = append(resp, &tfprotov6.Diagnostic{ 27 | Severity: tfprotov6.DiagnosticSeverityError, 28 | Summary: "Incompatible terraform version", 29 | Detail: fmt.Sprintf("The resource requires Terraform %s or above", minTFVersion), 30 | }) 31 | } 32 | 33 | return 34 | } 35 | -------------------------------------------------------------------------------- /stash/provider/provider_configure.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 8 | "golang.org/x/mod/semver" 9 | ) 10 | 11 | const minTFVersion string = "v0.14.8" 12 | 13 | // ValidateProviderConfig function 14 | func (s *RawProviderServer) ValidateProviderConfig(ctx context.Context, req *tfprotov6.ValidateProviderConfigRequest) (*tfprotov6.ValidateProviderConfigResponse, error) { 15 | resp := &tfprotov6.ValidateProviderConfigResponse{PreparedConfig: req.Config} 16 | return resp, nil 17 | } 18 | 19 | // ConfigureProvider function 20 | func (s *RawProviderServer) ConfigureProvider(ctx context.Context, req *tfprotov6.ConfigureProviderRequest) (*tfprotov6.ConfigureProviderResponse, error) { 21 | return &tfprotov6.ConfigureProviderResponse{}, nil 22 | } 23 | 24 | func (s *RawProviderServer) canExecute() (resp []*tfprotov6.Diagnostic) { 25 | if semver.IsValid(s.hostTFVersion) && semver.Compare(s.hostTFVersion, minTFVersion) < 0 { 26 | resp = append(resp, &tfprotov6.Diagnostic{ 27 | Severity: tfprotov6.DiagnosticSeverityError, 28 | Summary: "Incompatible terraform version", 29 | Detail: fmt.Sprintf("The resource requires Terraform %s or above", minTFVersion), 30 | }) 31 | } 32 | 33 | return 34 | } 35 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "value Provider" 4 | subcategory: "" 5 | description: |- 6 | 7 | --- 8 | 9 | # value Provider 10 | 11 | 12 | 13 | 14 | 15 | 16 | ## Schema 17 | 18 | ### Optional 19 | 20 | - `guid_seed_addition` (String) It serves as an guid seed addition to those resources that implement `guid_seed` as an attribute. But there are scopes you need to keep in mind: if `guid_seed_addition` has been specified in the provider block then top-level and nested modules are using the provider block seed addition. If `guid_seed_addition` has been specified in the provider_meta block then only the resources of that module are using the module-level seed addition. Besides `guid_seed`, the provider block seed addition, the provider_meta block seed addition and the resource type itself will become part of the final seed. 21 | 22 | **Placeholders**: 23 | - "{workdir}" (Keyword) The actual workdir; equals to terraform's path.root. This placeholder is 24 | recommended because this value won't be dragged along the plan and apply phase in comparison to 25 | "abspath(path.root)" that you would add to resource seed where a change to path.root would be 26 | recognized just as usual from terraform. 27 | -------------------------------------------------------------------------------- /examples/replaced_when/file_inexistence_check/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | value = { 4 | source = "github.com/pseudo-dynamic/value" 5 | version = "0.1.0" 6 | } 7 | } 8 | 9 | provider_meta "value" { 10 | guid_seed_addition = "module(file_inexistence_check)" 11 | } 12 | } 13 | 14 | provider "value" { 15 | guid_seed_addition = "project(file_inexistence_check)" 16 | } 17 | 18 | resource "value_unknown_proposer" "default" {} 19 | 20 | locals { 21 | files = { 22 | "findme" : { 23 | fullname = "${path.module}/findme" 24 | } 25 | } 26 | } 27 | 28 | resource "value_os_path" "findme" { 29 | path = local.files["findme"].fullname 30 | guid_seed = "findme" 31 | proposed_unknown = value_unknown_proposer.default.value 32 | } 33 | 34 | resource "value_replaced_when" "findme_inexistence" { 35 | condition = !value_os_path.findme.exists 36 | } 37 | 38 | resource "local_file" "findme" { 39 | count = !value_os_path.findme.exists ? 1 : 0 40 | content = "" 41 | filename = local.files["findme"].fullname 42 | } 43 | 44 | output "is_findme_inexistent" { 45 | value = !value_os_path.findme.exists 46 | } 47 | 48 | output "findme_inexistence_caused_new_value" { 49 | value = value_replaced_when.findme_inexistence.value 50 | } 51 | -------------------------------------------------------------------------------- /isknown/provider/resource_schema.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 7 | "github.com/hashicorp/terraform-plugin-go/tftypes" 8 | 9 | "github.com/pseudo-dynamic/terraform-provider-value/internal/schema" 10 | "github.com/pseudo-dynamic/terraform-provider-value/isknown/common" 11 | ) 12 | 13 | // getResourceType returns the tftypes.Type of a resource of type 'name' 14 | func getResourceType(name string) tftypes.Type { 15 | sch := getProviderResourceSchema(name) 16 | rsch, ok := sch[name] 17 | 18 | if !ok { 19 | panic(fmt.Errorf("unknown resource %s - cannot find schema", name)) 20 | } 21 | 22 | return schema.GetObjectTypeFromSchema(rsch) 23 | } 24 | 25 | // getDocumentedProviderResourceSchema contains the definitions of all supported resources with documentations 26 | func getDocumentedProviderResourceSchema(params common.ProviderResourceSchemaParameters) map[string]*tfprotov6.Schema { 27 | return common.GetProviderResourceSchema(params) 28 | } 29 | 30 | // getProviderResourceSchema contains the definitions of all supported resources 31 | func getProviderResourceSchema(resourceName string) map[string]*tfprotov6.Schema { 32 | return getDocumentedProviderResourceSchema(common.ProviderResourceSchemaParameters{ 33 | ResourceName: resourceName, 34 | }) 35 | } 36 | -------------------------------------------------------------------------------- /stash/provider/schema_resource.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 7 | "github.com/hashicorp/terraform-plugin-go/tftypes" 8 | 9 | "github.com/pseudo-dynamic/terraform-provider-value/internal/schema" 10 | ) 11 | 12 | // GetResourceType returns the tftypes.Type of a resource of type 'name' 13 | func GetResourceType(name string) (tftypes.Type, error) { 14 | sch := GetProviderResourceSchema() 15 | rsch, ok := sch[name] 16 | 17 | if !ok { 18 | return tftypes.DynamicPseudoType, fmt.Errorf("unknown resource %s - cannot find schema", name) 19 | } 20 | 21 | return schema.GetObjectTypeFromSchema(rsch), nil 22 | } 23 | 24 | // GetProviderResourceSchema contains the definitions of all supported resources 25 | func GetProviderResourceSchema() map[string]*tfprotov6.Schema { 26 | return map[string]*tfprotov6.Schema{ 27 | "value_stash": { 28 | Version: 1, 29 | Block: &tfprotov6.SchemaBlock{ 30 | Description: "Allows you to manage any kind of value as resource.", 31 | BlockTypes: []*tfprotov6.SchemaNestedBlock{}, 32 | Attributes: []*tfprotov6.SchemaAttribute{ 33 | { 34 | Name: "value", 35 | Type: tftypes.DynamicPseudoType, 36 | Required: false, 37 | Optional: true, 38 | Computed: false, 39 | Description: "The value to store.", 40 | }, 41 | }, 42 | }, 43 | }, 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /internal/fwkproviderconfig/attribute_validator_plan_known.go: -------------------------------------------------------------------------------- 1 | package fwkproviderconfig 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/hashicorp/terraform-plugin-framework/diag" 7 | "github.com/hashicorp/terraform-plugin-framework/tfsdk" 8 | "github.com/hashicorp/terraform-plugin-framework/types" 9 | ) 10 | 11 | type PlanKnownValidator struct{} 12 | 13 | func ValidatePlanKnownString(value types.String, attributeName string, diags *diag.Diagnostics) bool { 14 | if value.IsUnknown() { 15 | diags.AddError( 16 | attributeName+" is unknown", 17 | attributeName+" must be fully known at plan-time. For further informations take a look into the documentation.") 18 | 19 | return false 20 | } 21 | 22 | return true 23 | } 24 | 25 | func (v *PlanKnownValidator) Validate(ctx context.Context, req tfsdk.ValidateAttributeRequest, resp *tfsdk.ValidateAttributeResponse) { 26 | if req.AttributeConfig != nil && req.AttributeConfig.IsUnknown() { 27 | resp.Diagnostics.AddError( 28 | req.AttributePath.String()+" is unknown", 29 | req.AttributePath.String()+" must be fully known at plan-time. For further informations take a look into the documentation.") 30 | } 31 | } 32 | 33 | func getPlanKnownValidatorDescription() string { 34 | return "Validates that the value is not unknown." 35 | } 36 | 37 | func (*PlanKnownValidator) Description(context.Context) string { 38 | return getPlanKnownValidatorDescription() 39 | } 40 | 41 | func (*PlanKnownValidator) MarkdownDescription(context.Context) string { 42 | return getPlanKnownValidatorDescription() 43 | } 44 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "os" 7 | 8 | internal "github.com/pseudo-dynamic/terraform-provider-value/internal/fwkprovider" 9 | isfullyknown "github.com/pseudo-dynamic/terraform-provider-value/isfullyknown/provider" 10 | isknown "github.com/pseudo-dynamic/terraform-provider-value/isknown/provider" 11 | promise "github.com/pseudo-dynamic/terraform-provider-value/promise/provider" 12 | stash "github.com/pseudo-dynamic/terraform-provider-value/stash/provider" 13 | 14 | "github.com/hashicorp/terraform-plugin-framework/providerserver" 15 | "github.com/hashicorp/terraform-plugin-go/tfprotov6/tf6server" 16 | "github.com/hashicorp/terraform-plugin-mux/tf6muxserver" 17 | ) 18 | 19 | // Generate the Terraform provider documentation using `tfplugindocs`: 20 | //go:generate go run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs 21 | 22 | func main() { 23 | stashProvider := stash.Provider() 24 | promiseProvider := promise.Provider() 25 | isKnownProvider := isknown.Provider() 26 | isFullyKnownProvider := isfullyknown.Provider() 27 | internalProvider := providerserver.NewProtocol6(internal.NewProvider()) 28 | ctx := context.Background() 29 | 30 | muxer, err := tf6muxserver.NewMuxServer( 31 | ctx, 32 | stashProvider, 33 | promiseProvider, 34 | isKnownProvider, 35 | isFullyKnownProvider, 36 | internalProvider, 37 | ) 38 | 39 | if err != nil { 40 | log.Println(err.Error()) 41 | os.Exit(1) 42 | } 43 | 44 | tf6server.Serve("registry.terraform.io/pseudo-dynamic/value", muxer.ProviderServer) 45 | } 46 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | # This GitHub action can publish assets for release when a tag is created. 2 | # Currently its setup to run on any tag that matches the pattern "v*" (ie. v0.1.0). 3 | # 4 | # This uses an action (hashicorp/ghaction-import-gpg) that assumes you set your 5 | # private key in the `GPG_PRIVATE_KEY` secret and passphrase in the `PASSPHRASE` 6 | # secret. If you would rather own your own GPG handling, please fork this action 7 | # or use an alternative one for key handling. 8 | # 9 | # You will need to pass the `--batch` flag to `gpg` in your signing step 10 | # in `goreleaser` to indicate this is being used in a non-interactive mode. 11 | # 12 | name: release 13 | on: 14 | push: 15 | tags: 16 | - 'v*' 17 | permissions: 18 | contents: write 19 | jobs: 20 | goreleaser: 21 | runs-on: ubuntu-latest 22 | steps: 23 | - 24 | name: Checkout 25 | uses: actions/checkout@v3 26 | - 27 | name: Unshallow 28 | run: git fetch --prune --unshallow 29 | - 30 | name: Set up Go 31 | uses: actions/setup-go@v3 32 | with: 33 | go-version-file: 'go.mod' 34 | cache: true 35 | - 36 | name: Import GPG key 37 | uses: crazy-max/ghaction-import-gpg@v5 38 | id: import_gpg 39 | with: 40 | gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} 41 | - 42 | name: Run GoReleaser 43 | uses: goreleaser/goreleaser-action@v3.1.0 44 | with: 45 | version: latest 46 | args: release --rm-dist 47 | env: 48 | GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} 49 | # GitHub sets this automatically 50 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /isknown/provider/provider_configure.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 8 | "github.com/pseudo-dynamic/terraform-provider-value/internal/goproviderconfig" 9 | "golang.org/x/mod/semver" 10 | ) 11 | 12 | const minTFVersion string = "v0.14.8" 13 | 14 | // ValidateProviderConfig function 15 | func (s *UserProviderServer) ValidateProviderConfig(ctx context.Context, req *tfprotov6.ValidateProviderConfigRequest) (*tfprotov6.ValidateProviderConfigResponse, error) { 16 | resp := &tfprotov6.ValidateProviderConfigResponse{PreparedConfig: req.Config} 17 | return resp, nil 18 | } 19 | 20 | // ConfigureProvider function 21 | func (s *UserProviderServer) ConfigureProvider(ctx context.Context, req *tfprotov6.ConfigureProviderRequest) (*tfprotov6.ConfigureProviderResponse, error) { 22 | var isWorking bool 23 | resp := &tfprotov6.ConfigureProviderResponse{} 24 | var diags []*tfprotov6.Diagnostic 25 | 26 | var providerConfigSeedAddition string 27 | if providerConfigSeedAddition, diags, isWorking = goproviderconfig.TryExtractProviderConfigGuidSeedAddition(req.Config); !isWorking { 28 | resp.Diagnostics = append(resp.Diagnostics, diags...) 29 | return resp, nil 30 | } 31 | 32 | s.ProviderConfigSeedAddition = providerConfigSeedAddition 33 | return resp, nil 34 | } 35 | 36 | func (s *UserProviderServer) canExecute() (resp []*tfprotov6.Diagnostic) { 37 | if semver.IsValid(s.hostTFVersion) && semver.Compare(s.hostTFVersion, minTFVersion) < 0 { 38 | resp = append(resp, &tfprotov6.Diagnostic{ 39 | Severity: tfprotov6.DiagnosticSeverityError, 40 | Summary: "Incompatible terraform version", 41 | Detail: fmt.Sprintf("The resource requires Terraform %s or above", minTFVersion), 42 | }) 43 | } 44 | 45 | return 46 | } 47 | -------------------------------------------------------------------------------- /internal/fwkproviderconfig/attribute_guid_seed_addition.go: -------------------------------------------------------------------------------- 1 | package fwkproviderconfig 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/hashicorp/terraform-plugin-framework/tfsdk" 7 | "github.com/hashicorp/terraform-plugin-framework/types" 8 | "github.com/pseudo-dynamic/terraform-provider-value/internal/goproviderconfig" 9 | ) 10 | 11 | const GuidSeedAdditionAttributeName string = "guid_seed_addition" 12 | 13 | func getGuidSeedAdditionAttribute() tfsdk.Attribute { 14 | return tfsdk.Attribute{ 15 | Type: types.StringType, 16 | Required: false, 17 | Optional: true, 18 | Computed: false, 19 | Validators: []tfsdk.AttributeValidator{ 20 | &PlanKnownValidator{}, 21 | }, 22 | PlanModifiers: tfsdk.AttributePlanModifiers{ 23 | guidSeedAdditionDefaultEmptyModifier{}, 24 | }, 25 | Description: goproviderconfig.GetGuidSeedAdditionAttributeDescription(), 26 | } 27 | } 28 | 29 | type guidSeedAdditionDefaultEmptyModifier struct{} 30 | 31 | func (r guidSeedAdditionDefaultEmptyModifier) Modify(ctx context.Context, req tfsdk.ModifyAttributePlanRequest, resp *tfsdk.ModifyAttributePlanResponse) { 32 | if req.AttributePlan == nil || req.AttributePlan.IsNull() { 33 | resp.AttributePlan = types.String{Value: ""} 34 | } 35 | } 36 | 37 | func getGuidSeedAdditionAttributeModifierDescription() string { 38 | return "If the value is null, then its value is now empty." 39 | } 40 | 41 | // Description returns a human-readable description of the plan modifier. 42 | func (guidSeedAdditionDefaultEmptyModifier) Description(context.Context) string { 43 | return getGuidSeedAdditionAttributeModifierDescription() 44 | } 45 | 46 | // MarkdownDescription returns a markdown description of the plan modifier. 47 | func (guidSeedAdditionDefaultEmptyModifier) MarkdownDescription(context.Context) string { 48 | return getGuidSeedAdditionAttributeModifierDescription() 49 | } 50 | -------------------------------------------------------------------------------- /internal/fwkprovider/temp_dir_data.go: -------------------------------------------------------------------------------- 1 | package fwkprovider 2 | 3 | import ( 4 | "context" 5 | "os" 6 | 7 | "github.com/hashicorp/terraform-plugin-framework/datasource" 8 | "github.com/hashicorp/terraform-plugin-framework/diag" 9 | "github.com/hashicorp/terraform-plugin-framework/tfsdk" 10 | "github.com/hashicorp/terraform-plugin-framework/types" 11 | ) 12 | 13 | type tempDirDataSource struct { 14 | } 15 | 16 | type tempDirDataSourceWithTraits interface { 17 | datasource.DataSource 18 | } 19 | 20 | func NewTempDirDataSource() tempDirDataSourceWithTraits { 21 | return &tempDirDataSource{} 22 | } 23 | 24 | func (r tempDirDataSource) GetSchema(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { 25 | return tfsdk.Schema{ 26 | Description: "Simply returns the OS-dependent temporary directory (e.g. /tmp).", 27 | Attributes: map[string]tfsdk.Attribute{ 28 | "path": { 29 | Type: types.StringType, 30 | Computed: true, 31 | Description: "The OS-dependent temporary directory.", 32 | }, 33 | }, 34 | }, nil 35 | } 36 | 37 | func (r *tempDirDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { 38 | resp.TypeName = req.ProviderTypeName + "_temp_dir" 39 | } 40 | 41 | type tempDirState struct { 42 | Path types.String `tfsdk:"path"` 43 | } 44 | 45 | // Read resource information 46 | func (r tempDirDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { 47 | // Get current state 48 | var state tempDirState 49 | diags := req.Config.Get(ctx, &state) 50 | resp.Diagnostics.Append(diags...) 51 | 52 | if resp.Diagnostics.HasError() { 53 | return 54 | } 55 | 56 | state.Path = types.String{Value: os.TempDir()} 57 | diags = resp.State.Set(ctx, &state) 58 | resp.Diagnostics.Append(diags...) 59 | 60 | if resp.Diagnostics.HasError() { 61 | return 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | # Visit https://goreleaser.com for documentation on how to customize this 2 | # behavior. 3 | before: 4 | hooks: 5 | # this is just an example and not a requirement for provider building/publishing 6 | - go mod tidy 7 | builds: 8 | - env: 9 | # goreleaser does not work with CGO, it could also complicate 10 | # usage by users in CI/CD systems like Terraform Cloud where 11 | # they are unable to install libraries. 12 | - CGO_ENABLED=0 13 | mod_timestamp: "{{ .CommitTimestamp }}" 14 | flags: 15 | - -trimpath 16 | ldflags: 17 | - "-s -w -X main.version={{.Version}} -X main.commit={{.Commit}}" 18 | goos: 19 | - freebsd 20 | - windows 21 | - linux 22 | - darwin 23 | goarch: 24 | - amd64 25 | - "386" 26 | - arm 27 | - arm64 28 | ignore: 29 | - goos: darwin 30 | goarch: "386" 31 | binary: "{{ .ProjectName }}_v{{ .Version }}" 32 | archives: 33 | - format: zip 34 | name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}" 35 | checksum: 36 | name_template: "{{ .ProjectName }}_{{ .Version }}_SHA256SUMS" 37 | algorithm: sha256 38 | signs: 39 | - artifacts: checksum 40 | args: 41 | # if you are using this is a GitHub action or some other automated pipeline, you 42 | # need to pass the batch flag to indicate its not interactive. 43 | - "--batch" 44 | - "--local-user" 45 | - "{{ .Env.GPG_FINGERPRINT }}" # set this environment variable for your signing key 46 | - "--output" 47 | - "${signature}" 48 | - "--detach-sign" 49 | - "${artifact}" 50 | release: 51 | github: 52 | owner: "pseudo-dynamic" 53 | name: "terraform-provider-value" 54 | # If you want to manually examine the release before its live, uncomment this line: 55 | # draft: true 56 | changelog: 57 | skip: true 58 | -------------------------------------------------------------------------------- /internal/goproviderconfig/provider_meta_schema.go: -------------------------------------------------------------------------------- 1 | package goproviderconfig 2 | 3 | import ( 4 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 5 | "github.com/hashicorp/terraform-plugin-go/tftypes" 6 | 7 | "github.com/pseudo-dynamic/terraform-provider-value/internal/schema" 8 | ) 9 | 10 | // GetProviderMetaType returns the tftypes.Type of a resource of type 'name' 11 | func GetProviderMetaType() tftypes.Type { 12 | sch := GetProviderMetaSchema() 13 | return schema.GetObjectTypeFromSchema(sch) 14 | } 15 | 16 | func GetProviderMetaSchema() *tfprotov6.Schema { 17 | return &tfprotov6.Schema{ 18 | Version: 0, 19 | Block: &tfprotov6.SchemaBlock{ 20 | Attributes: []*tfprotov6.SchemaAttribute{ 21 | GetGuidSeedAdditionSchemaAttribute(GetGuidSeedAdditionAttributeDescription()), 22 | }, 23 | }, 24 | } 25 | } 26 | 27 | func GetProviderMetaGuidSeedAdditionAttributeDescription() string { 28 | return "## Provider Metadata\n" + 29 | "Each module can use provider_meta. Please keep in mind that these settings only count " + 30 | "for resources of this module! (see [https://www.terraform.io/internals/provider-meta](https://www.terraform.io/internals/provider-meta)):\n" + 31 | "```terraform\n" + `// Terraform provider_meta example 32 | terraform { 33 | // "value" is the provider name 34 | provider_meta "value" { 35 | // {workdir} -> The only available placeholder currently (see below for more information) 36 | guid_seed_addition = "{workdir}#for-example" // Results into "/path/to/workdir#for-example" 37 | } 38 | }` + "\n```\n" + 39 | "### Optional\n" + 40 | "- `guid_seed_addition` (String) " + GetGuidSeedAdditionAttributeDescription() 41 | } 42 | 43 | func TryExtractProviderMetaGuidSeedAddition(providerMeta *tfprotov6.DynamicValue) (string, []*tfprotov6.Diagnostic, bool) { 44 | providerMetaType := GetProviderMetaType() 45 | return TryUnmarshalDynamicValueThenExtractGuidSeedAddition(providerMeta, providerMetaType) 46 | } 47 | -------------------------------------------------------------------------------- /promise/provider/schema_resource.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 7 | "github.com/hashicorp/terraform-plugin-go/tftypes" 8 | 9 | "github.com/pseudo-dynamic/terraform-provider-value/internal/schema" 10 | ) 11 | 12 | // GetResourceType returns the tftypes.Type of a resource of type 'name' 13 | func GetResourceType(name string) (tftypes.Type, error) { 14 | sch := GetProviderResourceSchema() 15 | rsch, ok := sch[name] 16 | 17 | if !ok { 18 | return tftypes.DynamicPseudoType, fmt.Errorf("unknown resource %s - cannot find schema", name) 19 | } 20 | 21 | return schema.GetObjectTypeFromSchema(rsch), nil 22 | } 23 | 24 | // GetProviderResourceSchema contains the definitions of all supported resources 25 | func GetProviderResourceSchema() map[string]*tfprotov6.Schema { 26 | return map[string]*tfprotov6.Schema{ 27 | "value_promise": { 28 | Version: 1, 29 | Block: &tfprotov6.SchemaBlock{ 30 | Description: "Allows you to treat a value as unknown. This is desirable when you want postconditions first being evaluated during apply phase.", 31 | BlockTypes: []*tfprotov6.SchemaNestedBlock{}, 32 | Attributes: []*tfprotov6.SchemaAttribute{ 33 | { 34 | Name: "value", 35 | Type: tftypes.DynamicPseudoType, 36 | Required: true, 37 | Optional: false, 38 | Computed: false, 39 | Description: "The value to promise. Any (nested) change to `value` results into `result` to be marked as \"(known after apply)\"", 40 | }, 41 | { 42 | Name: "result", 43 | Type: tftypes.DynamicPseudoType, 44 | Required: false, 45 | Optional: false, 46 | Computed: true, 47 | Description: "`result` is as soon as you apply set to `value`. Every change of `value` results into `result` to be marked as \"(known after apply)\"", 48 | }, 49 | }, 50 | }, 51 | }, 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TEST?=$$(go list ./... | grep -v 'vendor') 2 | HOSTNAME=github.com 3 | NAMESPACE=pseudo-dynamic 4 | NAME=value 5 | VERSION=0.1.0 6 | BINARY=terraform-provider-${NAME} 7 | BINARY_VERSION_CORE=${BINARY}_v${VERSION} 8 | 9 | ifdef APPDATA 10 | OS_ARCH=windows_amd64 11 | PLUGIN_CACHE=${APPDATA}\terraform.d\plugins 12 | BINARY_VERSION=${BINARY_VERSION_CORE}.exe 13 | else 14 | OS_ARCH=darwin_amd64 15 | PLUGIN_CACHE=~/.terraform.d/plugins 16 | BINARY_VERSION=${BINARY_VERSION_CORE} 17 | endif 18 | 19 | default: install 20 | 21 | build: 22 | go build -o ${BINARY_VERSION} 23 | 24 | generate: 25 | go generate 26 | 27 | # release: generate 28 | # GOOS=darwin GOARCH=amd64 go build -o ./bin/${BINARY}_${VERSION}_darwin_amd64 29 | # GOOS=freebsd GOARCH=386 go build -o ./bin/${BINARY}_${VERSION}_freebsd_386 30 | # GOOS=freebsd GOARCH=amd64 go build -o ./bin/${BINARY}_${VERSION}_freebsd_amd64 31 | # GOOS=freebsd GOARCH=arm go build -o ./bin/${BINARY}_${VERSION}_freebsd_arm 32 | # GOOS=linux GOARCH=386 go build -o ./bin/${BINARY}_${VERSION}_linux_386 33 | # GOOS=linux GOARCH=amd64 go build -o ./bin/${BINARY}_${VERSION}_linux_amd64 34 | # GOOS=linux GOARCH=arm go build -o ./bin/${BINARY}_${VERSION}_linux_arm 35 | # GOOS=openbsd GOARCH=386 go build -o ./bin/${BINARY}_${VERSION}_openbsd_386 36 | # GOOS=openbsd GOARCH=amd64 go build -o ./bin/${BINARY}_${VERSION}_openbsd_amd64 37 | # GOOS=solaris GOARCH=amd64 go build -o ./bin/${BINARY}_${VERSION}_solaris_amd64 38 | # GOOS=windows GOARCH=386 go build -o ./bin/${BINARY}_${VERSION}_windows_386 39 | # GOOS=windows GOARCH=amd64 go build -o ./bin/${BINARY}_${VERSION}_windows_amd64 40 | 41 | install: build 42 | mkdir -p ${PLUGIN_CACHE}/${HOSTNAME}/${NAMESPACE}/${NAME}/${VERSION}/${OS_ARCH} 43 | mv ${BINARY_VERSION} ${PLUGIN_CACHE}/${HOSTNAME}/${NAMESPACE}/${NAME}/${VERSION}/${OS_ARCH}/ 44 | 45 | # test: 46 | # go test -i $(TEST) || exit 1 47 | # echo $(TEST) | xargs -t -n4 go test $(TESTARGS) -timeout=30s -parallel=4 48 | -------------------------------------------------------------------------------- /isknown/provider/provider_server.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 7 | "github.com/pseudo-dynamic/terraform-provider-value/internal/schema" 8 | "google.golang.org/grpc/codes" 9 | "google.golang.org/grpc/status" 10 | ) 11 | 12 | // UpgradeResourceState isn't really useful in this provider, but we have to loop the state back through to keep Terraform happy. 13 | func (s *UserProviderServer) UpgradeResourceState(ctx context.Context, req *tfprotov6.UpgradeResourceStateRequest) (*tfprotov6.UpgradeResourceStateResponse, error) { 14 | resp := &tfprotov6.UpgradeResourceStateResponse{} 15 | resp.Diagnostics = []*tfprotov6.Diagnostic{} 16 | 17 | sch := getProviderResourceSchema(req.TypeName) 18 | rt := schema.GetObjectTypeFromSchema(sch[req.TypeName]) 19 | 20 | rv, err := req.RawState.Unmarshal(rt) 21 | 22 | if err != nil { 23 | resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ 24 | Severity: tfprotov6.DiagnosticSeverityError, 25 | Summary: "Failed to decode old state during upgrade", 26 | Detail: err.Error(), 27 | }) 28 | return resp, nil 29 | } 30 | 31 | us, err := tfprotov6.NewDynamicValue(rt, rv) 32 | 33 | if err != nil { 34 | resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ 35 | Severity: tfprotov6.DiagnosticSeverityError, 36 | Summary: "Failed to encode new state during upgrade", 37 | Detail: err.Error(), 38 | }) 39 | } 40 | 41 | resp.UpgradedState = &us 42 | return resp, nil 43 | } 44 | 45 | // ReadDataSource function 46 | func (s *UserProviderServer) ReadDataSource(ctx context.Context, req *tfprotov6.ReadDataSourceRequest) (*tfprotov6.ReadDataSourceResponse, error) { 47 | return nil, status.Errorf(codes.Unimplemented, "method ReadDataSource not implemented") 48 | } 49 | 50 | // StopProvider function 51 | func (s *UserProviderServer) StopProvider(ctx context.Context, req *tfprotov6.StopProviderRequest) (*tfprotov6.StopProviderResponse, error) { 52 | return nil, status.Errorf(codes.Unimplemented, "method StopProvider not implemented") 53 | } 54 | -------------------------------------------------------------------------------- /isknown/common/provider_resource_schema.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 5 | "github.com/hashicorp/terraform-plugin-go/tftypes" 6 | "github.com/pseudo-dynamic/terraform-provider-value/internal/goproviderconfig" 7 | ) 8 | 9 | type ProviderResourceSchemaParameters struct { 10 | ResourceName string 11 | ResourceDescription string 12 | ValueDescription string 13 | ResultDescription string 14 | } 15 | 16 | // GetProviderResourceSchema contains the definitions of all supported resources 17 | func GetProviderResourceSchema(schema ProviderResourceSchemaParameters) map[string]*tfprotov6.Schema { 18 | return map[string]*tfprotov6.Schema{ 19 | schema.ResourceName: { 20 | Version: 1, 21 | Block: &tfprotov6.SchemaBlock{ 22 | Description: schema.ResourceDescription + "\n" + goproviderconfig.GetProviderMetaGuidSeedAdditionAttributeDescription(), 23 | BlockTypes: []*tfprotov6.SchemaNestedBlock{}, 24 | Attributes: []*tfprotov6.SchemaAttribute{ 25 | { 26 | Name: "value", 27 | Type: tftypes.DynamicPseudoType, 28 | Required: true, 29 | Optional: false, 30 | Computed: false, 31 | Description: schema.ValueDescription, 32 | }, 33 | { 34 | Name: "guid_seed", 35 | Type: tftypes.String, 36 | Required: true, 37 | Optional: false, 38 | Computed: false, 39 | Description: goproviderconfig.GetGuidSeedAttributeDescription(schema.ResourceName), 40 | }, 41 | { 42 | Name: "proposed_unknown", 43 | Type: tftypes.DynamicPseudoType, 44 | Required: true, 45 | Optional: false, 46 | Computed: false, 47 | Description: goproviderconfig.GetProposedUnknownAttributeDescription(), 48 | }, 49 | { 50 | Name: "result", 51 | Type: tftypes.Bool, 52 | Required: false, 53 | Optional: true, 54 | Computed: true, 55 | Description: schema.ResultDescription, 56 | }, 57 | }, 58 | }, 59 | }, 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /promise/provider/resource_type_validate.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | "log" 6 | 7 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 8 | "github.com/hashicorp/terraform-plugin-go/tftypes" 9 | ) 10 | 11 | // ValidateResourceConfig function 12 | func (s *RawProviderServer) ValidateResourceConfig(ctx context.Context, req *tfprotov6.ValidateResourceConfigRequest) (*tfprotov6.ValidateResourceConfigResponse, error) { 13 | resp := &tfprotov6.ValidateResourceConfigResponse{} 14 | rt, err := GetResourceType(req.TypeName) 15 | 16 | if err != nil { 17 | resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ 18 | Severity: tfprotov6.DiagnosticSeverityError, 19 | Summary: "Failed to determine resource type", 20 | Detail: err.Error(), 21 | }) 22 | 23 | return resp, nil 24 | } 25 | 26 | log.Println("--------------------TEST----------------------") 27 | log.Printf("ResourceType: %v\n", rt) 28 | 29 | // Decode proposed resource state 30 | config, err := req.Config.Unmarshal(rt) 31 | if err != nil { 32 | resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ 33 | Severity: tfprotov6.DiagnosticSeverityError, 34 | Summary: "Failed to unmarshal resource state", 35 | Detail: err.Error(), 36 | }) 37 | 38 | return resp, nil 39 | } 40 | 41 | att := tftypes.NewAttributePath() 42 | att = att.WithAttributeName("value") 43 | 44 | configVal := make(map[string]tftypes.Value) 45 | err = config.As(&configVal) 46 | 47 | if err != nil { 48 | resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ 49 | Severity: tfprotov6.DiagnosticSeverityError, 50 | Summary: "Failed to extract resource state from SDK value", 51 | Detail: err.Error(), 52 | }) 53 | 54 | return resp, nil 55 | } 56 | 57 | _, ok := configVal["value"] 58 | 59 | if !ok { 60 | resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ 61 | Severity: tfprotov6.DiagnosticSeverityError, 62 | Summary: "Value missing from resource configuration", 63 | Detail: "A value attribute containing a valid terraform value is required.", 64 | Attribute: att, 65 | }) 66 | 67 | return resp, nil 68 | } 69 | 70 | return resp, nil 71 | } 72 | -------------------------------------------------------------------------------- /stash/provider/resource_type_validate.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | "log" 6 | 7 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 8 | "github.com/hashicorp/terraform-plugin-go/tftypes" 9 | ) 10 | 11 | // ValidateResourceConfig function 12 | func (s *RawProviderServer) ValidateResourceConfig(ctx context.Context, req *tfprotov6.ValidateResourceConfigRequest) (*tfprotov6.ValidateResourceConfigResponse, error) { 13 | resp := &tfprotov6.ValidateResourceConfigResponse{} 14 | rt, err := GetResourceType(req.TypeName) 15 | 16 | if err != nil { 17 | resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ 18 | Severity: tfprotov6.DiagnosticSeverityError, 19 | Summary: "Failed to determine resource type", 20 | Detail: err.Error(), 21 | }) 22 | 23 | return resp, nil 24 | } 25 | 26 | log.Println("--------------------TEST----------------------") 27 | log.Printf("ResourceType: %v\n", rt) 28 | 29 | // Decode proposed resource state 30 | config, err := req.Config.Unmarshal(rt) 31 | if err != nil { 32 | resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ 33 | Severity: tfprotov6.DiagnosticSeverityError, 34 | Summary: "Failed to unmarshal resource state", 35 | Detail: err.Error(), 36 | }) 37 | 38 | return resp, nil 39 | } 40 | 41 | att := tftypes.NewAttributePath() 42 | att = att.WithAttributeName("value") 43 | 44 | configVal := make(map[string]tftypes.Value) 45 | err = config.As(&configVal) 46 | 47 | if err != nil { 48 | resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ 49 | Severity: tfprotov6.DiagnosticSeverityError, 50 | Summary: "Failed to extract resource state from SDK value", 51 | Detail: err.Error(), 52 | }) 53 | 54 | return resp, nil 55 | } 56 | 57 | _, ok := configVal["value"] 58 | 59 | if !ok { 60 | resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ 61 | Severity: tfprotov6.DiagnosticSeverityError, 62 | Summary: "Value missing from resource configuration", 63 | Detail: "A value attribute containing a valid terraform value is required.", 64 | Attribute: att, 65 | }) 66 | 67 | return resp, nil 68 | } 69 | 70 | return resp, nil 71 | } 72 | -------------------------------------------------------------------------------- /stash/provider/resource_read.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 7 | "github.com/hashicorp/terraform-plugin-go/tftypes" 8 | ) 9 | 10 | // ReadResource function 11 | func (s *RawProviderServer) ReadResource(ctx context.Context, req *tfprotov6.ReadResourceRequest) (*tfprotov6.ReadResourceResponse, error) { 12 | resp := &tfprotov6.ReadResourceResponse{} 13 | execDiag := s.canExecute() 14 | 15 | if len(execDiag) > 0 { 16 | resp.Diagnostics = append(resp.Diagnostics, execDiag...) 17 | return resp, nil 18 | } 19 | 20 | var resState map[string]tftypes.Value 21 | var err error 22 | rt, err := GetResourceType(req.TypeName) 23 | 24 | if err != nil { 25 | resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ 26 | Severity: tfprotov6.DiagnosticSeverityError, 27 | Summary: "Failed to determine resource type", 28 | Detail: err.Error(), 29 | }) 30 | return resp, nil 31 | } 32 | 33 | currentState, err := req.CurrentState.Unmarshal(rt) 34 | 35 | if err != nil { 36 | resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ 37 | Severity: tfprotov6.DiagnosticSeverityError, 38 | Summary: "Failed to decode current state", 39 | Detail: err.Error(), 40 | }) 41 | 42 | return resp, nil 43 | } 44 | 45 | if currentState.IsNull() { 46 | resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ 47 | Severity: tfprotov6.DiagnosticSeverityError, 48 | Summary: "Failed to read resource", 49 | Detail: "Incomplete or missing state", 50 | }) 51 | 52 | return resp, nil 53 | } 54 | 55 | err = currentState.As(&resState) 56 | 57 | if err != nil { 58 | resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ 59 | Severity: tfprotov6.DiagnosticSeverityError, 60 | Summary: "Failed to extract resource from current state", 61 | Detail: err.Error(), 62 | }) 63 | 64 | return resp, nil 65 | } 66 | 67 | _, isValueExisting := resState["value"] 68 | 69 | if !isValueExisting { 70 | resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ 71 | Severity: tfprotov6.DiagnosticSeverityError, 72 | Summary: "Current state of resource has no 'value' attribute", 73 | Detail: "This should not happen. The state may be incomplete or corrupted.\nIf this error is reproducible, please report issue to provider maintainers.", 74 | }) 75 | 76 | return resp, nil 77 | } 78 | 79 | resp.NewState = req.CurrentState 80 | return resp, nil 81 | } 82 | -------------------------------------------------------------------------------- /isknown/provider/provider.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/hashicorp/go-hclog" 7 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 8 | "github.com/pseudo-dynamic/terraform-provider-value/isknown/common" 9 | ) 10 | 11 | // UserProviderServer implements the ProviderServer interface as exported from ProtoBuf. 12 | type UserProviderServer struct { 13 | // Since the provider is essentially a gRPC server, the execution flow is dictated by the order of the client (Terraform) request calls. 14 | // Thus it needs a way to persist state between the gRPC calls. These attributes store values that need to be persisted between gRPC calls, 15 | // such as instances of the Kubernetes clients, configuration options needed at runtime. 16 | logger hclog.Logger 17 | //providerEnabled bool 18 | hostTFVersion string 19 | params ProviderParameters 20 | resourceSchemaParams common.ProviderResourceSchemaParameters 21 | ProviderConfigSeedAddition string 22 | } 23 | 24 | type ProviderParameters struct { 25 | CheckFullyKnown bool 26 | } 27 | 28 | // ProviderConstructor 29 | func ProviderConstructor(providerParams ProviderParameters, resourceSchemaParams common.ProviderResourceSchemaParameters) func() tfprotov6.ProviderServer { 30 | var logLevel string 31 | logLevel, ok := os.LookupEnv("TF_LOG") 32 | 33 | if !ok { 34 | logLevel = "info" 35 | } 36 | 37 | return func() tfprotov6.ProviderServer { 38 | logger := hclog.New(&hclog.LoggerOptions{ 39 | Level: hclog.LevelFromString(logLevel), 40 | Output: os.Stderr, 41 | }) 42 | 43 | return &UserProviderServer{ 44 | logger: logger, 45 | params: providerParams, 46 | resourceSchemaParams: resourceSchemaParams, 47 | } 48 | } 49 | } 50 | 51 | func Provider() func() tfprotov6.ProviderServer { 52 | return ProviderConstructor(ProviderParameters{ 53 | CheckFullyKnown: false, 54 | }, common.ProviderResourceSchemaParameters{ 55 | ResourceName: "value_is_known", 56 | ResourceDescription: "Allows you to have a access to `result` during plan phase that " + 57 | "states whether `value` marked as \"(known after apply)\" or not.", 58 | ValueDescription: "The `value` (not nested attributes) is test against \"(known after apply)\"", 59 | ResultDescription: "States whether `value` is marked as \"(known after apply)\" or not. If `value` is an aggregate " + 60 | "type, only the top level of the aggregate type is checked; elements and attributes " + 61 | "are not checked.", 62 | }) 63 | } 64 | -------------------------------------------------------------------------------- /isknown/provider/resource_type_validate.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 7 | "github.com/hashicorp/terraform-plugin-go/tftypes" 8 | ) 9 | 10 | // ValidateDataResourceConfig function 11 | func (s *UserProviderServer) ValidateDataResourceConfig(ctx context.Context, req *tfprotov6.ValidateDataResourceConfigRequest) (*tfprotov6.ValidateDataResourceConfigResponse, error) { 12 | resp := &tfprotov6.ValidateDataResourceConfigResponse{} 13 | return resp, nil 14 | } 15 | 16 | // ValidateResourceConfig function 17 | func (s *UserProviderServer) ValidateResourceConfig(ctx context.Context, req *tfprotov6.ValidateResourceConfigRequest) (*tfprotov6.ValidateResourceConfigResponse, error) { 18 | resp := &tfprotov6.ValidateResourceConfigResponse{} 19 | resourceType := getResourceType(req.TypeName) 20 | 21 | // Decode proposed resource state 22 | config, err := req.Config.Unmarshal(resourceType) 23 | if err != nil { 24 | resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ 25 | Severity: tfprotov6.DiagnosticSeverityError, 26 | Summary: "Failed to unmarshal resource state", 27 | Detail: err.Error(), 28 | }) 29 | 30 | return resp, nil 31 | } 32 | 33 | att := tftypes.NewAttributePath() 34 | att = att.WithAttributeName("value") 35 | 36 | configVal := make(map[string]tftypes.Value) 37 | err = config.As(&configVal) 38 | 39 | if err != nil { 40 | resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ 41 | Severity: tfprotov6.DiagnosticSeverityError, 42 | Summary: "Failed to extract resource state from SDK value", 43 | Detail: err.Error(), 44 | }) 45 | 46 | return resp, nil 47 | } 48 | 49 | _, ok := configVal["value"] 50 | 51 | if !ok { 52 | resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ 53 | Severity: tfprotov6.DiagnosticSeverityError, 54 | Summary: "Value missing from resource configuration", 55 | Detail: "A value attribute containing a valid terraform value is required.", 56 | Attribute: att, 57 | }) 58 | 59 | return resp, nil 60 | } 61 | 62 | _, ok = configVal["guid_seed"] 63 | 64 | if !ok { 65 | resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ 66 | Severity: tfprotov6.DiagnosticSeverityError, 67 | Summary: "Unique seed missing from resource configuration", 68 | Detail: "A guid_seed attribute containing a valid terraform value is required.", 69 | Attribute: att, 70 | }) 71 | 72 | return resp, nil 73 | } 74 | 75 | return resp, nil 76 | } 77 | -------------------------------------------------------------------------------- /internal/schema/main.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 5 | "github.com/hashicorp/terraform-plugin-go/tftypes" 6 | ) 7 | 8 | // GetObjectTypeFromSchema returns a tftypes.Type that can wholy represent the schema input 9 | // TODO: Outsource this and reduce redudancy across terraform-plugin-go providers 10 | func GetObjectTypeFromSchema(schema *tfprotov6.Schema) tftypes.Type { 11 | bm := map[string]tftypes.Type{} 12 | 13 | for _, att := range schema.Block.Attributes { 14 | bm[att.Name] = att.Type 15 | } 16 | 17 | for _, b := range schema.Block.BlockTypes { 18 | a := map[string]tftypes.Type{} 19 | for _, att := range b.Block.Attributes { 20 | a[att.Name] = att.Type 21 | } 22 | bm[b.TypeName] = tftypes.List{ 23 | ElementType: tftypes.Object{AttributeTypes: a}, 24 | } 25 | 26 | // FIXME we can make this function recursive to handle 27 | // n levels of nested blocks 28 | for _, bb := range b.Block.BlockTypes { 29 | aa := map[string]tftypes.Type{} 30 | for _, att := range bb.Block.Attributes { 31 | aa[att.Name] = att.Type 32 | } 33 | a[bb.TypeName] = tftypes.List{ 34 | ElementType: tftypes.Object{AttributeTypes: aa}, 35 | } 36 | } 37 | } 38 | 39 | return tftypes.Object{AttributeTypes: bm} 40 | } 41 | 42 | func UnmarshalDynamicValue(state *tfprotov6.DynamicValue, stateType tftypes.Type) (tftypes.Value, map[string]tftypes.Value, []*tfprotov6.Diagnostic, bool) { 43 | diags := []*tfprotov6.Diagnostic{} 44 | valueMap := make(map[string]tftypes.Value) 45 | value, err := state.Unmarshal(stateType) 46 | 47 | if err != nil { 48 | diags = append(diags, &tfprotov6.Diagnostic{ 49 | Severity: tfprotov6.DiagnosticSeverityError, 50 | Summary: "Failed to unmarshal state", 51 | Detail: err.Error(), 52 | }) 53 | 54 | goto End 55 | } 56 | 57 | { 58 | valueMap, diags, isErroneous := UnmarshalValue(&value) 59 | return value, valueMap, diags, isErroneous 60 | } 61 | 62 | End: 63 | return value, valueMap, diags, len(diags) != 0 64 | } 65 | 66 | func UnmarshalValue(value *tftypes.Value) (map[string]tftypes.Value, []*tfprotov6.Diagnostic, bool) { 67 | diags := []*tfprotov6.Diagnostic{} 68 | valueMap := make(map[string]tftypes.Value) 69 | err := value.As(&valueMap) 70 | 71 | if err != nil { 72 | diags = append(diags, &tfprotov6.Diagnostic{ 73 | Severity: tfprotov6.DiagnosticSeverityError, 74 | Summary: "Failed to extract state from tftypes.Value", 75 | Detail: err.Error(), 76 | }) 77 | 78 | goto End 79 | } 80 | 81 | End: 82 | return valueMap, diags, len(diags) != 0 83 | } 84 | -------------------------------------------------------------------------------- /internal/fwkprovider/provider.go: -------------------------------------------------------------------------------- 1 | package fwkprovider 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/hashicorp/terraform-plugin-framework/datasource" 7 | "github.com/hashicorp/terraform-plugin-framework/diag" 8 | fwkprovider "github.com/hashicorp/terraform-plugin-framework/provider" 9 | "github.com/hashicorp/terraform-plugin-framework/resource" 10 | "github.com/hashicorp/terraform-plugin-framework/tfsdk" 11 | "github.com/hashicorp/terraform-plugin-framework/types" 12 | "github.com/pseudo-dynamic/terraform-provider-value/internal/fwkproviderconfig" 13 | ) 14 | 15 | const providerName string = "value" 16 | 17 | type provider struct { 18 | } 19 | 20 | type providerWithTraits interface { 21 | fwkprovider.ProviderWithMetadata 22 | fwkprovider.ProviderWithResources 23 | fwkprovider.ProviderWithDataSources 24 | fwkprovider.ProviderWithMetaSchema 25 | } 26 | 27 | // Provider schema struct 28 | type providerConfig struct { 29 | GuidSeedAddition types.String `tfsdk:"guid_seed_addition"` 30 | } 31 | 32 | type providerData struct { 33 | GuidSeedAddition *string 34 | } 35 | 36 | func NewProvider() providerWithTraits { 37 | return &provider{} 38 | } 39 | 40 | // Metadata implements ProviderWithTraits 41 | func (p *provider) Metadata(ctx context.Context, req fwkprovider.MetadataRequest, resp *fwkprovider.MetadataResponse) { 42 | resp.TypeName = "value" 43 | } 44 | 45 | // GetSchema 46 | func (p *provider) GetSchema(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { 47 | return *fwkproviderconfig.GetProviderConfigSchema(), nil 48 | } 49 | 50 | // GetMetaSchema implements providerWithTraits 51 | func (*provider) GetMetaSchema(context.Context) (tfsdk.Schema, diag.Diagnostics) { 52 | return *fwkproviderconfig.GetProviderMetaSchema(), nil 53 | } 54 | 55 | func (p *provider) Configure(ctx context.Context, req fwkprovider.ConfigureRequest, resp *fwkprovider.ConfigureResponse) { 56 | // Retrieve provider data from configuration 57 | var config providerConfig 58 | diags := req.Config.Get(ctx, &config) 59 | resp.Diagnostics.Append(diags...) 60 | 61 | if resp.Diagnostics.HasError() { 62 | return 63 | } 64 | 65 | resp.ResourceData = &providerData{ 66 | GuidSeedAddition: &config.GuidSeedAddition.Value, 67 | } 68 | } 69 | 70 | // GetResources - Defines provider resources 71 | func (p *provider) Resources(_ context.Context) []func() resource.Resource { 72 | return []func() resource.Resource{ 73 | func() resource.Resource { 74 | return NewReplacedWhenResource() 75 | }, 76 | func() resource.Resource { 77 | return NewUnknownProposerResource() 78 | }, 79 | func() resource.Resource { 80 | return NewOSPathResource() 81 | }, 82 | } 83 | } 84 | 85 | // DataSources implements ProviderWithTraits 86 | func (*provider) DataSources(context.Context) []func() datasource.DataSource { 87 | return []func() datasource.DataSource{ 88 | func() datasource.DataSource { 89 | return NewTempDirDataSource() 90 | }, 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /promise/provider/resource_read.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 7 | "github.com/hashicorp/terraform-plugin-go/tftypes" 8 | ) 9 | 10 | // ReadResource function 11 | func (s *RawProviderServer) ReadResource(ctx context.Context, req *tfprotov6.ReadResourceRequest) (*tfprotov6.ReadResourceResponse, error) { 12 | resp := &tfprotov6.ReadResourceResponse{} 13 | execDiag := s.canExecute() 14 | 15 | if len(execDiag) > 0 { 16 | resp.Diagnostics = append(resp.Diagnostics, execDiag...) 17 | return resp, nil 18 | } 19 | 20 | var resState map[string]tftypes.Value 21 | var err error 22 | rt, err := GetResourceType(req.TypeName) 23 | 24 | if err != nil { 25 | resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ 26 | Severity: tfprotov6.DiagnosticSeverityError, 27 | Summary: "Failed to determine resource type", 28 | Detail: err.Error(), 29 | }) 30 | 31 | return resp, nil 32 | } 33 | 34 | currentState, err := req.CurrentState.Unmarshal(rt) 35 | 36 | if err != nil { 37 | resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ 38 | Severity: tfprotov6.DiagnosticSeverityError, 39 | Summary: "Failed to decode current state", 40 | Detail: err.Error(), 41 | }) 42 | 43 | return resp, nil 44 | } 45 | 46 | if currentState.IsNull() { 47 | resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ 48 | Severity: tfprotov6.DiagnosticSeverityError, 49 | Summary: "Failed to read resource", 50 | Detail: "Incomplete or missing state", 51 | }) 52 | 53 | return resp, nil 54 | } 55 | 56 | err = currentState.As(&resState) 57 | 58 | if err != nil { 59 | resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ 60 | Severity: tfprotov6.DiagnosticSeverityError, 61 | Summary: "Failed to extract resource from current state", 62 | Detail: err.Error(), 63 | }) 64 | 65 | return resp, nil 66 | } 67 | 68 | _, isValueExisting := resState["value"] 69 | 70 | if !isValueExisting { 71 | resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ 72 | Severity: tfprotov6.DiagnosticSeverityError, 73 | Summary: "Current state of resource has no 'value' attribute", 74 | Detail: "This should not happen. The state may be incomplete or corrupted.\nIf this error is reproducible, please report issue to provider maintainers.", 75 | }) 76 | 77 | return resp, nil 78 | } 79 | 80 | _, isResultExisting := resState["result"] 81 | 82 | if !isResultExisting { 83 | resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ 84 | Severity: tfprotov6.DiagnosticSeverityError, 85 | Summary: "Current state of resource has no 'result' attribute", 86 | Detail: "This should not happen. The state may be incomplete or corrupted.\nIf this error is reproducible, please report issue to provider maintainers.", 87 | }) 88 | 89 | return resp, nil 90 | } 91 | 92 | resp.NewState = req.CurrentState 93 | return resp, nil 94 | } 95 | -------------------------------------------------------------------------------- /promise/provider/provider_server.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/pseudo-dynamic/terraform-provider-value/internal/schema" 7 | 8 | "github.com/hashicorp/go-hclog" 9 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 10 | "google.golang.org/grpc/codes" 11 | "google.golang.org/grpc/status" 12 | ) 13 | 14 | // RawProviderServer implements the ProviderServer interface as exported from ProtoBuf. 15 | type RawProviderServer struct { 16 | // Since the provider is essentially a gRPC server, the execution flow is dictated by the order of the client (Terraform) request calls. 17 | // Thus it needs a way to persist state between the gRPC calls. These attributes store values that need to be persisted between gRPC calls, 18 | // such as instances of the Kubernetes clients, configuration options needed at runtime. 19 | logger hclog.Logger 20 | 21 | //providerEnabled bool 22 | hostTFVersion string 23 | } 24 | 25 | // ValidateDataResourceConfig function 26 | func (s *RawProviderServer) ValidateDataResourceConfig(ctx context.Context, req *tfprotov6.ValidateDataResourceConfigRequest) (*tfprotov6.ValidateDataResourceConfigResponse, error) { 27 | resp := &tfprotov6.ValidateDataResourceConfigResponse{} 28 | return resp, nil 29 | } 30 | 31 | // UpgradeResourceState isn't really useful in this provider, but we have to loop the state back through to keep Terraform happy. 32 | func (s *RawProviderServer) UpgradeResourceState(ctx context.Context, req *tfprotov6.UpgradeResourceStateRequest) (*tfprotov6.UpgradeResourceStateResponse, error) { 33 | resp := &tfprotov6.UpgradeResourceStateResponse{} 34 | resp.Diagnostics = []*tfprotov6.Diagnostic{} 35 | 36 | sch := GetProviderResourceSchema() 37 | rt := schema.GetObjectTypeFromSchema(sch[req.TypeName]) 38 | 39 | rv, err := req.RawState.Unmarshal(rt) 40 | 41 | if err != nil { 42 | resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ 43 | Severity: tfprotov6.DiagnosticSeverityError, 44 | Summary: "Failed to decode old state during upgrade", 45 | Detail: err.Error(), 46 | }) 47 | return resp, nil 48 | } 49 | 50 | us, err := tfprotov6.NewDynamicValue(rt, rv) 51 | 52 | if err != nil { 53 | resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ 54 | Severity: tfprotov6.DiagnosticSeverityError, 55 | Summary: "Failed to encode new state during upgrade", 56 | Detail: err.Error(), 57 | }) 58 | } 59 | 60 | resp.UpgradedState = &us 61 | return resp, nil 62 | } 63 | 64 | // ReadDataSource function 65 | func (s *RawProviderServer) ReadDataSource(ctx context.Context, req *tfprotov6.ReadDataSourceRequest) (*tfprotov6.ReadDataSourceResponse, error) { 66 | return nil, status.Errorf(codes.Unimplemented, "method ReadDataSource not implemented") 67 | } 68 | 69 | // StopProvider function 70 | func (s *RawProviderServer) StopProvider(ctx context.Context, req *tfprotov6.StopProviderRequest) (*tfprotov6.StopProviderResponse, error) { 71 | return nil, status.Errorf(codes.Unimplemented, "method StopProvider not implemented") 72 | } 73 | -------------------------------------------------------------------------------- /stash/provider/provider_server.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/pseudo-dynamic/terraform-provider-value/internal/schema" 7 | 8 | "github.com/hashicorp/go-hclog" 9 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 10 | "google.golang.org/grpc/codes" 11 | "google.golang.org/grpc/status" 12 | ) 13 | 14 | // RawProviderServer implements the ProviderServer interface as exported from ProtoBuf. 15 | type RawProviderServer struct { 16 | // Since the provider is essentially a gRPC server, the execution flow is dictated by the order of the client (Terraform) request calls. 17 | // Thus it needs a way to persist state between the gRPC calls. These attributes store values that need to be persisted between gRPC calls, 18 | // such as instances of the Kubernetes clients, configuration options needed at runtime. 19 | logger hclog.Logger 20 | 21 | //providerEnabled bool 22 | hostTFVersion string 23 | } 24 | 25 | // ValidateDataResourceConfig function 26 | func (s *RawProviderServer) ValidateDataResourceConfig(ctx context.Context, req *tfprotov6.ValidateDataResourceConfigRequest) (*tfprotov6.ValidateDataResourceConfigResponse, error) { 27 | resp := &tfprotov6.ValidateDataResourceConfigResponse{} 28 | return resp, nil 29 | } 30 | 31 | // UpgradeResourceState isn't really useful in this provider, but we have to loop the state back through to keep Terraform happy. 32 | func (s *RawProviderServer) UpgradeResourceState(ctx context.Context, req *tfprotov6.UpgradeResourceStateRequest) (*tfprotov6.UpgradeResourceStateResponse, error) { 33 | resp := &tfprotov6.UpgradeResourceStateResponse{} 34 | resp.Diagnostics = []*tfprotov6.Diagnostic{} 35 | 36 | sch := GetProviderResourceSchema() 37 | rt := schema.GetObjectTypeFromSchema(sch[req.TypeName]) 38 | 39 | rv, err := req.RawState.Unmarshal(rt) 40 | 41 | if err != nil { 42 | resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ 43 | Severity: tfprotov6.DiagnosticSeverityError, 44 | Summary: "Failed to decode old state during upgrade", 45 | Detail: err.Error(), 46 | }) 47 | return resp, nil 48 | } 49 | 50 | us, err := tfprotov6.NewDynamicValue(rt, rv) 51 | 52 | if err != nil { 53 | resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ 54 | Severity: tfprotov6.DiagnosticSeverityError, 55 | Summary: "Failed to encode new state during upgrade", 56 | Detail: err.Error(), 57 | }) 58 | } 59 | 60 | resp.UpgradedState = &us 61 | return resp, nil 62 | } 63 | 64 | // ReadDataSource function 65 | func (s *RawProviderServer) ReadDataSource(ctx context.Context, req *tfprotov6.ReadDataSourceRequest) (*tfprotov6.ReadDataSourceResponse, error) { 66 | return nil, status.Errorf(codes.Unimplemented, "method ReadDataSource not implemented") 67 | } 68 | 69 | // StopProvider function 70 | func (s *RawProviderServer) StopProvider(ctx context.Context, req *tfprotov6.StopProviderRequest) (*tfprotov6.StopProviderResponse, error) { 71 | return nil, status.Errorf(codes.Unimplemented, "method StopProvider not implemented") 72 | } 73 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/pseudo-dynamic/terraform-provider-value 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/google/uuid v1.3.0 7 | github.com/hashicorp/go-hclog v1.2.1 8 | github.com/hashicorp/terraform-plugin-docs v0.13.0 9 | github.com/hashicorp/terraform-plugin-framework v0.12.0 10 | github.com/hashicorp/terraform-plugin-go v0.14.0 11 | github.com/hashicorp/terraform-plugin-mux v0.7.0 12 | golang.org/x/mod v0.5.1 13 | google.golang.org/grpc v1.48.0 14 | ) 15 | 16 | require ( 17 | github.com/Masterminds/goutils v1.1.1 // indirect 18 | github.com/Masterminds/semver/v3 v3.1.1 // indirect 19 | github.com/Masterminds/sprig/v3 v3.2.2 // indirect 20 | github.com/armon/go-radix v1.0.0 // indirect 21 | github.com/bgentry/speakeasy v0.1.0 // indirect 22 | github.com/fatih/color v1.13.0 // indirect 23 | github.com/golang/protobuf v1.5.2 // indirect 24 | github.com/google/go-cmp v0.5.9 // indirect 25 | github.com/hashicorp/errwrap v1.1.0 // indirect 26 | github.com/hashicorp/go-checkpoint v0.5.0 // indirect 27 | github.com/hashicorp/go-cleanhttp v0.5.2 // indirect 28 | github.com/hashicorp/go-multierror v1.1.1 // indirect 29 | github.com/hashicorp/go-plugin v1.4.4 // indirect 30 | github.com/hashicorp/go-uuid v1.0.3 // indirect 31 | github.com/hashicorp/go-version v1.6.0 // indirect 32 | github.com/hashicorp/hc-install v0.4.0 // indirect 33 | github.com/hashicorp/terraform-exec v0.17.2 // indirect 34 | github.com/hashicorp/terraform-json v0.14.0 // indirect 35 | github.com/hashicorp/terraform-plugin-log v0.7.0 // indirect 36 | github.com/hashicorp/terraform-registry-address v0.0.0-20220623143253-7d51757b572c // indirect 37 | github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 // indirect 38 | github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb // indirect 39 | github.com/huandu/xstrings v1.3.2 // indirect 40 | github.com/imdario/mergo v0.3.13 // indirect 41 | github.com/mattn/go-colorable v0.1.12 // indirect 42 | github.com/mattn/go-isatty v0.0.14 // indirect 43 | github.com/mitchellh/cli v1.1.4 // indirect 44 | github.com/mitchellh/copystructure v1.2.0 // indirect 45 | github.com/mitchellh/go-testing-interface v1.14.1 // indirect 46 | github.com/mitchellh/reflectwalk v1.0.2 // indirect 47 | github.com/oklog/run v1.0.0 // indirect 48 | github.com/posener/complete v1.2.3 // indirect 49 | github.com/russross/blackfriday v1.6.0 // indirect 50 | github.com/shopspring/decimal v1.3.1 // indirect 51 | github.com/spf13/cast v1.5.0 // indirect 52 | github.com/vmihailenco/msgpack/v4 v4.3.12 // indirect 53 | github.com/vmihailenco/tagparser v0.1.1 // indirect 54 | github.com/zclconf/go-cty v1.10.0 // indirect 55 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect 56 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect 57 | golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b // indirect 58 | golang.org/x/text v0.3.7 // indirect 59 | google.golang.org/appengine v1.6.5 // indirect 60 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect 61 | google.golang.org/protobuf v1.28.1 // indirect 62 | ) 63 | -------------------------------------------------------------------------------- /promise/provider/resource_apply.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | // "fmt" 6 | // "time" 7 | 8 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 9 | "github.com/hashicorp/terraform-plugin-go/tftypes" 10 | ) 11 | 12 | // ApplyResourceChange function 13 | func (s *RawProviderServer) ApplyResourceChange(ctx context.Context, req *tfprotov6.ApplyResourceChangeRequest) (*tfprotov6.ApplyResourceChangeResponse, error) { 14 | response := &tfprotov6.ApplyResourceChangeResponse{} 15 | execDiag := s.canExecute() 16 | 17 | if len(execDiag) > 0 { 18 | response.Diagnostics = append(response.Diagnostics, execDiag...) 19 | return response, nil 20 | } 21 | 22 | rt, err := GetResourceType(req.TypeName) 23 | 24 | if err != nil { 25 | response.Diagnostics = append(response.Diagnostics, &tfprotov6.Diagnostic{ 26 | Severity: tfprotov6.DiagnosticSeverityError, 27 | Summary: "Failed to determine planned resource type", 28 | Detail: err.Error(), 29 | }) 30 | 31 | return response, nil 32 | } 33 | 34 | // response.NewState = request.PlannedState 35 | // return response, nil 36 | 37 | plannedState, err := req.PlannedState.Unmarshal(rt) 38 | 39 | if err != nil { 40 | response.Diagnostics = append(response.Diagnostics, &tfprotov6.Diagnostic{ 41 | Severity: tfprotov6.DiagnosticSeverityError, 42 | Summary: "Failed to unmarshal planned resource state", 43 | Detail: err.Error(), 44 | }) 45 | return response, nil 46 | } 47 | 48 | priorState, err := req.PriorState.Unmarshal(rt) 49 | 50 | if err != nil { 51 | response.Diagnostics = append(response.Diagnostics, &tfprotov6.Diagnostic{ 52 | Severity: tfprotov6.DiagnosticSeverityError, 53 | Summary: "Failed to unmarshal prior resource state", 54 | Detail: err.Error(), 55 | }) 56 | return response, nil 57 | } 58 | 59 | plannedValueMap := make(map[string]tftypes.Value) 60 | err = plannedState.As(&plannedValueMap) 61 | 62 | if err != nil { 63 | response.Diagnostics = append(response.Diagnostics, &tfprotov6.Diagnostic{ 64 | Severity: tfprotov6.DiagnosticSeverityError, 65 | Summary: "Failed to extract planned resource state from tftypes.Value", 66 | Detail: err.Error(), 67 | }) 68 | return response, nil 69 | } 70 | 71 | switch { 72 | case priorState.IsNull(): 73 | // This is a "create" 74 | fallthrough 75 | case !plannedState.IsNull() && !priorState.IsNull(): 76 | // This is a "create" OR "update" 77 | plannedValueMap["result"] = plannedValueMap["value"] 78 | customPlannedValue := tftypes.NewValue(plannedState.Type(), plannedValueMap) 79 | customPlannedState, err := tfprotov6.NewDynamicValue(rt, customPlannedValue) 80 | 81 | if err != nil { 82 | response.Diagnostics = append(response.Diagnostics, &tfprotov6.Diagnostic{ 83 | Severity: tfprotov6.DiagnosticSeverityError, 84 | Summary: "Failed to assemble proposed state during apply/update", 85 | Detail: err.Error(), 86 | }) 87 | return response, nil 88 | } 89 | 90 | response.NewState = &customPlannedState 91 | return response, nil 92 | case plannedState.IsNull(): 93 | // Delete the resource 94 | break 95 | } 96 | 97 | return response, nil 98 | } 99 | -------------------------------------------------------------------------------- /docs/resources/replaced_when.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "value_replaced_when Resource - terraform-provider-value" 4 | subcategory: "" 5 | description: |- 6 | Enables the scenario to only change a value when a condition is met. 7 | The value attribute can for example be used as target for replacetriggeredby. To detect 8 | resource creation and resource deletion as change you can try the following approach: 9 | resource "value_replaced_when" "true" { 10 | count = 1 11 | condition = true 12 | } 13 | 14 | resource "value_stash" "replacement_trigger" { 15 | // This workaround detects not only resource creation 16 | // and every change of value but also the deletion of 17 | // resource. 18 | value = try(value_replaced_when.true[0].value, null) 19 | } 20 | 21 | resource "value_stash" "replaced" { 22 | lifecycle { 23 | // Replace me whenever value_replaced_when.true[0].value 24 | // changes or value_replaced_when[0] gets deleted. 25 | replace_triggered_by = [ 26 | value_stash.replacement_trigger.value 27 | ] 28 | } 29 | } 30 | --- 31 | 32 | # value_replaced_when (Resource) 33 | 34 | Enables the scenario to only change a value when a condition is met. 35 | The value attribute can for example be used as target for replace_triggered_by. To detect 36 | resource creation and resource deletion as change you can try the following approach: 37 | 38 | resource "value_replaced_when" "true" { 39 | count = 1 40 | condition = true 41 | } 42 | 43 | resource "value_stash" "replacement_trigger" { 44 | // This workaround detects not only resource creation 45 | // and every change of value but also the deletion of 46 | // resource. 47 | value = try(value_replaced_when.true[0].value, null) 48 | } 49 | 50 | resource "value_stash" "replaced" { 51 | lifecycle { 52 | // Replace me whenever value_replaced_when.true[0].value 53 | // changes or value_replaced_when[0] gets deleted. 54 | replace_triggered_by = [ 55 | value_stash.replacement_trigger.value 56 | ] 57 | } 58 | } 59 | 60 | 61 | 62 | 63 | ## Schema 64 | 65 | ### Required 66 | 67 | - `condition` (Boolean) If already `true` or getting `true` then `value` will be replaced by a random value 68 | 69 | ### Read-Only 70 | 71 | - `value` (String) If the very first condition is false, then the value will be once initialized by a random value. 72 | 73 | If the condition is false or remains false, then the value remains unchanged. 74 | The condition change from true to false does not trigger a replacement of those who use the value as 75 | target for replace_triggered_by. 76 | 77 | If the condition is true or remains true, then the value will be always updated in-place with a random 78 | value. It will always trigger a replacement of those who use the value as target for replace_triggered_by. 79 | 80 | There is a special case that a replacement of those who use the value as target for replace_triggered_by 81 | occurs when the condition is unknown and uncomputed. This always happens whenever the condition becomes 82 | unknown and uncomputed again. 83 | 84 | 85 | -------------------------------------------------------------------------------- /isknown/common/resource_read.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 5 | "github.com/hashicorp/terraform-plugin-go/tftypes" 6 | ) 7 | 8 | // TryReadResource function 9 | func TryReadResource( 10 | currentState *tfprotov6.DynamicValue, 11 | resourceType tftypes.Type, 12 | resp *tfprotov6.ReadResourceResponse) ( 13 | tftypes.Value, 14 | map[string]tftypes.Value, 15 | bool) { 16 | var currentStateValue tftypes.Value 17 | var currentStateValueMap map[string]tftypes.Value 18 | var err error 19 | 20 | if currentStateValue, err = currentState.Unmarshal(resourceType); err != nil { 21 | resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ 22 | Severity: tfprotov6.DiagnosticSeverityError, 23 | Summary: "Failed to decode current state", 24 | Detail: err.Error(), 25 | }) 26 | 27 | return currentStateValue, currentStateValueMap, false 28 | } 29 | 30 | if currentStateValue.IsNull() { 31 | resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ 32 | Severity: tfprotov6.DiagnosticSeverityError, 33 | Summary: "Failed to read resource", 34 | Detail: "Incomplete or missing state", 35 | }) 36 | 37 | return currentStateValue, currentStateValueMap, false 38 | } 39 | 40 | if err = currentStateValue.As(¤tStateValueMap); err != nil { 41 | resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ 42 | Severity: tfprotov6.DiagnosticSeverityError, 43 | Summary: "Failed to extract resource from current state", 44 | Detail: err.Error(), 45 | }) 46 | 47 | return currentStateValue, currentStateValueMap, false 48 | } 49 | 50 | if _, isValueExisting := currentStateValueMap["value"]; !isValueExisting { 51 | resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ 52 | Severity: tfprotov6.DiagnosticSeverityError, 53 | Summary: "Current state of resource has no 'value' attribute", 54 | Detail: "This should not happen. The state may be incomplete or corrupted.\nIf this error is reproducible, please report issue to provider maintainers.", 55 | }) 56 | 57 | return currentStateValue, currentStateValueMap, false 58 | } 59 | 60 | if _, isResultExisting := currentStateValueMap["result"]; !isResultExisting { 61 | resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ 62 | Severity: tfprotov6.DiagnosticSeverityError, 63 | Summary: "Current state of resource has no 'result' attribute", 64 | Detail: "This should not happen. The state may be incomplete or corrupted.\nIf this error is reproducible, please report issue to provider maintainers.", 65 | }) 66 | 67 | return currentStateValue, currentStateValueMap, false 68 | } 69 | 70 | if _, isGuidSeedExisting := currentStateValueMap["guid_seed"]; !isGuidSeedExisting { 71 | resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ 72 | Severity: tfprotov6.DiagnosticSeverityError, 73 | Summary: "Current state of resource has no 'guid_seed' attribute", 74 | Detail: "This should not happen. The state may be incomplete or corrupted.\nIf this error is reproducible, please report issue to provider maintainers.", 75 | }) 76 | 77 | return currentStateValue, currentStateValueMap, false 78 | } 79 | 80 | return currentStateValue, currentStateValueMap, true 81 | } 82 | -------------------------------------------------------------------------------- /internal/guid/main.go: -------------------------------------------------------------------------------- 1 | package guid 2 | 3 | import ( 4 | "errors" 5 | "os" 6 | "path/filepath" 7 | 8 | "github.com/hashicorp/terraform-plugin-framework/types" 9 | "github.com/pseudo-dynamic/terraform-provider-value/internal/uuid" 10 | ) 11 | 12 | func ComposeGuidSeed( 13 | providerSeedAddition *string, 14 | providerMetaSeedAddition *string, 15 | resourceName string, 16 | attributeName string, 17 | resourceGuidSeed *string) string { 18 | empty := "" 19 | 20 | if providerSeedAddition == nil { 21 | providerSeedAddition = &empty 22 | } 23 | 24 | if providerMetaSeedAddition == nil { 25 | providerMetaSeedAddition = &empty 26 | } 27 | 28 | if resourceGuidSeed == nil { 29 | resourceGuidSeed = &empty 30 | } 31 | 32 | return *providerSeedAddition + "|" + 33 | *providerMetaSeedAddition + "|" + 34 | resourceName + "|" + 35 | attributeName + "|" + 36 | *resourceGuidSeed 37 | } 38 | 39 | func CreateResourceTempDir(resourceName string) (string, error) { 40 | providerTempDir := filepath.Join(os.TempDir(), "tf-"+resourceName) 41 | var err error 42 | if _, err = os.Stat(providerTempDir); os.IsNotExist(err) { 43 | os.MkdirAll(providerTempDir, 0700) // Create your file 44 | } 45 | return providerTempDir, err 46 | } 47 | 48 | func GetPlanCachedBoolean( 49 | isPlanPhase bool, 50 | composedGuidSeed, 51 | resourceName string, 52 | getActualValue func() types.Bool) (types.Bool, error) { 53 | providerTempDir, _ := CreateResourceTempDir(resourceName) 54 | 55 | deterministicFileName := uuid.DeterministicUuidFromString(composedGuidSeed).String() 56 | deterministicTempFilePath := filepath.Join(providerTempDir, deterministicFileName) 57 | 58 | var returningValue types.Bool 59 | 60 | if isPlanPhase { 61 | // This is the plan phase 62 | var actualValueByte byte 63 | actualValue := getActualValue() 64 | if actualValue.IsUnknown() { 65 | actualValueByte = 2 66 | } else if actualValue.Value { 67 | actualValueByte = 1 68 | } 69 | 70 | deterministicFile, err := os.OpenFile(deterministicTempFilePath, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0600) 71 | 72 | if err == nil { 73 | defer deterministicFile.Close() 74 | _, err = deterministicFile.Write([]byte{actualValueByte}) 75 | } 76 | 77 | if err != nil { 78 | return returningValue, errors.New("error while working with file in the plan phase") 79 | } 80 | 81 | returningValue = actualValue 82 | } else if _, err := os.Stat(deterministicTempFilePath); err == nil { 83 | deterministicFile, err := os.Open(deterministicTempFilePath) 84 | var readBytes []byte 85 | 86 | if errors.Is(err, os.ErrNotExist) { 87 | return returningValue, errors.New(`the file does not exist. This can mean 88 | 1. the file got deleted before apply-phase because guid collision or 89 | 2. this plan method got called the third time`) 90 | } else if err == nil { 91 | readBytes = make([]byte, 1) 92 | deterministicFile.Read(readBytes) 93 | deterministicFile.Close() 94 | } 95 | 96 | if err != nil { 97 | return returningValue, errors.New("error while working with the file in the apply phase") 98 | } 99 | 100 | readByte := readBytes[0] 101 | 102 | if readByte == 2 { 103 | returningValue = types.Bool{ 104 | Unknown: true, 105 | } 106 | } else if readByte == 1 { 107 | returningValue = types.Bool{ 108 | Value: true, 109 | } 110 | } else { 111 | returningValue = types.Bool{ 112 | Value: false, 113 | } 114 | } 115 | 116 | // ISSUE: check why it does not work 117 | // os.Remove(deterministicTempFilePath) 118 | } else { 119 | returningValue = getActualValue() 120 | } 121 | 122 | return returningValue, nil 123 | } 124 | -------------------------------------------------------------------------------- /internal/fwkprovider/unknown_proposer_resource.go: -------------------------------------------------------------------------------- 1 | package fwkprovider 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/hashicorp/terraform-plugin-framework/diag" 7 | "github.com/hashicorp/terraform-plugin-framework/resource" 8 | "github.com/hashicorp/terraform-plugin-framework/tfsdk" 9 | "github.com/hashicorp/terraform-plugin-framework/types" 10 | ) 11 | 12 | type unknownProposerResource struct { 13 | } 14 | 15 | type unknownProposerResourceWithTraits interface { 16 | resource.ResourceWithMetadata 17 | resource.ResourceWithGetSchema 18 | resource.ResourceWithModifyPlan 19 | } 20 | 21 | func NewUnknownProposerResource() unknownProposerResourceWithTraits { 22 | return &unknownProposerResource{} 23 | } 24 | 25 | func (r unknownProposerResource) GetSchema(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { 26 | return tfsdk.Schema{ 27 | Version: 0, 28 | Description: "This resource is very obscure and misbehaving and you really should only use " + 29 | "it for `value_is_known.proposed_unknown` or `value_is_fully_known.proposed_unknown`.", 30 | Attributes: map[string]tfsdk.Attribute{ 31 | "value": { 32 | Type: types.BoolType, 33 | Computed: true, 34 | Description: "This value will **always** be unknown during the plan phase but always true after apply phase.", 35 | }, 36 | }, 37 | }, nil 38 | } 39 | 40 | type unknownProposerState struct { 41 | Value types.Bool `tfsdk:"value"` 42 | } 43 | 44 | func (r *unknownProposerResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { 45 | resp.TypeName = req.ProviderTypeName + "_unknown_proposer" 46 | } 47 | 48 | // ModifyPlan implements UnknownProposerResourceWithTraits 49 | func (r *unknownProposerResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { 50 | if req.Config.Raw.IsNull() { 51 | // Ignore due to resource deletion 52 | return 53 | } 54 | 55 | var plan unknownProposerState 56 | diags := req.Plan.Get(ctx, &plan) 57 | resp.Diagnostics.Append(diags...) 58 | 59 | if resp.Diagnostics.HasError() { 60 | return 61 | } 62 | 63 | plan.Value = types.Bool{Unknown: true} 64 | diags = resp.Plan.Set(ctx, &plan) 65 | resp.Diagnostics.Append(diags...) 66 | } 67 | 68 | // Create a new resource 69 | func (r unknownProposerResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { 70 | var plan unknownProposerState 71 | diags := req.Plan.Get(ctx, &plan) 72 | resp.Diagnostics.Append(diags...) 73 | 74 | if resp.Diagnostics.HasError() { 75 | return 76 | } 77 | 78 | plan.Value = types.Bool{Value: true} 79 | diags = resp.State.Set(ctx, &plan) 80 | resp.Diagnostics.Append(diags...) 81 | } 82 | 83 | // Read resource information 84 | func (r unknownProposerResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { 85 | // Get current state 86 | var state unknownProposerState 87 | diags := req.State.Get(ctx, &state) 88 | resp.Diagnostics.Append(diags...) 89 | 90 | if resp.Diagnostics.HasError() { 91 | return 92 | } 93 | 94 | diags = resp.State.Set(ctx, &state) 95 | resp.Diagnostics.Append(diags...) 96 | } 97 | 98 | // Update resource 99 | func (r unknownProposerResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { 100 | // Get plan 101 | var plan unknownProposerState 102 | diags := req.Plan.Get(ctx, &plan) 103 | resp.Diagnostics.Append(diags...) 104 | 105 | if resp.Diagnostics.HasError() { 106 | return 107 | } 108 | 109 | plan.Value = types.Bool{Value: true} 110 | diags = resp.State.Set(ctx, &plan) 111 | resp.Diagnostics.Append(diags...) 112 | } 113 | 114 | // Delete resource 115 | func (r unknownProposerResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { 116 | var state unknownProposerState 117 | diags := req.State.Get(ctx, &state) 118 | resp.Diagnostics.Append(diags...) 119 | 120 | if resp.Diagnostics.HasError() { 121 | return 122 | } 123 | 124 | // Remove resource from state 125 | resp.State.RemoveResource(ctx) 126 | } 127 | -------------------------------------------------------------------------------- /promise/provider/resource_plan.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | // "fmt" 6 | 7 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 8 | "github.com/hashicorp/terraform-plugin-go/tftypes" 9 | ) 10 | 11 | // PlanResourceChange function 12 | func (s *RawProviderServer) PlanResourceChange(ctx context.Context, request *tfprotov6.PlanResourceChangeRequest) (*tfprotov6.PlanResourceChangeResponse, error) { 13 | response := &tfprotov6.PlanResourceChangeResponse{} 14 | execDiag := s.canExecute() 15 | 16 | if len(execDiag) > 0 { 17 | response.Diagnostics = append(response.Diagnostics, execDiag...) 18 | return response, nil 19 | } 20 | 21 | resourceType, err := GetResourceType(request.TypeName) 22 | 23 | if err != nil { 24 | response.Diagnostics = append(response.Diagnostics, &tfprotov6.Diagnostic{ 25 | Severity: tfprotov6.DiagnosticSeverityError, 26 | Summary: "Failed to determine planned resource type", 27 | Detail: err.Error(), 28 | }) 29 | 30 | return response, nil 31 | } 32 | 33 | // Decode proposed resource state 34 | proposedState, err := request.ProposedNewState.Unmarshal(resourceType) 35 | 36 | if err != nil { 37 | response.Diagnostics = append(response.Diagnostics, &tfprotov6.Diagnostic{ 38 | Severity: tfprotov6.DiagnosticSeverityError, 39 | Summary: "Failed to unmarshal planned resource state", 40 | Detail: err.Error(), 41 | }) 42 | 43 | return response, nil 44 | } 45 | 46 | proposedValueMap := make(map[string]tftypes.Value) 47 | err = proposedState.As(&proposedValueMap) 48 | 49 | if err != nil { 50 | response.Diagnostics = append(response.Diagnostics, &tfprotov6.Diagnostic{ 51 | Severity: tfprotov6.DiagnosticSeverityError, 52 | Summary: "Failed to extract planned resource state from tftypes.Value", 53 | Detail: err.Error(), 54 | }) 55 | 56 | return response, nil 57 | } 58 | 59 | // Decode prior resource state 60 | priorState, err := request.PriorState.Unmarshal(resourceType) 61 | 62 | if err != nil { 63 | response.Diagnostics = append(response.Diagnostics, &tfprotov6.Diagnostic{ 64 | Severity: tfprotov6.DiagnosticSeverityError, 65 | Summary: "Failed to unmarshal prior resource state", 66 | Detail: err.Error(), 67 | }) 68 | 69 | return response, nil 70 | } 71 | 72 | priorValueMap := make(map[string]tftypes.Value) 73 | err = priorState.As(&priorValueMap) 74 | 75 | if err != nil { 76 | response.Diagnostics = append(response.Diagnostics, &tfprotov6.Diagnostic{ 77 | Severity: tfprotov6.DiagnosticSeverityError, 78 | Summary: "Failed to extract prior resource state from tftypes.Value", 79 | Detail: err.Error(), 80 | }) 81 | 82 | return response, nil 83 | } 84 | 85 | if proposedState.IsNull() { 86 | // Plan to delete the resource 87 | response.PlannedState = request.ProposedNewState 88 | return response, nil 89 | } 90 | 91 | var isResultUnknown bool 92 | 93 | switch { 94 | case !proposedState.IsNull() && priorState.IsNull(): 95 | fallthrough 96 | case !proposedValueMap["value"].Type().Is(priorValueMap["value"].Type()): 97 | isResultUnknown = true 98 | default: 99 | valueDiffs, err := proposedValueMap["value"].Diff(priorValueMap["value"]) 100 | 101 | if err != nil { 102 | response.Diagnostics = append(response.Diagnostics, &tfprotov6.Diagnostic{ 103 | Severity: tfprotov6.DiagnosticSeverityError, 104 | Summary: "Failed to calculate value diff during plan", 105 | Detail: err.Error(), 106 | }) 107 | 108 | return response, nil 109 | } 110 | 111 | isResultUnknown = len(valueDiffs) != 0 112 | } 113 | 114 | if isResultUnknown { 115 | proposedValueMap["result"] = tftypes.NewValue(tftypes.DynamicPseudoType, tftypes.UnknownValue) 116 | customPlannedValue := tftypes.NewValue(proposedState.Type(), proposedValueMap) 117 | customPlannedState, err := tfprotov6.NewDynamicValue(resourceType, customPlannedValue) 118 | 119 | if err != nil { 120 | response.Diagnostics = append(response.Diagnostics, &tfprotov6.Diagnostic{ 121 | Severity: tfprotov6.DiagnosticSeverityError, 122 | Summary: "Failed to assemble proposed state during plan", 123 | Detail: err.Error(), 124 | }) 125 | 126 | return response, nil 127 | } 128 | 129 | response.PlannedState = &customPlannedState 130 | } else { 131 | // Plan to update 132 | response.PlannedState = request.ProposedNewState 133 | } 134 | 135 | return response, nil 136 | } 137 | -------------------------------------------------------------------------------- /internal/goproviderconfig/attribute_guid_seed_addition.go: -------------------------------------------------------------------------------- 1 | package goproviderconfig 2 | 3 | import ( 4 | "os" 5 | "strings" 6 | 7 | "github.com/pseudo-dynamic/terraform-provider-value/internal/schema" 8 | 9 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 10 | "github.com/hashicorp/terraform-plugin-go/tftypes" 11 | ) 12 | 13 | func TryUnmarshalDynamicValueThenExtractGuidSeedAddition(dynamicValue *tfprotov6.DynamicValue, dynamicValueType tftypes.Type) (string, []*tfprotov6.Diagnostic, bool) { 14 | seedAddition := "" 15 | 16 | var isErroneous bool 17 | 18 | var stateValue tftypes.Value 19 | var stateValueMap map[string]tftypes.Value 20 | var diags []*tfprotov6.Diagnostic 21 | 22 | if stateValue, stateValueMap, diags, isErroneous = schema.UnmarshalDynamicValue(dynamicValue, dynamicValueType); isErroneous { 23 | goto Return 24 | } 25 | _ = stateValue 26 | _ = stateValueMap 27 | 28 | seedAddition, diags, _ = TryUnmarshalValueThenExtractGuidSeedAddition(&stateValue) 29 | 30 | Return: 31 | return seedAddition, diags, len(diags) == 0 32 | } 33 | 34 | func TryUnmarshalValueThenExtractGuidSeedAddition(value *tftypes.Value) (string, []*tfprotov6.Diagnostic, bool) { 35 | var seedAdditionValue tftypes.Value 36 | seedAddition := "" 37 | 38 | var isErroneous bool 39 | var isSuccesful bool 40 | 41 | var valueMap map[string]tftypes.Value 42 | var diags []*tfprotov6.Diagnostic 43 | 44 | if valueMap, diags, isErroneous = schema.UnmarshalValue(value); isErroneous { 45 | diags = append(diags, &tfprotov6.Diagnostic{ 46 | Severity: tfprotov6.DiagnosticSeverityError, 47 | Summary: "Could unmarshal value to map[string]tftypes.Value", 48 | Detail: "Could unmarshal value to map[string]tftypes.Value to extract guid_seed_addition", 49 | }) 50 | goto Return 51 | } 52 | _ = value 53 | 54 | if seedAdditionValue, isSuccesful = valueMap["guid_seed_addition"]; !isSuccesful { 55 | // Not having guid_seed_addition is fine. 56 | goto Return 57 | } 58 | _ = seedAdditionValue 59 | 60 | if !seedAdditionValue.IsKnown() { 61 | diags = append(diags, &tfprotov6.Diagnostic{ 62 | Severity: tfprotov6.DiagnosticSeverityError, 63 | Summary: "Provider configuration has 'guid_seed_addition' attribute but it is not known at plan-time.", 64 | Detail: "The 'guid_seed_addition' attribute must be known during the plan phase. See attribute description for more informations.", 65 | }) 66 | 67 | goto Return 68 | } 69 | 70 | if seedAdditionValue.IsNull() { 71 | goto Return 72 | } 73 | 74 | if err := seedAdditionValue.As(&seedAddition); err != nil { 75 | diags = append(diags, &tfprotov6.Diagnostic{ 76 | Severity: tfprotov6.DiagnosticSeverityError, 77 | Summary: "Extraction failed", 78 | Detail: "The guid seed addition could not be extracted as string", 79 | }) 80 | 81 | goto Return 82 | } else { 83 | workdir, _ := os.Getwd() 84 | seedAddition = strings.ReplaceAll(seedAddition, "{workdir}", workdir) 85 | } 86 | 87 | Return: 88 | return seedAddition, diags, len(diags) == 0 89 | } 90 | 91 | func GetGuidSeedAdditionAttributeDescription() string { 92 | return "It serves as an guid seed addition to those resources that implement `guid_seed` as an " + 93 | "attribute. But there are scopes you need to keep in mind: if `guid_seed_addition` has been " + 94 | "specified in the provider block then top-level and nested modules are using the provider " + 95 | "block seed addition. If `guid_seed_addition` has been specified in the provider_meta block " + 96 | "then only the resources of that module are using the module-level seed addition. " + 97 | "Besides `guid_seed`, the provider block seed addition, the provider_meta block seed addition " + 98 | "and the resource type itself will become part of the final seed.\n" + ` 99 | **Placeholders**: 100 | - "{workdir}" (Keyword) The actual workdir; equals to terraform's path.root. This placeholder is 101 | recommended because this value won't be dragged along the plan and apply phase in comparison to 102 | "abspath(path.root)" that you would add to resource seed where a change to path.root would be 103 | recognized just as usual from terraform.` 104 | } 105 | 106 | func GetGuidSeedAdditionSchemaAttribute(attributeDescription string) *tfprotov6.SchemaAttribute { 107 | return &tfprotov6.SchemaAttribute{ 108 | Name: "guid_seed_addition", 109 | Type: tftypes.String, 110 | Required: false, 111 | Optional: true, 112 | Computed: false, 113 | Description: attributeDescription, 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /docs/resources/os_path.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "value_os_path Resource - terraform-provider-value" 4 | subcategory: "" 5 | description: |- 6 | Checks if an OS path exists and caches its computation at plan-time and won't change after apply-time even the path may have been removed. 7 | Provider Metadata 8 | Each module can use providermeta. Please keep in mind that these settings only count for resources of this module! (see https://www.terraform.io/internals/provider-meta https://www.terraform.io/internals/provider-meta): 9 | ```terraform 10 | // Terraform providermeta example 11 | terraform { 12 | // "value" is the provider name 13 | providermeta "value" { 14 | // {workdir} -> The only available placeholder currently (see below for more information) 15 | guidseed_addition = "{workdir}#for-example" // Results into "/path/to/workdir#for-example" 16 | } 17 | } 18 | ``` 19 | Optional 20 | guid_seed_addition (String) It serves as an guid seed addition to those resources that implement guid_seed as an attribute. But there are scopes you need to keep in mind: if guid_seed_addition has been specified in the provider block then top-level and nested modules are using the provider block seed addition. If guid_seed_addition has been specified in the providermeta block then only the resources of that module are using the module-level seed addition. Besides guid_seed, the provider block seed addition, the providermeta block seed addition and the resource type itself will become part of the final seed. 21 | Placeholders: 22 | "{workdir}" (Keyword) The actual workdir; equals to terraform's path.root. This placeholder is 23 | recommended because this value won't be dragged along the plan and apply phase in comparison to 24 | "abspath(path.root)" that you would add to resource seed where a change to path.root would be 25 | recognized just as usual from terraform. 26 | --- 27 | 28 | # value_os_path (Resource) 29 | 30 | Checks if an OS path exists and caches its computation at plan-time and won't change after apply-time even the path may have been removed. 31 | ## Provider Metadata 32 | Each module can use provider_meta. Please keep in mind that these settings only count for resources of this module! (see [https://www.terraform.io/internals/provider-meta](https://www.terraform.io/internals/provider-meta)): 33 | ```terraform 34 | // Terraform provider_meta example 35 | terraform { 36 | // "value" is the provider name 37 | provider_meta "value" { 38 | // {workdir} -> The only available placeholder currently (see below for more information) 39 | guid_seed_addition = "{workdir}#for-example" // Results into "/path/to/workdir#for-example" 40 | } 41 | } 42 | ``` 43 | ### Optional 44 | - `guid_seed_addition` (String) It serves as an guid seed addition to those resources that implement `guid_seed` as an attribute. But there are scopes you need to keep in mind: if `guid_seed_addition` has been specified in the provider block then top-level and nested modules are using the provider block seed addition. If `guid_seed_addition` has been specified in the provider_meta block then only the resources of that module are using the module-level seed addition. Besides `guid_seed`, the provider block seed addition, the provider_meta block seed addition and the resource type itself will become part of the final seed. 45 | 46 | **Placeholders**: 47 | - "{workdir}" (Keyword) The actual workdir; equals to terraform's path.root. This placeholder is 48 | recommended because this value won't be dragged along the plan and apply phase in comparison to 49 | "abspath(path.root)" that you would add to resource seed where a change to path.root would be 50 | recognized just as usual from terraform. 51 | 52 | 53 | 54 | 55 | ## Schema 56 | 57 | ### Required 58 | 59 | - `guid_seed` (String) Attention! The seed is being used to determine resource uniqueness prior (first plan phase) and during apply phase (second plan phase). Very important to state is that the **seed must be fully known during the plan phase**, otherwise, an error is thrown. Within one terraform plan & apply the **seed of every "value_os_path" must be unique**! I really recommend you to use the provider configuration and/or provider_meta configuration to increase resource uniqueness. Besides `guid_seed`, the provider block seed addition, the provider_meta block seed addition and the resource type itself will become part of the final seed. Under certain circumstances you may face problems if you run terraform concurrenctly. If you do so, then I recommend you to pass-through a random value via a user (environment) variable that you then add to the seed. 60 | - `path` (String) A path to a file or directory. 61 | - `proposed_unknown` (Boolean) It is very crucial that this field is **not** filled by any custom value except the one produced by `value_unknown_proposer` (resource). This has the reason as its `value` is **always** unknown during the plan phase. On this behaviour this resource must rely and it cannot check if you do not so! 62 | 63 | ### Read-Only 64 | 65 | - `exists` (Boolean) The computation whether the path exists or not. 66 | 67 | 68 | -------------------------------------------------------------------------------- /docs/resources/is_known.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "value_is_known Resource - terraform-provider-value" 4 | subcategory: "" 5 | description: |- 6 | Allows you to have a access to result during plan phase that states whether value marked as "(known after apply)" or not. 7 | Provider Metadata 8 | Each module can use providermeta. Please keep in mind that these settings only count for resources of this module! (see https://www.terraform.io/internals/provider-meta https://www.terraform.io/internals/provider-meta): 9 | ```terraform 10 | // Terraform providermeta example 11 | terraform { 12 | // "value" is the provider name 13 | providermeta "value" { 14 | // {workdir} -> The only available placeholder currently (see below for more information) 15 | guidseed_addition = "{workdir}#for-example" // Results into "/path/to/workdir#for-example" 16 | } 17 | } 18 | ``` 19 | Optional 20 | guid_seed_addition (String) It serves as an guid seed addition to those resources that implement guid_seed as an attribute. But there are scopes you need to keep in mind: if guid_seed_addition has been specified in the provider block then top-level and nested modules are using the provider block seed addition. If guid_seed_addition has been specified in the providermeta block then only the resources of that module are using the module-level seed addition. Besides guid_seed, the provider block seed addition, the providermeta block seed addition and the resource type itself will become part of the final seed. 21 | Placeholders: 22 | "{workdir}" (Keyword) The actual workdir; equals to terraform's path.root. This placeholder is 23 | recommended because this value won't be dragged along the plan and apply phase in comparison to 24 | "abspath(path.root)" that you would add to resource seed where a change to path.root would be 25 | recognized just as usual from terraform. 26 | --- 27 | 28 | # value_is_known (Resource) 29 | 30 | Allows you to have a access to `result` during plan phase that states whether `value` marked as "(known after apply)" or not. 31 | ## Provider Metadata 32 | Each module can use provider_meta. Please keep in mind that these settings only count for resources of this module! (see [https://www.terraform.io/internals/provider-meta](https://www.terraform.io/internals/provider-meta)): 33 | ```terraform 34 | // Terraform provider_meta example 35 | terraform { 36 | // "value" is the provider name 37 | provider_meta "value" { 38 | // {workdir} -> The only available placeholder currently (see below for more information) 39 | guid_seed_addition = "{workdir}#for-example" // Results into "/path/to/workdir#for-example" 40 | } 41 | } 42 | ``` 43 | ### Optional 44 | - `guid_seed_addition` (String) It serves as an guid seed addition to those resources that implement `guid_seed` as an attribute. But there are scopes you need to keep in mind: if `guid_seed_addition` has been specified in the provider block then top-level and nested modules are using the provider block seed addition. If `guid_seed_addition` has been specified in the provider_meta block then only the resources of that module are using the module-level seed addition. Besides `guid_seed`, the provider block seed addition, the provider_meta block seed addition and the resource type itself will become part of the final seed. 45 | 46 | **Placeholders**: 47 | - "{workdir}" (Keyword) The actual workdir; equals to terraform's path.root. This placeholder is 48 | recommended because this value won't be dragged along the plan and apply phase in comparison to 49 | "abspath(path.root)" that you would add to resource seed where a change to path.root would be 50 | recognized just as usual from terraform. 51 | 52 | 53 | 54 | 55 | ## Schema 56 | 57 | ### Required 58 | 59 | - `guid_seed` (String) Attention! The seed is being used to determine resource uniqueness prior (first plan phase) and during apply phase (second plan phase). Very important to state is that the **seed must be fully known during the plan phase**, otherwise, an error is thrown. Within one terraform plan & apply the **seed of every "value_is_known" must be unique**! I really recommend you to use the provider configuration and/or provider_meta configuration to increase resource uniqueness. Besides `guid_seed`, the provider block seed addition, the provider_meta block seed addition and the resource type itself will become part of the final seed. Under certain circumstances you may face problems if you run terraform concurrenctly. If you do so, then I recommend you to pass-through a random value via a user (environment) variable that you then add to the seed. 60 | - `proposed_unknown` (Dynamic) It is very crucial that this field is **not** filled by any custom value except the one produced by `value_unknown_proposer` (resource). This has the reason as its `value` is **always** unknown during the plan phase. On this behaviour this resource must rely and it cannot check if you do not so! 61 | - `value` (Dynamic) The `value` (not nested attributes) is test against "(known after apply)" 62 | 63 | ### Optional 64 | 65 | - `result` (Boolean) States whether `value` is marked as "(known after apply)" or not. If `value` is an aggregate type, only the top level of the aggregate type is checked; elements and attributes are not checked. 66 | 67 | 68 | -------------------------------------------------------------------------------- /docs/resources/is_fully_known.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "value_is_fully_known Resource - terraform-provider-value" 4 | subcategory: "" 5 | description: |- 6 | Allows you to have a access to result during plan phase that states whether value or any nested attribute is marked as "(known after apply)" or not. 7 | Provider Metadata 8 | Each module can use providermeta. Please keep in mind that these settings only count for resources of this module! (see https://www.terraform.io/internals/provider-meta https://www.terraform.io/internals/provider-meta): 9 | ```terraform 10 | // Terraform providermeta example 11 | terraform { 12 | // "value" is the provider name 13 | providermeta "value" { 14 | // {workdir} -> The only available placeholder currently (see below for more information) 15 | guidseed_addition = "{workdir}#for-example" // Results into "/path/to/workdir#for-example" 16 | } 17 | } 18 | ``` 19 | Optional 20 | guid_seed_addition (String) It serves as an guid seed addition to those resources that implement guid_seed as an attribute. But there are scopes you need to keep in mind: if guid_seed_addition has been specified in the provider block then top-level and nested modules are using the provider block seed addition. If guid_seed_addition has been specified in the providermeta block then only the resources of that module are using the module-level seed addition. Besides guid_seed, the provider block seed addition, the providermeta block seed addition and the resource type itself will become part of the final seed. 21 | Placeholders: 22 | "{workdir}" (Keyword) The actual workdir; equals to terraform's path.root. This placeholder is 23 | recommended because this value won't be dragged along the plan and apply phase in comparison to 24 | "abspath(path.root)" that you would add to resource seed where a change to path.root would be 25 | recognized just as usual from terraform. 26 | --- 27 | 28 | # value_is_fully_known (Resource) 29 | 30 | Allows you to have a access to `result` during plan phase that states whether `value` or any nested attribute is marked as "(known after apply)" or not. 31 | ## Provider Metadata 32 | Each module can use provider_meta. Please keep in mind that these settings only count for resources of this module! (see [https://www.terraform.io/internals/provider-meta](https://www.terraform.io/internals/provider-meta)): 33 | ```terraform 34 | // Terraform provider_meta example 35 | terraform { 36 | // "value" is the provider name 37 | provider_meta "value" { 38 | // {workdir} -> The only available placeholder currently (see below for more information) 39 | guid_seed_addition = "{workdir}#for-example" // Results into "/path/to/workdir#for-example" 40 | } 41 | } 42 | ``` 43 | ### Optional 44 | - `guid_seed_addition` (String) It serves as an guid seed addition to those resources that implement `guid_seed` as an attribute. But there are scopes you need to keep in mind: if `guid_seed_addition` has been specified in the provider block then top-level and nested modules are using the provider block seed addition. If `guid_seed_addition` has been specified in the provider_meta block then only the resources of that module are using the module-level seed addition. Besides `guid_seed`, the provider block seed addition, the provider_meta block seed addition and the resource type itself will become part of the final seed. 45 | 46 | **Placeholders**: 47 | - "{workdir}" (Keyword) The actual workdir; equals to terraform's path.root. This placeholder is 48 | recommended because this value won't be dragged along the plan and apply phase in comparison to 49 | "abspath(path.root)" that you would add to resource seed where a change to path.root would be 50 | recognized just as usual from terraform. 51 | 52 | 53 | 54 | 55 | ## Schema 56 | 57 | ### Required 58 | 59 | - `guid_seed` (String) Attention! The seed is being used to determine resource uniqueness prior (first plan phase) and during apply phase (second plan phase). Very important to state is that the **seed must be fully known during the plan phase**, otherwise, an error is thrown. Within one terraform plan & apply the **seed of every "value_is_fully_known" must be unique**! I really recommend you to use the provider configuration and/or provider_meta configuration to increase resource uniqueness. Besides `guid_seed`, the provider block seed addition, the provider_meta block seed addition and the resource type itself will become part of the final seed. Under certain circumstances you may face problems if you run terraform concurrenctly. If you do so, then I recommend you to pass-through a random value via a user (environment) variable that you then add to the seed. 60 | - `proposed_unknown` (Dynamic) It is very crucial that this field is **not** filled by any custom value except the one produced by `value_unknown_proposer` (resource). This has the reason as its `value` is **always** unknown during the plan phase. On this behaviour this resource must rely and it cannot check if you do not so! 61 | - `value` (Dynamic) The `value` and if existing, nested attributes, are tested against "(known after apply)" 62 | 63 | ### Optional 64 | 65 | - `result` (Boolean) States whether `value` or any nested attribute is marked as "(known after apply)" or not. If `value` is an aggregate type, not only the top level of the aggregate type is checked; elements and attributes are checked too. 66 | 67 | 68 | -------------------------------------------------------------------------------- /isknown/provider/resource_plan.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "os" 7 | "path/filepath" 8 | 9 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 10 | "github.com/hashicorp/terraform-plugin-go/tftypes" 11 | 12 | "github.com/pseudo-dynamic/terraform-provider-value/internal/goproviderconfig" 13 | "github.com/pseudo-dynamic/terraform-provider-value/internal/guid" 14 | "github.com/pseudo-dynamic/terraform-provider-value/internal/schema" 15 | "github.com/pseudo-dynamic/terraform-provider-value/internal/uuid" 16 | ) 17 | 18 | // PlanResourceChange function 19 | func (s *UserProviderServer) PlanResourceChange(ctx context.Context, req *tfprotov6.PlanResourceChangeRequest) (*tfprotov6.PlanResourceChangeResponse, error) { 20 | var isErroneous bool 21 | var isWorking bool 22 | var diags []*tfprotov6.Diagnostic 23 | 24 | resp := &tfprotov6.PlanResourceChangeResponse{} 25 | execDiag := s.canExecute() 26 | 27 | if len(execDiag) > 0 { 28 | resp.Diagnostics = append(resp.Diagnostics, execDiag...) 29 | return resp, nil 30 | } 31 | 32 | resourceType := getResourceType(req.TypeName) 33 | 34 | proposedValueDynamic := req.ProposedNewState 35 | var proposedValue tftypes.Value 36 | var proposedValueMap map[string]tftypes.Value 37 | if proposedValue, proposedValueMap, diags, isErroneous = schema.UnmarshalDynamicValue(proposedValueDynamic, resourceType); isErroneous { 38 | resp.Diagnostics = append(resp.Diagnostics, diags...) 39 | return resp, nil 40 | } 41 | 42 | // configValueDynamic := req.Config 43 | // var configValue tftypes.Value 44 | // var configValueMap map[string]tftypes.Value 45 | // if configValue, configValueMap, diags, isErroneous = schema.UnmarshalState(configValueDynamic, resourceType); isErroneous { 46 | // resp.Diagnostics = append(resp.Diagnostics, diags...) 47 | // return resp, nil 48 | // } 49 | // _ = configValue 50 | // _ = configValueMap 51 | 52 | if proposedValue.IsNull() { 53 | // Plan to delete the resource 54 | resp.PlannedState = proposedValueDynamic 55 | return resp, nil 56 | } 57 | 58 | var providerMetaSeedAddition string 59 | if providerMetaSeedAddition, diags, isWorking = goproviderconfig.TryExtractProviderMetaGuidSeedAddition(req.ProviderMeta); !isWorking { 60 | resp.Diagnostics = append(resp.Diagnostics, diags...) 61 | return resp, nil 62 | } 63 | 64 | guidSeedValue := proposedValueMap["guid_seed"] 65 | if !guidSeedValue.IsKnown() { 66 | resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ 67 | Severity: tfprotov6.DiagnosticSeverityError, 68 | Summary: "Current resource state has a 'guid_seed' attribute but it is not known.", 69 | Detail: "The 'guid_seed' attribute must be known during the plan phase. See attribute description for more informations.", 70 | }) 71 | 72 | return resp, nil 73 | } 74 | var guidSeed string 75 | _ = guidSeedValue.As(&guidSeed) // Why it should ever fail? 76 | 77 | composedGuidSeed := guid.ComposeGuidSeed(&s.ProviderConfigSeedAddition, 78 | &providerMetaSeedAddition, 79 | req.TypeName, 80 | "value", 81 | &guidSeed) 82 | 83 | isPlanPhase := !proposedValueMap["proposed_unknown"].IsKnown() // Unknown == plan 84 | 85 | providerTempDir, _ := guid.CreateResourceTempDir(req.TypeName) 86 | 87 | deterministicFileName := uuid.DeterministicUuidFromString(composedGuidSeed).String() 88 | deterministicTempFilePath := filepath.Join(providerTempDir, deterministicFileName) 89 | var isValueKnown bool 90 | 91 | if isPlanPhase { 92 | // This is the plan phase 93 | if s.params.CheckFullyKnown { 94 | isValueKnown = proposedValueMap["value"].IsFullyKnown() 95 | } else { 96 | isValueKnown = proposedValueMap["value"].IsKnown() 97 | } 98 | 99 | var isValueFullyKnownByte byte 100 | if isValueKnown { 101 | isValueFullyKnownByte = 1 102 | } 103 | 104 | deterministicFile, err := os.OpenFile(deterministicTempFilePath, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0600) 105 | 106 | if err == nil { 107 | defer deterministicFile.Close() 108 | _, err = deterministicFile.Write([]byte{isValueFullyKnownByte}) 109 | } 110 | 111 | if err != nil { 112 | resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ 113 | Severity: tfprotov6.DiagnosticSeverityError, 114 | Summary: "Error while working with file in the plan phase", 115 | Detail: err.Error(), 116 | }) 117 | 118 | return resp, nil 119 | } 120 | } else { 121 | deterministicFile, err := os.Open(deterministicTempFilePath) 122 | var readBytes []byte 123 | 124 | if errors.Is(err, os.ErrNotExist) { 125 | resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ 126 | Severity: tfprotov6.DiagnosticSeverityError, 127 | Summary: "File does not exist", 128 | Detail: `The file does not exist. This can mean 129 | 2. the file got deleted before apply-phase because guid collision or 130 | 2. this plan method got called the third time`, 131 | }) 132 | } else if err == nil { 133 | // ISSUE: end of else if block does not call deferred methods 134 | defer deterministicFile.Close() // ignore error intentionally 135 | readBytes = make([]byte, 1) 136 | deterministicFile.Read(readBytes) 137 | } 138 | 139 | if err != nil { 140 | resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ 141 | Severity: tfprotov6.DiagnosticSeverityError, 142 | Summary: "Error while working with the file in the apply phase", 143 | Detail: err.Error(), 144 | }) 145 | 146 | return resp, nil 147 | } 148 | 149 | readByte := readBytes[0] 150 | isValueKnown = readByte == 1 151 | // ISSUE: there is a silent error because file is still opened 152 | os.Remove(deterministicTempFilePath) 153 | } 154 | 155 | proposedValueMap["result"] = tftypes.NewValue(tftypes.Bool, isValueKnown) 156 | plannedValue := tftypes.NewValue(proposedValue.Type(), proposedValueMap) 157 | plannedState, err := tfprotov6.NewDynamicValue(resourceType, plannedValue) 158 | 159 | if err != nil { 160 | resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ 161 | Severity: tfprotov6.DiagnosticSeverityError, 162 | Summary: "Failed to assemble proposed state during plan", 163 | Detail: err.Error(), 164 | }) 165 | } 166 | 167 | // pass-through 168 | // resp.PlannedState = &newValueDynamic 169 | resp.PlannedState = &plannedState 170 | return resp, nil 171 | } 172 | -------------------------------------------------------------------------------- /internal/fwkprovider/os_path.go: -------------------------------------------------------------------------------- 1 | package fwkprovider 2 | 3 | import ( 4 | "context" 5 | "os" 6 | 7 | "github.com/pseudo-dynamic/terraform-provider-value/internal/fwkproviderconfig" 8 | "github.com/pseudo-dynamic/terraform-provider-value/internal/goproviderconfig" 9 | "github.com/pseudo-dynamic/terraform-provider-value/internal/guid" 10 | 11 | "github.com/hashicorp/terraform-plugin-framework/diag" 12 | "github.com/hashicorp/terraform-plugin-framework/resource" 13 | "github.com/hashicorp/terraform-plugin-framework/tfsdk" 14 | "github.com/hashicorp/terraform-plugin-framework/types" 15 | ) 16 | 17 | const osPathResourceSuffix string = "_os_path" 18 | const osPathResourceName string = providerName + osPathResourceSuffix 19 | 20 | type osPathResource struct { 21 | ProviderGuidSeedAddition *string 22 | } 23 | 24 | type osPathResourceWithTraits interface { 25 | resource.ResourceWithMetadata 26 | resource.ResourceWithGetSchema 27 | resource.ResourceWithModifyPlan 28 | resource.ResourceWithConfigure 29 | } 30 | 31 | func NewOSPathResource() osPathResourceWithTraits { 32 | return &osPathResource{} 33 | } 34 | 35 | // Configure implements osPathResourceWithTraits 36 | func (r *osPathResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { 37 | if req.ProviderData == nil { 38 | // For whatever reason Configure gets called with nil ProviderData. 39 | return 40 | } 41 | 42 | provderData := req.ProviderData.(*providerData) 43 | r.ProviderGuidSeedAddition = provderData.GuidSeedAddition 44 | } 45 | 46 | func (r osPathResource) GetSchema(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { 47 | return tfsdk.Schema{ 48 | Description: "Checks if an OS path exists and caches its computation at plan-time and won't change after " + 49 | "apply-time even the path may have been removed." + "\n" + goproviderconfig.GetProviderMetaGuidSeedAdditionAttributeDescription(), 50 | Attributes: map[string]tfsdk.Attribute{ 51 | "path": { 52 | Type: types.StringType, 53 | Required: true, 54 | Description: "A path to a file or directory.", 55 | }, 56 | "proposed_unknown": { 57 | Type: types.BoolType, 58 | Required: true, 59 | Description: goproviderconfig.GetProposedUnknownAttributeDescription(), 60 | }, 61 | "guid_seed": { 62 | Type: types.StringType, 63 | Required: true, 64 | Description: goproviderconfig.GetGuidSeedAttributeDescription(osPathResourceName), 65 | }, 66 | "exists": { 67 | Type: types.BoolType, 68 | Computed: true, 69 | Description: "The computation whether the path exists or not.", 70 | }, 71 | }, 72 | }, nil 73 | } 74 | 75 | type osPathState struct { 76 | Path types.String `tfsdk:"path"` 77 | GuidSeed types.String `tfsdk:"guid_seed"` 78 | ProposedUnknown types.Bool `tfsdk:"proposed_unknown"` 79 | Exists types.Bool `tfsdk:"exists"` 80 | } 81 | 82 | func (r *osPathResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { 83 | resp.TypeName = req.ProviderTypeName + osPathResourceSuffix 84 | } 85 | 86 | // ModifyPlan implements OSPathResourceWithTraits 87 | func (r *osPathResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { 88 | if r.ProviderGuidSeedAddition == nil { 89 | resp.Diagnostics.AddError("Bad provider guid seed", "Provider guid seed is null but was expected to be empty") 90 | return 91 | } 92 | 93 | // Get current config 94 | var config osPathState 95 | diags := req.Config.Get(ctx, &config) 96 | resp.Diagnostics.Append(diags...) 97 | 98 | if resp.Diagnostics.HasError() { 99 | return 100 | } 101 | suppliedGuidSeed := config.GuidSeed.Value 102 | isPlanPhase := config.ProposedUnknown.IsUnknown() 103 | 104 | if !fwkproviderconfig.ValidatePlanKnownString(config.GuidSeed, "guid_seed", &resp.Diagnostics) { 105 | return 106 | } 107 | 108 | var providerMetaSeedAddition string 109 | var isSuccessful bool 110 | if providerMetaSeedAddition, _, isSuccessful = goproviderconfig.TryUnmarshalValueThenExtractGuidSeedAddition(&req.ProviderMeta.Raw); !isSuccessful { 111 | resp.Diagnostics.AddError("Extraction failed", "Could not extract provider meta guid seed addition") 112 | return 113 | } 114 | 115 | composedGuidSeed := guid.ComposeGuidSeed(r.ProviderGuidSeedAddition, 116 | &providerMetaSeedAddition, 117 | osPathResourceName, 118 | "exists", 119 | &suppliedGuidSeed) 120 | 121 | checkPathExistence := func() types.Bool { 122 | if config.Path.Unknown { 123 | return types.Bool{Unknown: true} 124 | } 125 | 126 | _, err := os.Stat(config.Path.Value) 127 | osPath := err == nil 128 | return types.Bool{Value: osPath} 129 | } 130 | 131 | cachedExists, err := guid.GetPlanCachedBoolean( 132 | isPlanPhase, 133 | composedGuidSeed, 134 | osPathResourceName, 135 | checkPathExistence) 136 | 137 | if err != nil { 138 | resp.Diagnostics.AddError("Plan cache mechanism failed for exists attribute", err.Error()) 139 | return 140 | } 141 | 142 | config.Exists = cachedExists 143 | resp.Plan.Set(ctx, &config) 144 | } 145 | 146 | // Create a new resource 147 | func (r osPathResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { 148 | // Retrieve values from plan 149 | var plan osPathState 150 | diags := req.Plan.Get(ctx, &plan) 151 | resp.Diagnostics.Append(diags...) 152 | 153 | if resp.Diagnostics.HasError() { 154 | return 155 | } 156 | 157 | diags = resp.State.Set(ctx, &plan) 158 | resp.Diagnostics.Append(diags...) 159 | 160 | if resp.Diagnostics.HasError() { 161 | return 162 | } 163 | } 164 | 165 | // Read resource information 166 | func (r osPathResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { 167 | // Get current state 168 | var state osPathState 169 | diags := req.State.Get(ctx, &state) 170 | resp.Diagnostics.Append(diags...) 171 | 172 | if resp.Diagnostics.HasError() { 173 | return 174 | } 175 | 176 | diags = resp.State.Set(ctx, &state) 177 | resp.Diagnostics.Append(diags...) 178 | 179 | if resp.Diagnostics.HasError() { 180 | return 181 | } 182 | } 183 | 184 | // Update resource 185 | func (r osPathResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { 186 | // Get plan 187 | var plan osPathState 188 | diags := req.Plan.Get(ctx, &plan) 189 | resp.Diagnostics.Append(diags...) 190 | 191 | if resp.Diagnostics.HasError() { 192 | return 193 | } 194 | 195 | // Set new state 196 | diags = resp.State.Set(ctx, &plan) 197 | resp.Diagnostics.Append(diags...) 198 | 199 | if resp.Diagnostics.HasError() { 200 | return 201 | } 202 | } 203 | 204 | // Delete resource 205 | func (r osPathResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { 206 | var state osPathState 207 | diags := req.State.Get(ctx, &state) 208 | resp.Diagnostics.Append(diags...) 209 | 210 | if resp.Diagnostics.HasError() { 211 | return 212 | } 213 | 214 | // Remove resource from state 215 | resp.State.RemoveResource(ctx) 216 | } 217 | -------------------------------------------------------------------------------- /internal/fwkprovider/replaced_when_resource.go: -------------------------------------------------------------------------------- 1 | package fwkprovider 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/pseudo-dynamic/terraform-provider-value/internal/uuid" 7 | 8 | "github.com/hashicorp/terraform-plugin-framework/diag" 9 | "github.com/hashicorp/terraform-plugin-framework/path" 10 | "github.com/hashicorp/terraform-plugin-framework/resource" 11 | "github.com/hashicorp/terraform-plugin-framework/tfsdk" 12 | "github.com/hashicorp/terraform-plugin-framework/types" 13 | ) 14 | 15 | type replacedWhenResource struct { 16 | } 17 | 18 | type replacedWhenResourceWithTraits interface { 19 | resource.ResourceWithMetadata 20 | resource.ResourceWithGetSchema 21 | resource.ResourceWithModifyPlan 22 | } 23 | 24 | func NewReplacedWhenResource() replacedWhenResourceWithTraits { 25 | return &replacedWhenResource{} 26 | } 27 | 28 | func (r replacedWhenResource) GetSchema(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { 29 | return tfsdk.Schema{ 30 | Description: `Enables the scenario to only change a value when a condition is met. 31 | The value attribute can for example be used as target for replace_triggered_by. To detect 32 | resource creation and resource deletion as change you can try the following approach: 33 | 34 | resource "value_replaced_when" "true" { 35 | count = 1 36 | condition = true 37 | } 38 | 39 | resource "value_stash" "replacement_trigger" { 40 | // This workaround detects not only resource creation 41 | // and every change of value but also the deletion of 42 | // resource. 43 | value = try(value_replaced_when.true[0].value, null) 44 | } 45 | 46 | resource "value_stash" "replaced" { 47 | lifecycle { 48 | // Replace me whenever value_replaced_when.true[0].value 49 | // changes or value_replaced_when[0] gets deleted. 50 | replace_triggered_by = [ 51 | value_stash.replacement_trigger.value 52 | ] 53 | } 54 | }`, 55 | Attributes: map[string]tfsdk.Attribute{ 56 | "condition": { 57 | Type: types.BoolType, 58 | Required: true, 59 | Description: "If already true or getting true then value will be replaced by a random value", 60 | MarkdownDescription: "If already `true` or getting `true` then `value` will be replaced by a random value", 61 | }, 62 | "value": { 63 | Type: types.StringType, 64 | Computed: true, 65 | Description: `If the very first condition is false, then the value will be once initialized by a random value. 66 | 67 | If the condition is false or remains false, then the value remains unchanged. 68 | The condition change from true to false does not trigger a replacement of those who use the value as 69 | target for replace_triggered_by. 70 | 71 | If the condition is true or remains true, then the value will be always updated in-place with a random 72 | value. It will always trigger a replacement of those who use the value as target for replace_triggered_by. 73 | 74 | There is a special case that a replacement of those who use the value as target for replace_triggered_by 75 | occurs when the condition is unknown and uncomputed. This always happens whenever the condition becomes 76 | unknown and uncomputed again.`, 77 | }, 78 | }, 79 | }, nil 80 | } 81 | 82 | type replacedWhenState struct { 83 | When types.Bool `tfsdk:"condition"` 84 | Value types.String `tfsdk:"value"` 85 | } 86 | 87 | func (r *replacedWhenResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { 88 | resp.TypeName = req.ProviderTypeName + "_replaced_when" 89 | } 90 | 91 | // ModifyPlan implements ReplacedWhenResourceWithTraits 92 | func (r *replacedWhenResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { 93 | if req.Plan.Raw.IsNull() { 94 | // Ignore due to resource deletion 95 | return 96 | } 97 | 98 | var plannedValue types.String 99 | diags := req.Plan.GetAttribute(ctx, path.Root("value"), &plannedValue) 100 | 101 | resp.Diagnostics.Append(diags...) 102 | 103 | if resp.Diagnostics.HasError() { 104 | return 105 | } 106 | // resp.Diagnostics.AddWarning("Value (plan) is known: "+strconv.FormatBool(!plannedValue.IsUnknown()), "") 107 | // resp.Diagnostics.AddWarning("Value (plan) is null: "+strconv.FormatBool(plannedValue.IsNull()), "") 108 | // resp.Diagnostics.AddWarning("Value (plan) is: "+plannedValue.Value, "") 109 | 110 | var currentValue types.String 111 | diags = req.State.GetAttribute(ctx, path.Root("value"), ¤tValue) 112 | 113 | resp.Diagnostics.Append(diags...) 114 | 115 | if resp.Diagnostics.HasError() { 116 | return 117 | } 118 | 119 | // if plannedValue.Unknown { 120 | var suppliedCondition types.Bool 121 | diags = req.Config.GetAttribute(ctx, path.Root("condition"), &suppliedCondition) 122 | 123 | resp.Diagnostics.Append(diags...) 124 | 125 | if resp.Diagnostics.HasError() { 126 | return 127 | } 128 | // resp.Diagnostics.AddWarning("Condition (config) is known: "+strconv.FormatBool(!suppliedCondition.IsUnknown()), "") 129 | // resp.Diagnostics.AddWarning("Condition (config) is null: "+strconv.FormatBool(suppliedCondition.IsNull()), "") 130 | // resp.Diagnostics.AddWarning("Condition (config) is: "+strconv.FormatBool(suppliedCondition.Value), "") 131 | 132 | var suppliedValue types.String 133 | diags = req.Config.GetAttribute(ctx, path.Root("value"), &suppliedValue) 134 | 135 | resp.Diagnostics.Append(diags...) 136 | 137 | if resp.Diagnostics.HasError() { 138 | return 139 | } 140 | 141 | var newValue types.String 142 | var currentValueNotOnceSet = currentValue.IsUnknown() || currentValue.IsNull() 143 | 144 | if currentValueNotOnceSet || !suppliedCondition.Unknown { 145 | // Empty value (state) / state value won't never be unknown or null again 146 | // OR supplied condition (config) is known (the latter condition is also 147 | // true if the unknown condition has been computed once and didn't change 148 | // and is therefore not unknown anymore) 149 | if currentValueNotOnceSet || suppliedCondition.Value { 150 | // if currentValue.IsUnknown() { 151 | // newValue = types.String{Null: true} 152 | // } else { 153 | // Because ModifyPlan gets called twice but without any shared context 154 | // we need to create deterministic incremental UUIDs that won't change 155 | // in apply and its following plan phase. 156 | // I chose this approach because I want this resource to be as minimalistic 157 | // as possible. The alternative would be to work with two pre-chosen values 158 | // and swap between them back and forth. 159 | nextUuid := uuid.DeterministicUuidFromString(currentValue.Value).String() 160 | newValue = types.String{Value: nextUuid} 161 | // } 162 | } else { 163 | diags := req.State.GetAttribute(ctx, path.Root("value"), &newValue) 164 | resp.Diagnostics.Append(diags...) 165 | 166 | if resp.Diagnostics.HasError() { 167 | return 168 | } 169 | } 170 | } else { 171 | newValue = types.String{Unknown: true} 172 | } 173 | 174 | newPlan := req.Plan 175 | newPlan.SetAttribute(ctx, path.Root("value"), &newValue) 176 | resp.Plan = newPlan 177 | } 178 | 179 | // Create a new resource 180 | func (r replacedWhenResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { 181 | // Retrieve values from plan 182 | var plan replacedWhenState 183 | diags := req.Plan.Get(ctx, &plan) 184 | resp.Diagnostics.Append(diags...) 185 | 186 | if resp.Diagnostics.HasError() { 187 | return 188 | } 189 | 190 | diags = resp.State.Set(ctx, &plan) 191 | resp.Diagnostics.Append(diags...) 192 | 193 | if resp.Diagnostics.HasError() { 194 | return 195 | } 196 | } 197 | 198 | // Read resource information 199 | func (r replacedWhenResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { 200 | // Get current state 201 | var state replacedWhenState 202 | diags := req.State.Get(ctx, &state) 203 | resp.Diagnostics.Append(diags...) 204 | 205 | if resp.Diagnostics.HasError() { 206 | return 207 | } 208 | 209 | diags = resp.State.Set(ctx, &state) 210 | resp.Diagnostics.Append(diags...) 211 | 212 | if resp.Diagnostics.HasError() { 213 | return 214 | } 215 | 216 | // resp.State = req.State 217 | } 218 | 219 | // Update resource 220 | func (r replacedWhenResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { 221 | // Get plan 222 | var plan replacedWhenState 223 | diags := req.Plan.Get(ctx, &plan) 224 | resp.Diagnostics.Append(diags...) 225 | 226 | if resp.Diagnostics.HasError() { 227 | return 228 | } 229 | 230 | // Set new state 231 | diags = resp.State.Set(ctx, &plan) 232 | resp.Diagnostics.Append(diags...) 233 | 234 | if resp.Diagnostics.HasError() { 235 | return 236 | } 237 | } 238 | 239 | // Delete resource 240 | func (r replacedWhenResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { 241 | var state replacedWhenState 242 | diags := req.State.Get(ctx, &state) 243 | resp.Diagnostics.Append(diags...) 244 | 245 | if resp.Diagnostics.HasError() { 246 | return 247 | } 248 | 249 | // Remove resource from state 250 | resp.State.RemoveResource(ctx) 251 | } 252 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 4 | github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= 5 | github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= 6 | github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= 7 | github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= 8 | github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= 9 | github.com/Masterminds/sprig/v3 v3.2.0/go.mod h1:tWhwTbUTndesPNeF0C900vKoq283u6zp4APT9vaF3SI= 10 | github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= 11 | github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= 12 | github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= 13 | github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk= 14 | github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= 15 | github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 h1:YoJbenK9C67SkzkDfmQuVln04ygHj3vjZfd9FL+GmQQ= 16 | github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= 17 | github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk= 18 | github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= 19 | github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= 20 | github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= 21 | github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk= 22 | github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= 23 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 24 | github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= 25 | github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 26 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= 27 | github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= 28 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= 29 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 30 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 31 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 32 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 33 | github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 34 | github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= 35 | github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 36 | github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 37 | github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 38 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 39 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 40 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 41 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 42 | github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= 43 | github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= 44 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 45 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 46 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 47 | github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= 48 | github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= 49 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 50 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 51 | github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= 52 | github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= 53 | github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= 54 | github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= 55 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 56 | github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= 57 | github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= 58 | github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= 59 | github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= 60 | github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34= 61 | github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= 62 | github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0= 63 | github.com/go-git/go-git/v5 v5.4.2 h1:BXyZu9t0VkbiHtqrsvdq39UDhGJTl1h55VW6CSC4aY4= 64 | github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc= 65 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 66 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 67 | github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 68 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 69 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 70 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 71 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 72 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 73 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 74 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 75 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 76 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 77 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 78 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 79 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 80 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 81 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 82 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= 83 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 84 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 85 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 86 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 87 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 88 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 89 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 90 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 91 | github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 92 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 93 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 94 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 95 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 96 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 97 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 98 | github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= 99 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 100 | github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= 101 | github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 102 | github.com/hashicorp/go-checkpoint v0.5.0 h1:MFYpPZCnQqQTE18jFwSII6eUQrD/oxMFp3mlgcqk5mU= 103 | github.com/hashicorp/go-checkpoint v0.5.0/go.mod h1:7nfLNL10NsxqO4iWuW6tWW0HjZuDrwkBuEQsVcpCOgg= 104 | github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 105 | github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 106 | github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= 107 | github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= 108 | github.com/hashicorp/go-hclog v1.2.1 h1:YQsLlGDJgwhXFpucSPyVbCBviQtjlHv3jLTlp8YmtEw= 109 | github.com/hashicorp/go-hclog v1.2.1/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= 110 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 111 | github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= 112 | github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= 113 | github.com/hashicorp/go-plugin v1.4.4 h1:NVdrSdFRt3SkZtNckJ6tog7gbpRrcbOjQi/rgF7JYWQ= 114 | github.com/hashicorp/go-plugin v1.4.4/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s= 115 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 116 | github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= 117 | github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 118 | github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= 119 | github.com/hashicorp/go-version v1.5.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= 120 | github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= 121 | github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= 122 | github.com/hashicorp/hc-install v0.4.0 h1:cZkRFr1WVa0Ty6x5fTvL1TuO1flul231rWkGH92oYYk= 123 | github.com/hashicorp/hc-install v0.4.0/go.mod h1:5d155H8EC5ewegao9A4PUTMNPZaq+TbOzkJJZ4vrXeI= 124 | github.com/hashicorp/terraform-exec v0.17.2 h1:EU7i3Fh7vDUI9nNRdMATCEfnm9axzTnad8zszYZ73Go= 125 | github.com/hashicorp/terraform-exec v0.17.2/go.mod h1:tuIbsL2l4MlwwIZx9HPM+LOV9vVyEfBYu2GsO1uH3/8= 126 | github.com/hashicorp/terraform-json v0.14.0 h1:sh9iZ1Y8IFJLx+xQiKHGud6/TSUCM0N8e17dKDpqV7s= 127 | github.com/hashicorp/terraform-json v0.14.0/go.mod h1:5A9HIWPkk4e5aeeXIBbkcOvaZbIYnAIkEyqP2pNSckM= 128 | github.com/hashicorp/terraform-plugin-docs v0.13.0 h1:6e+VIWsVGb6jYJewfzq2ok2smPzZrt1Wlm9koLeKazY= 129 | github.com/hashicorp/terraform-plugin-docs v0.13.0/go.mod h1:W0oCmHAjIlTHBbvtppWHe8fLfZ2BznQbuv8+UD8OucQ= 130 | github.com/hashicorp/terraform-plugin-framework v0.12.0 h1:Bk3l5MQUaZoo5eplr+u1FomYqGS564e8Tp3rutnCfYg= 131 | github.com/hashicorp/terraform-plugin-framework v0.12.0/go.mod h1:wcZdk4+Uef6Ng+BiBJjGAcIPlIs5bhlEV/TA1k6Xkq8= 132 | github.com/hashicorp/terraform-plugin-go v0.14.0 h1:ttnSlS8bz3ZPYbMb84DpcPhY4F5DsQtcAS7cHo8uvP4= 133 | github.com/hashicorp/terraform-plugin-go v0.14.0/go.mod h1:2nNCBeRLaenyQEi78xrGrs9hMbulveqG/zDMQSvVJTE= 134 | github.com/hashicorp/terraform-plugin-log v0.7.0 h1:SDxJUyT8TwN4l5b5/VkiTIaQgY6R+Y2BQ0sRZftGKQs= 135 | github.com/hashicorp/terraform-plugin-log v0.7.0/go.mod h1:p4R1jWBXRTvL4odmEkFfDdhUjHf9zcs/BCoNHAc7IK4= 136 | github.com/hashicorp/terraform-plugin-mux v0.7.0 h1:wRbSYzg+v2sn5Mdee0UKm4YTt4wJG0LfSwtgNuBkglY= 137 | github.com/hashicorp/terraform-plugin-mux v0.7.0/go.mod h1:Ae30Mc5lz4d1awtiCbHP0YyvgBeiQ00Q1nAq0U3lb+I= 138 | github.com/hashicorp/terraform-registry-address v0.0.0-20220623143253-7d51757b572c h1:D8aRO6+mTqHfLsK/BC3j5OAoogv1WLRWzY1AaTo3rBg= 139 | github.com/hashicorp/terraform-registry-address v0.0.0-20220623143253-7d51757b572c/go.mod h1:Wn3Na71knbXc1G8Lh+yu/dQWWJeFQEpDeJMtWMtlmNI= 140 | github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 h1:HKLsbzeOsfXmKNpr3GiT18XAblV0BjCbzL8KQAMZGa0= 141 | github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734/go.mod h1:kNDNcF7sN4DocDLBkQYz73HGKwN1ANB1blq4lIYLYvg= 142 | github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= 143 | github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= 144 | github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= 145 | github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= 146 | github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= 147 | github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= 148 | github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= 149 | github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= 150 | github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= 151 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= 152 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= 153 | github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= 154 | github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= 155 | github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck= 156 | github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= 157 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 158 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 159 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 160 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= 161 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 162 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 163 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 164 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 165 | github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= 166 | github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= 167 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 168 | github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 169 | github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= 170 | github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= 171 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 172 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 173 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= 174 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 175 | github.com/mitchellh/cli v1.1.4 h1:qj8czE26AU4PbiaPXK5uVmMSM+V5BYsFBiM9HhGRLUA= 176 | github.com/mitchellh/cli v1.1.4/go.mod h1:vTLESy5mRhKOs9KDp0/RATawxP1UqBmdrpVRMnpcvKQ= 177 | github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= 178 | github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= 179 | github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= 180 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= 181 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 182 | github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= 183 | github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= 184 | github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= 185 | github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= 186 | github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= 187 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 188 | github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce h1:RPclfga2SEJmgMmz2k+Mg7cowZ8yv4Trqw9UsJby758= 189 | github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= 190 | github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= 191 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 192 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 193 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 194 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 195 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= 196 | github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo= 197 | github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= 198 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 199 | github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= 200 | github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= 201 | github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= 202 | github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= 203 | github.com/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4= 204 | github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= 205 | github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= 206 | github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= 207 | github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= 208 | github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= 209 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= 210 | github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 211 | github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= 212 | github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= 213 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 214 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 215 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 216 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 217 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 218 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 219 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 220 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 221 | github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= 222 | github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= 223 | github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= 224 | github.com/vmihailenco/msgpack/v4 v4.3.12 h1:07s4sz9IReOgdikxLTKNbBdqDMLsjPKXwvCazn8G65U= 225 | github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= 226 | github.com/vmihailenco/tagparser v0.1.1 h1:quXMXlA39OCbd2wAdTsGDlK9RkOk6Wuw+x37wVyIuWY= 227 | github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= 228 | github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI= 229 | github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= 230 | github.com/zclconf/go-cty v1.1.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s= 231 | github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= 232 | github.com/zclconf/go-cty v1.10.0 h1:mp9ZXQeIcN8kAwuqorjH+Q+njbJKjLrvB2yIh4q7U+0= 233 | github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= 234 | github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= 235 | go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= 236 | golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 237 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 238 | golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 239 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 240 | golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 241 | golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= 242 | golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= 243 | golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 244 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY= 245 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 246 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 247 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 248 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 249 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 250 | golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38= 251 | golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= 252 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 253 | golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 254 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 255 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 256 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 257 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 258 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 259 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 260 | golang.org/x/net v0.0.0-20191009170851-d66e71096ffb/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 261 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 262 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 263 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 264 | golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 265 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 266 | golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= 267 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= 268 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 269 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 270 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 271 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 272 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 273 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 274 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 275 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 276 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 277 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 278 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 279 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 280 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 281 | golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 282 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 283 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 284 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 285 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 286 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 287 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 288 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 289 | golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 290 | golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 291 | golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 292 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 293 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 294 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 295 | golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 296 | golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b h1:2n253B2r0pYSmEV+UNCQoPfU/FiaizQEK5Gu4Bq4JE8= 297 | golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 298 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 299 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 300 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 301 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 302 | golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 303 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 304 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 305 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 306 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 307 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 308 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 309 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 310 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 311 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 312 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 313 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 314 | google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= 315 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 316 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 317 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 318 | google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 319 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= 320 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 321 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 322 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 323 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 324 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 325 | google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= 326 | google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 327 | google.golang.org/grpc v1.48.0 h1:rQOsyJ/8+ufEDJd/Gdsz7HG220Mh9HAhFHRGnIjda0w= 328 | google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= 329 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 330 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 331 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 332 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 333 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 334 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 335 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 336 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 337 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 338 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 339 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 340 | google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 341 | google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= 342 | google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 343 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 344 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 345 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 346 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 347 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 348 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 349 | gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= 350 | gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= 351 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 352 | gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 353 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 354 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 355 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 356 | gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 357 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 358 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 359 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 360 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 361 | --------------------------------------------------------------------------------