From fe72e20901ff2f88d5944df5010e1e223ac69e17 Mon Sep 17 00:00:00 2001 From: Brandon Croft Date: Thu, 30 Jun 2022 15:39:53 -0600 Subject: [PATCH 1/7] feat: optionally send json outputs when creating state versions --- state_version.go | 8 +- state_version_integration_test.go | 14 +- test-fixtures/ext-state-version/state.json | 163 ------------------ .../json-state-outputs/everything.json | 113 ++++++++++++ 4 files changed, 125 insertions(+), 173 deletions(-) delete mode 100644 test-fixtures/ext-state-version/state.json create mode 100644 test-fixtures/json-state-outputs/everything.json diff --git a/state_version.go b/state_version.go index 078d3e177..1eb1aacf2 100644 --- a/state_version.go +++ b/state_version.go @@ -139,13 +139,13 @@ type StateVersionCreateOptions struct { // Optional: Specifies the run to associate the state with. Run *Run `jsonapi:"relation,run,omitempty"` - // Optional: The external, json representation of state data, base64 encoded. - // https://www.terraform.io/internals/json-format#state-representation - // Supplying this state representation can provide more details to the platform + // Optional: The external, json representation of state outputs, base64 encoded. + // https://www.terraform.io/internals/json-format#values-representation (the contents of the + // "outputs" attribute) Supplying detailed outputs will provide more details to the platform // about the current terraform state. // // **Note**: This field is in BETA, subject to change and not widely available yet. - JSONState *string `jsonapi:"attr,json-state,omitempty"` + JSONStateOutputs *string `jsonapi:"attr,json-state-outputs,omitempty"` } // List all the state versions for a given workspace. diff --git a/state_version_integration_test.go b/state_version_integration_test.go index de4166999..35e086e04 100644 --- a/state_version_integration_test.go +++ b/state_version_integration_test.go @@ -121,7 +121,7 @@ func TestStateVersionsCreate(t *testing.T) { t.Fatal(err) } - jsonState, err := ioutil.ReadFile("test-fixtures/ext-state-version/state.json") + jsonStateOutputs, err := ioutil.ReadFile("test-fixtures/json-state-outputs/everything.json") if err != nil { t.Fatal(err) } @@ -169,11 +169,11 @@ func TestStateVersionsCreate(t *testing.T) { } sv, err := client.StateVersions.Create(ctx, wTest.ID, StateVersionCreateOptions{ - Lineage: String("741c4949-60b9-5bb1-5bf8-b14f4bb14af3"), - MD5: String(fmt.Sprintf("%x", md5.Sum(state))), - Serial: Int64(1), - State: String(base64.StdEncoding.EncodeToString(state)), - JSONState: String(base64.StdEncoding.EncodeToString(jsonState)), + Lineage: String("741c4949-60b9-5bb1-5bf8-b14f4bb14af3"), + MD5: String(fmt.Sprintf("%x", md5.Sum(state))), + Serial: Int64(1), + State: String(base64.StdEncoding.EncodeToString(state)), + JSONStateOutputs: String(base64.StdEncoding.EncodeToString(jsonStateOutputs)), }) require.NoError(t, err) @@ -186,6 +186,8 @@ func TestStateVersionsCreate(t *testing.T) { t.Fatal(err) } + // TODO: check state outputs for the ones we sent in JSONStateOutputs + for _, item := range []*StateVersion{ sv, refreshed, diff --git a/test-fixtures/ext-state-version/state.json b/test-fixtures/ext-state-version/state.json deleted file mode 100644 index 5acced8cc..000000000 --- a/test-fixtures/ext-state-version/state.json +++ /dev/null @@ -1,163 +0,0 @@ -{ - "format_version": "1.0", - "terraform_version": "1.2.0", - "values": { - "outputs": { - "a-decimal": { - "sensitive": false, - "value": 1000.1, - "type": "number" - }, - "a-false-bool": { - "sensitive": false, - "value": false, - "type": "bool" - }, - "a-list": { - "sensitive": false, - "value": [ - "example", - "1001", - "1000.1" - ], - "type": [ - "list", - "string" - ] - }, - "a-long-string": { - "sensitive": false, - "value": "The private integer of the main server instance is where you want to go when you have the most fun in every Terraform instance you can see in the world that you live in except for dogs because they don't run servers in the same place that humans do.", - "type": "string" - }, - "a-object": { - "sensitive": false, - "value": { - "bar": 1000.1, - "example": 1001 - }, - "type": [ - "object", - { - "bar": "number", - "example": "number" - } - ] - }, - "a-sensitive-value": { - "sensitive": true, - "value": "hopefully you cannot see me", - "type": "string" - }, - "a-string": { - "sensitive": false, - "value": "example string", - "type": "string" - }, - "a-true-bool": { - "sensitive": false, - "value": true, - "type": "bool" - }, - "a-tuple": { - "sensitive": false, - "value": [ - 1, - "example" - ], - "type": [ - "tuple", - [ - "number", - "string" - ] - ] - }, - "an-int": { - "sensitive": false, - "value": 1001, - "type": "number" - }, - "escapes": { - "sensitive": false, - "value": "line 1\nline 2\n\\\\\\\\\n", - "type": "string" - }, - "myoutput": { - "sensitive": false, - "value": { - "nesting1": { - "nesting2": { - "nesting3": "4263891374290101092" - } - } - }, - "type": [ - "object", - { - "nesting1": [ - "object", - { - "nesting2": [ - "object", - { - "nesting3": "string" - } - ] - } - ] - } - ] - }, - "random": { - "sensitive": false, - "value": "8b3086889a9ef7a5", - "type": "string" - } - }, - "root_module": { - "resources": [ - { - "address": "null_resource.test", - "mode": "managed", - "type": "null_resource", - "name": "test", - "provider_name": "registry.terraform.io/hashicorp/null", - "schema_version": 0, - "values": { - "id": "4263891374290101092", - "triggers": { - "hello": "wat3" - } - }, - "sensitive_values": { - "triggers": {} - } - }, - { - "address": "random_id.random", - "mode": "managed", - "type": "random_id", - "name": "random", - "provider_name": "registry.terraform.io/hashicorp/random", - "schema_version": 0, - "values": { - "b64_std": "izCGiJqe96U=", - "b64_url": "izCGiJqe96U", - "byte_length": 8, - "dec": "10029664291421878181", - "hex": "8b3086889a9ef7a5", - "id": "izCGiJqe96U", - "keepers": { - "uuid": "437a1415-932b-9f74-c214-184d88215353" - }, - "prefix": null - }, - "sensitive_values": { - "keepers": {} - } - } - ] - } - } -} diff --git a/test-fixtures/json-state-outputs/everything.json b/test-fixtures/json-state-outputs/everything.json new file mode 100644 index 000000000..66c5b45b6 --- /dev/null +++ b/test-fixtures/json-state-outputs/everything.json @@ -0,0 +1,113 @@ +{ + "a-decimal": { + "sensitive": false, + "value": 1000.1, + "type": "number" + }, + "a-false-bool": { + "sensitive": false, + "value": false, + "type": "bool" + }, + "a-list": { + "sensitive": false, + "value": [ + "example", + "1001", + "1000.1" + ], + "type": [ + "list", + "string" + ] + }, + "a-long-string": { + "sensitive": false, + "value": "The private integer of the main server instance is where you want to go when you have the most fun in every Terraform instance you can see in the world that you live in except for dogs because they don't run servers in the same place that humans do.", + "type": "string" + }, + "a-object": { + "sensitive": false, + "value": { + "bar": 1000.1, + "example": 1001 + }, + "type": [ + "object", + { + "bar": "number", + "example": "number" + } + ] + }, + "a-sensitive-value": { + "sensitive": true, + "value": "hopefully you cannot see me", + "type": "string" + }, + "a-string": { + "sensitive": false, + "value": "example string", + "type": "string" + }, + "a-true-bool": { + "sensitive": false, + "value": true, + "type": "bool" + }, + "a-tuple": { + "sensitive": false, + "value": [ + 1, + "example" + ], + "type": [ + "tuple", + [ + "number", + "string" + ] + ] + }, + "an-int": { + "sensitive": false, + "value": 1001, + "type": "number" + }, + "escapes": { + "sensitive": false, + "value": "line 1\nline 2\n\\\\\\\\\n", + "type": "string" + }, + "myoutput": { + "sensitive": false, + "value": { + "nesting1": { + "nesting2": { + "nesting3": "4263891374290101092" + } + } + }, + "type": [ + "object", + { + "nesting1": [ + "object", + { + "nesting2": [ + "object", + { + "nesting3": "string" + } + ] + } + ] + } + ] + }, + "random": { + "sensitive": false, + "value": "8b3086889a9ef7a5", + "type": "string" + } +} From 1fbe39d03e0751389a5dd08e8dbb7f16d6b30bbf Mon Sep 17 00:00:00 2001 From: Brandon Croft Date: Tue, 5 Jul 2022 13:23:05 -0600 Subject: [PATCH 2/7] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1db49b805..a845e5740 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,8 +3,8 @@ ## Enhancements * Adds `RetryServerErrors` field to the `Config` object by @sebasslash [#439](https://github.com/hashicorp/go-tfe/pull/439) * Adds support for the GPG Keys API by @sebasslash [#429](https://github.com/hashicorp/go-tfe/pull/429) -* [beta] Renames the optional StateVersion field `ExtState` to `JSONState` and changes to string for base64 encoding by @annawinkler [#444](https://github.com/hashicorp/go-tfe/pull/444) * Add support for new `WorkspaceLimit` Admin setting for organizations [#425](https://github.com/hashicorp/go-tfe/pull/425) +* [beta] Renames the optional StateVersion field `ExtState` to `JSONStateOutputs` and changes the purpose and type by @annawinkler [#444](https://github.com/hashicorp/go-tfe/pull/444) and @brandoncroft [#452](https://github.com/hashicorp/go-tfe/pull/452) # v1.3.0 From 9bc3f45ea91c865d3dab0960f8df9d9cf6abff3c Mon Sep 17 00:00:00 2001 From: Brandon Croft Date: Tue, 5 Jul 2022 14:04:22 -0600 Subject: [PATCH 3/7] Update state_version.go Co-authored-by: Anna Winkler <3526523+annawinkler@users.noreply.github.com> --- state_version.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/state_version.go b/state_version.go index 1eb1aacf2..d3fd2d987 100644 --- a/state_version.go +++ b/state_version.go @@ -141,7 +141,8 @@ type StateVersionCreateOptions struct { // Optional: The external, json representation of state outputs, base64 encoded. // https://www.terraform.io/internals/json-format#values-representation (the contents of the - // "outputs" attribute) Supplying detailed outputs will provide more details to the platform + // "outputs" attribute) + // Supplying detailed outputs will provide more details to the platform // about the current terraform state. // // **Note**: This field is in BETA, subject to change and not widely available yet. From 90f07ddf69d9ca20af10cb073d4d7d6ddfcd83ae Mon Sep 17 00:00:00 2001 From: Brandon Croft Date: Tue, 5 Jul 2022 15:37:48 -0600 Subject: [PATCH 4/7] Update state_version.go Co-authored-by: Sebastian Rivera --- state_version.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/state_version.go b/state_version.go index d3fd2d987..39173aafb 100644 --- a/state_version.go +++ b/state_version.go @@ -139,10 +139,9 @@ type StateVersionCreateOptions struct { // Optional: Specifies the run to associate the state with. Run *Run `jsonapi:"relation,run,omitempty"` - // Optional: The external, json representation of state outputs, base64 encoded. - // https://www.terraform.io/internals/json-format#values-representation (the contents of the - // "outputs" attribute) - // Supplying detailed outputs will provide more details to the platform + // Optional: The external, json representation of state outputs, base64 encoded. Supplying this field + // will provide more detailed output type information to TFE. + // For more information on the contents of this field: https://www.terraform.io/internals/json-format#values-representation // about the current terraform state. // // **Note**: This field is in BETA, subject to change and not widely available yet. From b3fd1d716b67db6f58eccba31b93d0225618ba13 Mon Sep 17 00:00:00 2001 From: Brandon Croft Date: Wed, 6 Jul 2022 10:13:38 -0600 Subject: [PATCH 5/7] test fix: revert me soon --- oauth_client_integration_test.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/oauth_client_integration_test.go b/oauth_client_integration_test.go index b9b0817d1..2927097b1 100644 --- a/oauth_client_integration_test.go +++ b/oauth_client_integration_test.go @@ -5,6 +5,7 @@ package tfe import ( "context" + "fmt" "os" "testing" @@ -247,8 +248,14 @@ func TestOAuthClientsDelete(t *testing.T) { err := client.OAuthClients.Delete(ctx, ocTest.ID) require.NoError(t, err) - // Try loading the OAuth client - it should fail. - _, err = client.OAuthClients.Read(ctx, ocTest.ID) + _, err = retry(func() (interface{}, error) { + c, err := client.OAuthClients.Read(ctx, ocTest.ID) + if err != ErrResourceNotFound { + return nil, fmt.Errorf("expected %s, but err was %s", ErrResourceNotFound, err) + } + return c, err + }) + assert.Equal(t, err, ErrResourceNotFound) }) From 9008efbb2c1e293f07088489ee22e22e8c0f0a6b Mon Sep 17 00:00:00 2001 From: Brandon Croft Date: Wed, 6 Jul 2022 10:47:56 -0600 Subject: [PATCH 6/7] test: retry workspace read resource count test --- workspace_integration_test.go | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/workspace_integration_test.go b/workspace_integration_test.go index d5264d912..2604f1a47 100644 --- a/workspace_integration_test.go +++ b/workspace_integration_test.go @@ -472,11 +472,24 @@ func TestWorkspacesReadWithHistory(t *testing.T) { _, rCleanup := createRunApply(t, client, wTest) defer rCleanup() - w, err := client.Workspaces.Read(context.Background(), orgTest.Name, wTest.Name) - require.NoError(t, err) + _, err := retry(func() (interface{}, error) { + w, err := client.Workspaces.Read(context.Background(), orgTest.Name, wTest.Name) + require.NoError(t, err) + + if w.RunsCount != 1 { + return nil, fmt.Errorf("expected %d runs but found %d", 1, w.RunsCount) + } + + if w.ResourceCount != 1 { + return nil, fmt.Errorf("expected %d resources but found %d", 1, w.ResourceCount) + } + + return w, nil + }) - assert.Equal(t, 1, w.RunsCount) - assert.Equal(t, 1, w.ResourceCount) + if err != nil { + t.Error(err) + } } func TestWorkspacesReadReadme(t *testing.T) { From fd0b70ef67c68b1822e9b131d5d3e6b02c8eb42f Mon Sep 17 00:00:00 2001 From: Brandon Croft Date: Wed, 6 Jul 2022 17:15:27 -0600 Subject: [PATCH 7/7] test: TestConfigurationVersionsReadWithOptions should retry --- configuration_version_integration_test.go | 31 +++++++++++++---------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/configuration_version_integration_test.go b/configuration_version_integration_test.go index fe028698e..b85e6790e 100644 --- a/configuration_version_integration_test.go +++ b/configuration_version_integration_test.go @@ -7,10 +7,12 @@ import ( "bytes" "context" "encoding/json" - "github.com/hashicorp/go-slug" + "errors" "testing" "time" + "github.com/hashicorp/go-slug" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -155,22 +157,25 @@ func TestConfigurationVersionsReadWithOptions(t *testing.T) { wTest, wTestCleanup := createWorkspaceWithVCS(t, client, orgTest, WorkspaceCreateOptions{QueueAllRuns: Bool(true)}) defer wTestCleanup() - // Hack: Wait for TFC to ingress the configuration and queue a run - time.Sleep(3 * time.Second) + w, err := retry(func() (interface{}, error) { + w, err := client.Workspaces.ReadByIDWithOptions(ctx, wTest.ID, &WorkspaceReadOptions{ + Include: []WSIncludeOpt{WSCurrentRunConfigVer}, + }) - w, err := client.Workspaces.ReadByIDWithOptions(ctx, wTest.ID, &WorkspaceReadOptions{ - Include: []WSIncludeOpt{WSCurrentRunConfigVer}, - }) + if err != nil { + return nil, err + } - if err != nil { - require.NoError(t, err) - } + if w.CurrentRun == nil { + return nil, errors.New("A run was expected to be found on this workspace as a test pre-condition") + } - if w.CurrentRun == nil { - t.Fatal("A run was expected to be found on this workspace as a test pre-condition") - } + return w, nil + }) + + require.NoError(t, err) - cv := w.CurrentRun.ConfigurationVersion + cv := w.(*Workspace).CurrentRun.ConfigurationVersion t.Run("when the configuration version exists", func(t *testing.T) { options := &ConfigurationVersionReadOptions{