From bcdf44f5b485414a74fe5d4575bf4fd9435b7761 Mon Sep 17 00:00:00 2001 From: Megan Bang Date: Fri, 26 Aug 2022 10:37:40 -0500 Subject: [PATCH 1/5] add the optional StateVersion field JSONState and changes to string for base64 encoding --- state_version.go | 7 +++++++ state_version_integration_test.go | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/state_version.go b/state_version.go index 587286eb7..4997a4c4e 100644 --- a/state_version.go +++ b/state_version.go @@ -145,6 +145,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 + // 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"` // 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 diff --git a/state_version_integration_test.go b/state_version_integration_test.go index e55276a2f..39b79cdac 100644 --- a/state_version_integration_test.go +++ b/state_version_integration_test.go @@ -120,6 +120,11 @@ func TestStateVersionsCreate(t *testing.T) { t.Fatal(err) } + jsonState, err := ioutil.ReadFile("test-fixtures/ext-state-version/state.json") + if err != nil { + t.Fatal(err) + } + jsonStateOutputs, err := ioutil.ReadFile("test-fixtures/json-state-outputs/everything.json") if err != nil { t.Fatal(err) @@ -172,6 +177,7 @@ func TestStateVersionsCreate(t *testing.T) { MD5: String(fmt.Sprintf("%x", md5.Sum(state))), Serial: Int64(1), State: String(base64.StdEncoding.EncodeToString(state)), + JSONState: String(base64.StdEncoding.EncodeToString(state)), JSONStateOutputs: String(base64.StdEncoding.EncodeToString(jsonStateOutputs)), }) require.NoError(t, err) From 45afa49fcf52067714625c18a1d690dae4081471 Mon Sep 17 00:00:00 2001 From: Megan Bang Date: Fri, 26 Aug 2022 10:56:46 -0500 Subject: [PATCH 2/5] add test-fixtures/json-state/state.json --- state_version_integration_test.go | 2 +- test-fixtures/json-state/state.json | 163 ++++++++++++++++++++++++++++ 2 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 test-fixtures/json-state/state.json diff --git a/state_version_integration_test.go b/state_version_integration_test.go index 39b79cdac..9698a6b79 100644 --- a/state_version_integration_test.go +++ b/state_version_integration_test.go @@ -120,7 +120,7 @@ func TestStateVersionsCreate(t *testing.T) { t.Fatal(err) } - jsonState, err := ioutil.ReadFile("test-fixtures/ext-state-version/state.json") + jsonState, err := ioutil.ReadFile("test-fixtures/json-state/state.json") if err != nil { t.Fatal(err) } diff --git a/test-fixtures/json-state/state.json b/test-fixtures/json-state/state.json new file mode 100644 index 000000000..50d60c3f1 --- /dev/null +++ b/test-fixtures/json-state/state.json @@ -0,0 +1,163 @@ +{ + "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": {} + } + } + ] + } + } +} \ No newline at end of file From b28335218b42fb76344d311b0f7dcf53dd0abd9c Mon Sep 17 00:00:00 2001 From: Megan Bang Date: Fri, 26 Aug 2022 10:58:09 -0500 Subject: [PATCH 3/5] update to correct var --- state_version_integration_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/state_version_integration_test.go b/state_version_integration_test.go index 9698a6b79..b41e0be8d 100644 --- a/state_version_integration_test.go +++ b/state_version_integration_test.go @@ -177,7 +177,7 @@ func TestStateVersionsCreate(t *testing.T) { MD5: String(fmt.Sprintf("%x", md5.Sum(state))), Serial: Int64(1), State: String(base64.StdEncoding.EncodeToString(state)), - JSONState: String(base64.StdEncoding.EncodeToString(state)), + JSONState: String(base64.StdEncoding.EncodeToString(jsonState)), JSONStateOutputs: String(base64.StdEncoding.EncodeToString(jsonStateOutputs)), }) require.NoError(t, err) From 4efe422e9b4a5775a4668c4b03879dd3bf2afaf4 Mon Sep 17 00:00:00 2001 From: Brandon Croft Date: Fri, 26 Aug 2022 15:00:44 -0600 Subject: [PATCH 4/5] test: fix TestStateVersionsList test --- state_version_integration_test.go | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/state_version_integration_test.go b/state_version_integration_test.go index b41e0be8d..19cab436c 100644 --- a/state_version_integration_test.go +++ b/state_version_integration_test.go @@ -15,6 +15,15 @@ import ( "github.com/stretchr/testify/require" ) +func containsStateVersion(versions []*StateVersion, item *StateVersion) bool { + for _, sv := range versions { + if sv.ID == item.ID { + return true + } + } + return false +} + func TestStateVersionsList(t *testing.T) { client := testClient(t) ctx := context.Background() @@ -46,23 +55,9 @@ func TestStateVersionsList(t *testing.T) { require.NoError(t, err) require.NotEmpty(t, svl.Items) - // outputs, providers are populated only once the state has been parsed by TFC - // which can cause the tests to fail if it doesn't happen fast enough. - // download url is ignored since it is a dynamic lin - for idx := range svl.Items { - svl.Items[idx].DownloadURL = "" - svl.Items[idx].Outputs = nil - svl.Items[idx].Providers = nil - } - - for _, sv := range []*StateVersion{svTest1, svTest2} { - sv.DownloadURL = "" - sv.Outputs = nil - sv.Providers = nil - } + assert.True(t, containsStateVersion(svl.Items, svTest1), fmt.Sprintf("State Versions did not contain %s", svTest1.ID)) + assert.True(t, containsStateVersion(svl.Items, svTest2), fmt.Sprintf("State Versions did not contain %s", svTest2.ID)) - assert.Contains(t, svl.Items, svTest1) - assert.Contains(t, svl.Items, svTest2) assert.Equal(t, 1, svl.CurrentPage) assert.Equal(t, 2, svl.TotalCount) }) From bd3d82e75375e0cfacb09d3046f75c88e7bfbd26 Mon Sep 17 00:00:00 2001 From: Brandon Croft Date: Fri, 26 Aug 2022 16:03:42 -0600 Subject: [PATCH 5/5] test: fixes various TestWorkspaceCreate bugs --- workspace_integration_test.go | 37 +++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/workspace_integration_test.go b/workspace_integration_test.go index 61ac313b6..3c25b40af 100644 --- a/workspace_integration_test.go +++ b/workspace_integration_test.go @@ -9,6 +9,7 @@ import ( "encoding/json" "fmt" "io/ioutil" + "os" "sort" "strings" "testing" @@ -260,7 +261,7 @@ func TestWorkspacesCreate(t *testing.T) { t.Run("when options includes both an operations value and an enforcement mode value", func(t *testing.T) { options := WorkspaceCreateOptions{ - Name: String("foo"), + Name: String(fmt.Sprintf("foo-%s", randomString(t))), ExecutionMode: String("remote"), Operations: Bool(true), } @@ -272,7 +273,7 @@ func TestWorkspacesCreate(t *testing.T) { t.Run("when an agent pool ID is specified without 'agent' execution mode", func(t *testing.T) { options := WorkspaceCreateOptions{ - Name: String("foo"), + Name: String(fmt.Sprintf("foo-%s", randomString(t))), AgentPoolID: String("apool-xxxxx"), } @@ -283,7 +284,7 @@ func TestWorkspacesCreate(t *testing.T) { t.Run("when 'agent' execution mode is specified without an an agent pool ID", func(t *testing.T) { options := WorkspaceCreateOptions{ - Name: String("foo"), + Name: String(fmt.Sprintf("foo-%s", randomString(t))), ExecutionMode: String("agent"), } @@ -294,7 +295,7 @@ func TestWorkspacesCreate(t *testing.T) { t.Run("when an error is returned from the API", func(t *testing.T) { w, err := client.Workspaces.Create(ctx, "bar", WorkspaceCreateOptions{ - Name: String("bar"), + Name: String(fmt.Sprintf("bar-%s", randomString(t))), TerraformVersion: String("nonexisting"), }) assert.Nil(t, w) @@ -333,7 +334,7 @@ func TestWorkspacesCreate(t *testing.T) { t.Run("when options include both non-empty trigger-patterns and trigger-paths error is returned", func(t *testing.T) { options := WorkspaceCreateOptions{ - Name: String("foobar"), + Name: String(fmt.Sprintf("foobar-%s", randomString(t))), FileTriggersEnabled: Bool(true), TriggerPrefixes: []string{"/module-1", "/module-2"}, TriggerPatterns: []string{"/module-1/**/*", "/**/networking/*"}, @@ -377,7 +378,7 @@ func TestWorkspacesCreate(t *testing.T) { t.Run("when options include both non-empty tags-regex and trigger-patterns error is returned", func(t *testing.T) { options := WorkspaceCreateOptions{ - Name: String("foobar"), + Name: String(fmt.Sprintf("foobar-%s", randomString(t))), FileTriggersEnabled: Bool(false), VCSRepo: &VCSRepoOptions{TagsRegex: String("foobar")}, TriggerPatterns: []string{"/module-1/**/*", "/**/networking/*"}, @@ -390,7 +391,7 @@ func TestWorkspacesCreate(t *testing.T) { t.Run("when options include both non-empty tags-regex and trigger-prefixes error is returned", func(t *testing.T) { options := WorkspaceCreateOptions{ - Name: String("foobar"), + Name: String(fmt.Sprintf("foobar-%s", randomString(t))), FileTriggersEnabled: Bool(false), VCSRepo: &VCSRepoOptions{TagsRegex: String("foobar")}, TriggerPrefixes: []string{"/module-1", "/module-2"}, @@ -403,7 +404,7 @@ func TestWorkspacesCreate(t *testing.T) { t.Run("when options include both non-empty tags-regex and file-triggers-enabled as true an error is returned", func(t *testing.T) { options := WorkspaceCreateOptions{ - Name: String("foobar"), + Name: String(fmt.Sprintf("foobar-%s", randomString(t))), FileTriggersEnabled: Bool(true), VCSRepo: &VCSRepoOptions{TagsRegex: String("foobar")}, } @@ -414,15 +415,27 @@ func TestWorkspacesCreate(t *testing.T) { }) t.Run("when options include both non-empty tags-regex and file-triggers-enabled as false an error is not returned", func(t *testing.T) { + githubIdentifier := os.Getenv("GITHUB_POLICY_SET_IDENTIFIER") + if githubIdentifier == "" { + t.Fatal("Export a valid GITHUB_POLICY_SET_IDENTIFIER before running this test!") + } + + oc, ocCleanup := createOAuthToken(t, client, orgTest) + t.Cleanup(ocCleanup) + options := WorkspaceCreateOptions{ - Name: String("foobar"), + Name: String(fmt.Sprintf("foobar-%s", randomString(t))), FileTriggersEnabled: Bool(false), - VCSRepo: &VCSRepoOptions{TagsRegex: String("foobar")}, + VCSRepo: &VCSRepoOptions{ + Identifier: &githubIdentifier, + OAuthTokenID: &oc.ID, + TagsRegex: String("foobar"), + }, } w, err := client.Workspaces.Create(ctx, orgTest.Name, options) - require.NotNil(t, w) require.NoError(t, err) + require.NotNil(t, w) }) t.Run("when options include trigger-patterns populated and empty trigger-paths workspace is created", func(t *testing.T) { @@ -434,7 +447,7 @@ func TestWorkspacesCreate(t *testing.T) { defer orgTestCleanup() options := WorkspaceCreateOptions{ - Name: String("foobar"), + Name: String(fmt.Sprintf("foobar-%s", randomString(t))), FileTriggersEnabled: Bool(true), TriggerPrefixes: []string{}, TriggerPatterns: []string{"/module-1/**/*", "/**/networking/*"},