Skip to content

Commit 7b87e5c

Browse files
committed
feat: Add OIDC client resource.
This adds support for the OIDC Provider OIDCClient resource.
1 parent 6007b0b commit 7b87e5c

4 files changed

Lines changed: 350 additions & 0 deletions

File tree

aspell_custom.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,4 @@ oidc
4040
eslint
4141
destructuring
4242
yaml
43+
oidcclients

rancher2/provider.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ func Provider() terraform.ResourceProvider {
140140
"rancher2_namespace": resourceRancher2Namespace(),
141141
"rancher2_node_driver": resourceRancher2NodeDriver(),
142142
"rancher2_node_pool": resourceRancher2NodePool(),
143+
"rancher2_oidc_client": resourceRancher2OIDCClient(),
143144
"rancher2_pod_security_admission_configuration_template": resourceRancher2PodSecurityAdmissionConfigurationTemplate(),
144145
"rancher2_project": resourceRancher2Project(),
145146
"rancher2_project_role_template_binding": resourceRancher2ProjectRoleTemplateBinding(),
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
package rancher2
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"log"
7+
"maps"
8+
"time"
9+
10+
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
11+
"github.com/hashicorp/terraform-plugin-sdk/helper/validation"
12+
managementClient "github.com/rancher/rancher/pkg/client/generated/management/v3"
13+
)
14+
15+
func resourceRancher2OIDCClient() *schema.Resource {
16+
return &schema.Resource{
17+
Create: resourceRancher2OIDCClientCreate,
18+
Read: resourceRancher2OIDCClientRead,
19+
Update: resourceRancher2OIDCClientUpdate,
20+
Delete: resourceRancher2OIDCClientDelete,
21+
22+
Schema: oidcClientFields(),
23+
Timeouts: &schema.ResourceTimeout{
24+
Create: schema.DefaultTimeout(5 * time.Minute),
25+
Update: schema.DefaultTimeout(5 * time.Minute),
26+
Delete: schema.DefaultTimeout(5 * time.Minute),
27+
},
28+
}
29+
}
30+
31+
func oidcClientFields() map[string]*schema.Schema {
32+
s := map[string]*schema.Schema{
33+
"token_expiration_seconds": {
34+
Type: schema.TypeInt,
35+
Optional: true,
36+
Computed: true,
37+
Description: "The duration (in seconds) before an access token and ID token expires. Reducing this will invalidate existing tokens.",
38+
ValidateFunc: validation.IntAtLeast(1),
39+
},
40+
"refresh_token_expiration_seconds": {
41+
Type: schema.TypeInt,
42+
Optional: true,
43+
Computed: true,
44+
Description: "The duration (in seconds) a refresh token remains valid before expiration. Reducing this will invalidate existing tokens.",
45+
ValidateFunc: validation.IntAtLeast(1),
46+
},
47+
"redirect_uris": {
48+
Description: "List of allowed redirect_uris for this client.",
49+
Required: true,
50+
Type: schema.TypeList,
51+
Elem: &schema.Schema{
52+
Type: schema.TypeString,
53+
},
54+
},
55+
"description": {
56+
Type: schema.TypeString,
57+
Optional: true,
58+
Description: "OIDCClient description",
59+
},
60+
}
61+
62+
maps.Copy(s, commonAnnotationLabelFields())
63+
64+
return s
65+
}
66+
67+
func resourceRancher2OIDCClientCreate(d *schema.ResourceData, meta any) error {
68+
log.Printf("[INFO] Creating OIDCClient")
69+
oidcClient, err := expandOIDCClient(d)
70+
if err != nil {
71+
return err
72+
}
73+
74+
client, err := meta.(*Config).ManagementClient()
75+
if err != nil {
76+
log.Printf("[ERROR] getting a client when creating OIDC Client: %s", err)
77+
return err
78+
}
79+
80+
updatedClient, err := client.OIDCClient.Create(oidcClient)
81+
if err != nil {
82+
log.Printf("[ERROR] creating OIDCClient: %s", err)
83+
return err
84+
}
85+
86+
d.SetId(updatedClient.ID)
87+
88+
return resourceRancher2OIDCClientRead(d, meta)
89+
}
90+
91+
func resourceRancher2OIDCClientRead(d *schema.ResourceData, meta any) error {
92+
log.Printf("[INFO] Refreshing OIDCClient ID %s", d.Id())
93+
94+
client, err := meta.(*Config).ManagementClient()
95+
if err != nil {
96+
log.Printf("[ERROR] getting a client when reading OIDC Client: %s", err)
97+
return err
98+
}
99+
oidcClient, err := client.OIDCClient.ByID(d.Id())
100+
if err != nil {
101+
log.Printf("[ERROR] reading OIDC Client %s: %s", d.Id(), err)
102+
return err
103+
}
104+
105+
return flattenOIDCClient(d, oidcClient)
106+
}
107+
108+
func resourceRancher2OIDCClientUpdate(d *schema.ResourceData, meta any) error {
109+
log.Printf("[INFO] Updating OIDCClient ID %s", d.Id())
110+
111+
client, err := meta.(*Config).ManagementClient()
112+
if err != nil {
113+
log.Printf("[ERROR] getting a client when updating OIDC Client: %s", err)
114+
return err
115+
}
116+
117+
oidcClient, err := client.OIDCClient.ByID(d.Id())
118+
if err != nil {
119+
log.Printf("[ERROR] getting OIDC Client %s for update: %s", d.Id(), err)
120+
return err
121+
}
122+
123+
update := map[string]any{
124+
"description": d.Get("description"),
125+
"redirectURIs": d.Get("redirect_uris"),
126+
"token_expiration_seconds": d.Get("token_expiration_seconds"),
127+
"refresh_token_expiration_seconds": d.Get("refresh_token_expiration_seconds"),
128+
}
129+
130+
_, err = client.OIDCClient.Update(oidcClient, update)
131+
if err != nil {
132+
log.Printf("[ERROR] updating OIDC Client %s: %s", d.Id(), err)
133+
return fmt.Errorf("updating OIDC Client %s: %w", d.Id(), err)
134+
}
135+
136+
return resourceRancher2OIDCClientRead(d, meta)
137+
}
138+
139+
func resourceRancher2OIDCClientDelete(d *schema.ResourceData, meta any) error {
140+
log.Printf("[INFO] Deleting OIDCClient ID %s", d.Id())
141+
142+
client, err := meta.(*Config).ManagementClient()
143+
if err != nil {
144+
log.Printf("[ERROR] getting a client when deleting OIDC Client: %s", err)
145+
return err
146+
}
147+
148+
oidcClient, err := client.OIDCClient.ByID(d.Id())
149+
if err != nil {
150+
log.Printf("[ERROR] getting OIDC Client %s for deletion: %s", d.Id(), err)
151+
return nil
152+
}
153+
154+
if err := client.OIDCClient.Delete(oidcClient); err != nil {
155+
log.Printf("[ERROR] deleting OIDC Client %s: %s", d.Id(), err)
156+
return err
157+
}
158+
159+
return nil
160+
}
161+
162+
func expandOIDCClient(in *schema.ResourceData) (*managementClient.OIDCClient, error) {
163+
obj := &managementClient.OIDCClient{}
164+
if in == nil {
165+
return nil, fmt.Errorf("[ERROR] expanding OidcClient: Input ResourceData is nil")
166+
}
167+
168+
if v := in.Id(); len(v) > 0 {
169+
obj.ID = v
170+
}
171+
172+
if v, ok := in.GetOk("description"); ok {
173+
obj.Description = v.(string)
174+
}
175+
176+
v := in.Get("redirect_uris")
177+
obj.RedirectURIs = toArrayString(v.([]any))
178+
179+
v, ok := in.GetOk("token_expiration_seconds")
180+
if ok {
181+
// This should be safe because the field declared as an integer.
182+
tokenExpirationSeconds, _ := v.(int)
183+
obj.TokenExpirationSeconds = int64(tokenExpirationSeconds)
184+
} else {
185+
obj.TokenExpirationSeconds = 0
186+
}
187+
188+
v, ok = in.GetOk("refresh_token_expiration_seconds")
189+
if ok {
190+
// This should be safe because the field declared as an integer.
191+
refreshTokenExpirationSeconds, _ := v.(int)
192+
obj.RefreshTokenExpirationSeconds = int64(refreshTokenExpirationSeconds)
193+
} else {
194+
obj.RefreshTokenExpirationSeconds = 0
195+
}
196+
197+
if v, ok := in.Get("annotations").(map[string]any); ok && len(v) > 0 {
198+
obj.Annotations = toMapString(v)
199+
}
200+
201+
if v, ok := in.Get("labels").(map[string]any); ok && len(v) > 0 {
202+
obj.Labels = toMapString(v)
203+
}
204+
205+
return obj, nil
206+
}
207+
208+
func flattenOIDCClient(d *schema.ResourceData, in *managementClient.OIDCClient) error {
209+
if in == nil {
210+
return fmt.Errorf("[ERROR] flattening OIDCClient: Input config is nil")
211+
}
212+
213+
if in.ID != "" {
214+
d.SetId(in.ID)
215+
}
216+
217+
return errors.Join(
218+
d.Set("description", in.Description),
219+
d.Set("redirect_uris", in.RedirectURIs),
220+
d.Set("token_expiration_seconds", int(in.TokenExpirationSeconds)),
221+
d.Set("refresh_token_expiration_seconds", int(in.RefreshTokenExpirationSeconds)),
222+
d.Set("annotations", toMapInterface(in.Annotations)),
223+
d.Set("labels", toMapInterface(in.Labels)),
224+
)
225+
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package rancher2
2+
3+
import (
4+
"testing"
5+
6+
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
7+
managementClient "github.com/rancher/rancher/pkg/client/generated/management/v3"
8+
"github.com/stretchr/testify/assert"
9+
"github.com/stretchr/testify/require"
10+
)
11+
12+
func TestFlattenOIDCClient(t *testing.T) {
13+
oidcClient := &managementClient.OIDCClient{
14+
Name: "testing-client",
15+
Annotations: map[string]string{
16+
"example.com/testing": "annotation",
17+
},
18+
Labels: map[string]string{
19+
"example.com/testing": "label",
20+
},
21+
RedirectURIs: []string{
22+
"http://127.0.0.1:5556/auth/rancher/callback",
23+
"http://127.0.0.1:33418/",
24+
"https://vscode.dev/redirect",
25+
},
26+
Description: "Access for Rancher AI Agent",
27+
TokenExpirationSeconds: 6000,
28+
RefreshTokenExpirationSeconds: 12000,
29+
}
30+
31+
flattened := schema.TestResourceDataRaw(t, oidcClientFields(), nil)
32+
err := flattenOIDCClient(flattened, oidcClient)
33+
require.NoError(t, err)
34+
35+
want := map[string]any{
36+
"token_expiration_seconds": 6000,
37+
"refresh_token_expiration_seconds": 12000,
38+
"redirect_uris": []any{
39+
"http://127.0.0.1:5556/auth/rancher/callback",
40+
"http://127.0.0.1:33418/",
41+
"https://vscode.dev/redirect",
42+
},
43+
"annotations": map[string]any{
44+
"example.com/testing": "annotation",
45+
},
46+
"labels": map[string]any{
47+
"example.com/testing": "label",
48+
},
49+
}
50+
for key, want := range want {
51+
assert.Equal(t, want, flattened.Get(key), "unexpected output from flattenOIDCClient")
52+
}
53+
}
54+
55+
func TestExpandOIDCClient(t *testing.T) {
56+
expandTests := map[string]struct {
57+
data map[string]any
58+
want *managementClient.OIDCClient
59+
}{
60+
"all fields populated": {
61+
data: map[string]any{
62+
"token_expiration_seconds": 6000,
63+
"refresh_token_expiration_seconds": 12000,
64+
"redirect_uris": []any{
65+
"http://127.0.0.1:5556/auth/rancher/callback",
66+
"http://127.0.0.1:33418/",
67+
"https://vscode.dev/redirect",
68+
},
69+
"description": "Testing OIDC Client",
70+
"annotations": map[string]any{
71+
"example.com/testing": "annotation",
72+
},
73+
"labels": map[string]any{
74+
"example.com/testing": "label",
75+
},
76+
},
77+
want: &managementClient.OIDCClient{
78+
TokenExpirationSeconds: 6000,
79+
RefreshTokenExpirationSeconds: 12000,
80+
RedirectURIs: []string{
81+
"http://127.0.0.1:5556/auth/rancher/callback",
82+
"http://127.0.0.1:33418/",
83+
"https://vscode.dev/redirect",
84+
},
85+
Description: "Testing OIDC Client",
86+
Annotations: map[string]string{
87+
"example.com/testing": "annotation",
88+
},
89+
Labels: map[string]string{
90+
"example.com/testing": "label",
91+
},
92+
},
93+
},
94+
"only required fields populated": {
95+
data: map[string]any{
96+
"redirect_uris": []any{
97+
"http://127.0.0.1:5556/auth/rancher/callback",
98+
"http://127.0.0.1:33418/",
99+
"https://vscode.dev/redirect",
100+
},
101+
},
102+
want: &managementClient.OIDCClient{
103+
TokenExpirationSeconds: 0,
104+
RedirectURIs: []string{
105+
"http://127.0.0.1:5556/auth/rancher/callback",
106+
"http://127.0.0.1:33418/",
107+
"https://vscode.dev/redirect",
108+
},
109+
},
110+
},
111+
}
112+
113+
for name, tt := range expandTests {
114+
t.Run(name, func(t *testing.T) {
115+
inputResourceData := schema.TestResourceDataRaw(t, oidcClientFields(), tt.data)
116+
117+
expanded, err := expandOIDCClient(inputResourceData)
118+
assert.NoError(t, err, "Error in expandOIDCClient")
119+
120+
assert.Equal(t, tt.want, expanded, "Unexpected output from expandOIDCClient")
121+
})
122+
}
123+
}

0 commit comments

Comments
 (0)