Skip to content

Commit

Permalink
Merge pull request #31757 from hashicorp/jbardin/terraform-data
Browse files Browse the repository at this point in the history
New `terraform_data` managed resource to replace `null_resource`
  • Loading branch information
jbardin authored Dec 12, 2022
2 parents e206d4e + d0d6501 commit 404b284
Show file tree
Hide file tree
Showing 5 changed files with 599 additions and 20 deletions.
34 changes: 14 additions & 20 deletions internal/builtin/providers/terraform/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,7 @@ import (
)

// Provider is an implementation of providers.Interface
type Provider struct {
// Provider is the schema for the provider itself.
Schema providers.Schema

// DataSources maps the data source name to that data source's schema.
DataSources map[string]providers.Schema
}
type Provider struct{}

// NewProvider returns a new terraform provider
func NewProvider() providers.Interface {
Expand All @@ -27,6 +21,9 @@ func (p *Provider) GetProviderSchema() providers.GetProviderSchemaResponse {
DataSources: map[string]providers.Schema{
"terraform_remote_state": dataSourceRemoteStateGetSchema(),
},
ResourceTypes: map[string]providers.Schema{
"terraform_data": dataStoreResourceSchema(),
},
}
}

Expand Down Expand Up @@ -99,26 +96,26 @@ func (p *Provider) Stop() error {
// instance state whose schema version is less than the one reported by the
// currently-used version of the corresponding provider, and the upgraded
// result is used for any further processing.
func (p *Provider) UpgradeResourceState(providers.UpgradeResourceStateRequest) providers.UpgradeResourceStateResponse {
panic("unimplemented - terraform_remote_state has no resources")
func (p *Provider) UpgradeResourceState(req providers.UpgradeResourceStateRequest) providers.UpgradeResourceStateResponse {
return upgradeDataStoreResourceState(req)
}

// ReadResource refreshes a resource and returns its current state.
func (p *Provider) ReadResource(providers.ReadResourceRequest) providers.ReadResourceResponse {
panic("unimplemented - terraform_remote_state has no resources")
func (p *Provider) ReadResource(req providers.ReadResourceRequest) providers.ReadResourceResponse {
return readDataStoreResourceState(req)
}

// PlanResourceChange takes the current state and proposed state of a
// resource, and returns the planned final state.
func (p *Provider) PlanResourceChange(providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
panic("unimplemented - terraform_remote_state has no resources")
func (p *Provider) PlanResourceChange(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
return planDataStoreResourceChange(req)
}

// ApplyResourceChange takes the planned state for a resource, which may
// yet contain unknown computed values, and applies the changes returning
// the final state.
func (p *Provider) ApplyResourceChange(providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse {
panic("unimplemented - terraform_remote_state has no resources")
func (p *Provider) ApplyResourceChange(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse {
return applyDataStoreResourceChange(req)
}

// ImportResourceState requests that the given resource be imported.
Expand All @@ -127,11 +124,8 @@ func (p *Provider) ImportResourceState(providers.ImportResourceStateRequest) pro
}

// ValidateResourceConfig is used to to validate the resource configuration values.
func (p *Provider) ValidateResourceConfig(providers.ValidateResourceConfigRequest) providers.ValidateResourceConfigResponse {
// At this moment there is nothing to configure for the terraform provider,
// so we will happily return without taking any action
var res providers.ValidateResourceConfigResponse
return res
func (p *Provider) ValidateResourceConfig(req providers.ValidateResourceConfigRequest) providers.ValidateResourceConfigResponse {
return validateDataStoreResourceConfig(req)
}

// Close is a noop for this provider, since it's run in-process.
Expand Down
146 changes: 146 additions & 0 deletions internal/builtin/providers/terraform/resource_data.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package terraform

import (
"fmt"

"github.com/hashicorp/go-uuid"
"github.com/hashicorp/terraform/internal/configs/configschema"
"github.com/hashicorp/terraform/internal/providers"
"github.com/hashicorp/terraform/internal/tfdiags"
"github.com/zclconf/go-cty/cty"
ctyjson "github.com/zclconf/go-cty/cty/json"
)

func dataStoreResourceSchema() providers.Schema {
return providers.Schema{
Block: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"input": {Type: cty.DynamicPseudoType, Optional: true},
"output": {Type: cty.DynamicPseudoType, Computed: true},
"triggers_replace": {Type: cty.DynamicPseudoType, Optional: true},
"id": {Type: cty.String, Computed: true},
},
},
}
}

func validateDataStoreResourceConfig(req providers.ValidateResourceConfigRequest) (resp providers.ValidateResourceConfigResponse) {
if req.Config.IsNull() {
return resp
}

// Core does not currently validate computed values are not set in the
// configuration.
for _, attr := range []string{"id", "output"} {
if !req.Config.GetAttr(attr).IsNull() {
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf(`%q attribute is read-only`, attr))
}
}
return resp
}

func upgradeDataStoreResourceState(req providers.UpgradeResourceStateRequest) (resp providers.UpgradeResourceStateResponse) {
ty := dataStoreResourceSchema().Block.ImpliedType()
val, err := ctyjson.Unmarshal(req.RawStateJSON, ty)
if err != nil {
resp.Diagnostics = resp.Diagnostics.Append(err)
return resp
}

resp.UpgradedState = val
return resp
}

func readDataStoreResourceState(req providers.ReadResourceRequest) (resp providers.ReadResourceResponse) {
resp.NewState = req.PriorState
return resp
}

func planDataStoreResourceChange(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
if req.ProposedNewState.IsNull() {
// destroy op
resp.PlannedState = req.ProposedNewState
return resp
}

planned := req.ProposedNewState.AsValueMap()

input := req.ProposedNewState.GetAttr("input")
trigger := req.ProposedNewState.GetAttr("triggers_replace")

switch {
case req.PriorState.IsNull():
// Create
// Set the id value to unknown.
planned["id"] = cty.UnknownVal(cty.String)

// Only compute a new output if input has a non-null value.
if !input.IsNull() {
planned["output"] = cty.UnknownVal(input.Type())
}

resp.PlannedState = cty.ObjectVal(planned)
return resp

case !req.PriorState.GetAttr("triggers_replace").RawEquals(trigger):
// trigger changed, so we need to replace the entire instance
resp.RequiresReplace = append(resp.RequiresReplace, cty.GetAttrPath("triggers_replace"))
planned["id"] = cty.UnknownVal(cty.String)

// We need to check the input for the replacement instance to compute a
// new output.
if input.IsNull() {
planned["output"] = cty.NullVal(cty.DynamicPseudoType)
} else {
planned["output"] = cty.UnknownVal(input.Type())
}

case !req.PriorState.GetAttr("input").RawEquals(input):
// only input changed, so we only need to re-compute output
planned["output"] = cty.UnknownVal(input.Type())
}

resp.PlannedState = cty.ObjectVal(planned)
return resp
}

var testUUIDHook func() string

func applyDataStoreResourceChange(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) {
if req.PlannedState.IsNull() {
resp.NewState = req.PlannedState
return resp
}

newState := req.PlannedState.AsValueMap()

if !req.PlannedState.GetAttr("output").IsKnown() {
newState["output"] = req.PlannedState.GetAttr("input")
}

if !req.PlannedState.GetAttr("id").IsKnown() {
idString, err := uuid.GenerateUUID()
// Terraform would probably never get this far without a good random
// source, but catch the error anyway.
if err != nil {
diag := tfdiags.AttributeValue(
tfdiags.Error,
"Error generating id",
err.Error(),
cty.GetAttrPath("id"),
)

resp.Diagnostics = resp.Diagnostics.Append(diag)
}

if testUUIDHook != nil {
idString = testUUIDHook()
}

newState["id"] = cty.StringVal(idString)
}

resp.NewState = cty.ObjectVal(newState)

return resp
}
Loading

0 comments on commit 404b284

Please sign in to comment.