Skip to content

Commit 8303c9e

Browse files
Add support for HYOK Configurations and OIDC Configurations (#1162)
Co-authored-by: Helen Jiang <[email protected]>
1 parent f93f693 commit 8303c9e

13 files changed

+2547
-0
lines changed

aws_oidc_configuration.go

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
package tfe
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net/url"
7+
)
8+
9+
const OIDCConfigPathFormat = "oidc-configurations/%s"
10+
11+
type AWSOIDCConfigurations interface {
12+
Create(ctx context.Context, organization string, options AWSOIDCConfigurationCreateOptions) (*AWSOIDCConfiguration, error)
13+
14+
Read(ctx context.Context, oidcID string) (*AWSOIDCConfiguration, error)
15+
16+
Update(ctx context.Context, oidcID string, options AWSOIDCConfigurationUpdateOptions) (*AWSOIDCConfiguration, error)
17+
18+
Delete(ctx context.Context, oidcID string) error
19+
}
20+
21+
type awsOIDCConfigurations struct {
22+
client *Client
23+
}
24+
25+
var _ AWSOIDCConfigurations = &awsOIDCConfigurations{}
26+
27+
type AWSOIDCConfiguration struct {
28+
ID string `jsonapi:"primary,aws-oidc-configurations"`
29+
RoleARN string `jsonapi:"attr,role-arn"`
30+
31+
Organization *Organization `jsonapi:"relation,organization"`
32+
}
33+
34+
type AWSOIDCConfigurationCreateOptions struct {
35+
// Type is a public field utilized by JSON:API to
36+
// set the resource type via the field tag.
37+
// It is not a user-defined value and does not need to be set.
38+
// https://jsonapi.org/format/#crud-creating
39+
Type string `jsonapi:"primary,aws-oidc-configurations"`
40+
41+
// Attributes
42+
RoleARN string `jsonapi:"attr,role-arn"`
43+
}
44+
45+
type AWSOIDCConfigurationUpdateOptions struct {
46+
// Type is a public field utilized by JSON:API to
47+
// set the resource type via the field tag.
48+
// It is not a user-defined value and does not need to be set.
49+
// https://jsonapi.org/format/#crud-creating
50+
Type string `jsonapi:"primary,aws-oidc-configurations"`
51+
52+
// Attributes
53+
RoleARN string `jsonapi:"attr,role-arn"`
54+
}
55+
56+
func (o *AWSOIDCConfigurationCreateOptions) valid() error {
57+
if o.RoleARN == "" {
58+
return ErrRequiredRoleARN
59+
}
60+
61+
return nil
62+
}
63+
64+
func (aoc *awsOIDCConfigurations) Create(ctx context.Context, organization string, options AWSOIDCConfigurationCreateOptions) (*AWSOIDCConfiguration, error) {
65+
if !validStringID(&organization) {
66+
return nil, ErrInvalidOrg
67+
}
68+
69+
if err := options.valid(); err != nil {
70+
return nil, err
71+
}
72+
73+
req, err := aoc.client.NewRequest("POST", fmt.Sprintf("organizations/%s/oidc-configurations", organization), &options)
74+
if err != nil {
75+
return nil, err
76+
}
77+
78+
awsOIDCConfiguration := &AWSOIDCConfiguration{}
79+
err = req.Do(ctx, awsOIDCConfiguration)
80+
if err != nil {
81+
return nil, err
82+
}
83+
84+
return awsOIDCConfiguration, nil
85+
}
86+
87+
func (aoc *awsOIDCConfigurations) Read(ctx context.Context, oidcID string) (*AWSOIDCConfiguration, error) {
88+
req, err := aoc.client.NewRequest("GET", fmt.Sprintf(OIDCConfigPathFormat, url.PathEscape(oidcID)), nil)
89+
if err != nil {
90+
return nil, err
91+
}
92+
93+
awsOIDCConfiguration := &AWSOIDCConfiguration{}
94+
err = req.Do(ctx, awsOIDCConfiguration)
95+
if err != nil {
96+
return nil, err
97+
}
98+
99+
return awsOIDCConfiguration, nil
100+
}
101+
102+
func (o *AWSOIDCConfigurationUpdateOptions) valid() error {
103+
if o.RoleARN == "" {
104+
return ErrRequiredRoleARN
105+
}
106+
107+
return nil
108+
}
109+
110+
func (aoc *awsOIDCConfigurations) Update(ctx context.Context, oidcID string, options AWSOIDCConfigurationUpdateOptions) (*AWSOIDCConfiguration, error) {
111+
if !validStringID(&oidcID) {
112+
return nil, ErrInvalidOIDC
113+
}
114+
115+
if err := options.valid(); err != nil {
116+
return nil, err
117+
}
118+
119+
req, err := aoc.client.NewRequest("PATCH", fmt.Sprintf(OIDCConfigPathFormat, url.PathEscape(oidcID)), &options)
120+
if err != nil {
121+
return nil, err
122+
}
123+
124+
awsOIDCConfiguration := &AWSOIDCConfiguration{}
125+
err = req.Do(ctx, awsOIDCConfiguration)
126+
if err != nil {
127+
return nil, err
128+
}
129+
130+
return awsOIDCConfiguration, nil
131+
}
132+
133+
func (aoc *awsOIDCConfigurations) Delete(ctx context.Context, oidcID string) error {
134+
if !validStringID(&oidcID) {
135+
return ErrInvalidOIDC
136+
}
137+
138+
req, err := aoc.client.NewRequest("DELETE", fmt.Sprintf(OIDCConfigPathFormat, url.PathEscape(oidcID)), nil)
139+
if err != nil {
140+
return err
141+
}
142+
143+
return req.Do(ctx, nil)
144+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package tfe
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
// These tests are intended for local execution only, as OIDC configurations for HYOK requires specific conditions.
12+
// To run them locally, follow the instructions outlined in hyok_configuration_integration_test.go
13+
14+
func TestAWSOIDCConfigurationCreateDelete(t *testing.T) {
15+
if skipHYOKIntegrationTests {
16+
t.Skip()
17+
}
18+
19+
client := testClient(t)
20+
ctx := context.Background()
21+
22+
orgTest, err := client.Organizations.Read(ctx, hyokOrganizationName)
23+
if err != nil {
24+
t.Fatal(err)
25+
}
26+
27+
t.Run("with valid options", func(t *testing.T) {
28+
opts := AWSOIDCConfigurationCreateOptions{
29+
RoleARN: "arn:aws:iam::123456789012:role/some-role",
30+
}
31+
32+
oidcConfig, err := client.AWSOIDCConfigurations.Create(ctx, orgTest.Name, opts)
33+
require.NoError(t, err)
34+
require.NotNil(t, oidcConfig)
35+
assert.Equal(t, oidcConfig.RoleARN, opts.RoleARN)
36+
37+
// delete the created configuration
38+
err = client.AWSOIDCConfigurations.Delete(ctx, oidcConfig.ID)
39+
require.NoError(t, err)
40+
})
41+
42+
t.Run("missing role ARN", func(t *testing.T) {
43+
opts := AWSOIDCConfigurationCreateOptions{}
44+
45+
_, err := client.AWSOIDCConfigurations.Create(ctx, orgTest.Name, opts)
46+
assert.ErrorIs(t, err, ErrRequiredRoleARN)
47+
})
48+
}
49+
50+
func TestAWSOIDCConfigurationRead(t *testing.T) {
51+
if skipHYOKIntegrationTests {
52+
t.Skip()
53+
}
54+
55+
client := testClient(t)
56+
ctx := context.Background()
57+
58+
orgTest, err := client.Organizations.Read(ctx, hyokOrganizationName)
59+
if err != nil {
60+
t.Fatal(err)
61+
}
62+
63+
oidcConfig, oidcConfigCleanup := createAWSOIDCConfiguration(t, client, orgTest)
64+
t.Cleanup(oidcConfigCleanup)
65+
66+
t.Run("fetch existing configuration", func(t *testing.T) {
67+
fetched, err := client.AWSOIDCConfigurations.Read(ctx, oidcConfig.ID)
68+
require.NoError(t, err)
69+
require.NotEmpty(t, fetched)
70+
})
71+
72+
t.Run("fetching non-existing configuration", func(t *testing.T) {
73+
_, err := client.AWSOIDCConfigurations.Read(ctx, "awsoidc-notreal")
74+
assert.ErrorIs(t, err, ErrResourceNotFound)
75+
})
76+
}
77+
78+
func TestAWSOIDCConfigurationsUpdate(t *testing.T) {
79+
if skipHYOKIntegrationTests {
80+
t.Skip()
81+
}
82+
83+
client := testClient(t)
84+
ctx := context.Background()
85+
86+
orgTest, err := client.Organizations.Read(ctx, hyokOrganizationName)
87+
if err != nil {
88+
t.Fatal(err)
89+
}
90+
91+
oidcConfig, oidcConfigCleanup := createAWSOIDCConfiguration(t, client, orgTest)
92+
t.Cleanup(oidcConfigCleanup)
93+
94+
t.Run("with valid options", func(t *testing.T) {
95+
opts := AWSOIDCConfigurationUpdateOptions{
96+
RoleARN: "arn:aws:iam::123456789012:role/some-role-2",
97+
}
98+
updated, err := client.AWSOIDCConfigurations.Update(ctx, oidcConfig.ID, opts)
99+
require.NoError(t, err)
100+
require.NotEmpty(t, updated)
101+
assert.Equal(t, opts.RoleARN, updated.RoleARN)
102+
})
103+
104+
t.Run("missing role ARN", func(t *testing.T) {
105+
opts := AWSOIDCConfigurationUpdateOptions{}
106+
_, err := client.AWSOIDCConfigurations.Update(ctx, oidcConfig.ID, opts)
107+
assert.ErrorIs(t, err, ErrRequiredRoleARN)
108+
})
109+
}

azure_oidc_configuration.go

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
package tfe
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net/url"
7+
)
8+
9+
type AzureOIDCConfigurations interface {
10+
Create(ctx context.Context, organization string, options AzureOIDCConfigurationCreateOptions) (*AzureOIDCConfiguration, error)
11+
12+
Read(ctx context.Context, oidcID string) (*AzureOIDCConfiguration, error)
13+
14+
Update(ctx context.Context, oidcID string, options AzureOIDCConfigurationUpdateOptions) (*AzureOIDCConfiguration, error)
15+
16+
Delete(ctx context.Context, oidcID string) error
17+
}
18+
19+
type azureOIDCConfigurations struct {
20+
client *Client
21+
}
22+
23+
var _ AzureOIDCConfigurations = &azureOIDCConfigurations{}
24+
25+
type AzureOIDCConfiguration struct {
26+
ID string `jsonapi:"primary,azure-oidc-configurations"`
27+
ClientID string `jsonapi:"attr,client-id"`
28+
SubscriptionID string `jsonapi:"attr,subscription-id"`
29+
TenantID string `jsonapi:"attr,tenant-id"`
30+
31+
Organization *Organization `jsonapi:"relation,organization"`
32+
}
33+
34+
type AzureOIDCConfigurationCreateOptions struct {
35+
// Type is a public field utilized by JSON:API to
36+
// set the resource type via the field tag.
37+
// It is not a user-defined value and does not need to be set.
38+
// https://jsonapi.org/format/#crud-creating
39+
Type string `jsonapi:"primary,azure-oidc-configurations"`
40+
41+
// Attributes
42+
ClientID string `jsonapi:"attr,client-id"`
43+
SubscriptionID string `jsonapi:"attr,subscription-id"`
44+
TenantID string `jsonapi:"attr,tenant-id"`
45+
}
46+
47+
type AzureOIDCConfigurationUpdateOptions struct {
48+
// Type is a public field utilized by JSON:API to
49+
// set the resource type via the field tag.
50+
// It is not a user-defined value and does not need to be set.
51+
// https://jsonapi.org/format/#crud-creating
52+
Type string `jsonapi:"primary,azure-oidc-configurations"`
53+
54+
// Attributes
55+
ClientID *string `jsonapi:"attr,client-id,omitempty"`
56+
SubscriptionID *string `jsonapi:"attr,subscription-id,omitempty"`
57+
TenantID *string `jsonapi:"attr,tenant-id,omitempty"`
58+
}
59+
60+
func (o *AzureOIDCConfigurationCreateOptions) valid() error {
61+
if o.ClientID == "" {
62+
return ErrRequiredClientID
63+
}
64+
65+
if o.SubscriptionID == "" {
66+
return ErrRequiredSubscriptionID
67+
}
68+
69+
if o.TenantID == "" {
70+
return ErrRequiredTenantID
71+
}
72+
73+
return nil
74+
}
75+
76+
func (aoc *azureOIDCConfigurations) Create(ctx context.Context, organization string, options AzureOIDCConfigurationCreateOptions) (*AzureOIDCConfiguration, error) {
77+
if !validStringID(&organization) {
78+
return nil, ErrInvalidOrg
79+
}
80+
81+
if err := options.valid(); err != nil {
82+
return nil, err
83+
}
84+
85+
req, err := aoc.client.NewRequest("POST", fmt.Sprintf("organizations/%s/oidc-configurations", organization), &options)
86+
if err != nil {
87+
return nil, err
88+
}
89+
90+
azureOIDCConfiguration := &AzureOIDCConfiguration{}
91+
err = req.Do(ctx, azureOIDCConfiguration)
92+
if err != nil {
93+
return nil, err
94+
}
95+
96+
return azureOIDCConfiguration, nil
97+
}
98+
99+
func (aoc *azureOIDCConfigurations) Read(ctx context.Context, oidcID string) (*AzureOIDCConfiguration, error) {
100+
req, err := aoc.client.NewRequest("GET", fmt.Sprintf(OIDCConfigPathFormat, url.PathEscape(oidcID)), nil)
101+
if err != nil {
102+
return nil, err
103+
}
104+
105+
azureOIDCConfiguration := &AzureOIDCConfiguration{}
106+
err = req.Do(ctx, azureOIDCConfiguration)
107+
if err != nil {
108+
return nil, err
109+
}
110+
111+
return azureOIDCConfiguration, nil
112+
}
113+
114+
func (aoc *azureOIDCConfigurations) Update(ctx context.Context, oidcID string, options AzureOIDCConfigurationUpdateOptions) (*AzureOIDCConfiguration, error) {
115+
if !validStringID(&oidcID) {
116+
return nil, ErrInvalidOIDC
117+
}
118+
119+
req, err := aoc.client.NewRequest("PATCH", fmt.Sprintf(OIDCConfigPathFormat, url.PathEscape(oidcID)), &options)
120+
if err != nil {
121+
return nil, err
122+
}
123+
124+
azureOIDCConfiguration := &AzureOIDCConfiguration{}
125+
err = req.Do(ctx, azureOIDCConfiguration)
126+
if err != nil {
127+
return nil, err
128+
}
129+
130+
return azureOIDCConfiguration, nil
131+
}
132+
133+
func (aoc *azureOIDCConfigurations) Delete(ctx context.Context, oidcID string) error {
134+
if !validStringID(&oidcID) {
135+
return ErrInvalidOIDC
136+
}
137+
138+
req, err := aoc.client.NewRequest("DELETE", fmt.Sprintf(OIDCConfigPathFormat, url.PathEscape(oidcID)), nil)
139+
if err != nil {
140+
return err
141+
}
142+
143+
return req.Do(ctx, nil)
144+
}

0 commit comments

Comments
 (0)