Skip to content

Commit

Permalink
Merge pull request #31466 from hashicorp/aw/send-jsonstateoutputs
Browse files Browse the repository at this point in the history
Include JSONStateOutputs
  • Loading branch information
brandonc committed Jul 22, 2022
2 parents 8b1b05f + 7892cb6 commit 74ee19b
Show file tree
Hide file tree
Showing 8 changed files with 117 additions and 35 deletions.
10 changes: 5 additions & 5 deletions go.mod
Expand Up @@ -39,9 +39,9 @@ require (
github.com/hashicorp/go-hclog v0.15.0
github.com/hashicorp/go-multierror v1.1.1
github.com/hashicorp/go-plugin v1.4.3
github.com/hashicorp/go-retryablehttp v0.7.0
github.com/hashicorp/go-tfe v1.0.0
github.com/hashicorp/go-uuid v1.0.2
github.com/hashicorp/go-retryablehttp v0.7.1
github.com/hashicorp/go-tfe v1.5.0
github.com/hashicorp/go-uuid v1.0.3
github.com/hashicorp/go-version v1.6.0
github.com/hashicorp/hcl v0.0.0-20170504190234-a4b07c25de5f
github.com/hashicorp/hcl/v2 v2.13.0
Expand Down Expand Up @@ -144,7 +144,7 @@ require (
github.com/hashicorp/go-msgpack v0.5.4 // indirect
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
github.com/hashicorp/go-safetemp v1.0.0 // indirect
github.com/hashicorp/go-slug v0.8.0 // indirect
github.com/hashicorp/go-slug v0.9.1 // indirect
github.com/hashicorp/golang-lru v0.5.1 // indirect
github.com/hashicorp/jsonapi v0.0.0-20210826224640-ee7dae0fb22d // indirect
github.com/hashicorp/serf v0.9.5 // indirect
Expand Down Expand Up @@ -184,7 +184,7 @@ require (
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.66.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/klog/v2 v2.30.0 // indirect
k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect
sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect
Expand Down
19 changes: 11 additions & 8 deletions go.sum
Expand Up @@ -363,23 +363,25 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-plugin v1.4.3 h1:DXmvivbWD5qdiBts9TpBC7BYL1Aia5sxbRgQB+v6UZM=
github.com/hashicorp/go-plugin v1.4.3/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ=
github.com/hashicorp/go-retryablehttp v0.7.0 h1:eu1EI/mbirUgP5C8hVsTNaGZreBDlYiwC1FZWkvQPQ4=
github.com/hashicorp/go-retryablehttp v0.7.0/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
github.com/hashicorp/go-retryablehttp v0.7.1 h1:sUiuQAnLlbvmExtFQs72iFW/HXeUn8Z1aJLQ4LJJbTQ=
github.com/hashicorp/go-retryablehttp v0.7.1/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo=
github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I=
github.com/hashicorp/go-slug v0.8.0 h1:h7AGtXVAI/cJ/Wwa/JQQaftQnWQmZbAzkzgZeZVVmLw=
github.com/hashicorp/go-slug v0.8.0/go.mod h1:Ib+IWBYfEfJGI1ZyXMGNbu2BU+aa3Dzu41RKLH301v4=
github.com/hashicorp/go-slug v0.9.1 h1:gYNVJ3t0jAWx8AT2eYZci3Xd7NBHyjayW9AR1DU4ki0=
github.com/hashicorp/go-slug v0.9.1/go.mod h1:Ib+IWBYfEfJGI1ZyXMGNbu2BU+aa3Dzu41RKLH301v4=
github.com/hashicorp/go-sockaddr v1.0.0 h1:GeH6tui99pF4NJgfnhp+L6+FfobzVW3Ah46sLo0ICXs=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-tfe v1.0.0 h1:CmwoHrOs7WJfD/yEmVjJ65+dyKeVRrgvRHBLVSQQ6Ks=
github.com/hashicorp/go-tfe v1.0.0/go.mod h1:tJF/OlAXzVbmjiimAPLplSLgwg6kZDUOy0MzHuMwvF4=
github.com/hashicorp/go-tfe v1.5.0 h1:MtABkqH2s6lRFl8HaGt0qESLGAyrmMAFfecsEm+13K8=
github.com/hashicorp/go-tfe v1.5.0/go.mod h1:E8a90lC4kjU5Lc2c0D+SnWhUuyuoCIVm4Ewzv3jCD3A=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.0.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
Expand Down Expand Up @@ -596,8 +598,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.194/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.232 h1:kwsWbh4rEw42ZDe9/812ebhbwNZxlQyZ2sTmxBOKhN4=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.232/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
Expand Down Expand Up @@ -1060,8 +1062,9 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
Expand Down
23 changes: 18 additions & 5 deletions internal/backend/remote/backend_state.go
Expand Up @@ -5,9 +5,12 @@ import (
"context"
"crypto/md5"
"encoding/base64"
"encoding/json"
"fmt"

tfe "github.com/hashicorp/go-tfe"

"github.com/hashicorp/terraform/internal/command/jsonstate"
"github.com/hashicorp/terraform/internal/states/remote"
"github.com/hashicorp/terraform/internal/states/statefile"
"github.com/hashicorp/terraform/internal/states/statemgr"
Expand Down Expand Up @@ -65,12 +68,22 @@ func (r *remoteClient) Put(state []byte) error {
return fmt.Errorf("Error reading state: %s", err)
}

ov, err := jsonstate.MarshalOutputs(stateFile.State.RootModule().OutputValues)
if err != nil {
return fmt.Errorf("Error reading output values: %s", err)
}
o, err := json.Marshal(ov)
if err != nil {
return fmt.Errorf("Error converting output values to json: %s", err)
}

options := tfe.StateVersionCreateOptions{
Lineage: tfe.String(stateFile.Lineage),
Serial: tfe.Int64(int64(stateFile.Serial)),
MD5: tfe.String(fmt.Sprintf("%x", md5.Sum(state))),
State: tfe.String(base64.StdEncoding.EncodeToString(state)),
Force: tfe.Bool(r.forcePush),
Lineage: tfe.String(stateFile.Lineage),
Serial: tfe.Int64(int64(stateFile.Serial)),
MD5: tfe.String(fmt.Sprintf("%x", md5.Sum(state))),
State: tfe.String(base64.StdEncoding.EncodeToString(state)),
Force: tfe.Bool(r.forcePush),
JSONStateOutputs: tfe.String(base64.StdEncoding.EncodeToString(o)),
}

// If we have a run ID, make sure to add it to the options
Expand Down
36 changes: 25 additions & 11 deletions internal/cloud/backend_state.go
Expand Up @@ -5,9 +5,13 @@ import (
"context"
"crypto/md5"
"encoding/base64"
"encoding/json"
"errors"
"fmt"

tfe "github.com/hashicorp/go-tfe"

"github.com/hashicorp/terraform/internal/command/jsonstate"
"github.com/hashicorp/terraform/internal/states/remote"
"github.com/hashicorp/terraform/internal/states/statefile"
"github.com/hashicorp/terraform/internal/states/statemgr"
Expand All @@ -33,12 +37,12 @@ func (r *remoteClient) Get() (*remote.Payload, error) {
// If no state exists, then return nil.
return nil, nil
}
return nil, fmt.Errorf("Error retrieving state: %v", err)
return nil, fmt.Errorf("failed to retrieve state: %w", err)
}

state, err := r.client.StateVersions.Download(ctx, sv.DownloadURL)
if err != nil {
return nil, fmt.Errorf("Error downloading state: %v", err)
return nil, fmt.Errorf("failed to download state: %w", err)
}

// If the state is empty, then return nil.
Expand All @@ -62,15 +66,25 @@ func (r *remoteClient) Put(state []byte) error {
// Read the raw state into a Terraform state.
stateFile, err := statefile.Read(bytes.NewReader(state))
if err != nil {
return fmt.Errorf("Error reading state: %s", err)
return fmt.Errorf("failed to read state: %w", err)
}

ov, err := jsonstate.MarshalOutputs(stateFile.State.RootModule().OutputValues)
if err != nil {
return fmt.Errorf("failed to translate outputs: %w", err)
}
o, err := json.Marshal(ov)
if err != nil {
return fmt.Errorf("failed to marshal outputs to json: %w", err)
}

options := tfe.StateVersionCreateOptions{
Lineage: tfe.String(stateFile.Lineage),
Serial: tfe.Int64(int64(stateFile.Serial)),
MD5: tfe.String(fmt.Sprintf("%x", md5.Sum(state))),
State: tfe.String(base64.StdEncoding.EncodeToString(state)),
Force: tfe.Bool(r.forcePush),
Lineage: tfe.String(stateFile.Lineage),
Serial: tfe.Int64(int64(stateFile.Serial)),
MD5: tfe.String(fmt.Sprintf("%x", md5.Sum(state))),
State: tfe.String(base64.StdEncoding.EncodeToString(state)),
Force: tfe.Bool(r.forcePush),
JSONStateOutputs: tfe.String(base64.StdEncoding.EncodeToString(o)),
}

// If we have a run ID, make sure to add it to the options
Expand All @@ -83,7 +97,7 @@ func (r *remoteClient) Put(state []byte) error {
_, err = r.client.StateVersions.Create(ctx, r.workspace.ID, options)
if err != nil {
r.stateUploadErr = true
return fmt.Errorf("Error uploading state: %v", err)
return fmt.Errorf("failed to upload state: %w", err)
}

return nil
Expand All @@ -93,7 +107,7 @@ func (r *remoteClient) Put(state []byte) error {
func (r *remoteClient) Delete() error {
err := r.client.Workspaces.Delete(context.Background(), r.organization, r.workspace.Name)
if err != nil && err != tfe.ErrResourceNotFound {
return fmt.Errorf("Error deleting workspace %s: %v", r.workspace.Name, err)
return fmt.Errorf("failed to delete workspace %s: %w", r.workspace.Name, err)
}

return nil
Expand Down Expand Up @@ -146,7 +160,7 @@ func (r *remoteClient) Unlock(id string) error {
if r.lockInfo != nil {
// Verify the expected lock ID.
if r.lockInfo.ID != id {
lockErr.Err = fmt.Errorf("lock ID does not match existing lock")
lockErr.Err = errors.New("lock ID does not match existing lock")
return lockErr
}

Expand Down
47 changes: 46 additions & 1 deletion internal/cloud/backend_state_test.go
Expand Up @@ -5,6 +5,8 @@ import (
"os"
"testing"

tfe "github.com/hashicorp/go-tfe"

"github.com/hashicorp/terraform/internal/states"
"github.com/hashicorp/terraform/internal/states/remote"
"github.com/hashicorp/terraform/internal/states/statefile"
Expand All @@ -19,7 +21,50 @@ func TestRemoteClient(t *testing.T) {
remote.TestClient(t, client)
}

func TestRemoteClient_stateLock(t *testing.T) {
func TestRemoteClient_stateVersionCreated(t *testing.T) {
b, bCleanup := testBackendWithName(t)
defer bCleanup()

raw, err := b.StateMgr(testBackendSingleWorkspaceName)
if err != nil {
t.Fatalf("error: %v", err)
}

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

err = client.Put(([]byte)(`
{
"version": 4,
"terraform_version": "1.3.0",
"serial": 1,
"lineage": "backend-change",
"outputs": {
"foo": {
"type": "string",
"value": "bar"
}
}
}`))
if err != nil {
t.Fatalf("expected no error, got %v", err)
}

stateVersionsAPI := b.client.StateVersions.(*MockStateVersions)
if got, want := len(stateVersionsAPI.stateVersions), 1; got != want {
t.Fatalf("wrong number of state versions in the mock client %d; want %d", got, want)
}

var stateVersion *tfe.StateVersion
for _, sv := range stateVersionsAPI.stateVersions {
stateVersion = sv
}

if stateVersionsAPI.outputStates[stateVersion.ID] == nil || len(stateVersionsAPI.outputStates[stateVersion.ID]) == 0 {
t.Fatal("no state version outputs in the mock client")
}
}

func TestRemoteClient_TestRemoteLocks(t *testing.T) {
b, bCleanup := testBackendWithName(t)
defer bCleanup()

Expand Down
6 changes: 5 additions & 1 deletion internal/cloud/tfe_client_mock.go
Expand Up @@ -16,8 +16,9 @@ import (
"time"

tfe "github.com/hashicorp/go-tfe"
tfversion "github.com/hashicorp/terraform/version"
"github.com/mitchellh/copystructure"

tfversion "github.com/hashicorp/terraform/version"
)

type MockClient struct {
Expand Down Expand Up @@ -923,6 +924,7 @@ type MockStateVersions struct {
states map[string][]byte
stateVersions map[string]*tfe.StateVersion
workspaces map[string][]string
outputStates map[string][]byte
}

func newMockStateVersions(client *MockClient) *MockStateVersions {
Expand All @@ -931,6 +933,7 @@ func newMockStateVersions(client *MockClient) *MockStateVersions {
states: make(map[string][]byte),
stateVersions: make(map[string]*tfe.StateVersion),
workspaces: make(map[string][]string),
outputStates: make(map[string][]byte),
}
}

Expand Down Expand Up @@ -972,6 +975,7 @@ func (m *MockStateVersions) Create(ctx context.Context, workspaceID string, opti
}

m.states[sv.DownloadURL] = state
m.outputStates[sv.ID] = []byte(*options.JSONStateOutputs)
m.stateVersions[sv.ID] = sv
m.workspaces[workspaceID] = append(m.workspaces[workspaceID], sv.ID)

Expand Down
6 changes: 4 additions & 2 deletions internal/command/jsonstate/state.go
Expand Up @@ -159,7 +159,7 @@ func (jsonstate *state) marshalStateValues(s *states.State, schemas *terraform.S
var err error

// only marshal the root module outputs
sv.Outputs, err = marshalOutputs(s.RootModule().OutputValues)
sv.Outputs, err = MarshalOutputs(s.RootModule().OutputValues)
if err != nil {
return err
}
Expand All @@ -174,7 +174,9 @@ func (jsonstate *state) marshalStateValues(s *states.State, schemas *terraform.S
return nil
}

func marshalOutputs(outputs map[string]*states.OutputValue) (map[string]output, error) {
// MarshalOutputs translates a map of states.OutputValue to a map of jsonstate.output,
// which are defined for json encoding.
func MarshalOutputs(outputs map[string]*states.OutputValue) (map[string]output, error) {
if outputs == nil {
return nil, nil
}
Expand Down
5 changes: 3 additions & 2 deletions internal/command/jsonstate/state_test.go
Expand Up @@ -6,12 +6,13 @@ import (
"testing"

"github.com/google/go-cmp/cmp"
"github.com/zclconf/go-cty/cty"

"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/configs/configschema"
"github.com/hashicorp/terraform/internal/lang/marks"
"github.com/hashicorp/terraform/internal/states"
"github.com/hashicorp/terraform/internal/terraform"
"github.com/zclconf/go-cty/cty"
)

func TestMarshalOutputs(t *testing.T) {
Expand Down Expand Up @@ -92,7 +93,7 @@ func TestMarshalOutputs(t *testing.T) {
}

for _, test := range tests {
got, err := marshalOutputs(test.Outputs)
got, err := MarshalOutputs(test.Outputs)
if test.Err {
if err == nil {
t.Fatal("succeeded; want error")
Expand Down

0 comments on commit 74ee19b

Please sign in to comment.