diff --git a/docs/index.md b/docs/index.md index 5c40b32..3473f8c 100644 --- a/docs/index.md +++ b/docs/index.md @@ -14,3 +14,13 @@ description: |- ## Schema + +### Optional + +- `guid_seed_addition` (String) It serves as addition to each seed of any `value_is_fully_known` (resource) or `value_is_known` (resource) within the project if specified in provider, or within the same module if specified in provider-meta. + + **Placeholders**: + - "{workdir}" (Keyword) The actual workdir; equals to terraform's path.root. This placeholder is + recommended because this value won't be dragged along the plan and apply phase in comparison to + "abspath(path.root)" that you would add to resource seed where a change to path.root would be + recognized just as usual from terraform. diff --git a/docs/resources/is_fully_known.md b/docs/resources/is_fully_known.md index 1e2e2d7..2aa9b2b 100644 --- a/docs/resources/is_fully_known.md +++ b/docs/resources/is_fully_known.md @@ -5,19 +5,19 @@ subcategory: "" description: |- 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. Provider Metadata - 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): - terraform - // Terraform provider_meta example - terraform { - // "value" is the provider name - provider_meta "value" { - // {workdir} -> The only available placeholder currently (see below for more information) - seed_prefix = "{workdir}#for-example" // Results into "/path/to/workdir#for-example" + 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): + ```terraform + // Terraform providermeta example + terraform { + // "value" is the provider name + providermeta "value" { + // {workdir} -> The only available placeholder currently (see below for more information) + guidseed_addition = "{workdir}#for-example" // Results into "/path/to/workdir#for-example" + } } - } - + ``` Optional - seed_prefix (String) It gets appended to each seed of any value_is_fully_known (resource) or value_is_known (resource) within the same module. + guid_seed_addition (String) It serves as addition to each seed of any value_is_fully_known (resource) or value_is_known (resource) within the project if specified in provider, or within the same module if specified in provider-meta. Placeholders: "{workdir}" (Keyword) The actual workdir; equals to terraform's path.root. This placeholder is recommended because this value won't be dragged along the plan and apply phase in comparison to @@ -29,19 +29,19 @@ description: |- 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. ## Provider Metadata -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): +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)): ```terraform // Terraform provider_meta example -terraform { - // "value" is the provider name - provider_meta "value" { - // {workdir} -> The only available placeholder currently (see below for more information) - seed_prefix = "{workdir}#for-example" // Results into "/path/to/workdir#for-example" + terraform { + // "value" is the provider name + provider_meta "value" { + // {workdir} -> The only available placeholder currently (see below for more information) + guid_seed_addition = "{workdir}#for-example" // Results into "/path/to/workdir#for-example" + } } -} ``` ### Optional -- `seed_prefix` (String) It gets appended to each seed of any `value_is_fully_known` (resource) or `value_is_known` (resource) within the same module. +- `guid_seed_addition` (String) It serves as addition to each seed of any `value_is_fully_known` (resource) or `value_is_known` (resource) within the project if specified in provider, or within the same module if specified in provider-meta. **Placeholders**: - "{workdir}" (Keyword) The actual workdir; equals to terraform's path.root. This placeholder is @@ -56,8 +56,8 @@ terraform { ### Required +- `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 configuration seed, the provider_meta configuration seed 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. - `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! -- `unique_seed` (String) Attention! The seed is being used to determine resource uniqueness prior and during apply-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 recommend you to use the provider_meta-feature for increased uniqueness. 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. - `value` (Dynamic) The `value` and if existing, nested attributes, are tested against "(known after apply)" ### Optional diff --git a/docs/resources/is_known.md b/docs/resources/is_known.md index 34f2301..26952e5 100644 --- a/docs/resources/is_known.md +++ b/docs/resources/is_known.md @@ -5,19 +5,19 @@ subcategory: "" description: |- Allows you to have a access to result during plan phase that states whether value marked as "(known after apply)" or not. Provider Metadata - 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): - terraform - // Terraform provider_meta example - terraform { - // "value" is the provider name - provider_meta "value" { - // {workdir} -> The only available placeholder currently (see below for more information) - seed_prefix = "{workdir}#for-example" // Results into "/path/to/workdir#for-example" + 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): + ```terraform + // Terraform providermeta example + terraform { + // "value" is the provider name + providermeta "value" { + // {workdir} -> The only available placeholder currently (see below for more information) + guidseed_addition = "{workdir}#for-example" // Results into "/path/to/workdir#for-example" + } } - } - + ``` Optional - seed_prefix (String) It gets appended to each seed of any value_is_fully_known (resource) or value_is_known (resource) within the same module. + guid_seed_addition (String) It serves as addition to each seed of any value_is_fully_known (resource) or value_is_known (resource) within the project if specified in provider, or within the same module if specified in provider-meta. Placeholders: "{workdir}" (Keyword) The actual workdir; equals to terraform's path.root. This placeholder is recommended because this value won't be dragged along the plan and apply phase in comparison to @@ -29,19 +29,19 @@ description: |- Allows you to have a access to `result` during plan phase that states whether `value` marked as "(known after apply)" or not. ## Provider Metadata -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): +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)): ```terraform // Terraform provider_meta example -terraform { - // "value" is the provider name - provider_meta "value" { - // {workdir} -> The only available placeholder currently (see below for more information) - seed_prefix = "{workdir}#for-example" // Results into "/path/to/workdir#for-example" + terraform { + // "value" is the provider name + provider_meta "value" { + // {workdir} -> The only available placeholder currently (see below for more information) + guid_seed_addition = "{workdir}#for-example" // Results into "/path/to/workdir#for-example" + } } -} ``` ### Optional -- `seed_prefix` (String) It gets appended to each seed of any `value_is_fully_known` (resource) or `value_is_known` (resource) within the same module. +- `guid_seed_addition` (String) It serves as addition to each seed of any `value_is_fully_known` (resource) or `value_is_known` (resource) within the project if specified in provider, or within the same module if specified in provider-meta. **Placeholders**: - "{workdir}" (Keyword) The actual workdir; equals to terraform's path.root. This placeholder is @@ -56,8 +56,8 @@ terraform { ### Required +- `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 configuration seed, the provider_meta configuration seed 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. - `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! -- `unique_seed` (String) Attention! The seed is being used to determine resource uniqueness prior and during apply-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 recommend you to use the provider_meta-feature for increased uniqueness. 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. - `value` (Dynamic) The `value` (not nested attributes) is test against "(known after apply)" ### Optional diff --git a/examples/is_fully_known/provider_meta.tf b/examples/is_fully_known/provider_meta.tf deleted file mode 100644 index a10b1cf..0000000 --- a/examples/is_fully_known/provider_meta.tf +++ /dev/null @@ -1,13 +0,0 @@ -terraform { - required_providers { - value = { - source = "github.com/pseudo-dynamic/value" - version = "0.1.0" - } - } - - provider_meta "value" { - // {workdir} -> a placeholder (see docs) - seed_prefix = "{workdir}" - } -} \ No newline at end of file diff --git a/examples/is_fully_known/shared.tf b/examples/is_fully_known/shared.tf deleted file mode 100644 index 88150b7..0000000 --- a/examples/is_fully_known/shared.tf +++ /dev/null @@ -1,6 +0,0 @@ -// An "(known after apply)" value producer -resource "value_unknown_proposer" "default" {} - -resource "value_promise" "default" { - value = "test" -} diff --git a/examples/is_fully_known/terraform.tf b/examples/is_fully_known/terraform.tf new file mode 100644 index 0000000..63ab45d --- /dev/null +++ b/examples/is_fully_known/terraform.tf @@ -0,0 +1,21 @@ +terraform { + required_providers { + value = { + source = "github.com/pseudo-dynamic/value" + version = "0.1.0" + } + } + + provider_meta "value" { + // Module-scoped seed addition. + // {workdir} -> a placeholder (see docs) + guid_seed_addition = "module(is-fully-known)" + } +} + +provider "value" { + // Project-wide seed addition. + // Won't overwrite module-scoped seed addition, + // instead both serve are now considered as seed addition. + guid_seed_addition = "{workdir}" +} diff --git a/examples/is_fully_known/test_known.tf b/examples/is_fully_known/test_known.tf index e880f92..9c018fb 100644 --- a/examples/is_fully_known/test_known.tf +++ b/examples/is_fully_known/test_known.tf @@ -1,13 +1,15 @@ -// This example is incomplete. Please take a look at provider_meta.tf and shared.tf too! +// This example is complete but there are additional features implemented in terraform.tf! + +resource "value_unknown_proposer" "known" {} resource "value_is_fully_known" "known" { value = "test" - unique_seed = "known" - proposed_unknown = value_unknown_proposer.default.value + guid_seed = "known" + proposed_unknown = value_unknown_proposer.known.value } output "is_known_value" { value = { - is_fully_known = value_is_fully_known.known.result + fully_known = value_is_fully_known.known.result } -} \ No newline at end of file +} diff --git a/examples/is_fully_known/test_known_with_nested_unknown.tf b/examples/is_fully_known/test_known_with_nested_unknown.tf index ddbf661..a437635 100644 --- a/examples/is_fully_known/test_known_with_nested_unknown.tf +++ b/examples/is_fully_known/test_known_with_nested_unknown.tf @@ -1,12 +1,18 @@ -// This example is incomplete. Please take a look at provider_meta.tf and shared.tf too! +// This example is complete but there are additional features implemented in terraform.tf! + +resource "value_unknown_proposer" "known_with_nested_unknown" {} + +resource "value_promise" "known_with_nested_unknown" { + value = "test" +} resource "value_is_fully_known" "known_with_nested_unknown" { value = { - nested = value_promise.default.result + nested = value_promise.known_with_nested_unknown.result } - unique_seed = "nested_known" - proposed_unknown = value_unknown_proposer.default.value + guid_seed = "nested_unknown" + proposed_unknown = value_unknown_proposer.known_with_nested_unknown.value } output "is_known_with_nested_unknown_value" { diff --git a/examples/is_fully_known/test_unknown.tf b/examples/is_fully_known/test_unknown.tf index 28f16ec..b3ecc2d 100644 --- a/examples/is_fully_known/test_unknown.tf +++ b/examples/is_fully_known/test_unknown.tf @@ -1,13 +1,19 @@ -// This example is incomplete. Please take a look at provider_meta.tf and shared.tf too! +// This example is complete but there are additional features implemented in terraform.tf! + +resource "value_unknown_proposer" "unknown" {} + +resource "value_promise" "unknown" { + value = "test" +} resource "value_is_fully_known" "unknown" { - value = value_promise.default.result - unique_seed = "unknown" - proposed_unknown = value_unknown_proposer.default.value + value = value_promise.unknown.result + guid_seed = "unknown" + proposed_unknown = value_unknown_proposer.unknown.value } output "is_unknown_value" { value = { - is_fully_known = value_is_fully_known.unknown.result + fully_known = value_is_fully_known.unknown.result } } diff --git a/examples/is_known/provider_meta.tf b/examples/is_known/provider_meta.tf deleted file mode 100644 index a10b1cf..0000000 --- a/examples/is_known/provider_meta.tf +++ /dev/null @@ -1,13 +0,0 @@ -terraform { - required_providers { - value = { - source = "github.com/pseudo-dynamic/value" - version = "0.1.0" - } - } - - provider_meta "value" { - // {workdir} -> a placeholder (see docs) - seed_prefix = "{workdir}" - } -} \ No newline at end of file diff --git a/examples/is_known/shared.tf b/examples/is_known/shared.tf deleted file mode 100644 index 88150b7..0000000 --- a/examples/is_known/shared.tf +++ /dev/null @@ -1,6 +0,0 @@ -// An "(known after apply)" value producer -resource "value_unknown_proposer" "default" {} - -resource "value_promise" "default" { - value = "test" -} diff --git a/examples/is_known/terraform.tf b/examples/is_known/terraform.tf new file mode 100644 index 0000000..34ee00f --- /dev/null +++ b/examples/is_known/terraform.tf @@ -0,0 +1,21 @@ +terraform { + required_providers { + value = { + source = "github.com/pseudo-dynamic/value" + version = "0.1.0" + } + } + + provider_meta "value" { + // Module-scoped seed addition. + // {workdir} -> a placeholder (see docs) + guid_seed_addition = "module(is-known)" + } +} + +provider "value" { + // Project-wide seed addition. + // Won't overwrite module-scoped seed addition, + // instead both serve are now considered as seed addition. + guid_seed_addition = "{workdir}" +} diff --git a/examples/is_known/test_known.tf b/examples/is_known/test_known.tf index f54660a..e66d4d0 100644 --- a/examples/is_known/test_known.tf +++ b/examples/is_known/test_known.tf @@ -1,13 +1,15 @@ -# // This example is incomplete. Please take a look at provider_meta.tf and shared.tf too! +// This example is complete but there are additional features implemented in terraform.tf! -# resource "value_is_known" "known" { -# value = "test" -# unique_seed = "known" -# proposed_unknown = value_unknown_proposer.default.value -# } +resource "value_unknown_proposer" "known" {} -# output "is_known_value" { -# value = { -# known = value_is_known.known.result -# } -# } \ No newline at end of file +resource "value_is_known" "known" { + value = "test" + guid_seed = "known" + proposed_unknown = value_unknown_proposer.known.value +} + +output "is_known_value" { + value = { + known = value_is_known.known.result + } +} diff --git a/examples/is_known/test_known_with_nested_unknown.tf b/examples/is_known/test_known_with_nested_unknown.tf index 42d560b..a539cc4 100644 --- a/examples/is_known/test_known_with_nested_unknown.tf +++ b/examples/is_known/test_known_with_nested_unknown.tf @@ -1,12 +1,18 @@ -// This example is incomplete. Please take a look at provider_meta.tf and shared.tf too! +// This example is complete but there are additional features implemented in terraform.tf! + +resource "value_unknown_proposer" "known_with_nested_unknown" {} + +resource "value_promise" "known_with_nested_unknown" { + value = "test" +} resource "value_is_known" "known_with_nested_unknown" { value = { - nested = value_promise.default.result + nested = value_promise.known_with_nested_unknown.result } - unique_seed = "nested_known" - proposed_unknown = value_unknown_proposer.default.value + guid_seed = "nested_unknown" + proposed_unknown = value_unknown_proposer.known_with_nested_unknown.value } output "is_known_with_nested_unknown_value" { diff --git a/examples/is_known/test_unknown.tf b/examples/is_known/test_unknown.tf index 978815b..fe11789 100644 --- a/examples/is_known/test_unknown.tf +++ b/examples/is_known/test_unknown.tf @@ -1,13 +1,19 @@ -# // This example is incomplete. Please take a look at provider_meta.tf and shared.tf too! - -# resource "value_is_known" "unknown" { -# value = value_promise.default.result -# unique_seed = "unknown" -# proposed_unknown = value_unknown_proposer.default.value -# } - -# output "is_unknown_value" { -# value = { -# known = value_is_known.unknown.result -# } -# } +// This example is complete but there are additional features implemented in terraform.tf! + +resource "value_unknown_proposer" "unknown" {} + +resource "value_promise" "unknown" { + value = "test" +} + +resource "value_is_known" "unknown" { + value = value_promise.unknown.result + guid_seed = "unknown" + proposed_unknown = value_unknown_proposer.unknown.value +} + +output "is_unknown_value" { + value = { + known = value_is_known.unknown.result + } +} diff --git a/internal/fwkprovider/provider.go b/internal/fwkprovider/provider.go new file mode 100644 index 0000000..5aaff49 --- /dev/null +++ b/internal/fwkprovider/provider.go @@ -0,0 +1,73 @@ +package fwkprovider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/diag" + fwkprovider "github.com/hashicorp/terraform-plugin-framework/provider" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/pseudo-dynamic/terraform-provider-value/internal/fwkproviderconfig" +) + +type provider struct { +} + +type providerWithTraits interface { + fwkprovider.ProviderWithMetadata + fwkprovider.ProviderWithResources + fwkprovider.ProviderWithDataSources +} + +// Provider schema struct +type providerConfig struct { + GuidSeedAddition types.String `tfsdk:"guid_seed_addition"` +} + +func NewProvider() providerWithTraits { + return &provider{} +} + +// Metadata implements ProviderWithTraits +func (p *provider) Metadata(ctx context.Context, req fwkprovider.MetadataRequest, resp *fwkprovider.MetadataResponse) { + resp.TypeName = "value" +} + +// GetSchema +func (p *provider) GetSchema(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return *fwkproviderconfig.GetProviderConfigSchema(), nil +} + +func (p *provider) Configure(ctx context.Context, req fwkprovider.ConfigureRequest, resp *fwkprovider.ConfigureResponse) { + // Retrieve provider data from configuration + var config providerConfig + diags := req.Config.Get(ctx, &config) + resp.Diagnostics.Append(diags...) + + if resp.Diagnostics.HasError() { + return + } +} + +// GetResources - Defines provider resources +func (p *provider) Resources(_ context.Context) []func() resource.Resource { + return []func() resource.Resource{ + func() resource.Resource { + return NewReplacedWhenResource() + }, + func() resource.Resource { + return NewUnknownProposerResource() + }, + } +} + +// DataSources implements ProviderWithTraits +func (*provider) DataSources(context.Context) []func() datasource.DataSource { + return []func() datasource.DataSource{ + func() datasource.DataSource { + return NewTempDirDataSource() + }, + } +} diff --git a/internal/provider/replaced_when_resource.go b/internal/fwkprovider/replaced_when_resource.go similarity index 90% rename from internal/provider/replaced_when_resource.go rename to internal/fwkprovider/replaced_when_resource.go index 9448119..c53de87 100644 --- a/internal/provider/replaced_when_resource.go +++ b/internal/fwkprovider/replaced_when_resource.go @@ -1,4 +1,4 @@ -package provider +package fwkprovider import ( "context" @@ -12,20 +12,20 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" ) -type ReplacedWhenResource struct { +type replacedWhenResource struct { } -type ReplacedWhenResourceWithTraits interface { +type replacedWhenResourceWithTraits interface { resource.ResourceWithMetadata resource.ResourceWithGetSchema resource.ResourceWithModifyPlan } -func NewReplacedWhenResource() ReplacedWhenResourceWithTraits { - return &ReplacedWhenResource{} +func NewReplacedWhenResource() replacedWhenResourceWithTraits { + return &replacedWhenResource{} } -func (r ReplacedWhenResource) GetSchema(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { +func (r replacedWhenResource) GetSchema(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { return tfsdk.Schema{ Description: `Enables the scenario to only change a value when a condition is met. The value attribute can for example be used as target for replace_triggered_by. To detect @@ -79,17 +79,17 @@ unknown and uncomputed again.`, }, nil } -type ReplacedWhenState struct { +type replacedWhenState struct { When types.Bool `tfsdk:"condition"` Value types.String `tfsdk:"value"` } -func (r *ReplacedWhenResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { +func (r *replacedWhenResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { resp.TypeName = req.ProviderTypeName + "_replaced_when" } // ModifyPlan implements ReplacedWhenResourceWithTraits -func (r *ReplacedWhenResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { +func (r *replacedWhenResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { if req.Plan.Raw.IsNull() { // Ignore due to resource deletion return @@ -177,9 +177,9 @@ func (r *ReplacedWhenResource) ModifyPlan(ctx context.Context, req resource.Modi } // Create a new resource -func (r ReplacedWhenResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { +func (r replacedWhenResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { // Retrieve values from plan - var plan ReplacedWhenState + var plan replacedWhenState diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) @@ -196,9 +196,9 @@ func (r ReplacedWhenResource) Create(ctx context.Context, req resource.CreateReq } // Read resource information -func (r ReplacedWhenResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { +func (r replacedWhenResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { // Get current state - var state ReplacedWhenState + var state replacedWhenState diags := req.State.Get(ctx, &state) resp.Diagnostics.Append(diags...) @@ -217,9 +217,9 @@ func (r ReplacedWhenResource) Read(ctx context.Context, req resource.ReadRequest } // Update resource -func (r ReplacedWhenResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { +func (r replacedWhenResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { // Get plan - var plan ReplacedWhenState + var plan replacedWhenState diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) @@ -237,8 +237,8 @@ func (r ReplacedWhenResource) Update(ctx context.Context, req resource.UpdateReq } // Delete resource -func (r ReplacedWhenResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { - var state ReplacedWhenState +func (r replacedWhenResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state replacedWhenState diags := req.State.Get(ctx, &state) resp.Diagnostics.Append(diags...) diff --git a/internal/provider/temp_dir_data.go b/internal/fwkprovider/temp_dir_data.go similarity index 73% rename from internal/provider/temp_dir_data.go rename to internal/fwkprovider/temp_dir_data.go index 7aa86df..c8a6fbc 100644 --- a/internal/provider/temp_dir_data.go +++ b/internal/fwkprovider/temp_dir_data.go @@ -1,4 +1,4 @@ -package provider +package fwkprovider import ( "context" @@ -10,18 +10,18 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" ) -type TempDirDataSource struct { +type tempDirDataSource struct { } -type TempDirDataSourceWithTraits interface { +type tempDirDataSourceWithTraits interface { datasource.DataSource } -func NewTempDirDataSource() TempDirDataSourceWithTraits { - return &TempDirDataSource{} +func NewTempDirDataSource() tempDirDataSourceWithTraits { + return &tempDirDataSource{} } -func (r TempDirDataSource) GetSchema(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { +func (r tempDirDataSource) GetSchema(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { return tfsdk.Schema{ Description: "Simply returns the OS-dependent temporary directory (e.g. /tmp).", Attributes: map[string]tfsdk.Attribute{ @@ -34,18 +34,18 @@ func (r TempDirDataSource) GetSchema(_ context.Context) (tfsdk.Schema, diag.Diag }, nil } -func (r *TempDirDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { +func (r *tempDirDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { resp.TypeName = req.ProviderTypeName + "_temp_dir" } -type TempDirState struct { +type tempDirState struct { Path types.String `tfsdk:"path"` } // Read resource information -func (r TempDirDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { +func (r tempDirDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { // Get current state - var state TempDirState + var state tempDirState diags := req.Config.Get(ctx, &state) resp.Diagnostics.Append(diags...) diff --git a/internal/provider/unknown_proposer_resource.go b/internal/fwkprovider/unknown_proposer_resource.go similarity index 75% rename from internal/provider/unknown_proposer_resource.go rename to internal/fwkprovider/unknown_proposer_resource.go index 61a2eba..9e444d7 100644 --- a/internal/provider/unknown_proposer_resource.go +++ b/internal/fwkprovider/unknown_proposer_resource.go @@ -1,4 +1,4 @@ -package provider +package fwkprovider import ( "context" @@ -9,20 +9,20 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" ) -type UnknownProposerResource struct { +type unknownProposerResource struct { } -type UnknownProposerResourceWithTraits interface { +type unknownProposerResourceWithTraits interface { resource.ResourceWithMetadata resource.ResourceWithGetSchema resource.ResourceWithModifyPlan } -func NewUnknownProposerResource() UnknownProposerResourceWithTraits { - return &UnknownProposerResource{} +func NewUnknownProposerResource() unknownProposerResourceWithTraits { + return &unknownProposerResource{} } -func (r UnknownProposerResource) GetSchema(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { +func (r unknownProposerResource) GetSchema(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { return tfsdk.Schema{ Version: 0, Description: "This resource is very obscure and misbehaving and you really should only use " + @@ -37,22 +37,22 @@ func (r UnknownProposerResource) GetSchema(_ context.Context) (tfsdk.Schema, dia }, nil } -type UnknownProposerState struct { +type unknownProposerState struct { Value types.Bool `tfsdk:"value"` } -func (r *UnknownProposerResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { +func (r *unknownProposerResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { resp.TypeName = req.ProviderTypeName + "_unknown_proposer" } // ModifyPlan implements UnknownProposerResourceWithTraits -func (r *UnknownProposerResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { +func (r *unknownProposerResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { if req.Config.Raw.IsNull() { // Ignore due to resource deletion return } - var plan UnknownProposerState + var plan unknownProposerState diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) @@ -66,8 +66,8 @@ func (r *UnknownProposerResource) ModifyPlan(ctx context.Context, req resource.M } // Create a new resource -func (r UnknownProposerResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { - var plan UnknownProposerState +func (r unknownProposerResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan unknownProposerState diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) @@ -81,9 +81,9 @@ func (r UnknownProposerResource) Create(ctx context.Context, req resource.Create } // Read resource information -func (r UnknownProposerResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { +func (r unknownProposerResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { // Get current state - var state UnknownProposerState + var state unknownProposerState diags := req.State.Get(ctx, &state) resp.Diagnostics.Append(diags...) @@ -96,9 +96,9 @@ func (r UnknownProposerResource) Read(ctx context.Context, req resource.ReadRequ } // Update resource -func (r UnknownProposerResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { +func (r unknownProposerResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { // Get plan - var plan UnknownProposerState + var plan unknownProposerState diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) @@ -112,8 +112,8 @@ func (r UnknownProposerResource) Update(ctx context.Context, req resource.Update } // Delete resource -func (r UnknownProposerResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { - var state UnknownProposerState +func (r unknownProposerResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state unknownProposerState diags := req.State.Get(ctx, &state) resp.Diagnostics.Append(diags...) diff --git a/internal/fwkproviderconfig/attribute_guid_seed_attribute.go b/internal/fwkproviderconfig/attribute_guid_seed_attribute.go new file mode 100644 index 0000000..4faee43 --- /dev/null +++ b/internal/fwkproviderconfig/attribute_guid_seed_attribute.go @@ -0,0 +1,52 @@ +package fwkproviderconfig + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +type guidSeedAdditionUnknownValidator struct{} + +func (v *guidSeedAdditionUnknownValidator) Validate(ctx context.Context, req tfsdk.ValidateAttributeRequest, resp *tfsdk.ValidateAttributeResponse) { + if req.AttributeConfig != nil && req.AttributeConfig.IsUnknown() { + resp.Diagnostics.AddError( + "Guid seed addition is unknown", + "Guid seed addition must be fully known at plan-time. For further informations take a look into the documentation.") + } +} + +func getGuidSeedAdditionUnknownValidatorDescription() string { + return "Validates that guid seed addition is not unknown." +} + +func (*guidSeedAdditionUnknownValidator) Description(context.Context) string { + return getGuidSeedAdditionUnknownValidatorDescription() +} + +func (*guidSeedAdditionUnknownValidator) MarkdownDescription(context.Context) string { + return getGuidSeedAdditionUnknownValidatorDescription() +} + +type guidSeedAdditionDefaultEmptyModifier struct{} + +func (r guidSeedAdditionDefaultEmptyModifier) Modify(ctx context.Context, req tfsdk.ModifyAttributePlanRequest, resp *tfsdk.ModifyAttributePlanResponse) { + if req.AttributePlan == nil || req.AttributePlan.IsNull() { + resp.AttributePlan = types.String{Value: ""} + } +} + +func getGuidSeedAdditionAttributeModifierDescription() string { + return "If the value is null, then its value is now empty." +} + +// Description returns a human-readable description of the plan modifier. +func (guidSeedAdditionDefaultEmptyModifier) Description(context.Context) string { + return getGuidSeedAdditionAttributeModifierDescription() +} + +// MarkdownDescription returns a markdown description of the plan modifier. +func (guidSeedAdditionDefaultEmptyModifier) MarkdownDescription(context.Context) string { + return getGuidSeedAdditionAttributeModifierDescription() +} diff --git a/internal/fwkproviderconfig/provider_config_schema.go b/internal/fwkproviderconfig/provider_config_schema.go new file mode 100644 index 0000000..beedc66 --- /dev/null +++ b/internal/fwkproviderconfig/provider_config_schema.go @@ -0,0 +1,28 @@ +package fwkproviderconfig + +import ( + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/pseudo-dynamic/terraform-provider-value/internal/goproviderconfig" +) + +func GetProviderConfigSchema() *tfsdk.Schema { + return &tfsdk.Schema{ + Version: 0, + Attributes: map[string]tfsdk.Attribute{ + "guid_seed_addition": { + Type: types.StringType, + Required: false, + Optional: true, + Computed: false, + Validators: []tfsdk.AttributeValidator{ + &guidSeedAdditionUnknownValidator{}, + }, + PlanModifiers: tfsdk.AttributePlanModifiers{ + guidSeedAdditionDefaultEmptyModifier{}, + }, + Description: goproviderconfig.GetGuidSeedAdditionAttributeDescription(), + }, + }, + } +} diff --git a/internal/goproviderconfig/attribute_guid_seed_addition.go b/internal/goproviderconfig/attribute_guid_seed_addition.go new file mode 100644 index 0000000..ee573dd --- /dev/null +++ b/internal/goproviderconfig/attribute_guid_seed_addition.go @@ -0,0 +1,86 @@ +package goproviderconfig + +import ( + "os" + "strings" + + "github.com/pseudo-dynamic/terraform-provider-value/internal/schema" + + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +func TryExtractGetSeedAddition(state *tfprotov6.DynamicValue, stateType tftypes.Type) (string, []*tfprotov6.Diagnostic, bool) { + var seedAdditionValue tftypes.Value + seedAddition := "" + + var isErroneous bool + var isWorking bool + + stateValueDynamic := state + var stateValue tftypes.Value + var stateValueMap map[string]tftypes.Value + var diags []*tfprotov6.Diagnostic + + if stateValue, stateValueMap, diags, isErroneous = schema.UnmarshalState(stateValueDynamic, stateType); isErroneous { + goto Return + } + _ = stateValue + + if seedAdditionValue, isWorking = stateValueMap["guid_seed_addition"]; !isWorking { + goto Return + } + _ = seedAdditionValue + + if !seedAdditionValue.IsKnown() { + diags = append(diags, &tfprotov6.Diagnostic{ + Severity: tfprotov6.DiagnosticSeverityError, + Summary: "Provider configuration has 'guid_seed_addition' attribute but it is not known at plan-time.", + Detail: "The 'guid_seed_addition' attribute must be known during the plan phase. See attribute description for more informations.", + }) + + goto Return + } + + if seedAdditionValue.IsNull() { + goto Return + } + + if err := seedAdditionValue.As(&seedAddition); err != nil { + diags = append(diags, &tfprotov6.Diagnostic{ + Severity: tfprotov6.DiagnosticSeverityError, + Summary: "Extraction failed", + Detail: "The guid seed addition could not be extracted as string", + }) + + goto Return + } else { + workdir, _ := os.Getwd() + seedAddition = strings.ReplaceAll(seedAddition, "{workdir}", workdir) + } + +Return: + return seedAddition, diags, len(diags) == 0 +} + +func GetGuidSeedAdditionAttributeDescription() string { + return "It serves as addition to each seed of any `value_is_fully_known` (resource) or " + + "`value_is_known` (resource) within the project if specified in provider, or within the " + + "same module if specified in provider-meta.\n" + ` + **Placeholders**: + - "{workdir}" (Keyword) The actual workdir; equals to terraform's path.root. This placeholder is + recommended because this value won't be dragged along the plan and apply phase in comparison to + "abspath(path.root)" that you would add to resource seed where a change to path.root would be + recognized just as usual from terraform.` +} + +func GetGuidSeedAdditionSchemaAttribute(attributeDescription string) *tfprotov6.SchemaAttribute { + return &tfprotov6.SchemaAttribute{ + Name: "guid_seed_addition", + Type: tftypes.String, + Required: false, + Optional: true, + Computed: false, + Description: attributeDescription, + } +} diff --git a/internal/goproviderconfig/provider_config_schema.go b/internal/goproviderconfig/provider_config_schema.go new file mode 100644 index 0000000..8f914a7 --- /dev/null +++ b/internal/goproviderconfig/provider_config_schema.go @@ -0,0 +1,29 @@ +package goproviderconfig + +import ( + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-go/tftypes" + "github.com/pseudo-dynamic/terraform-provider-value/internal/schema" +) + +// GetProviderConfigType returns the tftypes.Type of a resource of type 'name' +func GetProviderConfigType() tftypes.Type { + sch := GetProviderConfigSchema() + return schema.GetObjectTypeFromSchema(sch) +} + +func GetProviderConfigSchema() *tfprotov6.Schema { + return &tfprotov6.Schema{ + Version: 0, + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + GetGuidSeedAdditionSchemaAttribute(GetGuidSeedAdditionAttributeDescription()), + }, + }, + } +} + +func TryExtractProviderConfigGuidSeedAddition(providerConfig *tfprotov6.DynamicValue) (string, []*tfprotov6.Diagnostic, bool) { + providerConfigType := GetProviderConfigType() + return TryExtractGetSeedAddition(providerConfig, providerConfigType) +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go deleted file mode 100644 index d9ed171..0000000 --- a/internal/provider/provider.go +++ /dev/null @@ -1,72 +0,0 @@ -package provider - -import ( - "context" - - "github.com/hashicorp/terraform-plugin-framework/datasource" - "github.com/hashicorp/terraform-plugin-framework/diag" - "github.com/hashicorp/terraform-plugin-framework/provider" - "github.com/hashicorp/terraform-plugin-framework/resource" - "github.com/hashicorp/terraform-plugin-framework/tfsdk" -) - -type Provider struct { -} - -type ProviderWithTraits interface { - provider.ProviderWithMetadata - provider.ProviderWithResources - provider.ProviderWithDataSources -} - -// Provider schema struct -type ProviderConfig struct { -} - -func NewProvider() ProviderWithTraits { - return &Provider{} -} - -// Metadata implements ProviderWithTraits -func (p *Provider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) { - resp.TypeName = "value" -} - -// GetSchema -func (p *Provider) GetSchema(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { - return tfsdk.Schema{ - Attributes: map[string]tfsdk.Attribute{}, - }, nil -} - -func (p *Provider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { - // Retrieve provider data from configuration - var config ProviderConfig - diags := req.Config.Get(ctx, &config) - resp.Diagnostics.Append(diags...) - - if resp.Diagnostics.HasError() { - return - } -} - -// GetResources - Defines provider resources -func (p *Provider) Resources(_ context.Context) []func() resource.Resource { - return []func() resource.Resource{ - func() resource.Resource { - return NewReplacedWhenResource() - }, - func() resource.Resource { - return NewUnknownProposerResource() - }, - } -} - -// DataSources implements ProviderWithTraits -func (*Provider) DataSources(context.Context) []func() datasource.DataSource { - return []func() datasource.DataSource{ - func() datasource.DataSource { - return NewTempDirDataSource() - }, - } -} diff --git a/internal/schema/main.go b/internal/schema/main.go new file mode 100644 index 0000000..049b329 --- /dev/null +++ b/internal/schema/main.go @@ -0,0 +1,71 @@ +package schema + +import ( + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +// GetObjectTypeFromSchema returns a tftypes.Type that can wholy represent the schema input +// TODO: Outsource this and reduce redudancy across terraform-plugin-go providers +func GetObjectTypeFromSchema(schema *tfprotov6.Schema) tftypes.Type { + bm := map[string]tftypes.Type{} + + for _, att := range schema.Block.Attributes { + bm[att.Name] = att.Type + } + + for _, b := range schema.Block.BlockTypes { + a := map[string]tftypes.Type{} + for _, att := range b.Block.Attributes { + a[att.Name] = att.Type + } + bm[b.TypeName] = tftypes.List{ + ElementType: tftypes.Object{AttributeTypes: a}, + } + + // FIXME we can make this function recursive to handle + // n levels of nested blocks + for _, bb := range b.Block.BlockTypes { + aa := map[string]tftypes.Type{} + for _, att := range bb.Block.Attributes { + aa[att.Name] = att.Type + } + a[bb.TypeName] = tftypes.List{ + ElementType: tftypes.Object{AttributeTypes: aa}, + } + } + } + + return tftypes.Object{AttributeTypes: bm} +} + +func UnmarshalState(state *tfprotov6.DynamicValue, stateType tftypes.Type) (tftypes.Value, map[string]tftypes.Value, []*tfprotov6.Diagnostic, bool) { + diags := []*tfprotov6.Diagnostic{} + valueMap := make(map[string]tftypes.Value) + value, err := state.Unmarshal(stateType) + + if err != nil { + diags = append(diags, &tfprotov6.Diagnostic{ + Severity: tfprotov6.DiagnosticSeverityError, + Summary: "Failed to unmarshal state", + Detail: err.Error(), + }) + + goto End + } + + err = value.As(&valueMap) + + if err != nil { + diags = append(diags, &tfprotov6.Diagnostic{ + Severity: tfprotov6.DiagnosticSeverityError, + Summary: "Failed to extract state from tftypes.Value", + Detail: err.Error(), + }) + + goto End + } + +End: + return value, valueMap, diags, len(diags) != 0 +} diff --git a/isfullyknown/provider/provider_plugin.go b/isfullyknown/provider/provider.go similarity index 100% rename from isfullyknown/provider/provider_plugin.go rename to isfullyknown/provider/provider.go diff --git a/isknown/common/provider_meta_schema.go b/isknown/common/provider_meta_schema.go index d3aeb4f..f070fed 100644 --- a/isknown/common/provider_meta_schema.go +++ b/isknown/common/provider_meta_schema.go @@ -1,132 +1,47 @@ package common import ( - "os" - "strings" - "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" + + "github.com/pseudo-dynamic/terraform-provider-value/internal/goproviderconfig" + "github.com/pseudo-dynamic/terraform-provider-value/internal/schema" ) // GetProviderMetaType returns the tftypes.Type of a resource of type 'name' func GetProviderMetaType() tftypes.Type { sch := GetProviderMetaSchema() - return getObjectTypeFromSchema(sch) + return schema.GetObjectTypeFromSchema(sch) } -// getObjectTypeFromSchema returns a tftypes.Type that can wholy represent the schema input -// TODO: Outsource this and reduce redudancy across terraform-plugin-go providers -func getObjectTypeFromSchema(schema *tfprotov6.Schema) tftypes.Type { - bm := map[string]tftypes.Type{} - - for _, att := range schema.Block.Attributes { - bm[att.Name] = att.Type - } - - for _, b := range schema.Block.BlockTypes { - a := map[string]tftypes.Type{} - for _, att := range b.Block.Attributes { - a[att.Name] = att.Type - } - bm[b.TypeName] = tftypes.List{ - ElementType: tftypes.Object{AttributeTypes: a}, - } - - // FIXME we can make this function recursive to handle - // n levels of nested blocks - for _, bb := range b.Block.BlockTypes { - aa := map[string]tftypes.Type{} - for _, att := range bb.Block.Attributes { - aa[att.Name] = att.Type - } - a[bb.TypeName] = tftypes.List{ - ElementType: tftypes.Object{AttributeTypes: aa}, - } - } - } - - return tftypes.Object{AttributeTypes: bm} -} - -var seedPrefixDescription = "## Provider Metadata\n" + - "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):\n" + - "```terraform\n" + `// Terraform provider_meta example -terraform { - // "value" is the provider name - provider_meta "value" { - // {workdir} -> The only available placeholder currently (see below for more information) - seed_prefix = "{workdir}#for-example" // Results into "/path/to/workdir#for-example" - } -}` + "\n```\n" + - "### Optional\n" + - "- `seed_prefix` (String) It gets appended to each seed of any `value_is_fully_known` (resource) or " + - "`value_is_known` (resource) within the same module.\n" + ` - **Placeholders**: - - "{workdir}" (Keyword) The actual workdir; equals to terraform's path.root. This placeholder is - recommended because this value won't be dragged along the plan and apply phase in comparison to - "abspath(path.root)" that you would add to resource seed where a change to path.root would be - recognized just as usual from terraform.` - func GetProviderMetaSchema() *tfprotov6.Schema { return &tfprotov6.Schema{ Version: 0, Block: &tfprotov6.SchemaBlock{ Attributes: []*tfprotov6.SchemaAttribute{ - { - Name: "seed_prefix", - Type: tftypes.String, - Required: false, - Optional: true, - Computed: false, - Description: seedPrefixDescription, - }, + goproviderconfig.GetGuidSeedAdditionSchemaAttribute(getProviderMetaGuidSeedAdditionAttributeDescription()), }, }, } } -func CanGetSeedPrefix(providerMeta *tfprotov6.DynamicValue) (string, []*tfprotov6.Diagnostic, bool) { - var seedPrefixValue tftypes.Value - var seedPrefix string - - var isErroneous bool - var isWorking bool +func getProviderMetaGuidSeedAdditionAttributeDescription() string { + return "## Provider Metadata\n" + + "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)):\n" + + "```terraform\n" + `// Terraform provider_meta example + terraform { + // "value" is the provider name + provider_meta "value" { + // {workdir} -> The only available placeholder currently (see below for more information) + guid_seed_addition = "{workdir}#for-example" // Results into "/path/to/workdir#for-example" + } + }` + "\n```\n" + + "### Optional\n" + + "- `guid_seed_addition` (String) " + goproviderconfig.GetGuidSeedAdditionAttributeDescription() +} +func TryExtractProviderMetaGuidSeedAddition(providerMeta *tfprotov6.DynamicValue) (string, []*tfprotov6.Diagnostic, bool) { providerMetaType := GetProviderMetaType() - providerMetaValueDynamic := providerMeta - var providerMetaValue tftypes.Value - var providerMetaValueMap map[string]tftypes.Value - var diags []*tfprotov6.Diagnostic - if providerMetaValue, providerMetaValueMap, diags, isErroneous = UnmarshalState(providerMetaValueDynamic, providerMetaType); isErroneous { - goto End - } - _ = providerMetaValue - - if seedPrefixValue, isWorking = providerMetaValueMap["seed_prefix"]; !isWorking { - goto End - } - _ = seedPrefixValue - - if !seedPrefixValue.IsKnown() { - goto End - } - - if err := seedPrefixValue.As(&seedPrefix); err != nil { - diags = append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Extraction failed", - Detail: "Seed prefix could not be extracted as string", - }) - - goto End - } - - { - workdir, _ := os.Getwd() - seedPrefix = strings.ReplaceAll(seedPrefix, "{workdir}", workdir) - } - -End: - return seedPrefix, diags, len(diags) == 0 + return goproviderconfig.TryExtractGetSeedAddition(providerMeta, providerMetaType) } diff --git a/isknown/common/provider_resource_schema.go b/isknown/common/provider_resource_schema.go index 2024c95..c5d60b1 100644 --- a/isknown/common/provider_resource_schema.go +++ b/isknown/common/provider_resource_schema.go @@ -18,7 +18,7 @@ func GetProviderResourceSchema(schema ProviderResourceSchemaParameters) map[stri schema.ResourceName: { Version: 1, Block: &tfprotov6.SchemaBlock{ - Description: schema.ResourceDescription + "\n" + seedPrefixDescription, + Description: schema.ResourceDescription + "\n" + getProviderMetaGuidSeedAdditionAttributeDescription(), BlockTypes: []*tfprotov6.SchemaNestedBlock{}, Attributes: []*tfprotov6.SchemaAttribute{ { @@ -30,19 +30,20 @@ func GetProviderResourceSchema(schema ProviderResourceSchemaParameters) map[stri Description: schema.ValueDescription, }, { - Name: "unique_seed", + Name: "guid_seed", Type: tftypes.String, Required: true, Optional: false, Computed: false, - Description: "Attention! The seed is being used to determine resource uniqueness prior and " + - "during apply-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 \"" + schema.ResourceName + "\" must be unique**! I recommend you to use the " + - "provider_meta-feature for increased uniqueness. 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.", + Description: "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 \"" + schema.ResourceName + "\" 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 configuration seed, the provider_meta configuration seed 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.", }, { Name: "proposed_unknown", diff --git a/isknown/common/resource_read.go b/isknown/common/resource_read.go index c5c24a6..3db3d7c 100644 --- a/isknown/common/resource_read.go +++ b/isknown/common/resource_read.go @@ -5,8 +5,8 @@ import ( "github.com/hashicorp/terraform-plugin-go/tftypes" ) -// CanReadResource function -func CanReadResource( +// TryReadResource function +func TryReadResource( currentState *tfprotov6.DynamicValue, resourceType tftypes.Type, resp *tfprotov6.ReadResourceResponse) ( @@ -16,9 +16,8 @@ func CanReadResource( var currentStateValue tftypes.Value var currentStateValueMap map[string]tftypes.Value var err error - currentStateValue, err = currentState.Unmarshal(resourceType) - if err != nil { + if currentStateValue, err = currentState.Unmarshal(resourceType); err != nil { resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ Severity: tfprotov6.DiagnosticSeverityError, Summary: "Failed to decode current state", @@ -38,9 +37,7 @@ func CanReadResource( return currentStateValue, currentStateValueMap, false } - err = currentStateValue.As(¤tStateValueMap) - - if err != nil { + if err = currentStateValue.As(¤tStateValueMap); err != nil { resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ Severity: tfprotov6.DiagnosticSeverityError, Summary: "Failed to extract resource from current state", @@ -50,9 +47,7 @@ func CanReadResource( return currentStateValue, currentStateValueMap, false } - _, isValueExisting := currentStateValueMap["value"] - - if !isValueExisting { + if _, isValueExisting := currentStateValueMap["value"]; !isValueExisting { resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ Severity: tfprotov6.DiagnosticSeverityError, Summary: "Current state of resource has no 'value' attribute", @@ -62,9 +57,7 @@ func CanReadResource( return currentStateValue, currentStateValueMap, false } - _, isResultExisting := currentStateValueMap["result"] - - if !isResultExisting { + if _, isResultExisting := currentStateValueMap["result"]; !isResultExisting { resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ Severity: tfprotov6.DiagnosticSeverityError, Summary: "Current state of resource has no 'result' attribute", @@ -74,27 +67,15 @@ func CanReadResource( return currentStateValue, currentStateValueMap, false } - _, isSeedExisting := currentStateValueMap["seed"] - - if !isSeedExisting { + if _, isGuidSeedExisting := currentStateValueMap["guid_seed"]; !isGuidSeedExisting { resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Current state of resource has no 'seed' attribute", + Summary: "Current state of resource has no 'guid_seed' attribute", Detail: "This should not happen. The state may be incomplete or corrupted.\nIf this error is reproducible, please report issue to provider maintainers.", }) return currentStateValue, currentStateValueMap, false } - // if !currentStateValueMap["seed"].IsFullyKnown() { - // resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ - // Severity: tfprotov6.DiagnosticSeverityError, - // Summary: "Current state of resource has a 'seed' attribute but it is not fully known.", - // Detail: "This should not happen. The state may be incomplete or corrupted.\nIf this error is reproducible, please report issue to provider maintainers.", - // }) - - // return currentStateValue, currentStateValueMap, false - // } - return currentStateValue, currentStateValueMap, true } diff --git a/isknown/common/resource_state.go b/isknown/common/resource_state.go deleted file mode 100644 index 21625c8..0000000 --- a/isknown/common/resource_state.go +++ /dev/null @@ -1,37 +0,0 @@ -package common - -import ( - "github.com/hashicorp/terraform-plugin-go/tfprotov6" - "github.com/hashicorp/terraform-plugin-go/tftypes" -) - -func UnmarshalState(state *tfprotov6.DynamicValue, stateType tftypes.Type) (tftypes.Value, map[string]tftypes.Value, []*tfprotov6.Diagnostic, bool) { - diags := []*tfprotov6.Diagnostic{} - resourceValueMap := make(map[string]tftypes.Value) - resourceValue, err := state.Unmarshal(stateType) - - if err != nil { - diags = append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Failed to unmarshal state", - Detail: err.Error(), - }) - - goto End - } - - err = resourceValue.As(&resourceValueMap) - - if err != nil { - diags = append(diags, &tfprotov6.Diagnostic{ - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Failed to extract state from tftypes.Value", - Detail: err.Error(), - }) - - goto End - } - -End: - return resourceValue, resourceValueMap, diags, len(diags) != 0 -} diff --git a/isknown/provider/provider_plugin.go b/isknown/provider/provider.go similarity index 91% rename from isknown/provider/provider_plugin.go rename to isknown/provider/provider.go index 9156632..4a44a47 100644 --- a/isknown/provider/provider_plugin.go +++ b/isknown/provider/provider.go @@ -15,9 +15,10 @@ type UserProviderServer struct { // such as instances of the Kubernetes clients, configuration options needed at runtime. logger hclog.Logger //providerEnabled bool - hostTFVersion string - params ProviderParameters - resourceSchemaParams common.ProviderResourceSchemaParameters + hostTFVersion string + params ProviderParameters + resourceSchemaParams common.ProviderResourceSchemaParameters + ProviderConfigSeedAddition string } type ProviderParameters struct { diff --git a/isknown/provider/provider_configure.go b/isknown/provider/provider_configure.go index 28192d6..9bf1cf4 100644 --- a/isknown/provider/provider_configure.go +++ b/isknown/provider/provider_configure.go @@ -5,14 +5,32 @@ import ( "fmt" "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/pseudo-dynamic/terraform-provider-value/internal/goproviderconfig" "golang.org/x/mod/semver" ) const minTFVersion string = "v0.14.8" +// ValidateProviderConfig function +func (s *UserProviderServer) ValidateProviderConfig(ctx context.Context, req *tfprotov6.ValidateProviderConfigRequest) (*tfprotov6.ValidateProviderConfigResponse, error) { + resp := &tfprotov6.ValidateProviderConfigResponse{PreparedConfig: req.Config} + return resp, nil +} + // ConfigureProvider function func (s *UserProviderServer) ConfigureProvider(ctx context.Context, req *tfprotov6.ConfigureProviderRequest) (*tfprotov6.ConfigureProviderResponse, error) { - return &tfprotov6.ConfigureProviderResponse{}, nil + var isWorking bool + resp := &tfprotov6.ConfigureProviderResponse{} + var diags []*tfprotov6.Diagnostic + + var providerConfigSeedAddition string + if providerConfigSeedAddition, diags, isWorking = goproviderconfig.TryExtractProviderConfigGuidSeedAddition(req.Config); !isWorking { + resp.Diagnostics = append(resp.Diagnostics, diags...) + return resp, nil + } + + s.ProviderConfigSeedAddition = providerConfigSeedAddition + return resp, nil } func (s *UserProviderServer) canExecute() (resp []*tfprotov6.Diagnostic) { diff --git a/isknown/provider/provider_schema.go b/isknown/provider/provider_schema.go index e13654a..e4019de 100644 --- a/isknown/provider/provider_schema.go +++ b/isknown/provider/provider_schema.go @@ -5,21 +5,15 @@ import ( "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/pseudo-dynamic/terraform-provider-value/internal/goproviderconfig" "github.com/pseudo-dynamic/terraform-provider-value/isknown/common" ) // GetProviderSchema function func (s *UserProviderServer) GetProviderSchema(ctx context.Context, req *tfprotov6.GetProviderSchemaRequest) (*tfprotov6.GetProviderSchemaResponse, error) { return &tfprotov6.GetProviderSchemaResponse{ - Provider: getProviderSchema(), + Provider: goproviderconfig.GetProviderConfigSchema(), ResourceSchemas: getDocumentedProviderResourceSchema(s.resourceSchemaParams), ProviderMeta: common.GetProviderMetaSchema(), }, nil } - -func getProviderSchema() *tfprotov6.Schema { - return &tfprotov6.Schema{ - Version: 0, - Block: &tfprotov6.SchemaBlock{}, - } -} diff --git a/isknown/provider/provider_server.go b/isknown/provider/provider_server.go index 7fc47ab..81e8039 100644 --- a/isknown/provider/provider_server.go +++ b/isknown/provider/provider_server.go @@ -4,23 +4,18 @@ import ( "context" "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/pseudo-dynamic/terraform-provider-value/internal/schema" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) -// ValidateProviderConfig function -func (s *UserProviderServer) ValidateProviderConfig(ctx context.Context, req *tfprotov6.ValidateProviderConfigRequest) (*tfprotov6.ValidateProviderConfigResponse, error) { - resp := &tfprotov6.ValidateProviderConfigResponse{PreparedConfig: req.Config} - return resp, nil -} - // UpgradeResourceState isn't really useful in this provider, but we have to loop the state back through to keep Terraform happy. func (s *UserProviderServer) UpgradeResourceState(ctx context.Context, req *tfprotov6.UpgradeResourceStateRequest) (*tfprotov6.UpgradeResourceStateResponse, error) { resp := &tfprotov6.UpgradeResourceStateResponse{} resp.Diagnostics = []*tfprotov6.Diagnostic{} sch := getProviderResourceSchema(req.TypeName) - rt := getObjectTypeFromSchema(sch[req.TypeName]) + rt := schema.GetObjectTypeFromSchema(sch[req.TypeName]) rv, err := req.RawState.Unmarshal(rt) diff --git a/isknown/provider/resource_plan.go b/isknown/provider/resource_plan.go index 7f3acff..5366a1e 100644 --- a/isknown/provider/resource_plan.go +++ b/isknown/provider/resource_plan.go @@ -7,8 +7,9 @@ import ( "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" - "github.com/pseudo-dynamic/terraform-provider-value/internal/uuid" + "github.com/pseudo-dynamic/terraform-provider-value/internal/schema" + "github.com/pseudo-dynamic/terraform-provider-value/internal/uuid" "github.com/pseudo-dynamic/terraform-provider-value/isknown/common" ) @@ -31,21 +32,20 @@ func (s *UserProviderServer) PlanResourceChange(ctx context.Context, req *tfprot proposedValueDynamic := req.ProposedNewState var proposedValue tftypes.Value var proposedValueMap map[string]tftypes.Value - if proposedValue, proposedValueMap, diags, isErroneous = common.UnmarshalState(proposedValueDynamic, resourceType); isErroneous { + if proposedValue, proposedValueMap, diags, isErroneous = schema.UnmarshalState(proposedValueDynamic, resourceType); isErroneous { resp.Diagnostics = append(resp.Diagnostics, diags...) return resp, nil } - _ = proposedValueMap - configValueDynamic := req.Config - var configValue tftypes.Value - var configValueMap map[string]tftypes.Value - if configValue, configValueMap, diags, isErroneous = common.UnmarshalState(configValueDynamic, resourceType); isErroneous { - resp.Diagnostics = append(resp.Diagnostics, diags...) - return resp, nil - } - _ = configValue - _ = configValueMap + // configValueDynamic := req.Config + // var configValue tftypes.Value + // var configValueMap map[string]tftypes.Value + // if configValue, configValueMap, diags, isErroneous = schema.UnmarshalState(configValueDynamic, resourceType); isErroneous { + // resp.Diagnostics = append(resp.Diagnostics, diags...) + // return resp, nil + // } + // _ = configValue + // _ = configValueMap if proposedValue.IsNull() { // Plan to delete the resource @@ -53,27 +53,30 @@ func (s *UserProviderServer) PlanResourceChange(ctx context.Context, req *tfprot return resp, nil } - var seedPrefix string - if seedPrefix, diags, isWorking = common.CanGetSeedPrefix(req.ProviderMeta); !isWorking { + var providerMetaSeedAddition string + if providerMetaSeedAddition, diags, isWorking = common.TryExtractProviderMetaGuidSeedAddition(req.ProviderMeta); !isWorking { resp.Diagnostics = append(resp.Diagnostics, diags...) return resp, nil } - _ = seedPrefix - uniqueSeedValue := proposedValueMap["unique_seed"] - if !uniqueSeedValue.IsKnown() { + guidSeedValue := proposedValueMap["guid_seed"] + if !guidSeedValue.IsKnown() { resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Current state of resource has a 'seed' attribute but it is not known.", - Detail: "The 'seed' attribute must be known during the plan phase. See attribute description for more informations.", + Summary: "Current resource state has a 'guid_seed' attribute but it is not known.", + Detail: "The 'guid_seed' attribute must be known during the plan phase. See attribute description for more informations.", }) return resp, nil } - var uniqueSeed string - _ = uniqueSeedValue.As(&uniqueSeed) // Why it should ever fail? + var guidSeed string + _ = guidSeedValue.As(&guidSeed) // Why it should ever fail? + + combinedSeed := s.ProviderConfigSeedAddition + "|" + + providerMetaSeedAddition + "|" + + req.TypeName + "|" + + guidSeed - combinedSeed := seedPrefix + uniqueSeed isPlanPhase := !proposedValueMap["proposed_unknown"].IsKnown() // Unknown == plan providerTempDir := filepath.Join(os.TempDir(), "tf-provider-"+req.TypeName) diff --git a/isknown/provider/resource_read.go b/isknown/provider/resource_read.go index b122174..1c2a4e7 100644 --- a/isknown/provider/resource_read.go +++ b/isknown/provider/resource_read.go @@ -4,6 +4,7 @@ import ( "context" "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/pseudo-dynamic/terraform-provider-value/isknown/common" ) // ReadResource function @@ -16,6 +17,12 @@ func (s *UserProviderServer) ReadResource(ctx context.Context, req *tfprotov6.Re return resp, nil } + resourceType := getResourceType(req.TypeName) + + if _, _, canReadCurrentState := common.TryReadResource(req.CurrentState, resourceType, resp); !canReadCurrentState { + return resp, nil + } + resp.NewState = req.CurrentState return resp, nil } diff --git a/isknown/provider/resource_schema.go b/isknown/provider/resource_schema.go index b1cfe52..ae5a5a2 100644 --- a/isknown/provider/resource_schema.go +++ b/isknown/provider/resource_schema.go @@ -6,6 +6,7 @@ import ( "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" + "github.com/pseudo-dynamic/terraform-provider-value/internal/schema" "github.com/pseudo-dynamic/terraform-provider-value/isknown/common" ) @@ -18,40 +19,7 @@ func getResourceType(name string) tftypes.Type { panic(fmt.Errorf("unknown resource %s - cannot find schema", name)) } - return getObjectTypeFromSchema(rsch) -} - -// getObjectTypeFromSchema returns a tftypes.Type that can wholy represent the schema input -func getObjectTypeFromSchema(schema *tfprotov6.Schema) tftypes.Type { - bm := map[string]tftypes.Type{} - - for _, att := range schema.Block.Attributes { - bm[att.Name] = att.Type - } - - for _, b := range schema.Block.BlockTypes { - a := map[string]tftypes.Type{} - for _, att := range b.Block.Attributes { - a[att.Name] = att.Type - } - bm[b.TypeName] = tftypes.List{ - ElementType: tftypes.Object{AttributeTypes: a}, - } - - // FIXME we can make this function recursive to handle - // n levels of nested blocks - for _, bb := range b.Block.BlockTypes { - aa := map[string]tftypes.Type{} - for _, att := range bb.Block.Attributes { - aa[att.Name] = att.Type - } - a[bb.TypeName] = tftypes.List{ - ElementType: tftypes.Object{AttributeTypes: aa}, - } - } - } - - return tftypes.Object{AttributeTypes: bm} + return schema.GetObjectTypeFromSchema(rsch) } // getDocumentedProviderResourceSchema contains the definitions of all supported resources with documentations diff --git a/isknown/provider/resource_type_validate.go b/isknown/provider/resource_type_validate.go index 2537c78..193188f 100644 --- a/isknown/provider/resource_type_validate.go +++ b/isknown/provider/resource_type_validate.go @@ -59,13 +59,13 @@ func (s *UserProviderServer) ValidateResourceConfig(ctx context.Context, req *tf return resp, nil } - _, ok = configVal["unique_seed"] + _, ok = configVal["guid_seed"] if !ok { resp.Diagnostics = append(resp.Diagnostics, &tfprotov6.Diagnostic{ Severity: tfprotov6.DiagnosticSeverityError, Summary: "Unique seed missing from resource configuration", - Detail: "A unique_seed attribute containing a valid terraform value is required.", + Detail: "A guid_seed attribute containing a valid terraform value is required.", Attribute: att, }) diff --git a/main.go b/main.go index 250af1d..fe9d60e 100644 --- a/main.go +++ b/main.go @@ -5,8 +5,8 @@ import ( "log" "os" - internal "github.com/pseudo-dynamic/terraform-provider-value/internal/provider" - "github.com/pseudo-dynamic/terraform-provider-value/isknown/common" + internal "github.com/pseudo-dynamic/terraform-provider-value/internal/fwkprovider" + isfullyknown "github.com/pseudo-dynamic/terraform-provider-value/isfullyknown/provider" isknown "github.com/pseudo-dynamic/terraform-provider-value/isknown/provider" promise "github.com/pseudo-dynamic/terraform-provider-value/promise/provider" stash "github.com/pseudo-dynamic/terraform-provider-value/stash/provider" @@ -22,31 +22,8 @@ import ( func main() { stashProvider := stash.Provider() promiseProvider := promise.Provider() - - isKnownProvider := isknown.ProviderConstructor(isknown.ProviderParameters{ - CheckFullyKnown: false, - }, common.ProviderResourceSchemaParameters{ - ResourceName: "value_is_known", - ResourceDescription: "Allows you to have a access to `result` during plan phase that " + - "states whether `value` marked as \"(known after apply)\" or not.", - ValueDescription: "The `value` (not nested attributes) is test against \"(known after apply)\"", - ResultDescription: "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.", - }) - - isFullyKnownProvider := isknown.ProviderConstructor(isknown.ProviderParameters{ - CheckFullyKnown: true, - }, common.ProviderResourceSchemaParameters{ - ResourceName: "value_is_fully_known", - ResourceDescription: "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.", - ValueDescription: "The `value` and if existing, nested attributes, are tested against \"(known after apply)\"", - ResultDescription: "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.", - }) - + isKnownProvider := isknown.Provider() + isFullyKnownProvider := isfullyknown.Provider() internalProvider := providerserver.NewProtocol6(internal.NewProvider()) ctx := context.Background() diff --git a/promise/provider/provider_plugin.go b/promise/provider/provider.go similarity index 100% rename from promise/provider/provider_plugin.go rename to promise/provider/provider.go diff --git a/promise/provider/provider_configure.go b/promise/provider/provider_configure.go index bd26362..ca84b0d 100644 --- a/promise/provider/provider_configure.go +++ b/promise/provider/provider_configure.go @@ -10,6 +10,12 @@ import ( const minTFVersion string = "v0.14.8" +// ValidateProviderConfig function +func (s *RawProviderServer) ValidateProviderConfig(ctx context.Context, req *tfprotov6.ValidateProviderConfigRequest) (*tfprotov6.ValidateProviderConfigResponse, error) { + resp := &tfprotov6.ValidateProviderConfigResponse{PreparedConfig: req.Config} + return resp, nil +} + // ConfigureProvider function func (s *RawProviderServer) ConfigureProvider(ctx context.Context, req *tfprotov6.ConfigureProviderRequest) (*tfprotov6.ConfigureProviderResponse, error) { return &tfprotov6.ConfigureProviderResponse{}, nil diff --git a/promise/provider/schema_provider.go b/promise/provider/provider_schema.go similarity index 79% rename from promise/provider/schema_provider.go rename to promise/provider/provider_schema.go index 6d1e58c..306fdb8 100644 --- a/promise/provider/schema_provider.go +++ b/promise/provider/provider_schema.go @@ -4,12 +4,14 @@ import ( "context" "log" + "github.com/pseudo-dynamic/terraform-provider-value/internal/goproviderconfig" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" ) // GetProviderSchema function func (s *RawProviderServer) GetProviderSchema(ctx context.Context, req *tfprotov6.GetProviderSchemaRequest) (*tfprotov6.GetProviderSchemaResponse, error) { - cfgSchema := GetProviderConfigSchema() + cfgSchema := goproviderconfig.GetProviderConfigSchema() resSchema := GetProviderResourceSchema() log.Println("--------------------------GetProviderSchema Called------------------------------") diff --git a/promise/provider/provider_server.go b/promise/provider/provider_server.go index 569cf9a..2a78a48 100644 --- a/promise/provider/provider_server.go +++ b/promise/provider/provider_server.go @@ -3,6 +3,8 @@ package provider import ( "context" + "github.com/pseudo-dynamic/terraform-provider-value/internal/schema" + "github.com/hashicorp/go-hclog" "github.com/hashicorp/terraform-plugin-go/tfprotov6" "google.golang.org/grpc/codes" @@ -20,12 +22,6 @@ type RawProviderServer struct { hostTFVersion string } -// ValidateProviderConfig function -func (s *RawProviderServer) ValidateProviderConfig(ctx context.Context, req *tfprotov6.ValidateProviderConfigRequest) (*tfprotov6.ValidateProviderConfigResponse, error) { - resp := &tfprotov6.ValidateProviderConfigResponse{PreparedConfig: req.Config} - return resp, nil -} - // ValidateDataResourceConfig function func (s *RawProviderServer) ValidateDataResourceConfig(ctx context.Context, req *tfprotov6.ValidateDataResourceConfigRequest) (*tfprotov6.ValidateDataResourceConfigResponse, error) { resp := &tfprotov6.ValidateDataResourceConfigResponse{} @@ -38,7 +34,7 @@ func (s *RawProviderServer) UpgradeResourceState(ctx context.Context, req *tfpro resp.Diagnostics = []*tfprotov6.Diagnostic{} sch := GetProviderResourceSchema() - rt := GetObjectTypeFromSchema(sch[req.TypeName]) + rt := schema.GetObjectTypeFromSchema(sch[req.TypeName]) rv, err := req.RawState.Unmarshal(rt) diff --git a/promise/provider/schema_provider_config.go b/promise/provider/schema_provider_config.go deleted file mode 100644 index e2f67d2..0000000 --- a/promise/provider/schema_provider_config.go +++ /dev/null @@ -1,15 +0,0 @@ -package provider - -import ( - "github.com/hashicorp/terraform-plugin-go/tfprotov6" -) - -// GetProviderConfigSchema contains the definitions of all configuration attributes -func GetProviderConfigSchema() *tfprotov6.Schema { - b := tfprotov6.SchemaBlock{} - - return &tfprotov6.Schema{ - Version: 0, - Block: &b, - } -} diff --git a/promise/provider/schema_resource.go b/promise/provider/schema_resource.go index 7ef001f..7b7583c 100644 --- a/promise/provider/schema_resource.go +++ b/promise/provider/schema_resource.go @@ -5,40 +5,9 @@ import ( "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" -) - -// GetObjectTypeFromSchema returns a tftypes.Type that can wholy represent the schema input -func GetObjectTypeFromSchema(schema *tfprotov6.Schema) tftypes.Type { - bm := map[string]tftypes.Type{} - - for _, att := range schema.Block.Attributes { - bm[att.Name] = att.Type - } - - for _, b := range schema.Block.BlockTypes { - a := map[string]tftypes.Type{} - for _, att := range b.Block.Attributes { - a[att.Name] = att.Type - } - bm[b.TypeName] = tftypes.List{ - ElementType: tftypes.Object{AttributeTypes: a}, - } - // FIXME we can make this function recursive to handle - // n levels of nested blocks - for _, bb := range b.Block.BlockTypes { - aa := map[string]tftypes.Type{} - for _, att := range bb.Block.Attributes { - aa[att.Name] = att.Type - } - a[bb.TypeName] = tftypes.List{ - ElementType: tftypes.Object{AttributeTypes: aa}, - } - } - } - - return tftypes.Object{AttributeTypes: bm} -} + "github.com/pseudo-dynamic/terraform-provider-value/internal/schema" +) // GetResourceType returns the tftypes.Type of a resource of type 'name' func GetResourceType(name string) (tftypes.Type, error) { @@ -49,7 +18,7 @@ func GetResourceType(name string) (tftypes.Type, error) { return tftypes.DynamicPseudoType, fmt.Errorf("unknown resource %s - cannot find schema", name) } - return GetObjectTypeFromSchema(rsch), nil + return schema.GetObjectTypeFromSchema(rsch), nil } // GetProviderResourceSchema contains the definitions of all supported resources diff --git a/stash/provider/provider_plugin.go b/stash/provider/provider.go similarity index 100% rename from stash/provider/provider_plugin.go rename to stash/provider/provider.go diff --git a/stash/provider/provider_configure.go b/stash/provider/provider_configure.go index bd26362..ca84b0d 100644 --- a/stash/provider/provider_configure.go +++ b/stash/provider/provider_configure.go @@ -10,6 +10,12 @@ import ( const minTFVersion string = "v0.14.8" +// ValidateProviderConfig function +func (s *RawProviderServer) ValidateProviderConfig(ctx context.Context, req *tfprotov6.ValidateProviderConfigRequest) (*tfprotov6.ValidateProviderConfigResponse, error) { + resp := &tfprotov6.ValidateProviderConfigResponse{PreparedConfig: req.Config} + return resp, nil +} + // ConfigureProvider function func (s *RawProviderServer) ConfigureProvider(ctx context.Context, req *tfprotov6.ConfigureProviderRequest) (*tfprotov6.ConfigureProviderResponse, error) { return &tfprotov6.ConfigureProviderResponse{}, nil diff --git a/stash/provider/schema_provider.go b/stash/provider/provider_schema.go similarity index 79% rename from stash/provider/schema_provider.go rename to stash/provider/provider_schema.go index 6d1e58c..306fdb8 100644 --- a/stash/provider/schema_provider.go +++ b/stash/provider/provider_schema.go @@ -4,12 +4,14 @@ import ( "context" "log" + "github.com/pseudo-dynamic/terraform-provider-value/internal/goproviderconfig" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" ) // GetProviderSchema function func (s *RawProviderServer) GetProviderSchema(ctx context.Context, req *tfprotov6.GetProviderSchemaRequest) (*tfprotov6.GetProviderSchemaResponse, error) { - cfgSchema := GetProviderConfigSchema() + cfgSchema := goproviderconfig.GetProviderConfigSchema() resSchema := GetProviderResourceSchema() log.Println("--------------------------GetProviderSchema Called------------------------------") diff --git a/stash/provider/provider_server.go b/stash/provider/provider_server.go index 569cf9a..2a78a48 100644 --- a/stash/provider/provider_server.go +++ b/stash/provider/provider_server.go @@ -3,6 +3,8 @@ package provider import ( "context" + "github.com/pseudo-dynamic/terraform-provider-value/internal/schema" + "github.com/hashicorp/go-hclog" "github.com/hashicorp/terraform-plugin-go/tfprotov6" "google.golang.org/grpc/codes" @@ -20,12 +22,6 @@ type RawProviderServer struct { hostTFVersion string } -// ValidateProviderConfig function -func (s *RawProviderServer) ValidateProviderConfig(ctx context.Context, req *tfprotov6.ValidateProviderConfigRequest) (*tfprotov6.ValidateProviderConfigResponse, error) { - resp := &tfprotov6.ValidateProviderConfigResponse{PreparedConfig: req.Config} - return resp, nil -} - // ValidateDataResourceConfig function func (s *RawProviderServer) ValidateDataResourceConfig(ctx context.Context, req *tfprotov6.ValidateDataResourceConfigRequest) (*tfprotov6.ValidateDataResourceConfigResponse, error) { resp := &tfprotov6.ValidateDataResourceConfigResponse{} @@ -38,7 +34,7 @@ func (s *RawProviderServer) UpgradeResourceState(ctx context.Context, req *tfpro resp.Diagnostics = []*tfprotov6.Diagnostic{} sch := GetProviderResourceSchema() - rt := GetObjectTypeFromSchema(sch[req.TypeName]) + rt := schema.GetObjectTypeFromSchema(sch[req.TypeName]) rv, err := req.RawState.Unmarshal(rt) diff --git a/stash/provider/schema_provider_config.go b/stash/provider/schema_provider_config.go deleted file mode 100644 index e2f67d2..0000000 --- a/stash/provider/schema_provider_config.go +++ /dev/null @@ -1,15 +0,0 @@ -package provider - -import ( - "github.com/hashicorp/terraform-plugin-go/tfprotov6" -) - -// GetProviderConfigSchema contains the definitions of all configuration attributes -func GetProviderConfigSchema() *tfprotov6.Schema { - b := tfprotov6.SchemaBlock{} - - return &tfprotov6.Schema{ - Version: 0, - Block: &b, - } -} diff --git a/stash/provider/schema_resource.go b/stash/provider/schema_resource.go index 2f4fc92..d0c0c62 100644 --- a/stash/provider/schema_resource.go +++ b/stash/provider/schema_resource.go @@ -5,40 +5,9 @@ import ( "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" -) - -// GetObjectTypeFromSchema returns a tftypes.Type that can wholy represent the schema input -func GetObjectTypeFromSchema(schema *tfprotov6.Schema) tftypes.Type { - bm := map[string]tftypes.Type{} - - for _, att := range schema.Block.Attributes { - bm[att.Name] = att.Type - } - - for _, b := range schema.Block.BlockTypes { - a := map[string]tftypes.Type{} - for _, att := range b.Block.Attributes { - a[att.Name] = att.Type - } - bm[b.TypeName] = tftypes.List{ - ElementType: tftypes.Object{AttributeTypes: a}, - } - // FIXME we can make this function recursive to handle - // n levels of nested blocks - for _, bb := range b.Block.BlockTypes { - aa := map[string]tftypes.Type{} - for _, att := range bb.Block.Attributes { - aa[att.Name] = att.Type - } - a[bb.TypeName] = tftypes.List{ - ElementType: tftypes.Object{AttributeTypes: aa}, - } - } - } - - return tftypes.Object{AttributeTypes: bm} -} + "github.com/pseudo-dynamic/terraform-provider-value/internal/schema" +) // GetResourceType returns the tftypes.Type of a resource of type 'name' func GetResourceType(name string) (tftypes.Type, error) { @@ -49,7 +18,7 @@ func GetResourceType(name string) (tftypes.Type, error) { return tftypes.DynamicPseudoType, fmt.Errorf("unknown resource %s - cannot find schema", name) } - return GetObjectTypeFromSchema(rsch), nil + return schema.GetObjectTypeFromSchema(rsch), nil } // GetProviderResourceSchema contains the definitions of all supported resources