Skip to content

Commit

Permalink
refactor: extract cloud state ctyValue conversion function
Browse files Browse the repository at this point in the history
  • Loading branch information
brandonc committed Jul 25, 2022
1 parent 15b938a commit 166d21b
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 14 deletions.
2 changes: 1 addition & 1 deletion internal/cloud/backend_state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func TestRemoteClient_stateVersionCreated(t *testing.T) {
t.Fatalf("error: %v", err)
}

client := raw.(*remote.State).Client
client := raw.(*State).Client

err = client.Put(([]byte)(`
{
Expand Down
50 changes: 37 additions & 13 deletions internal/cloud/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ package cloud
import (
"context"
"encoding/json"
"errors"
"fmt"
"strings"

"github.com/hashicorp/go-tfe"
"github.com/hashicorp/terraform/internal/states"
"github.com/hashicorp/terraform/internal/states/remote"
"github.com/hashicorp/terraform/internal/states/statemgr"
Expand All @@ -21,6 +24,11 @@ type State struct {
delegate remote.State
}

const ErrStateVersionOutputUpgrade = `
The remote state version was created by a version of terraform older than
1.3.0 and must be updated before outputs can be read by terraform.
`

// Proof that cloud State is a statemgr.Persistent interface
var _ statemgr.Persistent = (*State)(nil)

Expand Down Expand Up @@ -74,6 +82,10 @@ func (s *State) GetRootOutputValues() (map[string]*states.OutputValue, error) {
result := make(map[string]*states.OutputValue)

for _, output := range so.Items {
if output.DetailedType == nil {
return nil, errors.New(strings.TrimSpace(ErrStateVersionOutputUpgrade))
}

if output.Sensitive {
// Since this is a sensitive value, the output must be requested explicitly in order to
// read its value, which is assumed to be present by callers
Expand All @@ -84,20 +96,9 @@ func (s *State) GetRootOutputValues() (map[string]*states.OutputValue, error) {
output.Value = sensitiveOutput.Value
}

bufType, err := json.Marshal(output.DetailedType)
if err != nil {
return nil, fmt.Errorf("could not marshal output %s type: %w", output.ID, err)
}

var ctype cty.Type
err = ctype.UnmarshalJSON(bufType)
cval, err := tfeOutputToCtyValue(*output)
if err != nil {
return nil, fmt.Errorf("could not interpret output %s type: %w", output.ID, err)
}

cval, err := gocty.ToCtyValue(output.Value, ctype)
if err != nil {
return nil, fmt.Errorf("could not interpret value %v as type %s for output %s: %w", cval, ctype.FriendlyName(), output.ID, err)
return nil, fmt.Errorf("could not decode output %s (ID %s)", output.Name, output.ID)
}

result[output.Name] = &states.OutputValue{
Expand All @@ -108,3 +109,26 @@ func (s *State) GetRootOutputValues() (map[string]*states.OutputValue, error) {

return result, nil
}

// tfeOutputToCtyValue decodes a combination of TFE output value and detailed-type to create a
// cty value that is suitable for use in terraform.
func tfeOutputToCtyValue(output tfe.StateVersionOutput) (cty.Value, error) {
var result cty.Value
bufType, err := json.Marshal(output.DetailedType)
if err != nil {
return result, fmt.Errorf("could not marshal output %s type: %w", output.ID, err)
}

var ctype cty.Type
err = ctype.UnmarshalJSON(bufType)
if err != nil {
return result, fmt.Errorf("could not interpret output %s type: %w", output.ID, err)
}

result, err = gocty.ToCtyValue(output.Value, ctype)
if err != nil {
return result, fmt.Errorf("could not interpret value %v as type %s for output %s: %w", result, ctype.FriendlyName(), output.ID, err)
}

return result, nil
}

0 comments on commit 166d21b

Please sign in to comment.