Skip to content

Commit a78dac9

Browse files
committed
Implement polymorphic relationship fields
`LockedBy` and `CreatedBy` are new fields, but deprecates `Sourceable` on the `RunTrigger` model
1 parent 51a9039 commit a78dac9

14 files changed

+126
-29
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
# UNRELEASED
22

3+
## Deprecations
4+
* The `Sourceable` field has been deprecated on `RunTrigger`. Instead, use `SourceableChoice` to locate the non-empty field representing the actual sourceable value by @brandonc [#816](https://github.com/hashicorp/go-tfe/pull/816)
5+
36
## Bug Fixes
47
* Removed unused field `AgentPoolID` from the Workspace model. (Callers should be using the `AgentPool` relation instead) by @brandonc [#815](https://github.com/hashicorp/go-tfe/pull/815)
58

9+
## Features
10+
* Adds `LockedBy` relationship field to `Workspace` by @brandonc [#816](https://github.com/hashicorp/go-tfe/pull/816)
11+
* Adds `CreatedBy` relationship field to `TeamToken`, `UserToken`, and `OrganizationToken` by @brandonc [#816](https://github.com/hashicorp/go-tfe/pull/816)
12+
613
# v1.39.2
714

815
## Bug Fixes

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ require (
1010
github.com/hashicorp/go-slug v0.13.2
1111
github.com/hashicorp/go-uuid v1.0.3
1212
github.com/hashicorp/go-version v1.6.0
13-
github.com/hashicorp/jsonapi v0.0.0-20210826224640-ee7dae0fb22d
13+
github.com/hashicorp/jsonapi v1.2.0
1414
github.com/stretchr/testify v1.8.4
1515
golang.org/x/sync v0.5.0
1616
golang.org/x/time v0.4.0

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/C
1818
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
1919
github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
2020
github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
21-
github.com/hashicorp/jsonapi v0.0.0-20210826224640-ee7dae0fb22d h1:9ARUJJ1VVynB176G1HCwleORqCaXm/Vx0uUi0dL26I0=
22-
github.com/hashicorp/jsonapi v0.0.0-20210826224640-ee7dae0fb22d/go.mod h1:Yog5+CPEM3c99L1CL2CFCYoSzgWm5vTU58idbRUaLik=
21+
github.com/hashicorp/jsonapi v1.2.0 h1:ezDCzOFsKTL+KxVQuA1rNxkIGTvZph1rNu8kT5A8trI=
22+
github.com/hashicorp/jsonapi v1.2.0/go.mod h1:Yog5+CPEM3c99L1CL2CFCYoSzgWm5vTU58idbRUaLik=
2323
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
2424
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
2525
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=

helper_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"os"
2323
"os/exec"
2424
"path/filepath"
25+
"reflect"
2526
"runtime"
2627
"strings"
2728
"testing"
@@ -2850,6 +2851,53 @@ func betaFeaturesEnabled() bool {
28502851
return os.Getenv("ENABLE_BETA") == "1"
28512852
}
28522853

2854+
// isEmpty gets whether the specified object is considered empty or not.
2855+
func isEmpty(object interface{}) bool {
2856+
// get nil case out of the way
2857+
if object == nil {
2858+
return true
2859+
}
2860+
2861+
objValue := reflect.ValueOf(object)
2862+
2863+
switch objValue.Kind() {
2864+
// collection types are empty when they have no element
2865+
case reflect.Chan, reflect.Map, reflect.Slice:
2866+
return objValue.Len() == 0
2867+
// pointers are empty if nil or if the value they point to is empty
2868+
case reflect.Ptr:
2869+
if objValue.IsNil() {
2870+
return true
2871+
}
2872+
deref := objValue.Elem().Interface()
2873+
return isEmpty(deref)
2874+
// for all other types, compare against the zero value
2875+
// array types are empty when they match their zero-initialized state
2876+
default:
2877+
zero := reflect.Zero(objValue.Type())
2878+
return reflect.DeepEqual(object, zero.Interface())
2879+
}
2880+
}
2881+
2882+
// requireExactlyOneNotEmpty accepts any number of values and calls t.Fatal if
2883+
// less or more than one is empty.
2884+
func requireExactlyOneNotEmpty(t *testing.T, v ...any) {
2885+
if len(v) == 0 {
2886+
t.Fatal("Expected some values for requireExactlyOneNotEmpty, but received none")
2887+
}
2888+
2889+
empty := 0
2890+
for _, value := range v {
2891+
if isEmpty(value) {
2892+
empty += 1
2893+
}
2894+
}
2895+
2896+
if empty != len(v)-1 {
2897+
t.Fatalf("Expected exactly one value to not be empty, but found %d empty values", empty)
2898+
}
2899+
}
2900+
28532901
// Useless key but enough to pass validation in the API
28542902
const testGpgArmor string = `
28552903
-----BEGIN PGP PUBLIC KEY BLOCK-----

organization_token.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,13 @@ type organizationTokens struct {
3939

4040
// OrganizationToken represents a Terraform Enterprise organization token.
4141
type OrganizationToken struct {
42-
ID string `jsonapi:"primary,authentication-tokens"`
43-
CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"`
44-
Description string `jsonapi:"attr,description"`
45-
LastUsedAt time.Time `jsonapi:"attr,last-used-at,iso8601"`
46-
Token string `jsonapi:"attr,token"`
47-
ExpiredAt time.Time `jsonapi:"attr,expired-at,iso8601"`
42+
ID string `jsonapi:"primary,authentication-tokens"`
43+
CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"`
44+
Description string `jsonapi:"attr,description"`
45+
LastUsedAt time.Time `jsonapi:"attr,last-used-at,iso8601"`
46+
Token string `jsonapi:"attr,token"`
47+
ExpiredAt time.Time `jsonapi:"attr,expired-at,iso8601"`
48+
CreatedBy *CreatedByChoice `jsonapi:"polyrelation,created-by"`
4849
}
4950

5051
// OrganizationTokenCreateOptions contains the options for creating an organization token.

organization_token_integration_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ func TestOrganizationTokensCreate(t *testing.T) {
2424
ot, err := client.OrganizationTokens.Create(ctx, orgTest.Name)
2525
require.NoError(t, err)
2626
require.NotEmpty(t, ot.Token)
27+
requireExactlyOneNotEmpty(t, ot.CreatedBy.Organization, ot.CreatedBy.Team, ot.CreatedBy.User)
2728
tkToken = ot.Token
2829
})
2930

run_trigger.go

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,17 +43,23 @@ type RunTriggerList struct {
4343
Items []*RunTrigger
4444
}
4545

46+
// SourceableChoice is a choice type struct that represents the possible values
47+
// within a polymorphic relation. If a value is available, exactly one field
48+
// will be non-nil.
49+
type SourceableChoice struct {
50+
Workspace *Workspace
51+
}
52+
4653
// RunTrigger represents a run trigger.
4754
type RunTrigger struct {
4855
ID string `jsonapi:"primary,run-triggers"`
4956
CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"`
5057
SourceableName string `jsonapi:"attr,sourceable-name"`
5158
WorkspaceName string `jsonapi:"attr,workspace-name"`
52-
53-
// Relations
54-
// TODO: this will eventually need to be polymorphic
55-
Sourceable *Workspace `jsonapi:"relation,sourceable"`
56-
Workspace *Workspace `jsonapi:"relation,workspace"`
59+
// DEPRECATED. The sourceable field is polymorphic. Use SourceableChoice instead.
60+
Sourceable *Workspace `jsonapi:"relation,sourceable"`
61+
SourceableChoice *SourceableChoice `jsonapi:"polyrelation,sourceable"`
62+
Workspace *Workspace `jsonapi:"relation,workspace"`
5763
}
5864

5965
// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/run-triggers#query-parameters

run_trigger_integration_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,9 @@ func TestRunTriggerList(t *testing.T) {
129129
require.NoError(t, err)
130130
require.NotEmpty(t, rtl.Items)
131131
require.NotNil(t, rtl.Items[0].Sourceable)
132-
assert.NotEmpty(t, rtl.Items[0].Sourceable.Name)
132+
assert.NotEmpty(t, rtl.Items[0].Sourceable)
133+
assert.NotNil(t, rtl.Items[0].SourceableChoice.Workspace)
134+
assert.NotEmpty(t, rtl.Items[0].SourceableChoice.Workspace)
133135
})
134136

135137
t.Run("with a RunTriggerType that does not return included data", func(t *testing.T) {

team_token.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,13 @@ type teamTokens struct {
3939

4040
// TeamToken represents a Terraform Enterprise team token.
4141
type TeamToken struct {
42-
ID string `jsonapi:"primary,authentication-tokens"`
43-
CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"`
44-
Description string `jsonapi:"attr,description"`
45-
LastUsedAt time.Time `jsonapi:"attr,last-used-at,iso8601"`
46-
Token string `jsonapi:"attr,token"`
47-
ExpiredAt time.Time `jsonapi:"attr,expired-at,iso8601"`
42+
ID string `jsonapi:"primary,authentication-tokens"`
43+
CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"`
44+
Description string `jsonapi:"attr,description"`
45+
LastUsedAt time.Time `jsonapi:"attr,last-used-at,iso8601"`
46+
Token string `jsonapi:"attr,token"`
47+
ExpiredAt time.Time `jsonapi:"attr,expired-at,iso8601"`
48+
CreatedBy *CreatedByChoice `jsonapi:"polyrelation,created-by"`
4849
}
4950

5051
// TeamTokenCreateOptions contains the options for creating a team token.

team_token_integration_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ func TestTeamTokensCreate(t *testing.T) {
2424
tt, err := client.TeamTokens.Create(ctx, tmTest.ID)
2525
require.NoError(t, err)
2626
require.NotEmpty(t, tt.Token)
27+
require.NotEmpty(t, tt.CreatedBy)
28+
requireExactlyOneNotEmpty(t, tt.CreatedBy.Organization, tt.CreatedBy.Team, tt.CreatedBy.User)
2729
tmToken = tt.Token
2830
})
2931

0 commit comments

Comments
 (0)