Skip to content

Commit

Permalink
Use No state. to get well-structured state instead of reading directl…
Browse files Browse the repository at this point in the history
…y from .tfstate file.
  • Loading branch information
tjy9206 committed Oct 1, 2024
1 parent b9e8262 commit b867723
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 138 deletions.
30 changes: 2 additions & 28 deletions cli/bpmetadata/parser/state_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,37 +12,11 @@ import (
)

func ParseOutputTypesFromState(stateData []byte) (map[string]*structpb.Value, error) {
// Unmarshal the state data into a map[string]interface{} first
var rawState map[string]interface{}
err := json.Unmarshal(stateData, &rawState)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal state data: %w", err)
}

// Check if "format_version" key exists
if _, ok := rawState["format_version"]; !ok {
// If not present, add it with a default value
rawState["format_version"] = "1.0"
}

// Create a "values" field with "outputs" inside
rawState["values"] = map[string]interface{}{
"outputs": rawState["outputs"],
}

// Remove the top-level "outputs" field
delete(rawState, "outputs")

// Now marshal the updated map back to JSON
updatedStateData, err := json.MarshalIndent(rawState, "", " ")
if err != nil {
return nil, fmt.Errorf("failed to marshal updated state data: %w", err)
}

var state tfjson.State

// Unmarshal the updated JSON into tfjson.State
err = json.Unmarshal(updatedStateData, &state)
// Unmarshal the state data into tfjson.State
err := json.Unmarshal(stateData, &state)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal state data: %w", err)
}
Expand Down
155 changes: 76 additions & 79 deletions cli/bpmetadata/parser/state_parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,24 @@ func TestParseOutputTypesFromState_WithSimpleTypes(t *testing.T) {
t.Parallel()
stateData := []byte(`
{
"version": 4,
"terraform_version": "0.12.0",
"serial": 8,
"lineage": "491fd8f4-81b5-9890-520c-8a173c36e483",
"outputs": {
"boolean_output": {
"value": true,
"type": "bool"
},
"number_output": {
"value": 42,
"type": "number"
},
"string_output": {
"value": "foo",
"type": "string"
"format_version": "1.0",
"terraform_version": "1.2.0",
"values": {
"outputs": {
"boolean_output": {
"type": "bool",
"value": true
},
"number_output": {
"type": "number",
"value": 42
},
"string_output": {
"type": "string",
"value": "foo"
}
}
},
"resources": []
}
}
`)
want := map[string]*structpb.Value{
Expand All @@ -51,63 +50,62 @@ func TestParseOutputTypesFromState_WithComplexTypes(t *testing.T) {
t.Parallel()
stateData := []byte(`
{
"version": 4,
"terraform_version": "0.12.0",
"serial": 8,
"lineage": "491fd8f4-81b5-9890-520c-8a173c36e483",
"outputs": {
"interpolated_deep": {
"value": {
"foo": "bar",
"map": {
"bar": "baz",
"id": "424881806176056736"
},
"number": 42
},
"type": [
"object",
{
"foo": "string",
"map": [
"object",
{
"bar": "string",
"id": "string"
}
],
"number": "number"
"format_version": "1.0",
"terraform_version": "1.2.0",
"values": {
"outputs": {
"interpolated_deep": {
"type": [
"object",
{
"foo": "string",
"map": [
"object",
{
"bar": "string",
"id": "string"
}
],
"number": "number"
}
],
"value": {
"foo": "bar",
"map": {
"bar": "baz",
"id": "424881806176056736"
},
"number": 42
}
]
},
"list_output": {
"value": [
"foo",
"bar"
],
"type": [
"tuple",
[
"string",
"string"
},
"list_output": {
"type": [
"tuple",
[
"string",
"string"
]
],
"value": [
"foo",
"bar"
]
]
},
"map_output": {
"value": {
"foo": "bar",
"number": 42
},
"type": [
"object",
{
"foo": "string",
"number": "number"
"map_output": {
"type": [
"object",
{
"foo": "string",
"number": "number"
}
],
"value": {
"foo": "bar",
"number": 42
}
]
}
}
},
"resources": []
}
}
`)
want := map[string]*structpb.Value{
Expand Down Expand Up @@ -145,16 +143,15 @@ func TestParseOutputTypesFromState_WithoutTypes(t *testing.T) {
t.Parallel()
stateData := []byte(`
{
"version": 4,
"terraform_version": "0.12.0",
"serial": 8,
"lineage": "491fd8f4-81b5-9890-520c-8a173c36e483",
"outputs": {
"no_type_output": {
"value": "some_value"
"format_version": "1.0",
"terraform_version": "1.2.0",
"values": {
"outputs": {
"no_type_output": {
"value": "some_value"
}
}
},
"resources": []
}
}
`)
want := map[string]*structpb.Value{
Expand Down
17 changes: 8 additions & 9 deletions cli/bpmetadata/tfconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import (
"flag"
"fmt"
"os"
"path"
"path/filepath"
"regexp"
"sort"
"strings"

"github.com/GoogleCloudPlatform/cloud-foundation-toolkit/cli/bpmetadata/parser"
"github.com/GoogleCloudPlatform/cloud-foundation-toolkit/infra/blueprint-test/pkg/tft"
"github.com/gruntwork-io/terratest/modules/terraform"
hcl "github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/gohcl"
"github.com/hashicorp/hcl/v2/hclparse"
Expand Down Expand Up @@ -86,7 +86,7 @@ var moduleSchema = &hcl.BodySchema{
}

// Create alias for generateTFStateFile so we can mock it in unit test.
var tfStateFile = generateTFStateFile
var tfState = generateTFState

// getBlueprintVersion gets both the required core version and the
// version of the blueprint
Expand Down Expand Up @@ -526,7 +526,7 @@ func mergeExistingConnections(newInterfaces, existingInterfaces *BlueprintInterf
// and updates the output types in the provided BlueprintInterface.
func updateOutputTypes(bpPath string, bpInterfaces *BlueprintInterface) error {
// Generate the terraform.tfstate file
stateData, err := tfStateFile(bpPath)
stateData, err := tfState(bpPath)
if err != nil {
return fmt.Errorf("error generating terraform.tfstate file: %w", err)
}
Expand All @@ -546,8 +546,8 @@ func updateOutputTypes(bpPath string, bpInterfaces *BlueprintInterface) error {
return nil
}

// generateTFStateFile generates the terraform.tfstate file by running terraform init and apply.
func generateTFStateFile(bpPath string) ([]byte, error) {
// generateTFState generates the terraform.tfstate by running terraform init and apply, and terraform show to capture the state.
func generateTFState(bpPath string) ([]byte, error) {
var stateData []byte
// Construct the path to the test/setup directory
tfDir := filepath.Join(bpPath)
Expand All @@ -563,13 +563,12 @@ func generateTFStateFile(bpPath string) ([]byte, error) {
)

root.DefineVerify(func(assert *assert.Assertions) {
stateFilePath := path.Join(bpPath, "terraform.tfstate")
stateDataFromFile, err := os.ReadFile(stateFilePath)
stateStr, err := terraform.ShowE(&runtimeT, root.GetTFOptions())
if err != nil {
assert.FailNowf("Failed to read terraform.tfstate", "Error reading state file: %v", err)
assert.FailNowf("Failed to generate terraform.tfstate", "Error calling `terraform show`: %v", err)
}

stateData = stateDataFromFile
stateData = []byte(stateStr)
})

root.Test() // This will run terraform init and apply, and then destroy
Expand Down
2 changes: 1 addition & 1 deletion cli/bpmetadata/tfconfig_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -484,7 +484,7 @@ func TestUpdateOutputTypes(t *testing.T) {
require.NoError(t, err)

// Override with a function that reads a hard-coded tfstate file.
tfStateFile = func(_ string) ([]byte, error) {
tfState = func(_ string) ([]byte, error) {
if tt.expectError {
return nil, fmt.Errorf("simulated error generating state file")
}
Expand Down
41 changes: 20 additions & 21 deletions cli/testdata/bpmetadata/tf/sample-module/terraform.tfstate
Original file line number Diff line number Diff line change
@@ -1,26 +1,25 @@
{
"version": 4,
"terraform_version": "0.12.0",
"serial": 8,
"lineage": "491fd8f4-81b5-9890-520c-8a173c36e483",
"outputs": {
"cluster_id": {
"value": "sample-cluster-id",
"type": "string"
},
"endpoint": {
"value": {
"host": "127.0.0.1",
"port": 443
"format_version": "1.0",
"terraform_version": "1.2.0",
"values": {
"outputs": {
"cluster_id": {
"type": "string",
"value": "sample-cluster-id"
},
"type": [
"object",
{
"host": "string",
"port": "number"
"endpoint": {
"type": [
"object",
{
"host": "string",
"port": "number"
}
],
"value": {
"host": "127.0.0.1",
"port": 443
}
]
}
}
},
"resources": []
}
}

0 comments on commit b867723

Please sign in to comment.